Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround Bulk Copy for batch insert operation against specific datatypes. #912

Merged
merged 12 commits into from
Jan 4, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,20 @@ private Object convertValue(ColumnMetadata cm, Object data) throws SQLServerExce
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB: {
// Strip off 0x if present.
String binData = data.toString().trim();
if (binData.startsWith("0x") || binData.startsWith("0X")) {
return binData.substring(2);
if (data instanceof byte[]) {
/*
* if the binary data comes in as a byte array through setBytes through Bulk Copy for Batch Insert
* API, don't turn the binary array into a string.
*/
return data;
} else {
return binData;
// Strip off 0x if present.
String binData = data.toString().trim();
if (binData.startsWith("0x") || binData.startsWith("0X")) {
return binData.substring(2);
} else {
return binData;
}
}
}

Expand Down Expand Up @@ -229,8 +237,9 @@ public Object[] getRowData() throws SQLServerException {
Object rowData;
int columnListIndex = 0;

// check if the size of the list of values = size of the list of columns
// (which is optional)
/*
* check if the size of the list of values = size of the list of columns (which is optional)
*/
if (null != columnList && columnList.size() != valueList.size()) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DataSchemaMismatch"));
Object[] msgArgs = {};
Expand All @@ -240,50 +249,43 @@ public Object[] getRowData() throws SQLServerException {
for (Entry<Integer, ColumnMetadata> pair : columnMetadata.entrySet()) {
int index = pair.getKey() - 1;

// To explain what each variable represents:
// columnMetadata = map containing the ENTIRE list of columns in the
// table.
// columnList = the *optional* list of columns the user can provide.
// For example, the (c1, c3) part of this query: INSERT into t1 (c1,
// c3) values (?, ?)
// valueList = the *mandatory* list of columns the user needs
// provide. This is the (?, ?) part of the previous query. The size
// of this valueList will always equal the number of
// the entire columns in the table IF columnList has NOT been
// provided. If columnList HAS been provided, then this valueList
// may be smaller than the list of all columns (which is
// columnMetadata).

// case when the user has not provided the optional list of column
// names.
/*
* To explain what each variable represents: columnMetadata = map containing the ENTIRE list of columns in
* the table. columnList = the *optional* list of columns the user can provide. For example, the (c1, c3)
* part of this query: INSERT into t1 (c1, c3) values (?, ?) valueList = the *mandatory* list of columns the
* user needs provide. This is the (?, ?) part of the previous query. The size of this valueList will always
* equal the number of the entire columns in the table IF columnList has NOT been provided. If columnList
* HAS been provided, then this valueList may be smaller than the list of all columns (which is
* columnMetadata).
*/
// case when the user has not provided the optional list of column names.
peterbae marked this conversation as resolved.
Show resolved Hide resolved
if (null == columnList || columnList.size() == 0) {
valueData = valueList.get(index);
// if the user has provided a wildcard for this column, fetch
// the set value from the batchParam.
/*
* if the user has provided a wildcard for this column, fetch the set value from the batchParam.
*/
if (valueData.equalsIgnoreCase("?")) {
rowData = batchParam.get(batchParamIndex)[valueIndex++].getSetterValue();
} else if (valueData.equalsIgnoreCase("null")) {
rowData = null;
}
// if the user has provided a hardcoded value for this column,
// rowData is simply set to the hardcoded value.
/*
* if the user has provided a hardcoded value for this column, rowData is simply set to the hardcoded
* value.
*/
else {
rowData = removeSingleQuote(valueData);
}
}
// case when the user has provided the optional list of column
// names.
// case when the user has provided the optional list of column names.
else {
// columnListIndex is a separate counter we need to keep track
// of for each time we've processed a column
// that the user provided.
// for example, if the user provided an optional columnList of
// (c1, c3, c5, c7) in a table that has 8 columns (c1~c8),
// then the columnListIndex would increment only when we're
// dealing with the four columns inside columnMetadata.
// compare the list of the optional list of column names to the
// table's metadata, and match each other, so we assign the
// correct value to each column.
/*
* columnListIndex is a separate counter we need to keep track of for each time we've processed a column
* that the user provided. for example, if the user provided an optional columnList of (c1, c3, c5, c7)
* in a table that has 8 columns (c1~c8), then the columnListIndex would increment only when we're
* dealing with the four columns inside columnMetadata. compare the list of the optional list of column
* names to the table's metadata, and match each other, so we assign the correct value to each column.
*/
if (columnList.size() > columnListIndex
&& columnList.get(columnListIndex).equalsIgnoreCase(columnMetadata.get(index + 1).columnName)) {
valueData = valueList.get(columnListIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,12 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL

try {
if (this.useBulkCopyForBatchInsert && connection.isAzureDW() && isInsert(localUserSQL)) {
if (null == batchParamValues) {
updateCounts = new int[0];
loggerExternal.exiting(getClassNameLogging(), "executeBatch", updateCounts);
return updateCounts;
}

// From the JDBC spec, section 9.1.4 - Making Batch Updates:
// The CallableStatement.executeBatch method (inherited from PreparedStatement) will
// throw a BatchUpdateException if the stored procedure returns anything other than an
Expand All @@ -1955,12 +1961,6 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL
}
}

if (batchParamValues == null) {
updateCounts = new int[0];
loggerExternal.exiting(getClassNameLogging(), "executeBatch", updateCounts);
return updateCounts;
}

String tableName = parseUserSQLForTableNameDW(false, false, false, false);
ArrayList<String> columnList = parseUserSQLForColumnListDW();
ArrayList<String> valueList = parseUserSQLForValueListDW(false);
Expand Down Expand Up @@ -2032,7 +2032,7 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL
}
}

if (batchParamValues == null)
if (null == batchParamValues)
updateCounts = new int[0];
else
try {
Expand Down Expand Up @@ -2093,6 +2093,12 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio

try {
if (this.useBulkCopyForBatchInsert && connection.isAzureDW() && isInsert(localUserSQL)) {
if (null == batchParamValues) {
updateCounts = new long[0];
loggerExternal.exiting(getClassNameLogging(), "executeLargeBatch", updateCounts);
return updateCounts;
}

// From the JDBC spec, section 9.1.4 - Making Batch Updates:
// The CallableStatement.executeBatch method (inherited from PreparedStatement) will
// throw a BatchUpdateException if the stored procedure returns anything other than an
Expand All @@ -2112,12 +2118,6 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio
}
}

if (batchParamValues == null) {
updateCounts = new long[0];
loggerExternal.exiting(getClassNameLogging(), "executeLargeBatch", updateCounts);
return updateCounts;
}

String tableName = parseUserSQLForTableNameDW(false, false, false, false);
ArrayList<String> columnList = parseUserSQLForColumnListDW();
ArrayList<String> valueList = parseUserSQLForValueListDW(false);
Expand Down Expand Up @@ -2189,7 +2189,7 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio
}
}

if (batchParamValues == null)
if (null == batchParamValues)
updateCounts = new long[0];
else
try {
Expand Down Expand Up @@ -2234,17 +2234,26 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio

private void checkValidColumns(TypeInfo ti) throws SQLServerException {
int jdbctype = ti.getSSType().getJDBCType().getIntValue();
// currently, we do not support: geometry, geography, datetime and smalldatetime
String typeName;
MessageFormat form;
switch (jdbctype) {
case microsoft.sql.Types.MONEY:
case microsoft.sql.Types.SMALLMONEY:
case java.sql.Types.DATE:
case microsoft.sql.Types.DATETIME:
case microsoft.sql.Types.DATETIMEOFFSET:
case microsoft.sql.Types.SMALLDATETIME:
case java.sql.Types.TIME:
typeName = ti.getSSTypeName();
form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupportedDW"));
throw new IllegalArgumentException(form.format(new Object[] {typeName}));
case java.sql.Types.INTEGER:
case java.sql.Types.SMALLINT:
case java.sql.Types.BIGINT:
case java.sql.Types.BIT:
case java.sql.Types.TINYINT:
case java.sql.Types.DOUBLE:
case java.sql.Types.REAL:
case microsoft.sql.Types.MONEY:
case microsoft.sql.Types.SMALLMONEY:
case java.sql.Types.DECIMAL:
case java.sql.Types.NUMERIC:
case microsoft.sql.Types.GUID:
Expand All @@ -2258,21 +2267,18 @@ private void checkValidColumns(TypeInfo ti) throws SQLServerException {
case java.sql.Types.LONGVARBINARY:
case java.sql.Types.VARBINARY:
// Spatial datatypes fall under Varbinary, check if the UDT is geometry/geography.
String typeName = ti.getSSTypeName();
typeName = ti.getSSTypeName();
if (typeName.equalsIgnoreCase("geometry") || typeName.equalsIgnoreCase("geography")) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
throw new IllegalArgumentException(form.format(new Object[] {typeName}));
}
case java.sql.Types.TIMESTAMP:
case java.sql.Types.DATE:
case java.sql.Types.TIME:
case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
case microsoft.sql.Types.DATETIMEOFFSET:
case microsoft.sql.Types.SQL_VARIANT:
return;
default: {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
String unsupportedDataType = JDBCType.of(jdbctype).toString();
throw new IllegalArgumentException(form.format(new Object[] {unsupportedDataType}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ protected Object[][] getContents() {
{"R_unableRetrieveSourceData", "Unable to retrieve data from the source."},
{"R_ParsingError", "Failed to parse data for the {0} type."},
{"R_BulkTypeNotSupported", "Data type {0} is not supported in bulk copy."},
{"R_BulkTypeNotSupportedDW", "Data type {0} is not supported in bulk copy against Azure Data Warehouse."},
{"R_invalidTransactionOption",
"UseInternalTransaction option cannot be set to TRUE when used with a Connection object."},
{"R_invalidNegativeArg", "The {0} argument cannot be negative."},
Expand Down
26 changes: 6 additions & 20 deletions src/test/java/com/microsoft/sqlserver/jdbc/RandomData.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public class RandomData {
private static Random r = new Random();

public static boolean returnNull = (0 == r.nextInt(5)); // 20% chance of return null
public static boolean returnFullLength = (0 == r.nextInt(2)); // 50% chance of return full length for char/nchar and
// binary types
public static boolean returnMinMax = (0 == r.nextInt(5)); // 20% chance of return Min/Max value
public static boolean returnZero = (0 == r.nextInt(10)); // 10% chance of return zero

Expand Down Expand Up @@ -373,17 +371,10 @@ public static byte[] generateBinaryTypes(String columnLength, boolean nullable,
}
} else {
int columnLengthInt = Integer.parseInt(columnLength);
if (returnFullLength) {
length = columnLengthInt;
byte[] bytes = new byte[length];
r.nextBytes(bytes);
return bytes;
} else {
length = r.nextInt(columnLengthInt - minimumLength) + minimumLength;
byte[] bytes = new byte[length];
r.nextBytes(bytes);
return bytes;
}
length = columnLengthInt;
byte[] bytes = new byte[length];
r.nextBytes(bytes);
return bytes;
}
}

Expand Down Expand Up @@ -711,13 +702,8 @@ private static String buildCharOrNChar(String columnLength, boolean nullable, bo
}
} else {
int columnLengthInt = Integer.parseInt(columnLength);
if (returnFullLength) {
length = columnLengthInt;
return buildRandomString(length, charSet);
} else {
length = r.nextInt(columnLengthInt - minimumLength) + minimumLength;
return buildRandomString(length, charSet);
}
length = columnLengthInt;
peterbae marked this conversation as resolved.
Show resolved Hide resolved
return buildRandomString(length, charSet);
}
}

Expand Down
Loading