Skip to content

Commit

Permalink
Apply timezone to Timestamp when using bulkcopy for batch insert (#2291)
Browse files Browse the repository at this point in the history
* Apply timezone to Timestamps for batch insert with bulkcopy

* Formatted files

* Removed test comment

* Code review, adjusted implementation

* Added additional test

* New implementation; TODO: cleanup PR

* New fix implementation

* Removed unused imports

* Code review

* Bulkcopy CSV test fix
  • Loading branch information
tkyc authored Jan 19, 2024
1 parent 829321f commit bb76a78
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 20 deletions.
30 changes: 29 additions & 1 deletion src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ private void writeInternal(byte[] b, int off, int len) throws IOException {
private final class ProxyInputStream extends InputStream {
private InputStream filteredStream;

private final transient Lock proxyInputStreamLock = new ReentrantLock();
private final Lock proxyInputStreamLock = new ReentrantLock();

/**
* Bytes that have been read by a poll(s).
Expand Down Expand Up @@ -3825,6 +3825,20 @@ void writeDate(String value) throws SQLServerException {
SSType.DATE);
}

void writeDate(long utcMillis, Calendar cal) throws SQLServerException {
GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());

// Load the calendar with the desired value
calendar.setTimeInMillis(utcMillis);
if (cal != null) {
calendar.setTimeZone(cal.getTimeZone());
}

writeScaledTemporal(calendar, 0, // subsecond nanos (none for a date value)
0, // scale (dates are not scaled)
SSType.DATE);
}

void writeTime(java.sql.Timestamp value, int scale) throws SQLServerException {
GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
long utcMillis; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
Expand All @@ -3838,6 +3852,20 @@ void writeTime(java.sql.Timestamp value, int scale) throws SQLServerException {
writeScaledTemporal(calendar, subSecondNanos, scale, SSType.TIME);
}

void writeTime(java.sql.Timestamp value, int scale, Calendar cal) throws SQLServerException {
GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
long utcMillis = value.getTime(); // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
int subSecondNanos = value.getNanos();

// Load the calendar with the desired value
calendar.setTimeInMillis(utcMillis);
if (cal != null) {
calendar.setTimeZone(cal.getTimeZone());
}

writeScaledTemporal(calendar, subSecondNanos, scale, SSType.TIME);
}

void writeDateTimeOffset(Object value, int scale, SSType destSSType) throws SQLServerException {
GregorianCalendar calendar;
TimeZone timeZone; // Time zone to associate with the value in the Gregorian calendar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,14 +498,14 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold,
*/
void setCalcBigDecimalPrecision(boolean calcBigDecimalPrecision);

/**
/**
* Specifies the flag for using Bulk Copy API for batch insert operations.
*
* @param useBulkCopyForBatchInsert
* boolean value for useBulkCopyForBatchInsert.
*/
void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert) ;
void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert);

/**
* Returns the useBulkCopyForBatchInsert value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SQLServerBulkBatchInsertRecord extends SQLServerBulkRecord {
*/
private static final long serialVersionUID = -955998113956445541L;

private transient List<Parameter[]> batchParam;
transient List<Parameter[]> batchParam;
private int batchParamIndex = -1;
private List<String> columnList;
private List<String> valueList;
Expand Down
35 changes: 27 additions & 8 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
Original file line number Diff line number Diff line change
Expand Up @@ -2064,7 +2064,7 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType,

private void writeColumnToTdsWriter(TDSWriter tdsWriter, int bulkPrecision, int bulkScale, int bulkJdbcType,
boolean bulkNullable, // should it be destNullable instead?
int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue) throws SQLServerException {
int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, Calendar cal) throws SQLServerException {
SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType;

bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType,
Expand Down Expand Up @@ -2480,10 +2480,17 @@ else if (4 >= bulkScale)
tdsWriter.writeByte((byte) 0x07);
else
tdsWriter.writeByte((byte) 0x08);
String timeStampValue = colValue.toString();
tdsWriter.writeTime(java.sql.Timestamp.valueOf(timeStampValue), bulkScale);

Timestamp ts;
if (colValue instanceof java.sql.Timestamp) {
ts = (Timestamp) colValue;
} else {
ts = Timestamp.valueOf(colValue.toString());
}

tdsWriter.writeTime(ts, bulkScale, cal);
// Send only the date part
tdsWriter.writeDate(timeStampValue.substring(0, timeStampValue.lastIndexOf(' ')));
tdsWriter.writeDate(ts.getTime(), cal);
}
}
break;
Expand Down Expand Up @@ -2981,7 +2988,7 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole
* Reads the given column from the result set current row and writes the data to tdsWriter.
*/
private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal,
Object colValue) throws SQLServerException {
Object colValue, Calendar cal) throws SQLServerException {
String destName = destColumnMetadata.get(destColOrdinal).columnName;
int srcPrecision, srcScale, destPrecision, srcJdbcType;
SSType destSSType = null;
Expand Down Expand Up @@ -3102,7 +3109,7 @@ else if (null != serverBulkData && (null == destCryptoMeta)) {
}
}
writeColumnToTdsWriter(tdsWriter, srcPrecision, srcScale, srcJdbcType, srcNullable, srcColOrdinal,
destColOrdinal, isStreaming, colValue);
destColOrdinal, isStreaming, colValue, cal);
}

