diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8875a0e1cc..75e387bded 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
+## [6.4.0] Stable Release
+### Added
+- Support added for AAD Integrated Authentication with ADAL4J on Windows/Linux/Mac OS [#603](https://github.com/Microsoft/mssql-jdbc/pull/603)
+- Enable Recover after MSDTC is restarted [#581](https://github.com/Microsoft/mssql-jdbc/pull/581)
+- Added Version Update configuration rules to project [#541](https://github.com/Microsoft/mssql-jdbc/pull/541)
+- JDK 9 Compatibility + JDBC 4.3 API support added to the driver [#601 (https://github.com/Microsoft/mssql-jdbc/pull/601)
+
+### Fixed Issues
+- Re-introduced Retry Logic for Prepared Statement Caching implementation and remove detect change context function [#618](https://github.com/Microsoft/mssql-jdbc/pull/618) and [#620](https://github.com/Microsoft/mssql-jdbc/pull/620)
+- Fixes for SonarQube Reported issues [#599](https://github.com/Microsoft/mssql-jdbc/pull/599)
+- Fixes for Random Assertion Errors [#597](https://github.com/Microsoft/mssql-jdbc/pull/597)
+
+### Changed
+- Updated Appveyor to use JDK9 building driver and running tests [#619](https://github.com/Microsoft/mssql-jdbc/pull/619)
+- JDK 7 compilation support removed from the driver [#601](https://github.com/Microsoft/mssql-jdbc/pull/601)
+
## [6.3.6] Preview Release
### Added
- Added support for using database name as part of the key for handle cache [#561](https://github.com/Microsoft/mssql-jdbc/pull/561)
diff --git a/README.md b/README.md
index 6d2d0a746d..4983f0c104 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to
com.microsoft.sqlserver
mssql-jdbc
- 6.2.2.jre8
+ 6.4.0.jre9
```
The driver can be downloaded from the [Microsoft Download Center](https://go.microsoft.com/fwlink/?linkid=852460).
@@ -120,14 +120,14 @@ Projects that require either of the two features need to explicitly declare the
com.microsoft.sqlserver
mssql-jdbc
- 6.3.6.jre8-preview
+ 6.4.0.jre9
compile
com.microsoft.azure
adal4j
- 1.3.0
+ 1.4.0
```
@@ -136,14 +136,14 @@ Projects that require either of the two features need to explicitly declare the
com.microsoft.sqlserver
mssql-jdbc
- 6.3.6.jre8-preview
+ 6.4.0.jre9
compile
com.microsoft.azure
adal4j
- 1.3.0
+ 1.4.0
@@ -160,7 +160,7 @@ We love contributions from the community. To help improve the quality of our co
Thank you!
## Guidelines for Reporting Issues
-We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you:
+We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you:
- Report each issue as a new issue (but check first if it's already been reported)
- Try to be detailed in your report. Useful information for good bug reports include:
diff --git a/appveyor.yml b/appveyor.yml
index 8caae887ff..5fff6778cd 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -4,7 +4,7 @@ init:
- cmd: net start MSSQL$%SQL_Instance%
environment:
- JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+ JAVA_HOME: C:\Program Files\Java\jdk9
mssql_jdbc_test_connection_properties: jdbc:sqlserver://localhost:1433;instanceName=%SQL_Instance%;databaseName=master;username=sa;password=Password12!;
matrix:
@@ -32,9 +32,9 @@ build_script:
- keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore clientcert.jks -deststoretype JKS -srcstorepass password -deststorepass password
- keytool -list -v -keystore clientcert.jks -storepass "password" > JavaKeyStore.txt
- cd..
- # - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild41
- # - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild42
+ - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild43
+ - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild42
-#test_script:
-# - mvn test -B -Pbuild41
-# - mvn test -B -Pbuild42
+test_script:
+ - mvn test -B -Pbuild43
+ - mvn test -B -Pbuild42
diff --git a/pom.xml b/pom.xml
index c6e459c4ec..6c41936d55 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.sqlserver
mssql-jdbc
- 6.4.0-SNAPSHOT.${jreVersion}
+ 6.4.0.${jreVersion}
jar
Microsoft JDBC Driver for SQL Server
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
index 81ab1a6034..9aaaa8f527 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
@@ -10,7 +10,7 @@
final class SQLJdbcVersion {
static final int major = 6;
- static final int minor = 3;
- static final int patch = 6;
+ static final int minor = 4;
+ static final int patch = 0;
static final int build = 0;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index c8232d71a6..432aa9e32b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -83,8 +83,6 @@
// Note all the public functions in this class also need to be defined in SQLServerConnectionPoolProxy.
public class SQLServerConnection implements ISQLServerConnection {
- boolean contextIsAlreadyChanged = false;
- boolean contextChanged = false;
long timerExpire;
boolean attemptRefreshTokenLocked = false;
@@ -276,15 +274,20 @@ static ParsedSQLCacheItem parseAndCacheSQL(Sha1HashKey key, String sql) throws
return cacheItem;
}
+ /** Default size for prepared statement caches */
+ static final int DEFAULT_STATEMENT_POOLING_CACHE_SIZE = 0;
+
/** Size of the prepared statement handle cache */
- private int statementPoolingCacheSize = 10;
+ private int statementPoolingCacheSize = DEFAULT_STATEMENT_POOLING_CACHE_SIZE;
- /** Default size for prepared statement caches */
- static final int DEFAULT_STATEMENT_POOLING_CACHE_SIZE = 10;
/** Cache of prepared statement handles */
private ConcurrentLinkedHashMap preparedStatementHandleCache;
/** Cache of prepared statement parameter metadata */
private ConcurrentLinkedHashMap parameterMetadataCache;
+ /**
+ * Checks whether statement pooling is enabled or disabled. The default is set to true;
+ */
+ private boolean disableStatementPooling = true;
/**
* Find statement parameters.
@@ -925,17 +928,10 @@ final boolean attachConnId() {
connectionlogger.severe(message);
throw new UnsupportedOperationException(message);
}
-
+
// Caching turned on?
- if (0 < this.getStatementPoolingCacheSize()) {
- preparedStatementHandleCache = new Builder()
- .maximumWeightedCapacity(getStatementPoolingCacheSize())
- .listener(new PreparedStatementCacheEvictionListener())
- .build();
-
- parameterMetadataCache = new Builder()
- .maximumWeightedCapacity(getStatementPoolingCacheSize())
- .build();
+ if (!this.getDisableStatementPooling() && 0 < this.getStatementPoolingCacheSize()) {
+ prepareCache();
}
}
@@ -1437,10 +1433,8 @@ Connection connectInternal(Properties propsIn,
sPropKey = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (null != sPropValue) {
- // If disabled set cache size to 0 if disabled.
- if(booleanPropertyOn(sPropKey, sPropValue))
- this.setStatementPoolingCacheSize(0);
- }
+ setDisableStatementPooling(booleanPropertyOn(sPropKey, sPropValue));
+ }
sPropKey = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
@@ -3080,8 +3074,6 @@ final void poolCloseEventNotify() throws SQLServerException {
checkClosed();
if (catalog != null) {
connectionCommand("use " + Util.escapeSQLId(catalog), "setCatalog");
- contextIsAlreadyChanged = true;
- contextChanged = true;
sCatalog = catalog;
}
loggerExternal.exiting(getClassNameLogging(), "setCatalog");
@@ -5692,6 +5684,26 @@ final void unprepareUnreferencedPreparedStatementHandles(boolean force) {
}
}
+ /**
+ * Returns true if statement pooling is disabled.
+ *
+ * @return
+ */
+ public boolean getDisableStatementPooling() {
+ return this.disableStatementPooling;
+ }
+
+ /**
+ * Sets statement pooling to true or false;
+ *
+ * @param value
+ */
+ public void setDisableStatementPooling(boolean value) {
+ this.disableStatementPooling = value;
+ if (!value && 0 < this.getStatementPoolingCacheSize()) {
+ prepareCache();
+ }
+ }
/**
* Returns the size of the prepared statement cache for this connection. A value less than 1 means no cache.
@@ -5699,7 +5711,7 @@ final void unprepareUnreferencedPreparedStatementHandles(boolean force) {
*/
public int getStatementPoolingCacheSize() {
return statementPoolingCacheSize;
- }
+ }
/**
* Returns the current number of pooled prepared statement handles.
@@ -5717,25 +5729,40 @@ public int getStatementHandleCacheEntryCount() {
* @return Returns the current setting per the description.
*/
public boolean isStatementPoolingEnabled() {
- return null != preparedStatementHandleCache && 0 < this.getStatementPoolingCacheSize();
+ return null != preparedStatementHandleCache && 0 < this.getStatementPoolingCacheSize() && !this.getDisableStatementPooling();
}
/**
- * Specifies the size of the prepared statement cache for this conection. A value less than 1 means no cache.
- * @param value The new cache size.
+ * Specifies the size of the prepared statement cache for this connection. A value less than 1 means no cache.
+ *
+ * @param value
+ * The new cache size.
*
*/
public void setStatementPoolingCacheSize(int value) {
- if (value != this.statementPoolingCacheSize) {
- value = Math.max(0, value);
- statementPoolingCacheSize = value;
-
- if (null != preparedStatementHandleCache)
- preparedStatementHandleCache.setCapacity(value);
+ value = Math.max(0, value);
+ statementPoolingCacheSize = value;
- if (null != parameterMetadataCache)
- parameterMetadataCache.setCapacity(value);
+ if (!this.disableStatementPooling && value > 0) {
+ prepareCache();
}
+ if (null != preparedStatementHandleCache)
+ preparedStatementHandleCache.setCapacity(value);
+
+ if (null != parameterMetadataCache)
+ parameterMetadataCache.setCapacity(value);
+ }
+
+ /**
+ * Internal method to prepare the cache handle
+ * @param value
+ */
+ private void prepareCache() {
+ preparedStatementHandleCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize())
+ .listener(new PreparedStatementCacheEvictionListener()).build();
+
+ parameterMetadataCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize())
+ .build();
}
/** Get a parameter metadata cache entry if statement pooling is enabled */
@@ -5788,12 +5815,6 @@ final void evictCachedPreparedStatementHandle(PreparedStatementHandle handle) {
preparedStatementHandleCache.remove(handle.getKey());
}
- final void clearCachedPreparedStatementHandle() {
- if (null != preparedStatementHandleCache) {
- preparedStatementHandleCache.clear();
- }
- }
-
// Handle closing handles when removed from cache.
final class PreparedStatementCacheEvictionListener implements EvictionListener {
public void onEviction(Sha1HashKey key, PreparedStatementHandle handle) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
index 3117ec4036..80d3a234d9 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
@@ -169,8 +169,6 @@ public void run() {
if (wrappedConnection.getConnectionLogger().isLoggable(Level.FINER))
wrappedConnection.getConnectionLogger().finer(toString() + " Connection proxy closed ");
- // clear cached prepared statement handle on this connection
- wrappedConnection.clearCachedPreparedStatementHandle();
wrappedConnection.poolCloseEventNotify();
wrappedConnection = null;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
index ebe4fee357..2f354f1c4d 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
@@ -744,7 +744,7 @@ public int getServerPreparedStatementDiscardThreshold() {
}
/**
- * Specifies the size of the prepared statement cache for this conection. A value less than 1 means no cache.
+ * Specifies the size of the prepared statement cache for this connection. A value less than 1 means no cache.
*
* @param statementPoolingCacheSize
* Changes the setting per the description.
@@ -754,7 +754,7 @@ public void setStatementPoolingCacheSize(int statementPoolingCacheSize) {
}
/**
- * Returns the size of the prepared statement cache for this conection. A value less than 1 means no cache.
+ * Returns the size of the prepared statement cache for this connection. A value less than 1 means no cache.
*
* @return Returns the current setting per the description.
*/
@@ -762,6 +762,24 @@ public int getStatementPoolingCacheSize() {
int defaultSize = SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.getDefaultValue();
return getIntProperty(connectionProps, SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.toString(), defaultSize);
}
+
+ /**
+ * Sets the statement pooling to true or false
+ * @param disableStatementPooling
+ */
+ public void setDisableStatementPooling(boolean disableStatementPooling) {
+ setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(), disableStatementPooling);
+ }
+
+ /**
+ * Returns true if statement pooling is disabled.
+ * @return
+ */
+ public boolean getDisableStatementPooling() {
+ boolean defaultValue = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.getDefaultValue();
+ return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(),
+ defaultValue);
+ }
/**
* Setting the socket timeout
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
index ae4e1fd09e..f04f0e7f4f 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
@@ -340,14 +340,14 @@ public String toString() {
enum SQLServerDriverBooleanProperty
{
- DISABLE_STATEMENT_POOLING ("disableStatementPooling", false),
+ DISABLE_STATEMENT_POOLING ("disableStatementPooling", true),
ENCRYPT ("encrypt", false),
INTEGRATED_SECURITY ("integratedSecurity", false),
LAST_UPDATE_COUNT ("lastUpdateCount", true),
MULTI_SUBNET_FAILOVER ("multiSubnetFailover", false),
SERVER_NAME_AS_ACE ("serverNameAsACE", false),
SEND_STRING_PARAMETERS_AS_UNICODE ("sendStringParametersAsUnicode", true),
- SEND_TIME_AS_DATETIME ("sendTimeAsDatetime", false),
+ SEND_TIME_AS_DATETIME ("sendTimeAsDatetime", true),
TRANSPARENT_NETWORK_IP_RESOLUTION ("TransparentNetworkIPResolution", true),
TRUST_SERVER_CERTIFICATE ("trustServerCertificate", false),
XOPEN_STATES ("xopenStates", false),
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index bdffeab96f..262728a94b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -51,8 +51,6 @@
public class SQLServerPreparedStatement extends SQLServerStatement implements ISQLServerPreparedStatement {
/** Flag to indicate that it is an internal query to retrieve encryption metadata. */
boolean isInternalEncryptionQuery = false;
-
- boolean definitionChanged = false;
/** delimiter for multiple statements in a single batch */
private static final int BATCH_STATEMENT_DELIMITER_TDS_71 = 0x80;
@@ -330,14 +328,7 @@ private boolean buildPreparedStrings(Parameter[] params,
boolean renewDefinition) throws SQLServerException {
String newTypeDefinitions = buildParamTypeDefinitions(params, renewDefinition);
if (null != preparedTypeDefinitions && newTypeDefinitions.equals(preparedTypeDefinitions))
- return false;
-
- if(preparedTypeDefinitions == null) {
- definitionChanged = false;
- }
- else {
- definitionChanged = true;
- }
+ return false;
preparedTypeDefinitions = newTypeDefinitions;
@@ -498,8 +489,6 @@ final void processResponse(TDSReader tdsReader) throws SQLServerException {
final void doExecutePreparedStatement(PrepStmtExecCmd command) throws SQLServerException {
resetForReexecute();
- definitionChanged = false;
-
// If this request might be a query (as opposed to an update) then make
// sure we set the max number of rows and max field size for any ResultSet
// that may be returned.
@@ -539,19 +528,33 @@ final void doExecutePreparedStatement(PrepStmtExecCmd command) throws SQLServerE
}
String dbName = connection.getSCatalog();
- if (reuseCachedHandle(hasNewTypeDefinitions, false, dbName)) {
- hasNewTypeDefinitions = false;
- }
+ boolean needsPrepare = true;
+ // Retry execution if existing handle could not be re-used.
+ for (int attempt = 1; attempt <= 2; ++attempt) {
+ try {
+ // Re-use handle if available, requires parameter definitions which are not available until here.
+ if (reuseCachedHandle(hasNewTypeDefinitions, 1 < attempt, dbName)) {
+ hasNewTypeDefinitions = false;
+ }
- // Start the request and detach the response reader so that we can
- // continue using it after we return.
- TDSWriter tdsWriter = command.startRequest(TDS.PKT_RPC);
+ // Start the request and detach the response reader so that we can
+ // continue using it after we return.
+ TDSWriter tdsWriter = command.startRequest(TDS.PKT_RPC);
- doPrepExec(tdsWriter, inOutParam, hasNewTypeDefinitions, hasExistingTypeDefinitions);
+ needsPrepare = doPrepExec(tdsWriter, inOutParam, hasNewTypeDefinitions, hasExistingTypeDefinitions);
- ensureExecuteResultsReader(command.startResponse(getIsResponseBufferingAdaptive()));
- startResults();
- getNextResult();
+ ensureExecuteResultsReader(command.startResponse(getIsResponseBufferingAdaptive()));
+ startResults();
+ getNextResult();
+ }
+ catch (SQLException e) {
+ if (retryBasedOnFailedReuseOfCachedHandle(e, attempt, needsPrepare))
+ continue;
+ else
+ throw e;
+ }
+ break;
+ }
if (EXECUTE_QUERY == executeMethod && null == resultSet) {
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_noResultset"), null, true);
@@ -560,6 +563,17 @@ else if (EXECUTE_UPDATE == executeMethod && null != resultSet) {
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false);
}
}
+
+ /** Should the execution be retried because the re-used cached handle could not be re-used due to server side state changes? */
+ private boolean retryBasedOnFailedReuseOfCachedHandle(SQLException e,
+ int attempt, boolean needsPrepare) {
+ // Only retry based on these error codes and if statementPooling is enabled:
+ // 586: The prepared statement handle %d is not valid in this context. Please verify that current database, user default schema, and
+ // ANSI_NULLS and QUOTED_IDENTIFIER set options are not changed since the handle is prepared.
+ // 8179: Could not find prepared statement with handle %d.
+ if(needsPrepare) return false;
+ return 1 == attempt && (586 == e.getErrorCode() || 8179 == e.getErrorCode()) && connection.isStatementPoolingEnabled();
+ }
/**
* Consume the OUT parameter for the statement object itself.
@@ -915,18 +929,6 @@ private void getParameterEncryptionMetadata(Parameter[] params) throws SQLServer
/** Manage re-using cached handles */
private boolean reuseCachedHandle(boolean hasNewTypeDefinitions, boolean discardCurrentCacheItem, String dbName) {
- if (definitionChanged || connection.contextChanged) {
- prepStmtHandle = -1; // so that hasPreparedStatementHandle() also returns false
-
- if (connection.contextChanged) {
- connection.contextChanged = false;
- connection.contextIsAlreadyChanged = false;
- connection.clearCachedPreparedStatementHandle();
- }
-
- return false;
- }
-
// No re-use of caching for cursorable statements (statements that WILL use sp_cursor*)
if (isCursorable(executeMethod))
return false;
@@ -2576,7 +2578,7 @@ final void doExecutePreparedStatementBatch(PrepStmtBatchExecCmd batchCommand) th
// Create the parameter array that we'll use for all the items in this batch.
Parameter[] batchParam = new Parameter[inOutParam.length];
- definitionChanged = false;
+
/*
TDSWriter tdsWriter = null;
while (numBatchesExecuted < numBatches) {
@@ -2595,122 +2597,148 @@ final void doExecutePreparedStatementBatch(PrepStmtBatchExecCmd batchCommand) th
*/
TDSWriter tdsWriter = null;
- try {
- while (numBatchesExecuted < numBatches) {
- // Fill in the parameter values for this batch
- Parameter paramValues[] = batchParamValues.get(numBatchesPrepared);
- assert paramValues.length == batchParam.length;
- System.arraycopy(paramValues, 0, batchParam, 0, paramValues.length);
-
- boolean hasExistingTypeDefinitions = preparedTypeDefinitions != null;
- boolean hasNewTypeDefinitions = buildPreparedStrings(batchParam, false);
-
- // Get the encryption metadata for the first batch only.
- if ((0 == numBatchesExecuted) && (Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)) && (0 < batchParam.length)
- && !isInternalEncryptionQuery && !encryptionMetadataIsRetrieved) {
- getParameterEncryptionMetadata(batchParam);
-
- // fix an issue when inserting unicode into non-encrypted nchar column using setString() and AE is on on Connection
- buildPreparedStrings(batchParam, true);
-
- // Save the crypto metadata retrieved for the first batch. We will re-use these for the rest of the batches.
- for (Parameter aBatchParam : batchParam) {
- cryptoMetaBatch.add(aBatchParam.cryptoMeta);
- }
- }
+ while (numBatchesExecuted < numBatches) {
+ // Fill in the parameter values for this batch
+ Parameter paramValues[] = batchParamValues.get(numBatchesPrepared);
+ assert paramValues.length == batchParam.length;
+ System.arraycopy(paramValues, 0, batchParam, 0, paramValues.length);
- // Update the crypto metadata for this batch.
- if (0 < numBatchesExecuted) {
- // cryptoMetaBatch will be empty for non-AE connections/statements.
- for (int i = 0; i < cryptoMetaBatch.size(); i++) {
- batchParam[i].cryptoMeta = cryptoMetaBatch.get(i);
- }
- }
-
- String dbName = connection.getSCatalog();
- if (reuseCachedHandle(hasNewTypeDefinitions, false, dbName)) {
- hasNewTypeDefinitions = false;
- }
+ boolean hasExistingTypeDefinitions = preparedTypeDefinitions != null;
+ boolean hasNewTypeDefinitions = buildPreparedStrings(batchParam, false);
- if (numBatchesExecuted < numBatchesPrepared) {
- // assert null != tdsWriter;
- tdsWriter.writeByte((byte) nBatchStatementDelimiter);
+ // Get the encryption metadata for the first batch only.
+ if ((0 == numBatchesExecuted) && (Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)) && (0 < batchParam.length)
+ && !isInternalEncryptionQuery && !encryptionMetadataIsRetrieved) {
+ getParameterEncryptionMetadata(batchParam);
+
+ // fix an issue when inserting unicode into non-encrypted nchar column using setString() and AE is on on Connection
+ buildPreparedStrings(batchParam, true);
+
+ // Save the crypto metadata retrieved for the first batch. We will re-use these for the rest of the batches.
+ for (Parameter aBatchParam : batchParam) {
+ cryptoMetaBatch.add(aBatchParam.cryptoMeta);
}
- else {
- resetForReexecute();
- tdsWriter = batchCommand.startRequest(TDS.PKT_RPC);
+ }
+
+ // Update the crypto metadata for this batch.
+ if (0 < numBatchesExecuted) {
+ // cryptoMetaBatch will be empty for non-AE connections/statements.
+ for (int i = 0; i < cryptoMetaBatch.size(); i++) {
+ batchParam[i].cryptoMeta = cryptoMetaBatch.get(i);
}
+ }
- // If we have to (re)prepare the statement then we must execute it so
- // that we get back a (new) prepared statement handle to use to
- // execute additional batches.
- //
- // We must always prepare the statement the first time through.
- // But we may also need to reprepare the statement if, for example,
- // the size of a batch's string parameter values changes such
- // that repreparation is necessary.
- ++numBatchesPrepared;
-
- if (doPrepExec(tdsWriter, batchParam, hasNewTypeDefinitions, hasExistingTypeDefinitions) || numBatchesPrepared == numBatches) {
- ensureExecuteResultsReader(batchCommand.startResponse(getIsResponseBufferingAdaptive()));
-
- while (numBatchesExecuted < numBatchesPrepared) {
- // NOTE:
- // When making changes to anything below, consider whether similar changes need
- // to be made to Statement batch execution.
-
- startResults();
-
- try {
- // Get the first result from the batch. If there is no result for this batch
- // then bail, leaving EXECUTE_FAILED in the current and remaining slots of
- // the update count array.
- if (!getNextResult())
- return;
-
- // If the result is a ResultSet (rather than an update count) then throw an
- // exception for this result. The exception gets caught immediately below and
- // translated into (or added to) a BatchUpdateException.
- if (null != resultSet) {
- SQLServerException.makeFromDriverError(connection, this,
- SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false);
- }
- }
- catch (SQLServerException e) {
- // If the failure was severe enough to close the connection or roll back a
- // manual transaction, then propagate the error up as a SQLServerException
- // now, rather than continue with the batch.
- if (connection.isSessionUnAvailable() || connection.rolledBackTransaction())
- throw e;
-
- // Otherwise, the connection is OK and the transaction is still intact,
- // so just record the failure for the particular batch item.
- updateCount = Statement.EXECUTE_FAILED;
- if (null == batchCommand.batchException)
- batchCommand.batchException = e;
- }
+ String dbName = connection.getSCatalog();
+ boolean needsPrepare = true;
+ // Retry execution if existing handle could not be re-used.
+ for (int attempt = 1; attempt <= 2; ++attempt) {
+ try {
- // In batch execution, we have a special update count
- // to indicate that no information was returned
- batchCommand.updateCounts[numBatchesExecuted] = (-1 == updateCount) ? Statement.SUCCESS_NO_INFO : updateCount;
- processBatch();
+ // Re-use handle if available, requires parameter definitions which are not available until here.
+ if (reuseCachedHandle(hasNewTypeDefinitions, 1 < attempt, dbName)) {
+ hasNewTypeDefinitions = false;
+ }
- numBatchesExecuted++;
+ if (numBatchesExecuted < numBatchesPrepared) {
+ // assert null != tdsWriter;
+ tdsWriter.writeByte((byte) nBatchStatementDelimiter);
+ }
+ else {
+ resetForReexecute();
+ tdsWriter = batchCommand.startRequest(TDS.PKT_RPC);
}
- // Only way to proceed with preparing the next set of batches is if
- // we successfully executed the previously prepared set.
- assert numBatchesExecuted == numBatchesPrepared;
+ // If we have to (re)prepare the statement then we must execute it so
+ // that we get back a (new) prepared statement handle to use to
+ // execute additional batches.
+ //
+ // We must always prepare the statement the first time through.
+ // But we may also need to reprepare the statement if, for example,
+ // the size of a batch's string parameter values changes such
+ // that repreparation is necessary.
+ ++numBatchesPrepared;
+ needsPrepare = doPrepExec(tdsWriter, batchParam, hasNewTypeDefinitions, hasExistingTypeDefinitions);
+ if ( needsPrepare || numBatchesPrepared == numBatches) {
+ ensureExecuteResultsReader(batchCommand.startResponse(getIsResponseBufferingAdaptive()));
+
+ boolean retry = false;
+ while (numBatchesExecuted < numBatchesPrepared) {
+ // NOTE:
+ // When making changes to anything below, consider whether similar changes need
+ // to be made to Statement batch execution.
+
+ startResults();
+
+ try {
+ // Get the first result from the batch. If there is no result for this batch
+ // then bail, leaving EXECUTE_FAILED in the current and remaining slots of
+ // the update count array.
+ if (!getNextResult())
+ return;
+
+ // If the result is a ResultSet (rather than an update count) then throw an
+ // exception for this result. The exception gets caught immediately below and
+ // translated into (or added to) a BatchUpdateException.
+ if (null != resultSet) {
+ SQLServerException.makeFromDriverError(connection, this,
+ SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false);
+ }
+ }
+ catch (SQLServerException e) {
+ // If the failure was severe enough to close the connection or roll back a
+ // manual transaction, then propagate the error up as a SQLServerException
+ // now, rather than continue with the batch.
+ if (connection.isSessionUnAvailable() || connection.rolledBackTransaction())
+ throw e;
+
+ // Retry if invalid handle exception.
+ if (retryBasedOnFailedReuseOfCachedHandle(e, attempt, needsPrepare)) {
+ // reset number of batches prepare
+ numBatchesPrepared = numBatchesExecuted;
+ retry = true;
+ break;
+ }
+
+ // Otherwise, the connection is OK and the transaction is still intact,
+ // so just record the failure for the particular batch item.
+ updateCount = Statement.EXECUTE_FAILED;
+ if (null == batchCommand.batchException)
+ batchCommand.batchException = e;
+
+ }
+
+ // In batch execution, we have a special update count
+ // to indicate that no information was returned
+ batchCommand.updateCounts[numBatchesExecuted] = (-1 == updateCount) ? Statement.SUCCESS_NO_INFO : updateCount;
+ processBatch();
+
+ numBatchesExecuted++;
+ }
+ if (retry)
+ continue;
+
+ // Only way to proceed with preparing the next set of batches is if
+ // we successfully executed the previously prepared set.
+ assert numBatchesExecuted == numBatchesPrepared;
+ }
}
- }
- }
- catch (SQLServerException e) {
- // throw the initial batchException
- if (null != batchCommand.batchException) {
- throw batchCommand.batchException;
- }
- else {
- throw e;
+ catch (SQLException e) {
+ if (retryBasedOnFailedReuseOfCachedHandle(e, attempt, needsPrepare) && connection.isStatementPoolingEnabled()) {
+ // Reset number of batches prepared.
+ numBatchesPrepared = numBatchesExecuted;
+ continue;
+ }
+ else if (null != batchCommand.batchException) {
+ // if batch exception occurred, loop out to throw the initial batchException
+ numBatchesExecuted = numBatchesPrepared;
+ attempt++;
+ continue;
+ }
+ else {
+ throw e;
+ }
+ }
+ break;
}
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
index 696cfa6c50..7d76084330 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
@@ -800,21 +800,6 @@ final void setMaxRowsAndMaxFieldSize() throws SQLServerException {
}
}
- /*
- * check if context has been changed by monitoring statment call on the connection, since context is connection based.
- */
- void checkIfContextChanged(String sql) {
- if (connection.contextIsAlreadyChanged) {
- connection.contextChanged = true;
- return;
- }
- else if (sql.toUpperCase().contains("ANSI_NULLS") || sql.toUpperCase().contains("QUOTED_IDENTIFIER") || sql.toUpperCase().contains("USE")
- || sql.toUpperCase().contains("DEFAULT_SCHEMA")) {
- connection.contextIsAlreadyChanged = true;
- connection.contextChanged = true;
- }
- }
-
final void doExecuteStatement(StmtExecCmd execCmd) throws SQLServerException {
resetForReexecute();
@@ -826,8 +811,6 @@ final void doExecuteStatement(StmtExecCmd execCmd) throws SQLServerException {
// call syntax is rewritten here as SQL exec syntax.
String sql = ensureSQLSyntax(execCmd.sql);
- checkIfContextChanged(sql);
-
// If this request might be a query (as opposed to an update) then make
// sure we set the max number of rows and max field size for any ResultSet
// that may be returned.
@@ -931,9 +914,7 @@ private void doExecuteStatementBatch(StmtBatchExecCmd execCmd) throws SQLServerE
tdsWriter.writeString(batchIter.next());
while (batchIter.hasNext()) {
tdsWriter.writeString(" ; ");
- String sql = batchIter.next();
- tdsWriter.writeString(sql);
- checkIfContextChanged(sql);
+ tdsWriter.writeString(batchIter.next());
}
// Start the response
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
index 11edce8c65..58c7969ab4 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
@@ -8,10 +8,10 @@
package com.microsoft.sqlserver.jdbc.unit.statement;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.jupiter.api.Assertions.assertNotSame;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
import java.sql.BatchUpdateException;
@@ -173,7 +173,7 @@ public void testStatementPooling() throws SQLException {
}
}
- try (SQLServerConnection con = (SQLServerConnection) DriverManager.getConnection(connectionString)) {
+ try (SQLServerConnection con = (SQLServerConnection)DriverManager.getConnection(connectionString)) {
// Test behvaior with statement pooling.
con.setStatementPoolingCacheSize(10);
@@ -239,9 +239,10 @@ public void testStatementPooling() throws SQLException {
}
}
}
-
+
try (SQLServerConnection con = (SQLServerConnection)DriverManager.getConnection(connectionString)) {
// Test behvaior with statement pooling.
+ con.setDisableStatementPooling(false);
con.setStatementPoolingCacheSize(10);
String lookupUniqueifier = UUID.randomUUID().toString();
@@ -304,11 +305,12 @@ public void testStatementPoolingEviction() throws SQLException {
for (int testNo = 0; testNo < 2; ++testNo) {
try (SQLServerConnection con = (SQLServerConnection)DriverManager.getConnection(connectionString)) {
-
int cacheSize = 10;
int discardedStatementCount = testNo == 0 ? 5 /*batched unprepares*/ : 0 /*regular unprepares*/;
- con.setStatementPoolingCacheSize(cacheSize);
+ // enabling caching
+ con.setDisableStatementPooling(false);
+ con.setStatementPoolingCacheSize(cacheSize);
con.setServerPreparedStatementDiscardThreshold(discardedStatementCount);
String lookupUniqueifier = UUID.randomUUID().toString();
@@ -452,7 +454,7 @@ public void testStatementPoolingPreparedStatementExecAndUnprepareConfig() throws
SQLServerDataSource dataSource = new SQLServerDataSource();
dataSource.setURL(connectionString);
// Verify defaults.
- assertTrue(0 < dataSource.getStatementPoolingCacheSize());
+ assertTrue(0 == dataSource.getStatementPoolingCacheSize());
// Verify change
dataSource.setStatementPoolingCacheSize(0);
assertSame(0, dataSource.getStatementPoolingCacheSize());
@@ -469,12 +471,34 @@ public void testStatementPoolingPreparedStatementExecAndUnprepareConfig() throws
// Test disableStatementPooling
String connectionStringDisableStatementPooling = connectionString + ";disableStatementPooling=true;";
SQLServerConnection connectionDisableStatementPooling = (SQLServerConnection)DriverManager.getConnection(connectionStringDisableStatementPooling);
- assertSame(0, connectionDisableStatementPooling.getStatementPoolingCacheSize());
+ connectionDisableStatementPooling.setStatementPoolingCacheSize(10); // to turn on caching and check if disableStatementPooling is true, even setting cachesize won't matter and will disable it.
+ assertSame(10, connectionDisableStatementPooling.getStatementPoolingCacheSize());
assertTrue(!connectionDisableStatementPooling.isStatementPoolingEnabled());
String connectionStringEnableStatementPooling = connectionString + ";disableStatementPooling=false;";
SQLServerConnection connectionEnableStatementPooling = (SQLServerConnection)DriverManager.getConnection(connectionStringEnableStatementPooling);
- assertTrue(0 < connectionEnableStatementPooling.getStatementPoolingCacheSize());
-
+ connectionEnableStatementPooling.setStatementPoolingCacheSize(10); // to turn on caching.
+ assertTrue(0 < connectionEnableStatementPooling.getStatementPoolingCacheSize()); // for now, it won't affect if disable is false or true. Since statementPoolingCacheSize is set to 0 as default.
+ //If only disableStatementPooling is set to true, it makes sure that statementPoolingCacheSize is zero, thus disabling the prepared statement metadata caching.
+ assertTrue(connectionEnableStatementPooling.isStatementPoolingEnabled());
+
+ String connectionPropertyStringEnableStatementPooling = connectionString + ";disableStatementPooling=false;statementPoolingCacheSize=10";
+ SQLServerConnection connectionPropertyEnableStatementPooling = (SQLServerConnection)DriverManager.getConnection(connectionPropertyStringEnableStatementPooling);
+ assertTrue(0 < connectionPropertyEnableStatementPooling.getStatementPoolingCacheSize()); // for now, it won't affect if disable is false or true. Since statementPoolingCacheSize is set to 0 as default.
+ //If only disableStatementPooling is set to true, it makes sure that statementPoolingCacheSize is zero, thus disabling the prepared statement metadata caching.
+ assertTrue(connectionPropertyEnableStatementPooling.isStatementPoolingEnabled());
+
+ String connectionPropertyStringDisableStatementPooling = connectionString + ";disableStatementPooling=true;statementPoolingCacheSize=10";
+ SQLServerConnection connectionPropertyDisableStatementPooling = (SQLServerConnection)DriverManager.getConnection(connectionPropertyStringDisableStatementPooling);
+ assertTrue(0 < connectionPropertyDisableStatementPooling.getStatementPoolingCacheSize()); // for now, it won't affect if disable is false or true. Since statementPoolingCacheSize is set to 0 as default.
+ //If only disableStatementPooling is set to true, it makes sure that statementPoolingCacheSize is zero, thus disabling the prepared statement metadata caching.
+ assertTrue(!connectionPropertyDisableStatementPooling.isStatementPoolingEnabled());
+
+ String connectionPropertyStringDisableStatementPooling2 = connectionString + ";disableStatementPooling=false;statementPoolingCacheSize=0";
+ SQLServerConnection connectionPropertyDisableStatementPooling2 = (SQLServerConnection)DriverManager.getConnection(connectionPropertyStringDisableStatementPooling2);
+ assertTrue(0 == connectionPropertyDisableStatementPooling2.getStatementPoolingCacheSize()); // for now, it won't affect if disable is false or true. Since statementPoolingCacheSize is set to 0 as default.
+ //If only disableStatementPooling is set to true, it makes sure that statementPoolingCacheSize is zero, thus disabling the prepared statement metadata caching.
+ assertTrue(!connectionPropertyDisableStatementPooling2.isStatementPoolingEnabled());
+
// Test EnablePrepareOnFirstPreparedStatementCall
String connectionStringNoExecuteSQL = connectionString + ";enablePrepareOnFirstPreparedStatementCall=true;";
SQLServerConnection connectionNoExecuteSQL = (SQLServerConnection)DriverManager.getConnection(connectionStringNoExecuteSQL);
@@ -545,54 +569,4 @@ public void testStatementPoolingPreparedStatementExecAndUnprepareConfig() throws
assertSame(0, con.getDiscardedServerPreparedStatementCount());
}
}
-
- /**
- * Validate the right behavior for EnablePrepareOnFirstPreparedStatementCall with
- * statement pooling turned off.
- *
- * @throws SQLException
- */
- @Test
- public void testEnablePrepareOnFirstPreparedStatementCallWithStatementPoolingOff() throws SQLException {
-
- try (SQLServerConnection con = (SQLServerConnection)DriverManager.getConnection(connectionString)) {
-
- // Turn off use of prepared statement cache.
- con.setStatementPoolingCacheSize(0);
- // Disable EnablePrepareOnFirstPreparedStatementCall (default)
- con.setEnablePrepareOnFirstPreparedStatementCall(false);
-
- String query = "/*testEnablePrepareOnFirstPreparedStatementCallWithStatementPoolingOff*/SELECT * FROM sys.objects;";
-
- // Verify first use is never prepared.
- for(int i = 0; i < 10; ++i)
- {
- try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement)con.prepareStatement(query)) {
- pstmt.execute(); // sp_executesql
- pstmt.getMoreResults(); // Make sure handle is updated.
-
- // Validate no handle was created.
- assertTrue(0 >= pstmt.getPreparedStatementHandle());
- }
- }
-
- // Verify second use is prepared.
- for(int i = 0; i < 10; ++i)
- {
- try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement)con.prepareStatement(query)) {
- pstmt.execute(); // sp_executesql
- pstmt.getMoreResults(); // Make sure handle is updated.
-
- // Validate no handle was created.
- assertTrue(0 >= pstmt.getPreparedStatementHandle());
-
- pstmt.execute(); // sp_prepexec
- pstmt.getMoreResults(); // Make sure handle is updated.
-
- // Validate handle was created.
- assertTrue(0 < pstmt.getPreparedStatementHandle());
- }
- }
- }
- }
-}
+}
\ No newline at end of file