package com.microsoft.sqlserver.jdbc;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
public final class SQLServerDataTable {
int rowCount = 0;
int columnCount = 0;
Map<Integer, SQLServerDataColumn> columnMetadata = null;
Set<String> columnNames = null;
Map<Integer, Object[]> rows = null;
private String tvpName = null;
public SQLServerDataTable() throws SQLServerException {
columnMetadata = new LinkedHashMap<>();
columnNames = new HashSet<>();
rows = new HashMap<>();
}
public synchronized void clear() {
rowCount = 0;
columnCount = 0;
columnMetadata.clear();
columnNames.clear();
rows.clear();
}
public synchronized Iterator<Entry<Integer, Object[]>> getIterator() {
if ((null != rows) && (null != rows.entrySet())) {
return rows.entrySet().iterator();
}
return null;
}
public synchronized void addColumnMetadata(String columnName, int sqlType) throws SQLServerException {
Util.checkDuplicateColumnName(columnName, columnNames);
columnMetadata.put(columnCount++, new SQLServerDataColumn(columnName, sqlType));
}
public synchronized void addColumnMetadata(SQLServerDataColumn column) throws SQLServerException {
Util.checkDuplicateColumnName(column.columnName, columnNames);
columnMetadata.put(columnCount++, column);
}
public synchronized void addRow(Object... values) throws SQLServerException {
try {
int columnCount = columnMetadata.size();
if ((null != values) && values.length > columnCount) {
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_moreDataInRowThanColumnInTVP"));
Object[] msgArgs = {};
throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
}
Iterator<Entry<Integer, SQLServerDataColumn>> columnsIterator = columnMetadata.entrySet().iterator();
Object[] rowValues = new Object[columnCount];
int currentColumn = 0;
while (columnsIterator.hasNext()) {
Object val = null;
if ((null != values) && (currentColumn < values.length) && (null != values[currentColumn]))
val = values[currentColumn];
currentColumn++;
Map.Entry<Integer, SQLServerDataColumn> pair = columnsIterator.next();
JDBCType jdbcType = JDBCType.of(pair.getValue().javaSqlType);
internalAddrow(jdbcType, val, rowValues, pair);
}
rows.put(rowCount++, rowValues);
} catch (NumberFormatException e) {
throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
} catch (ClassCastException e) {
throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
}
}
private void internalAddrow(JDBCType jdbcType, Object val, Object[] rowValues,
Map.Entry<Integer, SQLServerDataColumn> pair) throws SQLServerException {
int key = pair.getKey();
if (null != val) {
SQLServerDataColumn currentColumnMetadata = pair.getValue();
int nValueLen;
switch (jdbcType) {
case BIGINT:
rowValues[key] = (val instanceof Long) ? val : Long.parseLong(val.toString());
break;
case BIT:
if (val instanceof Boolean) {
rowValues[key] = val;
} else {
String valString = val.toString();
if ("0".equals(valString) || valString.equalsIgnoreCase(Boolean.FALSE.toString())) {
rowValues[key] = Boolean.FALSE;
} else if ("1".equals(valString) || valString.equalsIgnoreCase(Boolean.TRUE.toString())) {
rowValues[key] = Boolean.TRUE;
} else {
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_TVPInvalidColumnValue"));
Object[] msgArgs = {jdbcType};
throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
}
}
break;
case INTEGER:
rowValues[key] = (val instanceof Integer) ? val : Integer.parseInt(val.toString());
break;
case SMALLINT:
case TINYINT:
rowValues[key] = (val instanceof Short) ? val : Short.parseShort(val.toString());
break;
case DECIMAL:
case NUMERIC:
BigDecimal bd = null;
boolean isColumnMetadataUpdated = false;
bd = new BigDecimal(val.toString());
int precision = Util.getValueLengthBaseOnJavaType(bd, JavaType.of(bd), null, null, jdbcType);
if (bd.scale() > currentColumnMetadata.scale) {
currentColumnMetadata.scale = bd.scale();
isColumnMetadataUpdated = true;
}
if (precision > currentColumnMetadata.precision) {
currentColumnMetadata.precision = precision;
isColumnMetadataUpdated = true;
}
int numberOfDigitsIntegerPart = precision - bd.scale();
if (numberOfDigitsIntegerPart > currentColumnMetadata.numberOfDigitsIntegerPart) {
currentColumnMetadata.numberOfDigitsIntegerPart = numberOfDigitsIntegerPart;
isColumnMetadataUpdated = true;
}
if (isColumnMetadataUpdated) {
currentColumnMetadata.precision = currentColumnMetadata.scale
+ currentColumnMetadata.numberOfDigitsIntegerPart;
columnMetadata.put(pair.getKey(), currentColumnMetadata);
}
rowValues[key] = bd;
break;
case DOUBLE:
rowValues[key] = (val instanceof Double) ? val : Double.parseDouble(val.toString());
break;
case FLOAT:
case REAL:
rowValues[key] = (val instanceof Float) ? val : Float.parseFloat(val.toString());
break;
case TIMESTAMP_WITH_TIMEZONE:
case TIME_WITH_TIMEZONE:
case DATE:
case TIME:
case TIMESTAMP:
case DATETIMEOFFSET:
case DATETIME:
case SMALLDATETIME:
if (val instanceof java.util.Date || val instanceof microsoft.sql.DateTimeOffset
|| val instanceof OffsetDateTime || val instanceof OffsetTime)
rowValues[key] = val.toString();
else
rowValues[key] = val;
break;
case BINARY:
case VARBINARY:
case LONGVARBINARY:
nValueLen = ((byte[]) val).length;
if (nValueLen > currentColumnMetadata.precision) {
currentColumnMetadata.precision = nValueLen;
columnMetadata.put(pair.getKey(), currentColumnMetadata);
}
rowValues[key] = val;
break;
case CHAR:
case VARCHAR:
case NCHAR:
case NVARCHAR:
case LONGVARCHAR:
case LONGNVARCHAR:
case SQLXML:
if (val instanceof UUID)
val = val.toString();
nValueLen = (2 * ((String) val).length());
if (nValueLen > currentColumnMetadata.precision) {
currentColumnMetadata.precision = nValueLen;
columnMetadata.put(pair.getKey(), currentColumnMetadata);
}
rowValues[key] = val;
break;
case SQL_VARIANT:
JDBCType internalJDBCType;
JavaType javaType = JavaType.of(val);
internalJDBCType = javaType.getJDBCType(SSType.UNKNOWN, jdbcType);
internalAddrow(internalJDBCType, val, rowValues, pair);
break;
default:
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedDataTypeTVP"));
Object[] msgArgs = {jdbcType};
throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
}
} else {
rowValues[key] = null;
if (jdbcType == JDBCType.SQL_VARIANT) {
throw new SQLServerException(SQLServerException.getErrString("R_invalidValueForTVPWithSQLVariant"),
null);
}
}
}
public synchronized Map<Integer, SQLServerDataColumn> getColumnMetadata() {
return columnMetadata;
}
public String getTvpName() {
return tvpName;
}
public void setTvpName(String tvpName) {
this.tvpName = tvpName;
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + rowCount;
hash = 31 * hash + columnCount;
hash = 31 * hash + (null != columnMetadata ? columnMetadata.hashCode() : 0);
hash = 31 * hash + (null != columnNames ? columnNames.hashCode() : 0);
hash = 31 * hash + getRowsHashCode();
hash = 31 * hash + (null != tvpName ? tvpName.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (null != object && object.getClass() == SQLServerDataTable.class) {
SQLServerDataTable aSQLServerDataTable = (SQLServerDataTable) object;
if (hashCode() == aSQLServerDataTable.hashCode()) {
boolean equalColumnMetadata = columnMetadata.equals(aSQLServerDataTable.columnMetadata);
boolean equalColumnNames = columnNames.equals(aSQLServerDataTable.columnNames);
boolean equalRowData = compareRows(aSQLServerDataTable.rows);
return (rowCount == aSQLServerDataTable.rowCount && columnCount == aSQLServerDataTable.columnCount
&& tvpName == aSQLServerDataTable.tvpName && equalColumnMetadata && equalColumnNames
&& equalRowData);
}
}
return false;
}
private int getRowsHashCode() {
if (null == rows) {
return 0;
}
int h = 0;
for (Entry<Integer, Object[]> entry : rows.entrySet()) {
h += entry.getKey() ^ Arrays.hashCode(entry.getValue());
}
return h;
}
private boolean compareRows(Map<Integer, Object[]> otherRows) {
if (rows == otherRows) {
return true;
}
if (rows.size() != otherRows.size()) {
return false;
}
try {
for (Entry<Integer, Object[]> e : rows.entrySet()) {
Integer key = e.getKey();
Object[] value = e.getValue();
if (null == value) {
if (!(null == otherRows.get(key) && otherRows.containsKey(key))) {
return false;
}
} else {
if (!Arrays.equals(value, otherRows.get(key))) {
return false;
}
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
}