From 34f60107d664269cc1e65b19c1b04557a4ae61aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Sandstr=C3=B6m?= Date: Tue, 15 Aug 2023 22:15:16 +0200 Subject: [PATCH 1/4] Performance optimizations when bulk loading large amounts of timestamps --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 78 ++++++------------- .../sqlserver/jdbc/SQLServerBulkCopy.java | 6 +- 2 files changed, 27 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index bb709307d..a84ee750c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5,28 +5,17 @@ package com.microsoft.sqlserver.jdbc; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; +import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; + +import javax.net.SocketFactory; +import javax.net.ssl.*; +import java.io.*; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.SocketTimeoutException; +import java.net.*; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -40,22 +29,11 @@ import java.sql.Timestamp; import java.text.MessageFormat; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import java.util.SimpleTimeZone; -import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -68,18 +46,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.SocketFactory; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; -import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; - final class TDS { // application protocol @@ -3756,28 +3722,28 @@ void writeSmalldatetime(String value) throws SQLServerException { } void writeDatetime(String value) throws SQLServerException { - GregorianCalendar calendar = initializeCalender(TimeZone.getDefault()); + writeDatetime(java.sql.Timestamp.valueOf(value)); + } + + void writeDatetime(java.sql.Timestamp dateValue) throws SQLServerException { + LocalDateTime ldt = dateValue.toLocalDateTime(); long utcMillis; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT) int subSecondNanos; - java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value); - utcMillis = timestampValue.getTime(); - subSecondNanos = timestampValue.getNanos(); + subSecondNanos = ldt.getNano(); - // Load the calendar with the desired value - calendar.setTimeInMillis(utcMillis); // Number of days there have been since the SQL Base Date. // These are based on SQL Server algorithms - int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(calendar.get(Calendar.YEAR), - calendar.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900); + int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(ldt.getYear(), + ldt.getDayOfYear(), TDS.BASE_YEAR_1900); // Number of milliseconds since midnight of the current day. int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into - // the current - // second - 1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute - 60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour - 60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day + // the current + // second + 1000 * ldt.getSecond() + // Seconds into the current minute + 60 * 1000 * ldt.getMinute() + // Minutes into the current hour + 60 * 60 * 1000 * ldt.getHour(); // Hours into the current day // The last millisecond of the current day is always rounded to the first millisecond // of the next day because DATETIME is only accurate to 1/300th of a second. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 48160ee70..c5401e51c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2461,7 +2461,11 @@ else if (null != sourceCryptoMeta) { case DATETIME: if (bulkNullable) tdsWriter.writeByte((byte) 0x08); - tdsWriter.writeDatetime(colValue.toString()); + + if (colValue instanceof java.sql.Timestamp) + tdsWriter.writeDatetime((java.sql.Timestamp)colValue); + else + tdsWriter.writeDatetime(colValue.toString()); break; default: // DATETIME2 if (2 >= bulkScale) From df938e173e1f91966421dc191f4a385bd02f9834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Sandstr=C3=B6m?= Date: Wed, 16 Aug 2023 18:03:01 +0200 Subject: [PATCH 2/4] Added test case for bulk loading many rows of timestamps --- .../jdbc/bulkCopy/BulkCopyTimestampTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java new file mode 100644 index 000000000..e0836582f --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java @@ -0,0 +1,111 @@ +package com.microsoft.sqlserver.jdbc.bulkCopy; + +import com.microsoft.sqlserver.jdbc.*; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import javax.sql.RowSetMetaData; +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetFactory; +import javax.sql.rowset.RowSetMetaDataImpl; +import javax.sql.rowset.RowSetProvider; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@RunWith(JUnitPlatform.class) +public class BulkCopyTimestampTest extends AbstractTest { + + public static final int COLUMN_COUNT = 16; + public static final int ROW_COUNT = 10000; + private static final String tableName = + AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); + + @Test + public void testBulkCopyTimestamp() throws SQLException { + List timeStamps = new ArrayList<>(); + try (Connection con = getConnection(); Statement stmt = connection.createStatement()) { + RowSetFactory rsf = RowSetProvider.newFactory(); + CachedRowSet crs = rsf.createCachedRowSet(); + RowSetMetaData rsmd = new RowSetMetaDataImpl(); + rsmd.setColumnCount(COLUMN_COUNT); + + for (int i = 1; i <= COLUMN_COUNT; i++) { + rsmd.setColumnName(i, String.format("c%d", i)); + rsmd.setColumnType(i, Types.TIMESTAMP); + } + + crs.setMetaData(rsmd); + + + for (int i = 0; i < COLUMN_COUNT; i++) { + timeStamps.add(RandomData.generateDatetime(false)); + } + + for (int ri = 0; ri < ROW_COUNT; ri++) { + crs.moveToInsertRow(); + + for (int i = 1; i <= COLUMN_COUNT; i++) { + crs.updateTimestamp(i, timeStamps.get(i - 1)); + + } + + crs.insertRow(); + } + + crs.moveToCurrentRow(); + + try (SQLServerBulkCopy bcOperation = new SQLServerBulkCopy(con)) { + SQLServerBulkCopyOptions bcOptions = new SQLServerBulkCopyOptions(); + bcOptions.setBatchSize(5000); + bcOperation.setDestinationTableName(tableName); + bcOperation.setBulkCopyOptions(bcOptions); + bcOperation.writeToServer(crs); + } + + try (ResultSet rs = stmt.executeQuery("select * from " + tableName)) { + assertTrue(rs.next()); + + for (int i = 1; i <= COLUMN_COUNT; i++) { + long expectedTimestamp = getTime(timeStamps.get(i - 1)); + long actualTimestamp = getTime(rs.getTimestamp(i)); + + assertEquals(expectedTimestamp, actualTimestamp); + } + } + } + } + + private static long getTime(Timestamp time) { + return (3 * time.getTime() + 5) / 10; + } + + @BeforeAll + public static void testSetup() throws Exception { + setConnection(); + + try (Statement stmt = connection.createStatement()) { + String colSpec = IntStream.range(1, COLUMN_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect(Collectors.joining(",")); + String sql1 = String.format("create table %s (%s)", tableName, colSpec); + stmt.execute(sql1); + } + } + + @AfterAll + public static void terminateVariation() throws SQLException { + try (Statement stmt = connection.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + } + } +} From 73c3d8862c749ce38bf81753d83dc967d95c66eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Sandstr=C3=B6m?= Date: Thu, 28 Sep 2023 21:10:56 +0200 Subject: [PATCH 3/4] Adjusted core per review remarks --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 51 ++++++-- .../sqlserver/jdbc/SQLServerBulkCopy.java | 11 +- .../jdbc/bulkCopy/BulkCopyAllTypesTest.java | 90 ++++++++++++++ .../jdbc/bulkCopy/BulkCopyTimestampTest.java | 111 ------------------ 4 files changed, 138 insertions(+), 125 deletions(-) delete mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index a84ee750c..f9aa2eb6d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5,17 +5,28 @@ package com.microsoft.sqlserver.jdbc; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; -import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; - -import javax.net.SocketFactory; -import javax.net.ssl.*; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.net.*; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -32,8 +43,20 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.SimpleTimeZone; +import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -46,6 +69,18 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.SocketFactory; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; +import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; + final class TDS { // application protocol diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index c5401e51c..e7918e26b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2699,15 +2699,14 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou tdsWriter.writeTime((java.sql.Timestamp) colValue, timeBulkScale); break; - case DATETIME8: - writeBulkCopySqlVariantHeader(10, TDSType.DATETIME8.byteValue(), (byte) 0, tdsWriter); - tdsWriter.writeDatetime(colValue.toString()); - break; - case DATETIME4: // when the type is ambiguous, we write to bigger type + case DATETIME8: writeBulkCopySqlVariantHeader(10, TDSType.DATETIME8.byteValue(), (byte) 0, tdsWriter); - tdsWriter.writeDatetime(colValue.toString()); + if (colValue instanceof java.sql.Timestamp) + tdsWriter.writeDatetime((java.sql.Timestamp)colValue); + else + tdsWriter.writeDatetime(colValue.toString()); break; case DATETIME2N: diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java index 11d88d616..da927ecf6 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java @@ -8,6 +8,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; @@ -16,8 +18,12 @@ import org.junit.runner.RunWith; import com.microsoft.sqlserver.jdbc.ComparisonUtil; +import com.microsoft.sqlserver.jdbc.RandomData; +import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; +import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions; import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; import com.microsoft.sqlserver.testframework.DBConnection; @@ -25,6 +31,20 @@ import com.microsoft.sqlserver.testframework.DBTable; import com.microsoft.sqlserver.testframework.PrepUtil; +import javax.sql.RowSetMetaData; +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetFactory; +import javax.sql.rowset.RowSetMetaDataImpl; +import javax.sql.rowset.RowSetProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + @RunWith(JUnitPlatform.class) public class BulkCopyAllTypesTest extends AbstractTest { @@ -35,6 +55,7 @@ public class BulkCopyAllTypesTest extends AbstractTest { @BeforeAll public static void setupTests() throws Exception { setConnection(); + createDatetimeTestTable(); } /** @@ -98,6 +119,75 @@ private void terminateVariation() throws SQLException { try (Statement stmt = connection.createStatement()) { TestUtils.dropTableIfExists(tableSrc.getEscapedTableName(), stmt); TestUtils.dropTableIfExists(tableDest.getEscapedTableName(), stmt); + TestUtils.dropTableIfExists(dateTimeTestTable, stmt); + } + } + + public static final int DATETIME_COL_COUNT = 2; + public static final int DATETIME_ROW_COUNT = 1; + private static final String dateTimeTestTable = + AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); + + @Test + public void testBulkCopyTimestamp() throws SQLException { + List timeStamps = new ArrayList<>(); + try (Connection con = getConnection(); Statement stmt = connection.createStatement()) { + RowSetFactory rsf = RowSetProvider.newFactory(); + CachedRowSet crs = rsf.createCachedRowSet(); + RowSetMetaData rsmd = new RowSetMetaDataImpl(); + rsmd.setColumnCount(DATETIME_COL_COUNT); + + for (int i = 1; i <= DATETIME_COL_COUNT; i++) { + rsmd.setColumnName(i, String.format("c%d", i)); + rsmd.setColumnType(i, Types.TIMESTAMP); + } + crs.setMetaData(rsmd); + + for (int i = 0; i < DATETIME_COL_COUNT; i++) { + timeStamps.add(RandomData.generateDatetime(false)); + } + + for (int ri = 0; ri < DATETIME_ROW_COUNT; ri++) { + crs.moveToInsertRow(); + + for (int i = 1; i <= DATETIME_COL_COUNT; i++) { + crs.updateTimestamp(i, timeStamps.get(i - 1)); + } + crs.insertRow(); + } + crs.moveToCurrentRow(); + + try (SQLServerBulkCopy bcOperation = new SQLServerBulkCopy(con)) { + SQLServerBulkCopyOptions bcOptions = new SQLServerBulkCopyOptions(); + bcOptions.setBatchSize(5000); + bcOperation.setDestinationTableName(dateTimeTestTable); + bcOperation.setBulkCopyOptions(bcOptions); + bcOperation.writeToServer(crs); + } + + try (ResultSet rs = stmt.executeQuery("select * from " + dateTimeTestTable)) { + assertTrue(rs.next()); + + for (int i = 1; i <= DATETIME_COL_COUNT; i++) { + long expectedTimestamp = getTime(timeStamps.get(i - 1)); + long actualTimestamp = getTime(rs.getTimestamp(i)); + + assertEquals(expectedTimestamp, actualTimestamp); + } + } + } + } + + private static long getTime(Timestamp time) { + return (3 * time.getTime() + 5) / 10; + } + + private static void createDatetimeTestTable() throws SQLException { + try (Statement stmt = connection.createStatement()) { + String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect( + Collectors.joining(",")); + String sql1 = String.format("create table %s (%s)", dateTimeTestTable, colSpec); + stmt.execute(sql1); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java deleted file mode 100644 index e0836582f..000000000 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTimestampTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.microsoft.sqlserver.jdbc.bulkCopy; - -import com.microsoft.sqlserver.jdbc.*; -import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; -import com.microsoft.sqlserver.testframework.AbstractTest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.platform.runner.JUnitPlatform; -import org.junit.runner.RunWith; - -import javax.sql.RowSetMetaData; -import javax.sql.rowset.CachedRowSet; -import javax.sql.rowset.RowSetFactory; -import javax.sql.rowset.RowSetMetaDataImpl; -import javax.sql.rowset.RowSetProvider; -import java.sql.*; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -@RunWith(JUnitPlatform.class) -public class BulkCopyTimestampTest extends AbstractTest { - - public static final int COLUMN_COUNT = 16; - public static final int ROW_COUNT = 10000; - private static final String tableName = - AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); - - @Test - public void testBulkCopyTimestamp() throws SQLException { - List timeStamps = new ArrayList<>(); - try (Connection con = getConnection(); Statement stmt = connection.createStatement()) { - RowSetFactory rsf = RowSetProvider.newFactory(); - CachedRowSet crs = rsf.createCachedRowSet(); - RowSetMetaData rsmd = new RowSetMetaDataImpl(); - rsmd.setColumnCount(COLUMN_COUNT); - - for (int i = 1; i <= COLUMN_COUNT; i++) { - rsmd.setColumnName(i, String.format("c%d", i)); - rsmd.setColumnType(i, Types.TIMESTAMP); - } - - crs.setMetaData(rsmd); - - - for (int i = 0; i < COLUMN_COUNT; i++) { - timeStamps.add(RandomData.generateDatetime(false)); - } - - for (int ri = 0; ri < ROW_COUNT; ri++) { - crs.moveToInsertRow(); - - for (int i = 1; i <= COLUMN_COUNT; i++) { - crs.updateTimestamp(i, timeStamps.get(i - 1)); - - } - - crs.insertRow(); - } - - crs.moveToCurrentRow(); - - try (SQLServerBulkCopy bcOperation = new SQLServerBulkCopy(con)) { - SQLServerBulkCopyOptions bcOptions = new SQLServerBulkCopyOptions(); - bcOptions.setBatchSize(5000); - bcOperation.setDestinationTableName(tableName); - bcOperation.setBulkCopyOptions(bcOptions); - bcOperation.writeToServer(crs); - } - - try (ResultSet rs = stmt.executeQuery("select * from " + tableName)) { - assertTrue(rs.next()); - - for (int i = 1; i <= COLUMN_COUNT; i++) { - long expectedTimestamp = getTime(timeStamps.get(i - 1)); - long actualTimestamp = getTime(rs.getTimestamp(i)); - - assertEquals(expectedTimestamp, actualTimestamp); - } - } - } - } - - private static long getTime(Timestamp time) { - return (3 * time.getTime() + 5) / 10; - } - - @BeforeAll - public static void testSetup() throws Exception { - setConnection(); - - try (Statement stmt = connection.createStatement()) { - String colSpec = IntStream.range(1, COLUMN_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect(Collectors.joining(",")); - String sql1 = String.format("create table %s (%s)", tableName, colSpec); - stmt.execute(sql1); - } - } - - @AfterAll - public static void terminateVariation() throws SQLException { - try (Statement stmt = connection.createStatement()) { - TestUtils.dropTableIfExists(tableName, stmt); - } - } -} From 74db912e836d9dc3636f809b3c7f82995ef05419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Sandstr=C3=B6m?= Date: Thu, 19 Oct 2023 20:38:02 +0200 Subject: [PATCH 4/4] Adjusted code per review --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 5 ----- .../sqlserver/jdbc/SQLServerBulkCopy.java | 18 ++++++++++-------- .../jdbc/bulkCopy/BulkCopyAllTypesTest.java | 19 +++++++------------ 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index f9aa2eb6d..9b777ee91 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -3756,13 +3756,8 @@ void writeSmalldatetime(String value) throws SQLServerException { writeShort((short) minutesSinceMidnight); } - void writeDatetime(String value) throws SQLServerException { - writeDatetime(java.sql.Timestamp.valueOf(value)); - } - void writeDatetime(java.sql.Timestamp dateValue) throws SQLServerException { LocalDateTime ldt = dateValue.toLocalDateTime(); - long utcMillis; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT) int subSecondNanos; subSecondNanos = ldt.getNano(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index e7918e26b..9f95699e2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2462,10 +2462,11 @@ else if (null != sourceCryptoMeta) { if (bulkNullable) tdsWriter.writeByte((byte) 0x08); - if (colValue instanceof java.sql.Timestamp) - tdsWriter.writeDatetime((java.sql.Timestamp)colValue); - else - tdsWriter.writeDatetime(colValue.toString()); + if (colValue instanceof java.sql.Timestamp) { + tdsWriter.writeDatetime((java.sql.Timestamp) colValue); + } else { + tdsWriter.writeDatetime(java.sql.Timestamp.valueOf(colValue.toString())); + } break; default: // DATETIME2 if (2 >= bulkScale) @@ -2703,10 +2704,11 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou // when the type is ambiguous, we write to bigger type case DATETIME8: writeBulkCopySqlVariantHeader(10, TDSType.DATETIME8.byteValue(), (byte) 0, tdsWriter); - if (colValue instanceof java.sql.Timestamp) - tdsWriter.writeDatetime((java.sql.Timestamp)colValue); - else - tdsWriter.writeDatetime(colValue.toString()); + if (colValue instanceof java.sql.Timestamp) { + tdsWriter.writeDatetime((java.sql.Timestamp) colValue); + } else { + tdsWriter.writeDatetime(java.sql.Timestamp.valueOf(colValue.toString())); + } break; case DATETIME2N: diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java index da927ecf6..a7e69b014 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java @@ -55,7 +55,6 @@ public class BulkCopyAllTypesTest extends AbstractTest { @BeforeAll public static void setupTests() throws Exception { setConnection(); - createDatetimeTestTable(); } /** @@ -123,8 +122,8 @@ private void terminateVariation() throws SQLException { } } - public static final int DATETIME_COL_COUNT = 2; - public static final int DATETIME_ROW_COUNT = 1; + private static final int DATETIME_COL_COUNT = 2; + private static final int DATETIME_ROW_COUNT = 1; private static final String dateTimeTestTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); @@ -132,6 +131,11 @@ private void terminateVariation() throws SQLException { public void testBulkCopyTimestamp() throws SQLException { List timeStamps = new ArrayList<>(); try (Connection con = getConnection(); Statement stmt = connection.createStatement()) { + String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect( + Collectors.joining(",")); + String sql1 = String.format("create table %s (%s)", dateTimeTestTable, colSpec); + stmt.execute(sql1); + RowSetFactory rsf = RowSetProvider.newFactory(); CachedRowSet crs = rsf.createCachedRowSet(); RowSetMetaData rsmd = new RowSetMetaDataImpl(); @@ -181,13 +185,4 @@ public void testBulkCopyTimestamp() throws SQLException { private static long getTime(Timestamp time) { return (3 * time.getTime() + 5) / 10; } - - private static void createDatetimeTestTable() throws SQLException { - try (Statement stmt = connection.createStatement()) { - String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect( - Collectors.joining(",")); - String sql1 = String.format("create table %s (%s)", dateTimeTestTable, colSpec); - stmt.execute(sql1); - } - } }