From 025d1974619bd203aae2e8275cc774ba7c3c6b3c Mon Sep 17 00:00:00 2001 From: mthakur Date: Thu, 15 Feb 2024 15:32:04 +0700 Subject: [PATCH 1/8] use re-sentrant locks instead of synchronized blocks to make driver loom friendly --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index e5e175b9e..7ab950129 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -1440,8 +1440,13 @@ public int getPort() { } @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return tdsChannel.tcpSocket.getReceiveBufferSize(); + public int getReceiveBufferSize() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getReceiveBufferSize(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1455,8 +1460,13 @@ public boolean getReuseAddress() throws SocketException { } @Override - public synchronized int getSendBufferSize() throws SocketException { - return tdsChannel.tcpSocket.getSendBufferSize(); + public int getSendBufferSize() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getSendBufferSize(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1465,8 +1475,13 @@ public int getSoLinger() throws SocketException { } @Override - public synchronized int getSoTimeout() throws SocketException { - return tdsChannel.tcpSocket.getSoTimeout(); + public int getSoTimeout() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getSoTimeout(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1536,21 +1551,36 @@ public void connect(SocketAddress endpoint, int timeout) throws IOException { // Ignore calls to methods that would otherwise allow the SSL socket // to directly manipulate the underlying TCP socket @Override - public synchronized void close() throws IOException { - if (logger.isLoggable(Level.FINER)) - logger.finer(logContext + " Ignoring close"); + public void close() throws IOException { + try { + tdsChannelLock.lock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(logContext + " Ignoring close"); + } finally { + tdsChannelLock.unlock(); + } } @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); + public void setReceiveBufferSize(int size) throws SocketException { + try { + tdsChannelLock.lock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); + } finally { + tdsChannelLock.unlock(); + } } @Override - public synchronized void setSendBufferSize(int size) throws SocketException { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); + public void setSendBufferSize(int size) throws SocketException { + try { + tdsChannelLock.unlock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1566,8 +1596,13 @@ public void setSoLinger(boolean on, int linger) throws SocketException { } @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - tdsChannel.tcpSocket.setSoTimeout(timeout); + public void setSoTimeout(int timeout) throws SocketException { + try { + tdsChannelLock.lock(); + tdsChannel.tcpSocket.setSoTimeout(timeout); + } finally { + tdsChannelLock.unlock(); + } } @Override From e352d89b8cd785f3170402697ca97ebbd456e608 Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Thu, 15 Feb 2024 14:25:04 -0800 Subject: [PATCH 2/8] Update CodeQL action versions (#2327) --- .github/workflows/codeql.yml | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dc42ea428..c4908ce5a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,14 +1,3 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL" on: @@ -18,8 +7,6 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] - schedule: - - cron: '27 5 * * 2' jobs: analyze: @@ -34,16 +21,14 @@ jobs: fail-fast: false matrix: language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +41,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) #- name: Autobuild - # uses: github/codeql-action/autobuild@v2 + # uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -67,9 +52,10 @@ jobs: # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh + - run: mvn install -Pjre11 -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" From ba88da8e97d10b2ffda325599699a37525981ffd Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:26:30 -0800 Subject: [PATCH 3/8] Re-added support for stored procedure 'exec' escape syntax in CallableStatements (#2325) * EXEC system stored procedure regression fix * Additional test * Additional test * Indenting * Switched error string to TestResource error string * CR comments * Test update p1 * Test update p2 * CR comment changes; Test update * call escape syntax check * CR changes * formatting --- .../sqlserver/jdbc/SQLServerBulkCopy.java | 11 ++- .../jdbc/SQLServerPreparedStatement.java | 37 +++++++- .../CallableStatementTest.java | 95 +++++++++++++++++-- .../unit/statement/BatchExecutionTest.java | 11 ++- 4 files changed, 137 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index c2f51aef5..e5fa8825e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2064,7 +2064,8 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType, private void writeColumnToTdsWriter(TDSWriter tdsWriter, int bulkPrecision, int bulkScale, int bulkJdbcType, boolean bulkNullable, // should it be destNullable instead? - int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, Calendar cal) throws SQLServerException { + int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, + Calendar cal) throws SQLServerException { SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType; bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, @@ -2987,8 +2988,8 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole /** * Reads the given column from the result set current row and writes the data to tdsWriter. */ - private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, - Object colValue, Calendar cal) throws SQLServerException { + private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, Object colValue, + Calendar cal) throws SQLServerException { String destName = destColumnMetadata.get(destColOrdinal).columnName; int srcPrecision, srcScale, destPrecision, srcJdbcType; SSType destSSType = null; @@ -3640,8 +3641,8 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command, // Loop for each destination column. The mappings is a many to one mapping // where multiple source columns can be mapped to one destination column. for (ColumnMapping columnMapping : columnMappings) { - writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, null, - null // cell + writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, + null, null // cell // value is // retrieved // inside diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index d5b9b5499..7789b3726 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -29,6 +29,7 @@ import java.util.Map.Entry; import java.util.Vector; import java.util.logging.Level; +import java.util.regex.Pattern; import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; import com.microsoft.sqlserver.jdbc.SQLServerConnection.PreparedStatementHandle; @@ -70,6 +71,10 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS /** Processed SQL statement text, may not be same as what user initially passed. */ final String userSQL; + private boolean isExecEscapeSyntax; + + private boolean isCallEscapeSyntax; + /** Parameter positions in processed SQL statement text. */ final int[] userSQLParamPositions; @@ -128,6 +133,17 @@ private void setPreparedStatementHandle(int handle) { */ private boolean useBulkCopyForBatchInsert; + /** + * Regex for JDBC 'call' escape syntax + */ + private static final Pattern callEscapePattern = Pattern + .compile("^\\s*(?i)\\{(\\s*\\??\\s*=?\\s*)call (.+)\\s*\\(?\\?*,?\\)?\\s*}\\s*$"); + + /** + * Regex for 'exec' escape syntax + */ + private static final Pattern execEscapePattern = Pattern.compile("^\\s*(?i)(?:exec|execute)\\b"); + /** Returns the prepared statement SQL */ @Override public String toString() { @@ -253,6 +269,8 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) { procedureName = parsedSQL.procedureName; bReturnValueSyntax = parsedSQL.bReturnValueSyntax; userSQL = parsedSQL.processedSQL; + isExecEscapeSyntax = isExecEscapeSyntax(sql); + isCallEscapeSyntax = isCallEscapeSyntax(sql); userSQLParamPositions = parsedSQL.parameterPositions; initParams(userSQLParamPositions.length); useBulkCopyForBatchInsert = conn.getUseBulkCopyForBatchInsert(); @@ -1210,7 +1228,16 @@ else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCal */ boolean callRPCDirectly(Parameter[] params) throws SQLServerException { int paramCount = SQLServerConnection.countParams(userSQL); - return (null != procedureName && paramCount != 0 && !isTVPType(params)); + + // In order to execute sprocs directly the following must be true: + // 1. There must be a sproc name + // 2. There must be parameters + // 3. Parameters must not be a TVP type + // 4. Compliant CALL escape syntax + // If isExecEscapeSyntax is true, EXEC escape syntax is used then use prior behaviour to + // execute the procedure + return (null != procedureName && paramCount != 0 && !isTVPType(params) && isCallEscapeSyntax + && !isExecEscapeSyntax); } /** @@ -1230,6 +1257,14 @@ private boolean isTVPType(Parameter[] params) throws SQLServerException { return false; } + private boolean isExecEscapeSyntax(String sql) { + return execEscapePattern.matcher(sql).find(); + } + + private boolean isCallEscapeSyntax(String sql) { + return callEscapePattern.matcher(sql).find(); + } + /** * Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java index 3a480548d..6570abf72 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -81,7 +81,7 @@ public class CallableStatementTest extends AbstractTest { /** * Setup before test - * + * * @throws SQLException */ @BeforeAll @@ -201,7 +201,7 @@ public void testCallableStatementSpPrepare() throws SQLException { /** * Tests CallableStatement.getString() with uniqueidentifier parameter - * + * * @throws SQLException */ @Test @@ -226,7 +226,7 @@ public void getStringGUIDTest() throws SQLException { /** * test for setNull(index, varchar) to behave as setNull(index, nvarchar) when SendStringParametersAsUnicode is true - * + * * @throws SQLException */ @Test @@ -302,7 +302,7 @@ public void testGetObjectAsLocalDateTime() throws SQLException { /** * Tests getObject(n, java.time.OffsetDateTime.class) and getObject(n, java.time.OffsetTime.class). - * + * * @throws SQLException */ @Test @@ -332,7 +332,7 @@ public void testGetObjectAsOffsetDateTime() throws SQLException { /** * recognize parameter names with and without leading '@' - * + * * @throws SQLException */ @Test @@ -1067,9 +1067,92 @@ public void testRegisteringOutputByIndexandAcquiringOutputParamByName() throws S } } + @Test + public void testExecuteSystemStoredProcedureNamedParametersAndIndexedParameterNoResultset() throws SQLException { + String call0 = "EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; + String call1 = "\rEXEC\r\rsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; + String call2 = " EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; + String call3 = "\tEXEC\t\t\tsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; + + try (CallableStatement cstmt0 = connection.prepareCall(call0); + CallableStatement cstmt1 = connection.prepareCall(call1); + CallableStatement cstmt2 = connection.prepareCall(call2); + CallableStatement cstmt3 = connection.prepareCall(call3);) { + cstmt0.setString(1, "Resource-" + UUID.randomUUID()); + cstmt0.execute(); + + cstmt1.setString(1, "Resource-" + UUID.randomUUID()); + cstmt1.execute(); + + cstmt2.setString(1, "Resource-" + UUID.randomUUID()); + cstmt2.execute(); + + cstmt3.setString(1, "Resource-" + UUID.randomUUID()); + cstmt3.execute(); + } + } + + @Test + public void testExecSystemStoredProcedureNamedParametersAndIndexedParameterResultSet() throws SQLException { + String call = "exec sp_sproc_columns_100 ?, @ODBCVer=3, @fUsePattern=0"; + + try (CallableStatement cstmt = connection.prepareCall(call)) { + cstmt.setString(1, "sp_getapplock"); + + try (ResultSet rs = cstmt.executeQuery()) { + while (rs.next()) { + assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty()); + } + } + } + } + + @Test + public void testExecSystemStoredProcedureNoIndexedParametersResultSet() throws SQLException { + String call = "execute sp_sproc_columns_100 sp_getapplock, @ODBCVer=3, @fUsePattern=0"; + + try (CallableStatement cstmt = connection.prepareCall(call); ResultSet rs = cstmt.executeQuery()) { + while (rs.next()) { + assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty()); + } + } + } + + @Test + public void testExecDocumentedSystemStoredProceduresIndexedParameters() throws SQLException { + String serverName; + String testTableName = "testTable"; + Integer integer = new Integer(1); + + try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT @@SERVERNAME")) { + rs.next(); + serverName = rs.getString(1); + } + + String[] sprocs = {"EXEC sp_column_privileges ?", "exec sp_catalogs ?", "execute sp_column_privileges ?", + "EXEC sp_column_privileges_ex ?", "EXECUTE sp_columns ?", "execute sp_datatype_info ?", + "EXEC sp_sproc_columns ?", "EXECUTE sp_server_info ?", "exec sp_special_columns ?", + "execute sp_statistics ?", "EXEC sp_table_privileges ?", "exec sp_tables ?"}; + + Object[] params = {testTableName, serverName, testTableName, serverName, testTableName, integer, + "sp_column_privileges", integer, testTableName, testTableName, testTableName, testTableName}; + + int paramIndex = 0; + + for (String sproc : sprocs) { + try (CallableStatement cstmt = connection.prepareCall(sproc)) { + cstmt.setObject(1, params[paramIndex]); + cstmt.execute(); + paramIndex++; + } catch (Exception e) { + fail("Failed executing '" + sproc + "' with indexed parameter '" + params[paramIndex]); + } + } + } + /** * Cleanup after test - * + * * @throws SQLException */ @AfterAll 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 3384f278c..0dace62b2 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 @@ -170,7 +170,7 @@ public void testValidTimezoneForTimestampBatchInsertWithBulkCopy() throws Except public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Exception { Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - for (String tzId: TimeZone.getAvailableIDs()) { + for (String tzId : TimeZone.getAvailableIDs()) { TimeZone.setDefault(TimeZone.getTimeZone(tzId)); long ms = 1696127400000L; // DST @@ -191,8 +191,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep } // Insert Timestamp using bulkcopy for batch insert - try (Connection con = DriverManager.getConnection( - connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); + try (Connection con = DriverManager.getConnection(connectionString + + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable1 + " VALUES(?)")) { Timestamp timestamp = new Timestamp(ms); @@ -235,8 +235,9 @@ public void testBatchInsertTimestampNoTimezoneDoubleConversion() throws Exceptio long ms = 1578743412000L; // Insert Timestamp using prepared statement when useBulkCopyForBatchInsert=true - try (Connection con = DriverManager.getConnection(connectionString - + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); Statement stmt = con.createStatement(); + try (Connection con = DriverManager.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); + Statement stmt = con.createStatement(); PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable2 + " VALUES(?)")) { TestUtils.dropTableIfExists(timestampTable2, stmt); From 09a6d3fd7f3d54b4ae8b4da371d66ef5c17b18fb Mon Sep 17 00:00:00 2001 From: mthakur Date: Fri, 16 Feb 2024 11:37:53 +0700 Subject: [PATCH 4/8] moev lock statement outside of try --- .../com/microsoft/sqlserver/jdbc/IOBuffer.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 7ab950129..56f0e7f3f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -1441,8 +1441,8 @@ public int getPort() { @Override public int getReceiveBufferSize() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getReceiveBufferSize(); } finally { tdsChannelLock.unlock(); @@ -1461,8 +1461,8 @@ public boolean getReuseAddress() throws SocketException { @Override public int getSendBufferSize() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getSendBufferSize(); } finally { tdsChannelLock.unlock(); @@ -1476,8 +1476,8 @@ public int getSoLinger() throws SocketException { @Override public int getSoTimeout() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getSoTimeout(); } finally { tdsChannelLock.unlock(); @@ -1552,8 +1552,8 @@ public void connect(SocketAddress endpoint, int timeout) throws IOException { // to directly manipulate the underlying TCP socket @Override public void close() throws IOException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); if (logger.isLoggable(Level.FINER)) logger.finer(logContext + " Ignoring close"); } finally { @@ -1563,8 +1563,8 @@ public void close() throws IOException { @Override public void setReceiveBufferSize(int size) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); if (logger.isLoggable(Level.FINER)) logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); } finally { @@ -1574,8 +1574,8 @@ public void setReceiveBufferSize(int size) throws SocketException { @Override public void setSendBufferSize(int size) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.unlock(); if (logger.isLoggable(Level.FINER)) logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); } finally { @@ -1597,8 +1597,8 @@ public void setSoLinger(boolean on, int linger) throws SocketException { @Override public void setSoTimeout(int timeout) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); tdsChannel.tcpSocket.setSoTimeout(timeout); } finally { tdsChannelLock.unlock(); From 834a67ba181b90bd4cff1d248af82d82beaa1a21 Mon Sep 17 00:00:00 2001 From: mthakur Date: Thu, 15 Feb 2024 15:32:04 +0700 Subject: [PATCH 5/8] use re-sentrant locks instead of synchronized blocks to make driver loom friendly --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index e5e175b9e..7ab950129 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -1440,8 +1440,13 @@ public int getPort() { } @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return tdsChannel.tcpSocket.getReceiveBufferSize(); + public int getReceiveBufferSize() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getReceiveBufferSize(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1455,8 +1460,13 @@ public boolean getReuseAddress() throws SocketException { } @Override - public synchronized int getSendBufferSize() throws SocketException { - return tdsChannel.tcpSocket.getSendBufferSize(); + public int getSendBufferSize() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getSendBufferSize(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1465,8 +1475,13 @@ public int getSoLinger() throws SocketException { } @Override - public synchronized int getSoTimeout() throws SocketException { - return tdsChannel.tcpSocket.getSoTimeout(); + public int getSoTimeout() throws SocketException { + try { + tdsChannelLock.lock(); + return tdsChannel.tcpSocket.getSoTimeout(); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1536,21 +1551,36 @@ public void connect(SocketAddress endpoint, int timeout) throws IOException { // Ignore calls to methods that would otherwise allow the SSL socket // to directly manipulate the underlying TCP socket @Override - public synchronized void close() throws IOException { - if (logger.isLoggable(Level.FINER)) - logger.finer(logContext + " Ignoring close"); + public void close() throws IOException { + try { + tdsChannelLock.lock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(logContext + " Ignoring close"); + } finally { + tdsChannelLock.unlock(); + } } @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); + public void setReceiveBufferSize(int size) throws SocketException { + try { + tdsChannelLock.lock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); + } finally { + tdsChannelLock.unlock(); + } } @Override - public synchronized void setSendBufferSize(int size) throws SocketException { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); + public void setSendBufferSize(int size) throws SocketException { + try { + tdsChannelLock.unlock(); + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); + } finally { + tdsChannelLock.unlock(); + } } @Override @@ -1566,8 +1596,13 @@ public void setSoLinger(boolean on, int linger) throws SocketException { } @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - tdsChannel.tcpSocket.setSoTimeout(timeout); + public void setSoTimeout(int timeout) throws SocketException { + try { + tdsChannelLock.lock(); + tdsChannel.tcpSocket.setSoTimeout(timeout); + } finally { + tdsChannelLock.unlock(); + } } @Override From a6d69714419cdfd63f7eb142a1e11a72c46b4fc1 Mon Sep 17 00:00:00 2001 From: mthakur Date: Fri, 16 Feb 2024 11:37:53 +0700 Subject: [PATCH 6/8] moev lock statement outside of try --- .../com/microsoft/sqlserver/jdbc/IOBuffer.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 7ab950129..56f0e7f3f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -1441,8 +1441,8 @@ public int getPort() { @Override public int getReceiveBufferSize() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getReceiveBufferSize(); } finally { tdsChannelLock.unlock(); @@ -1461,8 +1461,8 @@ public boolean getReuseAddress() throws SocketException { @Override public int getSendBufferSize() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getSendBufferSize(); } finally { tdsChannelLock.unlock(); @@ -1476,8 +1476,8 @@ public int getSoLinger() throws SocketException { @Override public int getSoTimeout() throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); return tdsChannel.tcpSocket.getSoTimeout(); } finally { tdsChannelLock.unlock(); @@ -1552,8 +1552,8 @@ public void connect(SocketAddress endpoint, int timeout) throws IOException { // to directly manipulate the underlying TCP socket @Override public void close() throws IOException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); if (logger.isLoggable(Level.FINER)) logger.finer(logContext + " Ignoring close"); } finally { @@ -1563,8 +1563,8 @@ public void close() throws IOException { @Override public void setReceiveBufferSize(int size) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); if (logger.isLoggable(Level.FINER)) logger.finer(toString() + " Ignoring setReceiveBufferSize size:" + size); } finally { @@ -1574,8 +1574,8 @@ public void setReceiveBufferSize(int size) throws SocketException { @Override public void setSendBufferSize(int size) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.unlock(); if (logger.isLoggable(Level.FINER)) logger.finer(toString() + " Ignoring setSendBufferSize size:" + size); } finally { @@ -1597,8 +1597,8 @@ public void setSoLinger(boolean on, int linger) throws SocketException { @Override public void setSoTimeout(int timeout) throws SocketException { + tdsChannelLock.lock(); try { - tdsChannelLock.lock(); tdsChannel.tcpSocket.setSoTimeout(timeout); } finally { tdsChannelLock.unlock(); From fec92b1d19cd0352acb5d1177ee90c791a7b2352 Mon Sep 17 00:00:00 2001 From: mthakur Date: Fri, 16 Feb 2024 13:42:51 +0700 Subject: [PATCH 7/8] rollback unncessary changes --- .github/workflows/codeql.yml | 24 +++++++++--- .../sqlserver/jdbc/SQLServerBulkCopy.java | 11 +++--- .../jdbc/SQLServerPreparedStatement.java | 37 +------------------ 3 files changed, 25 insertions(+), 47 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c4908ce5a..dc42ea428 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,14 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# name: "CodeQL" on: @@ -7,6 +18,8 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] + schedule: + - cron: '27 5 * * 2' jobs: analyze: @@ -21,14 +34,16 @@ jobs: fail-fast: false matrix: language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -41,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) #- name: Autobuild - # uses: github/codeql-action/autobuild@v3 + # uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -52,10 +67,9 @@ jobs: # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - - run: mvn install -Pjre11 -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v2 with: category: "/language:${{matrix.language}}" diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index e5fa8825e..c2f51aef5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2064,8 +2064,7 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType, private void writeColumnToTdsWriter(TDSWriter tdsWriter, int bulkPrecision, int bulkScale, int bulkJdbcType, boolean bulkNullable, // should it be destNullable instead? - int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, - Calendar cal) throws SQLServerException { + int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, Calendar cal) throws SQLServerException { SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType; bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, @@ -2988,8 +2987,8 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole /** * Reads the given column from the result set current row and writes the data to tdsWriter. */ - private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, Object colValue, - Calendar cal) throws SQLServerException { + private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, + Object colValue, Calendar cal) throws SQLServerException { String destName = destColumnMetadata.get(destColOrdinal).columnName; int srcPrecision, srcScale, destPrecision, srcJdbcType; SSType destSSType = null; @@ -3641,8 +3640,8 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command, // Loop for each destination column. The mappings is a many to one mapping // where multiple source columns can be mapped to one destination column. for (ColumnMapping columnMapping : columnMappings) { - writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, - null, null // cell + writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, null, + null // cell // value is // retrieved // inside diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 7789b3726..d5b9b5499 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -29,7 +29,6 @@ import java.util.Map.Entry; import java.util.Vector; import java.util.logging.Level; -import java.util.regex.Pattern; import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; import com.microsoft.sqlserver.jdbc.SQLServerConnection.PreparedStatementHandle; @@ -71,10 +70,6 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS /** Processed SQL statement text, may not be same as what user initially passed. */ final String userSQL; - private boolean isExecEscapeSyntax; - - private boolean isCallEscapeSyntax; - /** Parameter positions in processed SQL statement text. */ final int[] userSQLParamPositions; @@ -133,17 +128,6 @@ private void setPreparedStatementHandle(int handle) { */ private boolean useBulkCopyForBatchInsert; - /** - * Regex for JDBC 'call' escape syntax - */ - private static final Pattern callEscapePattern = Pattern - .compile("^\\s*(?i)\\{(\\s*\\??\\s*=?\\s*)call (.+)\\s*\\(?\\?*,?\\)?\\s*}\\s*$"); - - /** - * Regex for 'exec' escape syntax - */ - private static final Pattern execEscapePattern = Pattern.compile("^\\s*(?i)(?:exec|execute)\\b"); - /** Returns the prepared statement SQL */ @Override public String toString() { @@ -269,8 +253,6 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) { procedureName = parsedSQL.procedureName; bReturnValueSyntax = parsedSQL.bReturnValueSyntax; userSQL = parsedSQL.processedSQL; - isExecEscapeSyntax = isExecEscapeSyntax(sql); - isCallEscapeSyntax = isCallEscapeSyntax(sql); userSQLParamPositions = parsedSQL.parameterPositions; initParams(userSQLParamPositions.length); useBulkCopyForBatchInsert = conn.getUseBulkCopyForBatchInsert(); @@ -1228,16 +1210,7 @@ else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCal */ boolean callRPCDirectly(Parameter[] params) throws SQLServerException { int paramCount = SQLServerConnection.countParams(userSQL); - - // In order to execute sprocs directly the following must be true: - // 1. There must be a sproc name - // 2. There must be parameters - // 3. Parameters must not be a TVP type - // 4. Compliant CALL escape syntax - // If isExecEscapeSyntax is true, EXEC escape syntax is used then use prior behaviour to - // execute the procedure - return (null != procedureName && paramCount != 0 && !isTVPType(params) && isCallEscapeSyntax - && !isExecEscapeSyntax); + return (null != procedureName && paramCount != 0 && !isTVPType(params)); } /** @@ -1257,14 +1230,6 @@ private boolean isTVPType(Parameter[] params) throws SQLServerException { return false; } - private boolean isExecEscapeSyntax(String sql) { - return execEscapePattern.matcher(sql).find(); - } - - private boolean isCallEscapeSyntax(String sql) { - return callEscapePattern.matcher(sql).find(); - } - /** * Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle * From 855932e259e1dbaab652a1ddf63ccf5a9fbf90f7 Mon Sep 17 00:00:00 2001 From: mthakur Date: Fri, 16 Feb 2024 13:46:52 +0700 Subject: [PATCH 8/8] rollback unncessary changes --- .../CallableStatementTest.java | 95 ++----------------- .../unit/statement/BatchExecutionTest.java | 11 +-- 2 files changed, 11 insertions(+), 95 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java index 6570abf72..3a480548d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -81,7 +81,7 @@ public class CallableStatementTest extends AbstractTest { /** * Setup before test - * + * * @throws SQLException */ @BeforeAll @@ -201,7 +201,7 @@ public void testCallableStatementSpPrepare() throws SQLException { /** * Tests CallableStatement.getString() with uniqueidentifier parameter - * + * * @throws SQLException */ @Test @@ -226,7 +226,7 @@ public void getStringGUIDTest() throws SQLException { /** * test for setNull(index, varchar) to behave as setNull(index, nvarchar) when SendStringParametersAsUnicode is true - * + * * @throws SQLException */ @Test @@ -302,7 +302,7 @@ public void testGetObjectAsLocalDateTime() throws SQLException { /** * Tests getObject(n, java.time.OffsetDateTime.class) and getObject(n, java.time.OffsetTime.class). - * + * * @throws SQLException */ @Test @@ -332,7 +332,7 @@ public void testGetObjectAsOffsetDateTime() throws SQLException { /** * recognize parameter names with and without leading '@' - * + * * @throws SQLException */ @Test @@ -1067,92 +1067,9 @@ public void testRegisteringOutputByIndexandAcquiringOutputParamByName() throws S } } - @Test - public void testExecuteSystemStoredProcedureNamedParametersAndIndexedParameterNoResultset() throws SQLException { - String call0 = "EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; - String call1 = "\rEXEC\r\rsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; - String call2 = " EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; - String call3 = "\tEXEC\t\t\tsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'"; - - try (CallableStatement cstmt0 = connection.prepareCall(call0); - CallableStatement cstmt1 = connection.prepareCall(call1); - CallableStatement cstmt2 = connection.prepareCall(call2); - CallableStatement cstmt3 = connection.prepareCall(call3);) { - cstmt0.setString(1, "Resource-" + UUID.randomUUID()); - cstmt0.execute(); - - cstmt1.setString(1, "Resource-" + UUID.randomUUID()); - cstmt1.execute(); - - cstmt2.setString(1, "Resource-" + UUID.randomUUID()); - cstmt2.execute(); - - cstmt3.setString(1, "Resource-" + UUID.randomUUID()); - cstmt3.execute(); - } - } - - @Test - public void testExecSystemStoredProcedureNamedParametersAndIndexedParameterResultSet() throws SQLException { - String call = "exec sp_sproc_columns_100 ?, @ODBCVer=3, @fUsePattern=0"; - - try (CallableStatement cstmt = connection.prepareCall(call)) { - cstmt.setString(1, "sp_getapplock"); - - try (ResultSet rs = cstmt.executeQuery()) { - while (rs.next()) { - assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty()); - } - } - } - } - - @Test - public void testExecSystemStoredProcedureNoIndexedParametersResultSet() throws SQLException { - String call = "execute sp_sproc_columns_100 sp_getapplock, @ODBCVer=3, @fUsePattern=0"; - - try (CallableStatement cstmt = connection.prepareCall(call); ResultSet rs = cstmt.executeQuery()) { - while (rs.next()) { - assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty()); - } - } - } - - @Test - public void testExecDocumentedSystemStoredProceduresIndexedParameters() throws SQLException { - String serverName; - String testTableName = "testTable"; - Integer integer = new Integer(1); - - try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT @@SERVERNAME")) { - rs.next(); - serverName = rs.getString(1); - } - - String[] sprocs = {"EXEC sp_column_privileges ?", "exec sp_catalogs ?", "execute sp_column_privileges ?", - "EXEC sp_column_privileges_ex ?", "EXECUTE sp_columns ?", "execute sp_datatype_info ?", - "EXEC sp_sproc_columns ?", "EXECUTE sp_server_info ?", "exec sp_special_columns ?", - "execute sp_statistics ?", "EXEC sp_table_privileges ?", "exec sp_tables ?"}; - - Object[] params = {testTableName, serverName, testTableName, serverName, testTableName, integer, - "sp_column_privileges", integer, testTableName, testTableName, testTableName, testTableName}; - - int paramIndex = 0; - - for (String sproc : sprocs) { - try (CallableStatement cstmt = connection.prepareCall(sproc)) { - cstmt.setObject(1, params[paramIndex]); - cstmt.execute(); - paramIndex++; - } catch (Exception e) { - fail("Failed executing '" + sproc + "' with indexed parameter '" + params[paramIndex]); - } - } - } - /** * Cleanup after test - * + * * @throws SQLException */ @AfterAll 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 0dace62b2..3384f278c 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 @@ -170,7 +170,7 @@ public void testValidTimezoneForTimestampBatchInsertWithBulkCopy() throws Except public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Exception { Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - for (String tzId : TimeZone.getAvailableIDs()) { + for (String tzId: TimeZone.getAvailableIDs()) { TimeZone.setDefault(TimeZone.getTimeZone(tzId)); long ms = 1696127400000L; // DST @@ -191,8 +191,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep } // Insert Timestamp using bulkcopy for batch insert - try (Connection con = DriverManager.getConnection(connectionString - + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); + try (Connection con = DriverManager.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable1 + " VALUES(?)")) { Timestamp timestamp = new Timestamp(ms); @@ -235,9 +235,8 @@ public void testBatchInsertTimestampNoTimezoneDoubleConversion() throws Exceptio long ms = 1578743412000L; // Insert Timestamp using prepared statement when useBulkCopyForBatchInsert=true - try (Connection con = DriverManager.getConnection( - connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); - Statement stmt = con.createStatement(); + try (Connection con = DriverManager.getConnection(connectionString + + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); Statement stmt = con.createStatement(); PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable2 + " VALUES(?)")) { TestUtils.dropTableIfExists(timestampTable2, stmt);