diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java index 1689cde87..b8230f5f9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java @@ -1,19 +1,24 @@ package com.microsoft.sqlserver.jdbc; +import java.sql.SQLException; import java.sql.ShardingKey; public interface ISQLServerConnection43 extends ISQLServerConnection { + public void beginRequest() throws SQLException; + + public void endRequest() throws SQLException; + public void setShardingKey(ShardingKey shardingKey) throws SQLServerException; - + public void setShardingKey(ShardingKey shardingKey, ShardingKey superShardingKey) throws SQLServerException; - + public boolean setShardingKeyIfValid(ShardingKey shardingKey, int timeout) throws SQLServerException; - + public boolean setShardingKeyIfValid(ShardingKey shardingKey, ShardingKey superShardingKey, int timeout) throws SQLServerException; - + } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index ab7a758aa..918d58ee4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -39,6 +39,7 @@ import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -3218,6 +3219,9 @@ public Statement createStatement(int resultSetType, checkClosed(); Statement st = new SQLServerStatement(this, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting); + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "createStatement", st); return st; } @@ -3241,7 +3245,9 @@ public PreparedStatement prepareStatement(String sql, st = new SQLServerPreparedStatement(this, sql, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting); } - + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st); return st; } @@ -3264,7 +3270,9 @@ private PreparedStatement prepareStatement(String sql, else { st = new SQLServerPreparedStatement(this, sql, resultSetType, resultSetConcurrency, stmtColEncSetting); } - + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st); return st; } @@ -3288,7 +3296,9 @@ public CallableStatement prepareCall(String sql, st = new SQLServerCallableStatement(this, sql, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting); } - + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "prepareCall", st); return st; } @@ -4646,6 +4656,9 @@ public Statement createStatement(int nType, checkValidHoldability(resultSetHoldability); checkMatchesCurrentHoldability(resultSetHoldability); Statement st = new SQLServerStatement(this, nType, nConcur, stmtColEncSetting); + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "createStatement", st); return st; } @@ -4708,7 +4721,9 @@ public PreparedStatement prepareStatement(java.lang.String sql, else { st = new SQLServerPreparedStatement(this, sql, nType, nConcur, stmtColEncSetting); } - + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st); return st; } @@ -4744,7 +4759,9 @@ public CallableStatement prepareCall(String sql, else { st = new SQLServerCallableStatement(this, sql, nType, nConcur, stmtColEncSetiing); } - + if (requestStarted) { + addOpenStatement(st); + } loggerExternal.exiting(getClassNameLogging(), "prepareCall", st); return st; } @@ -5296,14 +5313,87 @@ public T unwrap(Class iface) throws SQLException { return t; } - public void beginRequest() throws SQLFeatureNotSupportedException { - DriverJDBCVersion.checkSupportsJDBC43(); - throw new SQLFeatureNotSupportedException("beginRequest not implemented"); + private boolean requestStarted = false; + private boolean originalDatabaseAutoCommitMode; + private int originalTransactionIsolationLevel; + private int originalNetworkTimeout; + private int originalHoldability; + private boolean originalSendTimeAsDatetime; + private int originalStatementPoolingCacheSize; + private boolean originalDisableStatementPooling; + private int originalServerPreparedStatementDiscardThreshold; + private Boolean originalEnablePrepareOnFirstPreparedStatementCall; + private String originalSCatalog; + private volatile SQLWarning originalSqlWarnings; + private List openStatements; + + protected void beginRequestInternal() throws SQLException { + synchronized (this) { + if (!requestStarted) { + originalDatabaseAutoCommitMode = databaseAutoCommitMode; + originalTransactionIsolationLevel = transactionIsolationLevel; + originalNetworkTimeout = getNetworkTimeout(); + originalHoldability = holdability; + originalSendTimeAsDatetime = sendTimeAsDatetime; + originalStatementPoolingCacheSize = statementPoolingCacheSize; + originalDisableStatementPooling = disableStatementPooling; + originalServerPreparedStatementDiscardThreshold = getServerPreparedStatementDiscardThreshold(); + originalEnablePrepareOnFirstPreparedStatementCall = getEnablePrepareOnFirstPreparedStatementCall(); + originalSCatalog = sCatalog; + originalSqlWarnings = sqlWarnings; + openStatements = new LinkedList(); + requestStarted = true; + } + } } - public void endRequest() throws SQLFeatureNotSupportedException { - DriverJDBCVersion.checkSupportsJDBC43(); - throw new SQLFeatureNotSupportedException("endRequest not implemented"); + protected void endRequestInternal() throws SQLException { + synchronized (this) { + if (requestStarted) { + if (!databaseAutoCommitMode) { + rollback(); + } + if (databaseAutoCommitMode != originalDatabaseAutoCommitMode) { + setAutoCommit(originalDatabaseAutoCommitMode); + } + if (transactionIsolationLevel != originalTransactionIsolationLevel) { + setTransactionIsolation(originalTransactionIsolationLevel); + } + if (getNetworkTimeout() != originalNetworkTimeout) { + setNetworkTimeout(null, originalNetworkTimeout); + } + if (holdability != originalHoldability) { + setHoldability(originalHoldability); + } + if (sendTimeAsDatetime != originalSendTimeAsDatetime) { + setSendTimeAsDatetime(originalSendTimeAsDatetime); + } + if (statementPoolingCacheSize != originalStatementPoolingCacheSize) { + setStatementPoolingCacheSize(originalStatementPoolingCacheSize); + } + if (disableStatementPooling != originalDisableStatementPooling) { + setDisableStatementPooling(originalDisableStatementPooling); + } + if (getServerPreparedStatementDiscardThreshold() != originalServerPreparedStatementDiscardThreshold) { + setServerPreparedStatementDiscardThreshold(originalServerPreparedStatementDiscardThreshold); + } + if (getEnablePrepareOnFirstPreparedStatementCall() != originalEnablePrepareOnFirstPreparedStatementCall) { + setEnablePrepareOnFirstPreparedStatementCall(originalEnablePrepareOnFirstPreparedStatementCall); + } + if (!sCatalog.equals(originalSCatalog)) { + setCatalog(originalSCatalog); + } + sqlWarnings = originalSqlWarnings; + if (null != openStatements) { + while (!openStatements.isEmpty()) { + try (Statement st = openStatements.get(0)) { + } + } + openStatements.clear(); + } + requestStarted = false; + } + } } /** @@ -5879,6 +5969,26 @@ public void onEviction(Sha1HashKey key, PreparedStatementHandle handle) { } } } + + /** + * @param st + * Statement to add to openStatements + */ + final synchronized void addOpenStatement(Statement st) { + if (null != openStatements) { + openStatements.add(st); + } + } + + /** + * @param st + * Statement to remove from openStatements + */ + final synchronized void removeOpenStatement(Statement st) { + if (null != openStatements) { + openStatements.remove(st); + } + } } // Helper class for security manager functions used by SQLServerConnection class. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java index 217bcfc72..bf1dcd5f2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java @@ -1,5 +1,6 @@ package com.microsoft.sqlserver.jdbc; +import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.ShardingKey; @@ -8,7 +9,51 @@ public class SQLServerConnection43 extends SQLServerConnection implements ISQLSe SQLServerConnection43(String parentInfo) throws SQLServerException { super(parentInfo); } - + + /** + * Hints to the driver that a request, an independent unit of work, is beginning on this connection. It backs up the values of the connection + * properties that are modifiable through public methods. Each request is independent of all other requests with regard to state local to the + * connection either on the client or the server. Work done between {@code beginRequest}, {@code endRequest} pairs does not depend on any other + * work done on the connection either as part of another request or outside of any request. A request may include multiple transactions. There may + * be dependencies on committed database state as that is not local to the connection. {@code beginRequest} marks the beginning of the work unit. + *

+ * Local state is defined as any state associated with a Connection that is local to the current Connection either in the client or the database + * that is not transparently reproducible. + *

+ * Calls to {@code beginRequest} and {@code endRequest} are not nested. Multiple calls to {@code beginRequest} without an intervening call to + * {@code endRequest} is not an error. The first {@code beginRequest} call marks the start of the request and subsequent calls are treated as a + * no-op It is recommended to enclose each unit of work in {@code beginRequest}, {@code endRequest} pairs such that there is no open transaction + * at the beginning or end of the request and no dependency on local state that crosses request boundaries. Committed database state is not local. + * + * This method is to be used by Connection pooling managers. + *

+ * The pooling manager should call {@code beginRequest} on the underlying connection prior to returning a connection to the caller. + *

+ * + * @throws SQLException + * if an error occurs + * @see endRequest + */ + public void beginRequest() throws SQLException { + beginRequestInternal(); + } + + /** + * Hints to the driver that a request, an independent unit of work, has completed. It rolls back the open transactions. Resets the connection + * properties that are modifiable through public methods back to their original values. Calls to {@code beginRequest} and {@code endRequest} are + * not nested. Multiple calls to {@code endRequest} without an intervening call to {@code beginRequest} is not an error. The first + * {@code endRequest} call marks the request completed and subsequent calls are treated as a no-op. If {@code endRequest} is called without an + * initial call to {@code beginRequest} is a no-op. This method is to be used by Connection pooling managers. + *

+ * + * @throws SQLException + * if an error occurs + * @see beginRequest + */ + public void endRequest() throws SQLException { + endRequestInternal(); + } + public void setShardingKey(ShardingKey shardingKey) throws SQLServerException { DriverJDBCVersion.checkSupportsJDBC43(); throw new SQLServerException("setShardingKey not implemented", new SQLFeatureNotSupportedException("setShardingKey not implemented")); @@ -17,20 +62,22 @@ public void setShardingKey(ShardingKey shardingKey) throws SQLServerException { public void setShardingKey(ShardingKey shardingKey, ShardingKey superShardingKey) throws SQLServerException { DriverJDBCVersion.checkSupportsJDBC43(); - throw new SQLServerException("setShardingKey not implemented", new SQLFeatureNotSupportedException("setShardingKey not implemented")) ; + throw new SQLServerException("setShardingKey not implemented", new SQLFeatureNotSupportedException("setShardingKey not implemented")); } public boolean setShardingKeyIfValid(ShardingKey shardingKey, int timeout) throws SQLServerException { DriverJDBCVersion.checkSupportsJDBC43(); - throw new SQLServerException("setShardingKeyIfValid not implemented", new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); + throw new SQLServerException("setShardingKeyIfValid not implemented", + new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); } public boolean setShardingKeyIfValid(ShardingKey shardingKey, ShardingKey superShardingKey, int timeout) throws SQLServerException { DriverJDBCVersion.checkSupportsJDBC43(); - throw new SQLServerException("setShardingKeyIfValid not implemented", new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); + throw new SQLServerException("setShardingKeyIfValid not implemented", + new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 441d8b5b0..162120ae9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -631,13 +631,14 @@ void closeInternal() { // Regardless what happens when cleaning up, // the statement is considered closed. assert !bIsClosed; - discardLastExecutionResults(); bIsClosed = true; autoGeneratedKeys = null; sqlWarnings = null; inOutParam = null; + + connection.removeOpenStatement(this); } public void close() throws SQLServerException { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java new file mode 100644 index 000000000..e646f63ed --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -0,0 +1,409 @@ +package com.microsoft.sqlserver.jdbc.connection; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.PrepUtil; +import com.microsoft.sqlserver.testframework.Utils; +import com.microsoft.sqlserver.testframework.util.RandomUtil; + +/** + * A class for testing Request Boundary Methods. + */ +@RunWith(JUnitPlatform.class) +public class RequestBoundaryMethodsTest extends AbstractTest { + + /** + * Tests Request Boundary methods with SQLServerConnection properties that are modifiable through public APIs. + * + * @throws SQLException + */ + + @Test + public void testModifiableConnectionProperties() throws SQLException { + // List of SQLServerConnection fields that can be modified through public APIs. + boolean autoCommitMode1 = true; + int transactionIsolationLevel1 = SQLServerConnection.TRANSACTION_READ_COMMITTED; + int networkTimeout1 = 5000; + int holdability1 = ResultSet.HOLD_CURSORS_OVER_COMMIT; + boolean sendTimeAsDatetime1 = true; + int statementPoolingCacheSize1 = 0; + boolean disableStatementPooling1 = true; + int serverPreparedStatementDiscardThreshold1 = 10; + boolean enablePrepareOnFirstPreparedStatementCall1 = false; + String sCatalog1 = "master"; + + boolean autoCommitMode2 = false; + int transactionIsolationLevel2 = SQLServerConnection.TRANSACTION_SERIALIZABLE; + int networkTimeout2 = 10000; + int holdability2 = ResultSet.CLOSE_CURSORS_AT_COMMIT; + boolean sendTimeAsDatetime2 = false; + int statementPoolingCacheSize2 = 10; + boolean disableStatementPooling2 = false; + int serverPreparedStatementDiscardThreshold2 = 100; + boolean enablePrepareOnFirstPreparedStatementCall2 = true; + String sCatalog2 = RandomUtil.getIdentifier("RequestBoundaryDatabase"); + + try (SQLServerConnection con = connect()) { + if (Utils.isJDBC43OrGreater(con)) { + // Second database + con.createStatement().executeUpdate("CREATE DATABASE [" + sCatalog2 + "]"); + + // First set of values. + setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, + statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, + enablePrepareOnFirstPreparedStatementCall1, sCatalog1); + con.beginRequest(); + // Call setters with the second set of values inside beginRequest()/endRequest() block. + setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, + statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, + enablePrepareOnFirstPreparedStatementCall2, sCatalog2); + con.endRequest(); + // Test if endRequest() resets the SQLServerConnection properties back to the first set of values. + compareValuesAgainstConnection(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, + statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, + enablePrepareOnFirstPreparedStatementCall1, sCatalog1); + + // Multiple calls to beginRequest() without an intervening call to endRequest() are no-op. + setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, + statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, + enablePrepareOnFirstPreparedStatementCall2, sCatalog2); + con.beginRequest(); + setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, + statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, + enablePrepareOnFirstPreparedStatementCall1, sCatalog1); + con.beginRequest(); + con.endRequest(); + // Same values as before the first beginRequest() + compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, + statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, + enablePrepareOnFirstPreparedStatementCall2, sCatalog2); + + // A call to endRequest() without an intervening call to beginRequest() is no-op. + setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, + statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, + enablePrepareOnFirstPreparedStatementCall1, sCatalog1); + setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, + statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, + enablePrepareOnFirstPreparedStatementCall2, sCatalog2); + con.endRequest(); + // No change. + compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, + statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, + enablePrepareOnFirstPreparedStatementCall2, sCatalog2); + // drop the database + con.setCatalog("master"); + Utils.dropDatabaseIfExists(sCatalog2, con.createStatement()); + } + } + } + + /** + * Tests Request Boundary methods with warnings. + * + * @throws SQLException + */ + @Test + public void testWarnings() throws SQLException { + try (SQLServerConnection con = connect()) { + if (Utils.isJDBC43OrGreater(con)) { + con.beginRequest(); + generateWarning(con); + assertNotNull(con.getWarnings()); + con.endRequest(); + assertNull(con.getWarnings()); + + generateWarning(con); + con.endRequest(); + assertNotNull(con.getWarnings()); + + con.clearWarnings(); + con.beginRequest(); + generateWarning(con); + con.beginRequest(); + con.endRequest(); + assertNull(con.getWarnings()); + } + } + } + + /** + * Tests Request Boundary methods when there are open transactions. + * + * @throws SQLException + */ + @Test + public void testOpenTransactions() throws SQLException { + ResultSet rs = null; + String tableName = null; + + try (SQLServerConnection con = connect(); Statement stmt = con.createStatement();) { + if (Utils.isJDBC43OrGreater(con)) { + tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("RequestBoundaryTable")); + Utils.dropTableIfExists(tableName, stmt); + stmt.executeUpdate("CREATE TABLE " + tableName + " (col int)"); + con.beginRequest(); + con.setAutoCommit(false); + stmt.executeUpdate("INSERT INTO " + tableName + " values(5)"); + // endRequest() does a rollback here, the value does not get inserted into the table. + con.endRequest(); + con.commit(); + + rs = con.createStatement().executeQuery("SELECT * from " + tableName); + assertTrue(!rs.isBeforeFirst(), "Should not have returned a result set."); + Utils.dropTableIfExists(tableName, con.createStatement()); + } + } + } + + /** + * Tests Request Boundary methods with statements. + * + * @throws SQLException + */ + @SuppressWarnings("resource") + @Test + public void testStatements() throws SQLException { + Statement stmt = null; + ResultSet rs = null; + Statement stmt1 = null; + PreparedStatement ps = null; + CallableStatement cs = null; + ResultSet rs1 = null; + String tableName = null; + + try (SQLServerConnection con = connect();) { + if (Utils.isJDBC43OrGreater(con)) { + stmt1 = con.createStatement(); + con.beginRequest(); + stmt = con.createStatement(); + rs = stmt.executeQuery("SELECT 1"); + rs.next(); + assertEquals(1, rs.getInt(1)); + con.endRequest(); + + assertTrue(!stmt1.isClosed(), "Statement created outside of beginRequest()/endRequest() block should not be closed."); + assertTrue(stmt.isClosed(), "Statment created inside beginRequest()/endRequest() block should be closed after endRequest()."); + assertTrue(rs.isClosed(), "ResultSet should be closed after endRequest()."); + stmt1.close(); + + // Multiple statements inside beginRequest()/endRequest() block + con.beginRequest(); + stmt = con.createStatement(); + tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("RequestBoundary")); + Utils.dropTableIfExists(tableName, stmt); + stmt.executeUpdate("CREATE TABLE " + tableName + " (col int)"); + ps = con.prepareStatement("INSERT INTO " + tableName + " values (?)"); + ps.setInt(1, 2); + ps.executeUpdate(); + + stmt1 = con.createStatement(); + rs1 = stmt1.executeQuery("SELECT * FROM " + tableName); + rs1.next(); + assertEquals(2, rs1.getInt(1)); + Utils.dropTableIfExists(tableName, stmt); + + cs = con.prepareCall("{call sp_server_info}"); + cs.execute(); + con.endRequest(); + + assertTrue(stmt.isClosed()); + assertTrue(ps.isClosed()); + assertTrue(stmt1.isClosed()); + assertTrue(cs.isClosed()); + assertTrue(rs1.isClosed()); + } + } + finally { + if (null != stmt) { + stmt.close(); + } + if (null != stmt1) { + stmt1.close(); + } + if (null != ps) { + ps.close(); + } + if (null != cs) { + cs.close(); + } + } + } + + /** + * Tests Request Boundary methods in a multi-threaded environment. + * + * @throws SQLException + */ + @Test + public void testThreads() throws SQLException { + class Variables { + volatile SQLServerConnection con = null; + volatile Statement stmt = null; + volatile PreparedStatement pstmt = null; + } + + final Variables sharedVariables = new Variables(); + final CountDownLatch latch = new CountDownLatch(3); + try { + sharedVariables.con = connect(); + if (Utils.isJDBC43OrGreater(sharedVariables.con)) { + Thread thread1 = new Thread() { + public void run() { + try { + sharedVariables.con.setNetworkTimeout(null, 100); + sharedVariables.con.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT); + latch.countDown(); + } + catch (SQLException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + } + }; + + Thread thread2 = new Thread() { + public void run() { + try { + sharedVariables.stmt = sharedVariables.con.createStatement(); + ResultSet rs = sharedVariables.stmt.executeQuery("SELECT 1"); + rs.next(); + assertEquals(1, rs.getInt(1)); + latch.countDown(); + } + catch (SQLException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + } + }; + + Thread thread3 = new Thread() { + public void run() { + try { + sharedVariables.pstmt = sharedVariables.con.prepareStatement("SELECT 1"); + ResultSet rs = sharedVariables.pstmt.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + latch.countDown(); + } + catch (SQLException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + + } + }; + + int originalNetworkTimeout = sharedVariables.con.getNetworkTimeout(); + int originalHoldability = sharedVariables.con.getHoldability(); + sharedVariables.con.beginRequest(); + thread1.start(); + thread2.start(); + thread3.start(); + latch.await(); + sharedVariables.con.endRequest(); + + assertEquals(originalNetworkTimeout, sharedVariables.con.getNetworkTimeout()); + assertEquals(originalHoldability, sharedVariables.con.getHoldability()); + assertTrue(sharedVariables.stmt.isClosed()); + assertTrue(sharedVariables.pstmt.isClosed()); + } + } + catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + finally { + if (null != sharedVariables.stmt) { + sharedVariables.stmt.close(); + } + if (null != sharedVariables.pstmt) { + sharedVariables.pstmt.close(); + } + if (null != sharedVariables.con) { + sharedVariables.con.close(); + } + } + } + + private SQLServerConnection connect() throws SQLException { + SQLServerConnection connection = null; + try { + connection = PrepUtil.getConnection(getConfiguredProperty("mssql_jdbc_test_connection_properties")); + } + catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return connection; + } + + private void setConnectionFields(SQLServerConnection con, + boolean autoCommitMode, + int transactionIsolationLevel, + int networkTimeout, + int holdability, + boolean sendTimeAsDatetime, + int statementPoolingCacheSize, + boolean disableStatementPooling, + int serverPreparedStatementDiscardThreshold, + boolean enablePrepareOnFirstPreparedStatementCall, + String sCatalog) throws SQLException { + con.setAutoCommit(autoCommitMode); + con.setTransactionIsolation(transactionIsolationLevel); + con.setNetworkTimeout(null, networkTimeout); + con.setHoldability(holdability); + con.setSendTimeAsDatetime(sendTimeAsDatetime); + con.setStatementPoolingCacheSize(statementPoolingCacheSize); + con.setDisableStatementPooling(disableStatementPooling); + con.setServerPreparedStatementDiscardThreshold(serverPreparedStatementDiscardThreshold); + con.setEnablePrepareOnFirstPreparedStatementCall(enablePrepareOnFirstPreparedStatementCall); + con.setCatalog(sCatalog); + } + + private void compareValuesAgainstConnection(SQLServerConnection con, + boolean autoCommitMode, + int transactionIsolationLevel, + int networkTimeout, + int holdability, + boolean sendTimeAsDatetime, + int statementPoolingCacheSize, + boolean disableStatementPooling, + int serverPreparedStatementDiscardThreshold, + boolean enablePrepareOnFirstPreparedStatementCall, + String sCatalog) throws SQLException { + final String description = " values do not match."; + assertEquals(autoCommitMode, con.getAutoCommit(), "autoCommitmode" + description); + assertEquals(transactionIsolationLevel, con.getTransactionIsolation(), "transactionIsolationLevel" + description); + assertEquals(networkTimeout, con.getNetworkTimeout(), "networkTimeout" + description); + assertEquals(holdability, con.getHoldability(), "holdability" + description); + assertEquals(sendTimeAsDatetime, con.getSendTimeAsDatetime(), "sendTimeAsDatetime" + description); + assertEquals(statementPoolingCacheSize, con.getStatementPoolingCacheSize(), "statementPoolingCacheSize" + description); + assertEquals(disableStatementPooling, con.getDisableStatementPooling(), "disableStatementPooling" + description); + assertEquals(serverPreparedStatementDiscardThreshold, con.getServerPreparedStatementDiscardThreshold(), + "serverPreparedStatementDiscardThreshold" + description); + assertEquals(enablePrepareOnFirstPreparedStatementCall, con.getEnablePrepareOnFirstPreparedStatementCall(), + "enablePrepareOnFirstPreparedStatementCall" + description); + assertEquals(sCatalog, con.getCatalog(), "sCatalog" + description); + } + + private void generateWarning(SQLServerConnection con) throws SQLException { + con.setClientInfo("name", "value"); + } +} diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index e72ab2dc5..3df03ef53 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -181,5 +181,4 @@ else if ("file".equalsIgnoreCase(loggingHandler)) { System.err.println("Some how could not invoke logging: " + e.getMessage()); } } - } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java index 229f7f4c6..cf761bbb1 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java @@ -14,6 +14,7 @@ import java.io.ByteArrayInputStream; import java.io.CharArrayReader; import java.net.URI; +import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -283,6 +284,11 @@ public static void dropProcedureIfExists(String procName, java.sql.Statement stm dropObjectIfExists(procName, "IsProcedure", stmt); } + public static void dropDatabaseIfExists(String databaseName, + java.sql.Statement stmt) throws SQLException { + stmt.executeUpdate("IF EXISTS(SELECT * from sys.databases WHERE name='" + databaseName + "') DROP DATABASE [" + databaseName + "]"); + } + /** * actually perform the "DROP TABLE / PROCEDURE" */ @@ -315,4 +321,11 @@ public static boolean parseByte(byte[] expectedData, return true; } -} \ No newline at end of file + public static boolean isJDBC43OrGreater(Connection connection) throws SQLException{ + return getJDBCVersion(connection) >= 4.3F; + } + + public static float getJDBCVersion(Connection connection) throws SQLException { + return Float.valueOf(connection.getMetaData().getJDBCMajorVersion() + "." + connection.getMetaData().getJDBCMinorVersion()); + } +}