diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkBatchInsertRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkBatchInsertRecord.java index 3203f07f1..3c662b267 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkBatchInsertRecord.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkBatchInsertRecord.java @@ -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; + } } } @@ -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 = {}; @@ -240,50 +249,43 @@ public Object[] getRowData() throws SQLServerException { for (Entry 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. 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); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index fea6c22e3..acce3058b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -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 @@ -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 columnList = parseUserSQLForColumnListDW(); ArrayList valueList = parseUserSQLForValueListDW(false); @@ -2032,7 +2032,7 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL } } - if (batchParamValues == null) + if (null == batchParamValues) updateCounts = new int[0]; else try { @@ -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 @@ -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 columnList = parseUserSQLForColumnListDW(); ArrayList valueList = parseUserSQLForValueListDW(false); @@ -2189,7 +2189,7 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio } } - if (batchParamValues == null) + if (null == batchParamValues) updateCounts = new long[0]; else try { @@ -2234,8 +2234,19 @@ 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: @@ -2243,8 +2254,6 @@ private void checkValidColumns(TypeInfo ti) throws SQLServerException { 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: @@ -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})); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 266f8131a..1deca52cc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -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."}, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/RandomData.java b/src/test/java/com/microsoft/sqlserver/jdbc/RandomData.java index 2e2b2e8a0..f091f8f00 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/RandomData.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/RandomData.java @@ -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 @@ -357,8 +355,8 @@ public static byte[] generateBinaryTypes(String columnLength, boolean nullable, minimumLength = 1; } - int length; if (columnLength.toLowerCase().equals("max")) { + int length; // 50% chance of return value longer than 8000/4000 if (r.nextBoolean()) { length = r.nextInt(100000) + maxBound; @@ -373,17 +371,9 @@ 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; - } + byte[] bytes = new byte[columnLengthInt]; + r.nextBytes(bytes); + return bytes; } } @@ -699,8 +689,8 @@ private static String buildCharOrNChar(String columnLength, boolean nullable, bo minimumLength = 1; } - int length; if (columnLength.toLowerCase().equals("max")) { + int length; // 50% chance of return value longer than 8000/4000 if (r.nextBoolean()) { length = r.nextInt(100000) + maxBound; @@ -711,13 +701,7 @@ 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); - } + return buildRandomString(columnLengthInt, charSet); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java index e3fbaa8b0..c684dab00 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java @@ -7,6 +7,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.BatchUpdateException; import java.sql.Connection; import java.sql.Date; @@ -15,8 +17,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; @@ -28,6 +35,7 @@ import com.microsoft.sqlserver.jdbc.Geography; import com.microsoft.sqlserver.jdbc.Geometry; +import com.microsoft.sqlserver.jdbc.RandomData; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerConnection; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; @@ -37,6 +45,8 @@ import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; +import microsoft.sql.DateTimeOffset; + @RunWith(JUnitPlatform.class) @Tag("AzureDWTest") @@ -49,15 +59,68 @@ public class BatchExecutionWithBulkCopyTest extends AbstractTest { static String doubleQuoteTableName = RandomUtil.getIdentifier("\"BulkCopy\"\"\"\"test\""); static String schemaTableName = "\"dbo\" . /*some comment */ " + squareBracketTableName; + private Object[] generateExpectedValues() { + float randomFloat = RandomData.generateReal(false); + int ramdonNum = RandomData.generateInt(false); + short randomShort = RandomData.generateTinyint(false); + String randomString = RandomData.generateCharTypes("6", false, false); + String randomChar = RandomData.generateCharTypes("1", false, false); + byte[] randomBinary = RandomData.generateBinaryTypes("5", false, false); + BigDecimal randomBigDecimal = new BigDecimal(ramdonNum); + BigDecimal randomMoney = RandomData.generateMoney(false); + BigDecimal randomSmallMoney = RandomData.generateSmallMoney(false); + + // Temporal datatypes + Date randomDate = Date.valueOf(LocalDateTime.now().toLocalDate()); + Time randomTime = new Time(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + Timestamp randomTimestamp = new Timestamp( + LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + + // Datetime can only end in 0,3,7 and will be rounded to those numbers on the server. Manually set nanos + Timestamp roundedDatetime = randomTimestamp; + roundedDatetime.setNanos(0); + // Smalldatetime does not have seconds. Manually set nanos and seconds. + Timestamp smallTimestamp = randomTimestamp; + smallTimestamp.setNanos(0); + smallTimestamp.setSeconds(0); + + Object[] expected = new Object[23]; + expected[0] = ThreadLocalRandom.current().nextLong(); + expected[1] = randomBinary; + expected[2] = true; + expected[3] = randomChar; + expected[4] = randomDate; + expected[5] = roundedDatetime; + expected[6] = randomTimestamp; + expected[7] = microsoft.sql.DateTimeOffset.valueOf(randomTimestamp, 0); + expected[8] = randomBigDecimal.setScale(0, RoundingMode.HALF_UP); + expected[9] = (double) ramdonNum; + expected[10] = ramdonNum; + expected[11] = randomMoney; + expected[12] = randomChar; + expected[13] = BigDecimal.valueOf(ThreadLocalRandom.current().nextInt()); + expected[14] = randomString; + expected[15] = randomFloat; + expected[16] = smallTimestamp; + expected[17] = randomShort; + expected[18] = randomSmallMoney; + expected[19] = randomTime; + expected[20] = randomShort; + expected[21] = randomBinary; + expected[22] = randomString; + + return expected; + } + @Test public void testIsInsert() throws Exception { try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); Statement stmt = (SQLServerStatement) connection.createStatement()) { - String valid1 = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + " values (1, 2)"; - String valid2 = " INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + " values (1, 2)"; - String valid3 = "/* asdf */ INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + String valid1 = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + " values (1, 2)"; + String valid2 = " insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + " values (1, 2)"; + String valid3 = "/* asdf */ insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk) + " values (1, 2)"; - String invalid = "Select * from " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk); + String invalid = "select * from " + AbstractSQLGenerator.escapeIdentifier(tableNameBulk); Method method = stmt.getClass().getDeclaredMethod("isInsert", String.class); method.setAccessible(true); @@ -164,9 +227,9 @@ public void testAll() throws Exception { @Test public void testAllcolumns() throws Exception { - assumeFalse(isSqlAzureDW(), TestResource.getResource("R_issueAzureDW")); - String valid = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values " + "(" + "?, " - + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "? " + ")"; + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values " + "(" + "?, " + + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?" + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -175,41 +238,44 @@ public void testAllcolumns() throws Exception { f1.setAccessible(true); f1.set(connection, true); - Timestamp myTimestamp = new Timestamp(114550L); - - Date d = new Date(114550L); + Object[] expected = generateExpectedValues(); + + pstmt.setLong(1, (long) expected[0]); // bigint + pstmt.setBytes(2, (byte[]) expected[1]); // binary(5) + pstmt.setBoolean(3, (boolean) expected[2]); // bit + pstmt.setString(4, (String) expected[3]); // char + pstmt.setDate(5, (Date) expected[4]); // date + pstmt.setDateTime(6, (Timestamp) expected[5]);// datetime + pstmt.setDateTime(7, (Timestamp) expected[6]); // datetime2 + pstmt.setDateTimeOffset(8, (DateTimeOffset) expected[7]); // datetimeoffset + pstmt.setBigDecimal(9, (BigDecimal) expected[8]); // decimal + pstmt.setDouble(10, (double) expected[9]); // float + pstmt.setInt(11, (int) expected[10]); // int + pstmt.setMoney(12, (BigDecimal) expected[11]); // money + pstmt.setString(13, (String) expected[12]); // nchar + pstmt.setBigDecimal(14, (BigDecimal) expected[13]); // numeric + pstmt.setString(15, (String) expected[14]); // nvarchar(20) + pstmt.setFloat(16, (float) expected[15]); // real + pstmt.setSmallDateTime(17, (Timestamp) expected[16]); // smalldatetime + pstmt.setShort(18, (short) expected[17]); // smallint + pstmt.setSmallMoney(19, (BigDecimal) expected[18]); // smallmoney + pstmt.setTime(20, (Time) expected[19]); // time + pstmt.setShort(21, (short) expected[20]); // tinyint + pstmt.setBytes(22, (byte[]) expected[21]); // varbinary(5) + pstmt.setString(23, (String) expected[22]); // varchar(20) - pstmt.setInt(1, 1234); - pstmt.setBoolean(2, false); - pstmt.setString(3, "a"); - pstmt.setDate(4, d); - pstmt.setDateTime(5, myTimestamp); - pstmt.setFloat(6, (float) 123.45); - pstmt.setString(7, "b"); - pstmt.setString(8, "varc"); - pstmt.setString(9, "''"); pstmt.addBatch(); - pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(tableName))) { - - Object[] expected = new Object[9]; - - expected[0] = 1234; - expected[1] = false; - expected[2] = "a"; - expected[3] = d; - expected[4] = myTimestamp; - expected[5] = 123.45; - expected[6] = "b"; - expected[7] = "varc"; - expected[8] = "''"; - + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { rs.next(); for (int i = 0; i < expected.length; i++) { - assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); + if (rs.getObject(i + 1) instanceof byte[]) { + assertTrue(Arrays.equals((byte[]) expected[i], (byte[]) rs.getObject(i + 1))); + } else { + assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); + } } } } @@ -217,8 +283,7 @@ public void testAllcolumns() throws Exception { @Test public void testMixColumns() throws Exception { - assumeFalse(isSqlAzureDW(), TestResource.getResource("R_issueAzureDW")); - String valid = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (c1, c3, c5, c8) values " + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (c1, c3, c5, c8) values " + "(" + "?, " + "?, " + "?, " + "? " + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); @@ -228,34 +293,29 @@ public void testMixColumns() throws Exception { f1.setAccessible(true); f1.set(connection, true); - Timestamp myTimestamp = new Timestamp(114550L); - - Date d = new Date(114550L); - - pstmt.setInt(1, 1234); - pstmt.setString(2, "a"); - pstmt.setDateTime(3, myTimestamp); - pstmt.setString(4, "varc"); + Timestamp randomTimestamp = new Timestamp(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + Date randomDate = Date.valueOf(LocalDateTime.now().toLocalDate()); + long randomLong = ThreadLocalRandom.current().nextLong(); + + pstmt.setLong(1, randomLong); // bigint + pstmt.setBoolean(2, true); // bit + pstmt.setDate(3, randomDate); // date + pstmt.setDateTimeOffset(4, microsoft.sql.DateTimeOffset.valueOf(randomTimestamp, 0)); // datetimeoffset pstmt.addBatch(); pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(tableName))) { - - Object[] expected = new Object[9]; + .executeQuery("select c1, c3, c5, c8 from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { - expected[0] = 1234; - expected[1] = false; - expected[2] = "a"; - expected[3] = d; - expected[4] = myTimestamp; - expected[5] = 123.45; - expected[6] = "b"; - expected[7] = "varc"; - expected[8] = "varcmax"; + Object[] expected = new Object[4]; + expected[0] = randomLong; + expected[1] = true; + expected[2] = randomDate; + expected[3] = microsoft.sql.DateTimeOffset.valueOf(randomTimestamp, 0); rs.next(); + for (int i = 0; i < expected.length; i++) { if (null != rs.getObject(i + 1)) { assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); @@ -267,8 +327,7 @@ public void testMixColumns() throws Exception { @Test public void testNullOrEmptyColumns() throws Exception { - assumeFalse(isSqlAzureDW(), TestResource.getResource("R_issueAzureDW")); - String valid = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (c1, c2, c3, c4, c5, c6, c7) values " + "(" + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "? " + ")"; @@ -279,29 +338,32 @@ public void testNullOrEmptyColumns() throws Exception { f1.setAccessible(true); f1.set(connection, true); - pstmt.setInt(1, 1234); - pstmt.setBoolean(2, false); - pstmt.setString(3, null); - pstmt.setDate(4, null); - pstmt.setDateTime(5, null); - pstmt.setFloat(6, (float) 123.45); - pstmt.setString(7, ""); + long randomLong = ThreadLocalRandom.current().nextLong(); + String randomChar = RandomData.generateCharTypes("1", false, false); + + pstmt.setLong(1, randomLong); // bigint + pstmt.setBytes(2, null); // binary(5) + pstmt.setBoolean(3, true); // bit + pstmt.setString(4, randomChar); // char + pstmt.setDate(5, null); // date + pstmt.setDateTime(6, null);// datetime + pstmt.setDateTime(7, null); // datetime2 pstmt.addBatch(); pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(tableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { - Object[] expected = new Object[9]; + Object[] expected = new Object[7]; - expected[0] = 1234; - expected[1] = false; - expected[2] = null; - expected[3] = null; + expected[0] = randomLong; + expected[1] = null; + expected[2] = true; + expected[3] = randomChar; expected[4] = null; - expected[5] = 123.45; - expected[6] = " "; + expected[5] = null; + expected[6] = null; rs.next(); for (int i = 0; i < expected.length; i++) { @@ -335,7 +397,7 @@ public void testSquareBracketAgainstDB() throws Exception { pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(squareBracketTableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(squareBracketTableName))) { rs.next(); assertEquals(1, rs.getObject(1)); @@ -365,7 +427,7 @@ public void testDoubleQuoteAgainstDB() throws Exception { pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(doubleQuoteTableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(doubleQuoteTableName))) { rs.next(); assertEquals(1, rs.getObject(1)); @@ -394,7 +456,7 @@ public void testSchemaAgainstDB() throws Exception { pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(schemaTableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(schemaTableName))) { rs.next(); assertEquals(1, rs.getObject(1)); @@ -425,7 +487,7 @@ public void testColumnNameMixAgainstDB() throws Exception { pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(squareBracketTableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(squareBracketTableName))) { rs.next(); assertEquals(1, rs.getObject(1)); @@ -435,9 +497,9 @@ public void testColumnNameMixAgainstDB() throws Exception { @Test public void testAllColumnsLargeBatch() throws Exception { - assumeFalse(isSqlAzureDW(), TestResource.getResource("R_issueAzureDW")); - String valid = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values " + "(" + "?, " - + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "? " + ")"; + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values " + "(" + "?, " + + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " + "?" + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -446,41 +508,44 @@ public void testAllColumnsLargeBatch() throws Exception { f1.setAccessible(true); f1.set(connection, true); - Timestamp myTimestamp = new Timestamp(114550L); - - Date d = new Date(114550L); + Object[] expected = generateExpectedValues(); + + pstmt.setLong(1, (long) expected[0]); // bigint + pstmt.setBytes(2, (byte[]) expected[1]); // binary(5) + pstmt.setBoolean(3, (boolean) expected[2]); // bit + pstmt.setString(4, (String) expected[3]); // char + pstmt.setDate(5, (Date) expected[4]); // date + pstmt.setDateTime(6, (Timestamp) expected[5]);// datetime + pstmt.setDateTime(7, (Timestamp) expected[6]); // datetime2 + pstmt.setDateTimeOffset(8, (DateTimeOffset) expected[7]); // datetimeoffset + pstmt.setBigDecimal(9, (BigDecimal) expected[8]); // decimal + pstmt.setDouble(10, (double) expected[9]); // float + pstmt.setInt(11, (int) expected[10]); // int + pstmt.setMoney(12, (BigDecimal) expected[11]); // money + pstmt.setString(13, (String) expected[12]); // nchar + pstmt.setBigDecimal(14, (BigDecimal) expected[13]); // numeric + pstmt.setString(15, (String) expected[14]); // nvarchar(20) + pstmt.setFloat(16, (float) expected[15]); // real + pstmt.setSmallDateTime(17, (Timestamp) expected[16]); // smalldatetime + pstmt.setShort(18, (short) expected[17]); // smallint + pstmt.setSmallMoney(19, (BigDecimal) expected[18]); // smallmoney + pstmt.setTime(20, (Time) expected[19]); // time + pstmt.setShort(21, (short) expected[20]); // tinyint + pstmt.setBytes(22, (byte[]) expected[21]); // varbinary(5) + pstmt.setString(23, (String) expected[22]); // varchar(20) - pstmt.setInt(1, 1234); - pstmt.setBoolean(2, false); - pstmt.setString(3, "a"); - pstmt.setDate(4, d); - pstmt.setDateTime(5, myTimestamp); - pstmt.setFloat(6, (float) 123.45); - pstmt.setString(7, "b"); - pstmt.setString(8, "varc"); - pstmt.setString(9, "''"); pstmt.addBatch(); - pstmt.executeLargeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(tableName))) { - - Object[] expected = new Object[9]; - - expected[0] = 1234; - expected[1] = false; - expected[2] = "a"; - expected[3] = d; - expected[4] = myTimestamp; - expected[5] = 123.45; - expected[6] = "b"; - expected[7] = "varc"; - expected[8] = "''"; - + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { rs.next(); for (int i = 0; i < expected.length; i++) { - assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); + if (rs.getObject(i + 1) instanceof byte[]) { + assertTrue(Arrays.equals((byte[]) expected[i], (byte[]) rs.getObject(i + 1))); + } else { + assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); + } } } } @@ -538,7 +603,6 @@ public void testIllegalNumberOfArgNoColumnList() throws Exception { @Test public void testNonParameterizedQuery() throws Exception { - assumeFalse(isSqlAzureDW(), TestResource.getResource("R_issueAzureDW")); String invalid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values ((SELECT * from table where c1=?), ?,? ,?) "; @@ -619,7 +683,7 @@ public void testNonSupportedColumns() throws Exception { pstmt.executeBatch(); try (ResultSet rs = stmt - .executeQuery("SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(unsupportedTableName))) { + .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(unsupportedTableName))) { rs.next(); assertEquals(g1.toString(), Geometry.STGeomFromWKB((byte[]) rs.getObject(1)).toString()); assertEquals(g2.toString(), Geography.STGeomFromWKB((byte[]) rs.getObject(2)).toString()); @@ -631,14 +695,19 @@ public void testNonSupportedColumns() throws Exception { @BeforeEach public void testSetup() throws TestAbortedException, Exception { - try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); - Statement stmt = (SQLServerStatement) connection.createStatement()) { - TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); - String sql1 = "create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + " " + "(" - + "c1 int DEFAULT 1234, " + "c2 bit, " + "c3 char DEFAULT NULL, " + "c4 date, " + "c5 datetime2, " - + "c6 float, " + "c7 nchar, " + "c8 varchar(20), " + "c9 varchar(8000)" + ")"; - - stmt.execute(sql1); + try (Connection connection = DriverManager + .getConnection(connectionString + ";useBulkCopyForBatchInsert=true;")) { + try (Statement stmt = (SQLServerStatement) connection.createStatement()) { + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + String sql1 = "create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + " " + "(" + + "c1 bigint, " + "c2 binary(5), " + "c3 bit, " + "c4 char, " + "c5 date, " + "c6 datetime, " + + "c7 datetime2, " + "c8 datetimeoffset, " + "c9 decimal, " + "c10 float, " + "c11 int, " + + "c12 money, " + "c13 nchar, " + "c14 numeric, " + "c15 nvarchar(20), " + "c16 real, " + + "c17 smalldatetime, " + "c18 smallint, " + "c19 smallmoney, " + "c20 time, " + "c21 tinyint, " + + "c22 varbinary(5), " + "c23 varchar(20) " + ")"; + + stmt.execute(sql1); + } } }