diff --git a/CHANGELOG.md b/CHANGELOG.md
index d55b3b59b0..5721f3adae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
+## [11.2.1] HotFix & Stable Release
+### Fixed issues
+- Made com.microsoft.azure:msal4j an optional dependency again [1893](https://github.com/microsoft/mssql-jdbc/pull/1893)
+- Fixed query cancellation bug that intermittently occurs in batch queries [1897](https://github.com/microsoft/mssql-jdbc/pull/1897)
+
## [11.2.0] Stable Release
### Added
- Added support for caching parameter metadata for Always Encrypted with secure enclaves [1866](https://github.com/microsoft/mssql-jdbc/pull/1866)
diff --git a/README.md b/README.md
index a11e25e934..9cb5be3c00 100644
--- a/README.md
+++ b/README.md
@@ -82,7 +82,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0.jre18
+ 11.2.1.jre18
```
The driver can be downloaded from [Microsoft](https://aka.ms/downloadmssqljdbc).
@@ -93,7 +93,7 @@ To get the latest version of the driver, add the following to your POM file:
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0.jre18
+ 11.2.1.jre18
```
@@ -128,7 +128,7 @@ Projects that require either of the two features need to explicitly declare the
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0.jre18
+ 11.2.1.jre18
compile
@@ -146,7 +146,7 @@ Projects that require either of the two features need to explicitly declare the
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0.jre18
+ 11.2.1.jre18
compile
@@ -173,7 +173,7 @@ When setting 'useFmtOnly' property to 'true' for establishing a connection or cr
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0.jre18
+ 11.2.1.jre18
diff --git a/build.gradle b/build.gradle
index ccb889768c..57db8095e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,7 @@
apply plugin: 'java'
-version = '11.2.0'
+version = '11.2.1'
def jreVersion = ""
def testOutputDir = file("build/classes/java/test")
def archivesBaseName = 'mssql-jdbc'
diff --git a/pom.xml b/pom.xml
index 589a3ff07e..fdf6b7c574 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.microsoft.sqlserver
mssql-jdbc
- 11.2.0
+ 11.2.1
jar
Microsoft JDBC Driver for SQL Server
@@ -110,6 +110,7 @@
com.microsoft.azure
msal4j
1.13.0
+ true
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index b8b3a459ae..84600b90e6 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -7500,7 +7500,7 @@ protected void setInterruptsEnabled(boolean interruptsEnabled) {
// Flag set to indicate that an interrupt has happened.
private volatile boolean wasInterrupted = false;
- private boolean wasInterrupted() {
+ boolean wasInterrupted() {
return wasInterrupted;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
index 2e15d61859..ac2288e752 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
@@ -8,7 +8,7 @@
final class SQLJdbcVersion {
static final int major = 11;
static final int minor = 2;
- static final int patch = 0;
+ static final int patch = 1;
static final int build = 0;
/*
* Used to load mssql-jdbc_auth DLL.
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index e91c466f9c..9b9eeb968b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -2868,6 +2868,21 @@ final void doExecutePreparedStatementBatch(PrepStmtBatchExecCmd batchCommand) th
for (int attempt = 1; attempt <= 2; ++attempt) {
try {
+ // If the command was interrupted, that means the TDS.PKT_CANCEL_REQ was sent to the server.
+ // Since the cancellation request was sent, stop processing the batch query and process the
+ // cancellation request and then return.
+ //
+ // Otherwise, if we do continue processing the batch query, in the case where a query requires
+ // prepexec/sp_prepare, the TDS request for prepexec/sp_prepare will be sent regardless of
+ // query cancellation. This will cause a TDS token error in the post processing when we
+ // close the query.
+ if (batchCommand.wasInterrupted()) {
+ ensureExecuteResultsReader(batchCommand.startResponse(getIsResponseBufferingAdaptive()));
+ startResults();
+ getNextResult(true);
+ return;
+ }
+
// Re-use handle if available, requires parameter definitions which are not available until here.
if (reuseCachedHandle(hasNewTypeDefinitions, 1 < attempt)) {
hasNewTypeDefinitions = false;
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java
index b74d99cb5b..45e3fc5c73 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java
@@ -5,6 +5,7 @@
package com.microsoft.sqlserver.jdbc.unit.statement;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.reflect.Field;
@@ -69,6 +70,51 @@ public void testBatchSpPrepare() throws Exception {
testExecuteBatch1UseBulkCopyAPI();
}
+ @Test
+ public void testBatchStatementCancellation() throws Exception {
+ try (Connection connection = PrepUtil.getConnection(connectionString)) {
+ connection.setAutoCommit(false);
+
+ try (PreparedStatement statement = connection
+ .prepareStatement("if object_id('test_table') is not null drop table test_table")) {
+ statement.execute();
+ }
+ connection.commit();
+
+ try (PreparedStatement statement = connection
+ .prepareStatement("create table test_table (column_name bit)")) {
+ statement.execute();
+ }
+ connection.commit();
+
+ for (long delayInMilliseconds : new long[] {1, 2, 4, 8, 16, 32, 64, 128}) {
+ for (int numberOfCommands : new int[] {1, 2, 4, 8, 16, 32, 64}) {
+ int parameterCount = 512;
+
+ try (PreparedStatement statement = connection.prepareStatement(
+ "insert into test_table values (?)" + repeat(",(?)", parameterCount - 1))) {
+
+ for (int i = 0; i < numberOfCommands; i++) {
+ for (int j = 0; j < parameterCount; j++) {
+ statement.setBoolean(j + 1, true);
+ }
+ statement.addBatch();
+ }
+
+ Thread cancelThread = cancelAsync(statement, delayInMilliseconds);
+ try {
+ statement.executeBatch();
+ } catch (SQLException e) {
+ assertEquals(TestResource.getResource("R_queryCancelled"), e.getMessage());
+ }
+ cancelThread.join();
+ }
+ connection.commit();
+ }
+ }
+ }
+ }
+
/**
* Get a PreparedStatement object and call the addBatch() method with 3 SQL statements and call the executeBatch()
* method and it should return array of Integer values of length 3
@@ -240,6 +286,29 @@ private void modifyConnectionForBulkCopyAPI(SQLServerConnection con) throws Exce
con.setUseBulkCopyForBatchInsert(true);
}
+ private static String repeat(String string, int count) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ sb.append(string);
+ }
+ return sb.toString();
+ }
+
+ private static Thread cancelAsync(Statement statement, long delayInMilliseconds) {
+ Thread thread = new Thread(() -> {
+ try {
+ Thread.sleep(delayInMilliseconds);
+ statement.cancel();
+ } catch (SQLException | InterruptedException e) {
+ // does not/must not happen
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ }
+ });
+ thread.start();
+ return thread;
+ }
+
@BeforeAll
public static void testSetup() throws TestAbortedException, Exception {
setConnection();