/**
Expand Down Expand Up @@ -3633,7 +3640,7 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command,
// Loop for each destination column. The mappings is a many to one mapping
// where multiple source columns can be mapped to one destination column.
for (ColumnMapping columnMapping : columnMappings) {
writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal,
writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, null,
null // cell
// value is
// retrieved
Expand All @@ -3647,20 +3654,32 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command,
else {
// Get all the column values of the current row.
Object[] rowObjects;
Parameter[] params = null;

try {
rowObjects = serverBulkData.getRowData();
if (serverBulkData instanceof SQLServerBulkBatchInsertRecord) {
params = ((SQLServerBulkBatchInsertRecord) serverBulkData).batchParam.get(row);
}
} catch (Exception ex) {
// if no more data available to retrive
throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), ex);
}

for (ColumnMapping columnMapping : columnMappings) {

Object rowObject = rowObjects[columnMapping.sourceColumnOrdinal - 1];
Calendar cal = null;

if (rowObject instanceof Timestamp && params != null) {
cal = params[columnMapping.sourceColumnOrdinal - 1].getInputDTV().getCalendar();
}

// If the SQLServerBulkCSVRecord does not have metadata for columns, it returns strings in the
// object array.
// COnvert the strings using destination table types.
writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal,
rowObjects[columnMapping.sourceColumnOrdinal - 1]);
rowObject, cal);
}
}
row++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2589,7 +2589,8 @@ public void registerOutParameter(String parameterName, int sqlType) throws SQLSe
loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
new Object[] {parameterName, sqlType});
checkClosed();
registerOutParameterByName(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sqlType);
registerOutParameterByName(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD),
sqlType);
loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,14 +712,15 @@ public boolean getCalcBigDecimalPrecision() {
public void setCalcBigDecimalPrecision(boolean calcBigDecimalPrecision) {
wrappedConnection.setCalcBigDecimalPrecision(calcBigDecimalPrecision);
}

/**
* Returns the useBulkCopyForBatchInsert value.
*
* @return flag for using Bulk Copy API for batch insert operations.
*/
@Override
public boolean getUseBulkCopyForBatchInsert() {
return wrappedConnection.getUseBulkCopyForBatchInsert();
return wrappedConnection.getUseBulkCopyForBatchInsert();
}

/**
Expand All @@ -730,6 +731,6 @@ public boolean getUseBulkCopyForBatchInsert() {
*/
@Override
public void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert) {
wrappedConnection.setUseBulkCopyForBatchInsert(useBulkCopyForBatchInsert);
wrappedConnection.setUseBulkCopyForBatchInsert(useBulkCopyForBatchInsert);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -907,8 +907,8 @@ public final class SQLServerDriver implements java.sql.Driver {
Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue()), false,
TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION.getDefaultValue()), false,
TRUE_FALSE),
Boolean.toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION.getDefaultValue()),
false, TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(),
SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false,
new String[] {SSLProtocol.TLS.toString(), SSLProtocol.TLS_V10.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3422,6 +3422,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar c
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, cal});
checkClosed();

setValue(n, JDBCType.TIMESTAMP, x, JavaType.TIMESTAMP, cal, false);
loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
}
Expand All @@ -3433,6 +3434,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar c
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, cal, forceEncrypt});
checkClosed();

setValue(n, JDBCType.TIMESTAMP, x, JavaType.TIMESTAMP, cal, forceEncrypt);
loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ public void testDataSource() throws SQLServerException {
assertEquals(booleanPropValue, ds.getUseFlexibleCallableStatements(),
TestResource.getResource("R_valuesAreDifferent"));
ds.setCalcBigDecimalPrecision(booleanPropValue);
assertEquals(booleanPropValue, ds.getCalcBigDecimalPrecision(), TestResource.getResource("R_valuesAreDifferent"));
assertEquals(booleanPropValue, ds.getCalcBigDecimalPrecision(),
TestResource.getResource("R_valuesAreDifferent"));

ds.setServerCertificate(stringPropValue);
assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void testBasicConnectionAAD() throws Exception {
basicReconnect("jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";user="
+ azureUserName + ";password=" + azurePassword
+ ";loginTimeout=90;Authentication=ActiveDirectoryPassword");
retry = THROTTLE_RETRY_COUNT + 1;
retry = THROTTLE_RETRY_COUNT + 1;
} catch (Exception e) {
if (e.getMessage().matches(TestUtils.formatErrorMsg("R_crClientAllRecoveryAttemptsFailed"))) {
System.out.println(e.getMessage() + ". Recovery failed, retry #" + retry + " in "
Expand Down
Loading

0 comments on commit bb76a78

Please sign in to comment.