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();