From 7f170e03f97cc0b0c817814809e125ccade769a3 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Tue, 6 Dec 2022 08:57:11 -0800 Subject: [PATCH 1/5] Updated onDone with reason/comment for unecessary decrement for future reference --- .../jdbc/SQLServerCallableStatement.java | 5 +- .../sqlserver/jdbc/SQLServerConnection.java | 3 + .../sqlserver/jdbc/SQLServerResultSet.java | 11 ++- .../sqlserver/jdbc/SQLServerStatement.java | 5 +- .../jdbc/resiliency/BasicConnectionTest.java | 73 ++++++++++++++++++- 5 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 0c3d5538a..5834652f3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -258,7 +258,10 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { // Consume the done token and decide what to do with it... StreamDone doneToken = new StreamDone(); doneToken.setFromTDS(tdsReader); - connection.getSessionRecovery().decrementUnprocessedResponseCount(); + + if (doneToken.isFinal()) { + connection.getSessionRecovery().decrementUnprocessedResponseCount(); + } // If this is a non-final batch-terminating DONE token, // then stop parsing the response now and set up for diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index eb4992c68..3d326742b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -3996,6 +3996,9 @@ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { false); } try { + if (null != preparedStatementHandleCache) + preparedStatementHandleCache.clear(); + sessionRecovery.reconnect(newCommand); } catch (InterruptedException e) { // re-interrupt thread diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index c50143eaf..2796275cc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -393,7 +393,10 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { SQLServerException.makeFromDriverError(stmt.connection, stmt, form.format(msgArgs), null, false); } - stmt.connection.getSessionRecovery().decrementUnprocessedResponseCount(); + // An ICR unprocessedResponseCount decrement is unnecessary here because we are throwing an exception + // above on doneToken errors. + // If we didn't throw an exception, we would decrement only on a DONE_ERROR and if the subsequent token + // is a final doneToken. eg. tds status flag of 0x0002 && tdsToken.isFinal(). return false; } @@ -5382,6 +5385,11 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { StreamDone doneToken = new StreamDone(); doneToken.setFromTDS(tdsReader); + + if (doneToken.isFinal()) { + stmt.connection.getSessionRecovery().decrementUnprocessedResponseCount(); + } + if (doneToken.isFinal() && doneToken.isError()) { short status = tdsReader.peekStatusFlag(); SQLServerError databaseError = getDatabaseError(); @@ -5390,7 +5398,6 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { SQLServerException.makeFromDriverError(stmt.connection, stmt, form.format(msgArgs), null, false); } - stmt.connection.getSessionRecovery().decrementUnprocessedResponseCount(); // Done with all the rows in this fetch buffer and done with parsing // unless it's a server cursor, in which case there is a RETSTAT and diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 85a5986bb..6a78d6608 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1494,7 +1494,10 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { // Handling DONE/DONEPROC/DONEINPROC tokens is a little tricky... StreamDone doneToken = new StreamDone(); doneToken.setFromTDS(tdsReader); - connection.getSessionRecovery().decrementUnprocessedResponseCount(); + + if (doneToken.isFinal()) { + connection.getSessionRecovery().decrementUnprocessedResponseCount(); + } // If the done token has the attention ack bit set, then record // it as the attention ack DONE token. We may or may not throw diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java index ce2f285fc..27f49a02e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java @@ -14,18 +14,21 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.UUID; import javax.sql.PooledConnection; +import com.microsoft.sqlserver.jdbc.RandomUtil; +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource; import com.microsoft.sqlserver.jdbc.SQLServerPooledConnection; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.TestResource; +import com.microsoft.sqlserver.jdbc.TestUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import com.microsoft.sqlserver.jdbc.RandomUtil; -import com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource; -import com.microsoft.sqlserver.jdbc.TestResource; -import com.microsoft.sqlserver.jdbc.TestUtils; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; @@ -301,6 +304,68 @@ public void testDSPooledConnectionAccessTokenCallbackIdleConnectionResiliency() } } + @Test + public void testPreparedStatementCacheShouldBeCleared() throws SQLException { + try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { + int cacheSize = 2; + String query = String.format("/*testPreparedStatementCacheShouldBeCleared_%s*/SELECT 1; -- ", + UUID.randomUUID().toString()); + int discardedStatementCount = 1; + + // enable caching + con.setDisableStatementPooling(false); + con.setStatementPoolingCacheSize(cacheSize); + con.setServerPreparedStatementDiscardThreshold(discardedStatementCount); + + // add new statements to fill cache + for (int i = 0; i < cacheSize; ++i) { + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement(query + i)) { + pstmt.execute(); + pstmt.execute(); + } + } + + // nothing should be discarded yet + assertEquals(0, con.getDiscardedServerPreparedStatementCount()); + + ResiliencyUtils.killConnection(con, connectionString, 1); + + // add 1 more - if cache was not cleared this would cause it to be discarded + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con.prepareStatement(query)) { + pstmt.execute(); + pstmt.execute(); + } + assertEquals(0, con.getDiscardedServerPreparedStatementCount()); + } + } + + @Test + public void testUnprocessedResponseCountSuccessfulIdleConnectionRecovery() throws SQLException { + try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { + int queriesToSend = 5; + String query = String.format("/*testUnprocessedResponseCountSuccessfulIdleConnectionRecovery_%s*/SELECT 1; -- ", + UUID.randomUUID().toString()); + + for (int i = 0; i < queriesToSend; ++i) { + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement(query + i)) { + pstmt.executeQuery(); + pstmt.executeQuery(); + } + } + + // Kill the connection. If the unprocessedResponseCount is negative, test will fail. + ResiliencyUtils.killConnection(con, connectionString, 1); + + // Should successfully recover. + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con.prepareStatement(query)) { + pstmt.executeQuery(); + pstmt.executeQuery(); + } + } + } + private void basicReconnect(String connectionString) throws SQLException { // Ensure reconnects can happen multiple times over the same connection and subsequent connections for (int i1 = 0; i1 < 2; i1++) { From dc0ddd6ca71592305d36da4743b861b745bb1e44 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 12 Dec 2022 14:05:44 -0800 Subject: [PATCH 2/5] Added more ICR tests --- .../sqlserver/jdbc/SQLServerResultSet.java | 12 +- .../jdbc/resiliency/BasicConnectionTest.java | 2 +- .../ResultSetsWithResiliencyTest.java | 327 ++++++++++++++++++ 3 files changed, 335 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 2796275cc..eb3966f62 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -385,19 +385,21 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { // following the column metadata indicates an empty result set. rowCount = 0; + // decrementUnprocessedResponseCount() outside the "if" is not necessary here. It will over decrement if added. + short status = tdsReader.peekStatusFlag(); if ((status & TDS.DONE_ERROR) != 0 || (status & TDS.DONE_SRVERROR) != 0) { + StreamDone doneToken = new StreamDone(); + doneToken.setFromTDS(tdsReader); + if (doneToken.isFinal()) { + stmt.connection.getSessionRecovery().decrementUnprocessedResponseCount(); + } SQLServerError databaseError = this.getDatabaseError(); MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_serverError")); Object[] msgArgs = {status, (databaseError != null) ? databaseError.getErrorMessage() : ""}; SQLServerException.makeFromDriverError(stmt.connection, stmt, form.format(msgArgs), null, false); } - // An ICR unprocessedResponseCount decrement is unnecessary here because we are throwing an exception - // above on doneToken errors. - // If we didn't throw an exception, we would decrement only on a DONE_ERROR and if the subsequent token - // is a final doneToken. eg. tds status flag of 0x0002 && tdsToken.isFinal(). - return false; } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java index 27f49a02e..8b508e5b9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java @@ -345,7 +345,7 @@ public void testUnprocessedResponseCountSuccessfulIdleConnectionRecovery() throw try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { int queriesToSend = 5; String query = String.format("/*testUnprocessedResponseCountSuccessfulIdleConnectionRecovery_%s*/SELECT 1; -- ", - UUID.randomUUID().toString()); + UUID.randomUUID()); for (int i = 0; i < queriesToSend; ++i) { try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java index 42e8cfc51..a26d1af87 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java @@ -9,13 +9,16 @@ import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Types; +import com.microsoft.sqlserver.jdbc.SQLServerConnection; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; @@ -36,14 +39,47 @@ public class ResultSetsWithResiliencyTest extends AbstractTest { static String tableName = AbstractSQLGenerator.escapeIdentifier("resilencyTestTable"); static int numberOfRows = 10; + private static String callableStatementICROnDoneTestSp = RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP"); + private static String callableStatementICROnDoneErrorTestSp = RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP"); + private static String createClientCursorInitTableQuery = "create table %s (col1 int, col2 varchar(8000), col3 int identity(1,1))"; + private static String createFetchBufferTableQuery = "create table %s (col1 int not null)"; + private static String insertIntoFetchBufferTableQuery = "insert into %s (col1) values (%s);"; + private static final String clientCursorInitTable1 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable1"); + private static final String clientCursorInitTable2 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable2"); + private static final String clientCursorInitTable3 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable3"); + private static final String fetchBufferTestTable1 = AbstractSQLGenerator.escapeIdentifier("fetchBufferTestTable1"); + private static final String fetchBufferTestTable2 = AbstractSQLGenerator.escapeIdentifier("fetchBufferTestTable2"); + private static final String clientCursorInitTableQuery1 = "select * from " + clientCursorInitTable1; + private static final String clientCursorInitTableQuery2 = "select * from " + clientCursorInitTable2; + private static final String clientCursorInitTableQuery3 = "select * from " + clientCursorInitTable3; + private static final String fetchBufferTableQuery1 = "select * from " + fetchBufferTestTable1; + private static final String fetchBufferTableQuery2 = "select * from " + fetchBufferTestTable2; + private static final String mockErrorMsg = "This is a mock query error."; + private static final String errorQuery = "RAISERROR('" + mockErrorMsg + "', 16, 1)"; + @BeforeAll public static void setupTests() throws Exception { setConnection(); try (Connection c = DriverManager.getConnection(connectionString); Statement s = c.createStatement();) { TestUtils.dropTableIfExists(tableName, s); + TestUtils.dropTableIfExists(clientCursorInitTable1, s); + TestUtils.dropTableIfExists(clientCursorInitTable2, s); + TestUtils.dropTableIfExists(clientCursorInitTable3, s); + TestUtils.dropTableIfExists(fetchBufferTestTable1, s); + TestUtils.dropTableIfExists(fetchBufferTestTable2, s); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp), s); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp), s); + createTable(s); insertData(s); + + createCallableStatementOnDoneTestSp(s); + createCallableStatementOnDoneErrorTestSp(s); + + createTable(s, String.format(createClientCursorInitTableQuery, clientCursorInitTable1)); + createTable(s, String.format(createClientCursorInitTableQuery, clientCursorInitTable2)); + createTable(s, String.format(createClientCursorInitTableQuery, clientCursorInitTable3)); } } @@ -210,16 +246,285 @@ public void testKillSession() throws Exception { } } + @Test + public void testResultSetClientCursorInitializerOnDone() throws SQLException { + try (Connection con = ResiliencyUtils.getConnection(connectionString); Statement stmt = con.createStatement()) { + + boolean hasResults = stmt.execute(clientCursorInitTableQuery1+ "; " + clientCursorInitTableQuery2); + while(hasResults) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) {} + hasResults = stmt.getMoreResults(); + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (ResultSet rs = con.createStatement() + .executeQuery(clientCursorInitTableQuery3)) { + while(rs.next()) {} + } + } + } + + @Test + public void testResultSetErrorClientCursorInitializerOnDone() throws SQLException { + try (Connection con = ResiliencyUtils.getConnection(connectionString); Statement stmt = con.createStatement()) { + + try { + boolean hasResults = stmt.execute(clientCursorInitTableQuery1 + "; " + errorQuery); + while(hasResults) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) {} + hasResults = stmt.getMoreResults(); + } + } catch (SQLServerException se) { + if (!se.getMessage().equals(mockErrorMsg)) { + se.printStackTrace(); + fail("Mock Sql Server error message was expected."); + } + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (ResultSet rs = con.createStatement() + .executeQuery(clientCursorInitTableQuery3)) { + while (rs.next()) {} + } + } + } + + @Test + public void testCallableStatementOnDone() throws SQLException { + String sql = "{CALL " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + " (?, ?)}"; + + try (Connection con = ResiliencyUtils.getConnection(connectionString)) { + + try (CallableStatement cs = con.prepareCall(sql)) { + cs.registerOutParameter(1, Types.TIMESTAMP); + cs.registerOutParameter(2, Types.TIMESTAMP); + cs.execute(); + cs.execute(); + cs.execute(); + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (CallableStatement cs2 = con.prepareCall(sql)) { + cs2.registerOutParameter(1, Types.TIMESTAMP); + cs2.registerOutParameter(2, Types.TIMESTAMP); + cs2.execute(); + } + } + } + + @Test + public void testCallableStatementErrorOnDone() throws SQLException { + String errorCallableStmt = "{CALL " + + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp) + " (?, ?)}"; + String validCallableStmt = "{CALL " + + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + " (?, ?)}"; + + try (Connection con = ResiliencyUtils.getConnection(connectionString)) { + + try (CallableStatement cs = con.prepareCall(errorCallableStmt)) { + cs.registerOutParameter(1, Types.TIMESTAMP); + cs.registerOutParameter(2, Types.TIMESTAMP); + cs.execute(); + } catch (SQLServerException se) { + if (!se.getMessage().equals(mockErrorMsg)) { + se.printStackTrace(); + fail("Mock Sql Server error message was expected."); + } + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (CallableStatement cs2 = con.prepareCall(validCallableStmt)) { + cs2.registerOutParameter(1, Types.TIMESTAMP); + cs2.registerOutParameter(2, Types.TIMESTAMP); + cs2.execute(); + } + } + } + + @Test + public void testResultSetFetchBufferOnDone() throws SQLException { + + try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { + try (Statement stmt = con.createStatement()) { + createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable1)); + insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable1, 1), 10); + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (Statement stmt = con.createStatement()) { + createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable2)); + insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable2, 1), 10); + } + + try (Statement stmt = con.createStatement()) { + boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + fetchBufferTableQuery2); + while(hasResults) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) {} + hasResults = stmt.getMoreResults(); + } + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (Statement stmt = con.createStatement()) { + ResultSet rs = stmt.executeQuery(fetchBufferTableQuery2); + while (rs.next()) {} + } + } + } + + @Test + public void testResultSetErrorFetchBufferOnDone() throws SQLException { + try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { + try (Statement stmt = con.createStatement()) { + createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable1)); + insertData(stmt, errorQuery, 10); + } catch (SQLServerException se) { + if (!se.getMessage().equals(mockErrorMsg)) { + se.printStackTrace(); + fail("Mock Sql Server error message was expected."); + } + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (Statement stmt = con.createStatement()) { + createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable2)); + insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable2, 1), 10); + } + + try (Statement stmt = con.createStatement()) { + boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + errorQuery); + while(hasResults) { + ResultSet rs = stmt.getResultSet(); + while (rs.next()) {} + hasResults = stmt.getMoreResults(); + } + } catch (SQLServerException se) { + if (!se.getMessage().equals(mockErrorMsg)) { + se.printStackTrace(); + fail("Mock Sql Server error message was expected."); + } + } + + ResiliencyUtils.killConnection(con, connectionString, 1); + + try (Statement stmt = con.createStatement()) { + ResultSet rs = stmt.executeQuery(fetchBufferTableQuery2); + while (rs.next()) {} + } + } + } + + /* + * Test killing a session while retrieving multiple result sets + */ + @Test + public void testMultipleResultSets() throws Exception { + try (Connection c = ResiliencyUtils.getConnection(connectionString); Statement s = c.createStatement()) { + boolean results = s.execute("SELECT 1;SELECT 2"); + int rsCount = 0; + do { + if (results) { + try (ResultSet rs = s.getResultSet()) { + rsCount++; + + while (rs.next()) { + ResiliencyUtils.killConnection(c, connectionString, 0); + assertTrue(rs.getString(1).equals(String.valueOf(rsCount))); + } + } + } + results = s.getMoreResults(); + } while (results); + } catch (SQLException e) { + if (!("08S01" == e.getSQLState() + || e.getMessage().matches(TestUtils.formatErrorMsg("R_crClientUnrecoverable")))) { + e.printStackTrace(); + } + assertTrue( + "08S01" == e.getSQLState() + || e.getMessage().matches(TestUtils.formatErrorMsg("R_crClientUnrecoverable")), + e.getMessage()); + } + } + + /* + * Test killing a session while retrieving result set that causes an exception + */ + @Test + public void testResultSetWithException() throws Exception { + try (Connection c = ResiliencyUtils.getConnection(connectionString); Statement s = c.createStatement(); + ResultSet rs = s.executeQuery("SELECT 1/0")) { + + while (rs.next()) { + ResiliencyUtils.killConnection(c, connectionString, 0); + // driver should not have successfully reconnected but it did + fail(TestResource.getResource("R_expectedFailPassed")); + } + } catch (SQLException e) { + if (!e.getMessage().contains("Divide by zero error")) { + e.printStackTrace(); + } + } + } + + /* + * Test killing a session while retrieving multiple result sets that causes an exception + */ + @Test + public void testMultipleResultSetsWithException() throws Exception { + try (Connection c = ResiliencyUtils.getConnection(connectionString); Statement s = c.createStatement()) { + boolean results = s.execute("SELECT 1;SELECT 1/0"); + int rsCount = 0; + do { + if (results) { + try (ResultSet rs = s.getResultSet()) { + rsCount++; + + while (rs.next()) { + ResiliencyUtils.killConnection(c, connectionString, 0); + assertTrue(rs.getString(1).equals(String.valueOf(rsCount))); + } + } + } + results = s.getMoreResults(); + } while (results); + } catch (SQLException e) { + if (!e.getMessage().contains("Divide by zero error")) { + e.printStackTrace(); + } + } + } + private static void createTable(Statement s) throws SQLException { s.execute("CREATE TABLE " + tableName + " (id int IDENTITY, data varchar(50));"); } + private static void createTable(Statement s, String query) throws SQLException { + s.execute(query); + } + private static void insertData(Statement s) throws SQLException { for (int i = 1; i <= numberOfRows; i++) { s.executeUpdate("INSERT INTO " + tableName + " VALUES ('testData" + i + "');"); } } + private static void insertData(Statement s, String query, int rows) throws SQLException { + for (int i = 0; i < rows; i++) { + s.executeUpdate(query); + } + } + private void verifyResultSet(ResultSet rs) throws SQLException { int count = 0; while (rs.next()) { @@ -246,10 +551,32 @@ private void verifyResultSetResponseBuffering(String responseBuffering, } } + private static void createCallableStatementOnDoneTestSp(Statement stmt) throws SQLException { + String sql = "CREATE PROCEDURE " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + + "(@p1 datetime2(7) OUTPUT, @p2 datetime2(7) OUTPUT) AS " + + "SELECT @p1 = '2018-03-11T02:00:00.1234567'; SELECT @p2 = '2022-03-11T02:00:00.1234567';"; + stmt.execute(sql); + } + + private static void createCallableStatementOnDoneErrorTestSp(Statement stmt) throws SQLException { + String sql = "CREATE PROCEDURE " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp) + + "(@p1 datetime2(7) OUTPUT, @p2 datetime2(7) OUTPUT) AS " + + "SELECT @p1 = '2018-03-11T02:00:00.1234567'; SELECT @p2 = '2022-03-11T02:00:00.1234567'; " + + errorQuery; + stmt.execute(sql); + } + @AfterAll public static void cleanUp() throws SQLException { try (Connection c = DriverManager.getConnection(connectionString); Statement s = c.createStatement()) { TestUtils.dropTableIfExists(tableName, s); + TestUtils.dropTableIfExists(clientCursorInitTable1, s); + TestUtils.dropTableIfExists(clientCursorInitTable2, s); + TestUtils.dropTableIfExists(clientCursorInitTable3, s); + TestUtils.dropTableIfExists(fetchBufferTestTable1, s); + TestUtils.dropTableIfExists(fetchBufferTestTable2, s); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp), s); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp), s); } } } From 964a0edb31c126c492fe2b3655acb45293be78f9 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 12 Dec 2022 14:14:37 -0800 Subject: [PATCH 3/5] Added missing brackets --- .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 3d326742b..07ca7f997 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -3996,8 +3996,9 @@ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { false); } try { - if (null != preparedStatementHandleCache) + if (null != preparedStatementHandleCache) { preparedStatementHandleCache.clear(); + } sessionRecovery.reconnect(newCommand); } catch (InterruptedException e) { From 9b812b12b2d1683d68e6fcffaff7bac031301e85 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 12 Dec 2022 15:27:05 -0800 Subject: [PATCH 4/5] Test table names needed to be unique --- .../ResultSetsWithResiliencyTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java index a26d1af87..a1dbdf9f0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java @@ -39,16 +39,16 @@ public class ResultSetsWithResiliencyTest extends AbstractTest { static String tableName = AbstractSQLGenerator.escapeIdentifier("resilencyTestTable"); static int numberOfRows = 10; - private static String callableStatementICROnDoneTestSp = RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP"); - private static String callableStatementICROnDoneErrorTestSp = RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP"); + private static String callableStatementICROnDoneTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP")); + private static String callableStatementICROnDoneErrorTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP")); private static String createClientCursorInitTableQuery = "create table %s (col1 int, col2 varchar(8000), col3 int identity(1,1))"; private static String createFetchBufferTableQuery = "create table %s (col1 int not null)"; private static String insertIntoFetchBufferTableQuery = "insert into %s (col1) values (%s);"; - private static final String clientCursorInitTable1 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable1"); - private static final String clientCursorInitTable2 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable2"); - private static final String clientCursorInitTable3 = AbstractSQLGenerator.escapeIdentifier("clientCursorInitTable3"); - private static final String fetchBufferTestTable1 = AbstractSQLGenerator.escapeIdentifier("fetchBufferTestTable1"); - private static final String fetchBufferTestTable2 = AbstractSQLGenerator.escapeIdentifier("fetchBufferTestTable2"); + private static final String clientCursorInitTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable1")); + private static final String clientCursorInitTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable2")); + private static final String clientCursorInitTable3 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable3")); + private static final String fetchBufferTestTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable1")); + private static final String fetchBufferTestTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable2")); private static final String clientCursorInitTableQuery1 = "select * from " + clientCursorInitTable1; private static final String clientCursorInitTableQuery2 = "select * from " + clientCursorInitTable2; private static final String clientCursorInitTableQuery3 = "select * from " + clientCursorInitTable3; @@ -68,8 +68,8 @@ public static void setupTests() throws Exception { TestUtils.dropTableIfExists(clientCursorInitTable3, s); TestUtils.dropTableIfExists(fetchBufferTestTable1, s); TestUtils.dropTableIfExists(fetchBufferTestTable2, s); - TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp), s); - TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp), s); + TestUtils.dropProcedureIfExists(callableStatementICROnDoneTestSp, s); + TestUtils.dropProcedureIfExists(callableStatementICROnDoneErrorTestSp, s); createTable(s); insertData(s); @@ -295,7 +295,7 @@ public void testResultSetErrorClientCursorInitializerOnDone() throws SQLExceptio @Test public void testCallableStatementOnDone() throws SQLException { - String sql = "{CALL " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + " (?, ?)}"; + String sql = "{CALL " + callableStatementICROnDoneTestSp + " (?, ?)}"; try (Connection con = ResiliencyUtils.getConnection(connectionString)) { @@ -320,9 +320,9 @@ public void testCallableStatementOnDone() throws SQLException { @Test public void testCallableStatementErrorOnDone() throws SQLException { String errorCallableStmt = "{CALL " - + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp) + " (?, ?)}"; + + callableStatementICROnDoneErrorTestSp + " (?, ?)}"; String validCallableStmt = "{CALL " - + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + " (?, ?)}"; + + callableStatementICROnDoneTestSp + " (?, ?)}"; try (Connection con = ResiliencyUtils.getConnection(connectionString)) { @@ -552,14 +552,14 @@ private void verifyResultSetResponseBuffering(String responseBuffering, } private static void createCallableStatementOnDoneTestSp(Statement stmt) throws SQLException { - String sql = "CREATE PROCEDURE " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp) + String sql = "CREATE PROCEDURE " + callableStatementICROnDoneTestSp + "(@p1 datetime2(7) OUTPUT, @p2 datetime2(7) OUTPUT) AS " + "SELECT @p1 = '2018-03-11T02:00:00.1234567'; SELECT @p2 = '2022-03-11T02:00:00.1234567';"; stmt.execute(sql); } private static void createCallableStatementOnDoneErrorTestSp(Statement stmt) throws SQLException { - String sql = "CREATE PROCEDURE " + AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp) + String sql = "CREATE PROCEDURE " + callableStatementICROnDoneErrorTestSp + "(@p1 datetime2(7) OUTPUT, @p2 datetime2(7) OUTPUT) AS " + "SELECT @p1 = '2018-03-11T02:00:00.1234567'; SELECT @p2 = '2022-03-11T02:00:00.1234567'; " + errorQuery; @@ -575,8 +575,8 @@ public static void cleanUp() throws SQLException { TestUtils.dropTableIfExists(clientCursorInitTable3, s); TestUtils.dropTableIfExists(fetchBufferTestTable1, s); TestUtils.dropTableIfExists(fetchBufferTestTable2, s); - TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneTestSp), s); - TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(callableStatementICROnDoneErrorTestSp), s); + TestUtils.dropProcedureIfExists(callableStatementICROnDoneTestSp, s); + TestUtils.dropProcedureIfExists(callableStatementICROnDoneErrorTestSp, s); } } } From 2cc15e5afcd47cf4ed1fe05a70616d70d6c4128f Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 12 Dec 2022 16:35:31 -0800 Subject: [PATCH 5/5] Drop tables within test if exist --- .../jdbc/resiliency/ResultSetsWithResiliencyTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java index a1dbdf9f0..2a381976d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java @@ -352,6 +352,7 @@ public void testResultSetFetchBufferOnDone() throws SQLException { try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(fetchBufferTestTable1, stmt); createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable1)); insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable1, 1), 10); } @@ -359,6 +360,7 @@ public void testResultSetFetchBufferOnDone() throws SQLException { ResiliencyUtils.killConnection(con, connectionString, 1); try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(fetchBufferTestTable2, stmt); createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable2)); insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable2, 1), 10); } @@ -385,6 +387,7 @@ public void testResultSetFetchBufferOnDone() throws SQLException { public void testResultSetErrorFetchBufferOnDone() throws SQLException { try (SQLServerConnection con = (SQLServerConnection) ResiliencyUtils.getConnection(connectionString)) { try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(fetchBufferTestTable1, stmt); createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable1)); insertData(stmt, errorQuery, 10); } catch (SQLServerException se) { @@ -397,6 +400,7 @@ public void testResultSetErrorFetchBufferOnDone() throws SQLException { ResiliencyUtils.killConnection(con, connectionString, 1); try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(fetchBufferTestTable2, stmt); createTable(stmt, String.format(createFetchBufferTableQuery, fetchBufferTestTable2)); insertData(stmt, String.format(insertIntoFetchBufferTableQuery, fetchBufferTestTable2, 1), 10); }