From f312d1ba42f1f1b680b29ce01a56a01faaf40f06 Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Wed, 8 May 2024 10:40:50 -0700 Subject: [PATCH 01/12] Cleanup orphans from tests (#2408) --- .../sqlserver/jdbc/TestResource.java | 3 ++- .../microsoft/sqlserver/jdbc/TestUtils.java | 26 ++++++++++++++++++- .../DatabaseMetaDataTest.java | 24 +++++++++-------- .../jdbc/unit/SQLServerErrorTest.java | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index b3e854fd1..d542434fb 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -218,5 +218,6 @@ protected Object[][] getContents() { "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"}, {"R_unexpectedThreadCount", "Thread count is higher than expected."}, {"R_expectedClassDoesNotMatchActualClass", - "Expected column class {0} does not match actual column class {1} for column {2}."}}; + "Expected column class {0} does not match actual column class {1} for column {2}."}, + {"R_loginFailedMSI", "Login failed for user ''"}}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java index 4283a80f5..8db118b4d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java @@ -535,7 +535,31 @@ public static void dropDatabaseIfExists(String databaseName, String connectionSt */ public static void dropSchemaIfExists(String schemaName, Statement stmt) throws SQLException { stmt.execute("if EXISTS (SELECT * FROM sys.schemas where name = '" + escapeSingleQuotes(schemaName) - + "') drop schema " + AbstractSQLGenerator.escapeIdentifier(schemaName)); + + "') DROP SCHEMA " + AbstractSQLGenerator.escapeIdentifier(schemaName)); + } + + /** + * mimic "DROP USER..." + * + * @param userName + * @param stmt + * @throws SQLException + */ + public static void dropUserIfExists(String userName, Statement stmt) throws SQLException { + stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName) + + "') DROP USER " + AbstractSQLGenerator.escapeIdentifier(userName)); + } + + /** + * mimic "DROP LOGIN..." + * + * @param userName + * @param stmt + * @throws SQLException + */ + public static void dropLoginIfExists(String userName, Statement stmt) throws SQLException { + stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName) + + "') DROP LOGIN " + AbstractSQLGenerator.escapeIdentifier(userName)); } /** diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index 044cc6a3e..c140c19f8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -62,6 +62,7 @@ public class DatabaseMetaDataTest extends AbstractTest { private static final String uuid = UUID.randomUUID().toString().replaceAll("-", ""); private static final String tableName = RandomUtil.getIdentifier("DBMetadataTable"); private static final String functionName = RandomUtil.getIdentifier("DBMetadataFunction"); + private static final String newUserName = "newUser" + uuid; private static final String schema = "schema_demo" + uuid; private static final String escapedSchema = "schema\\_demo" + uuid; private static final String tableNameWithSchema = schema + ".resource"; @@ -227,14 +228,13 @@ public void testDBUserLogin() throws SQLException { @Test @Tag(Constants.xAzureSQLDW) public void testImpersonateGetUserName() throws SQLException { - String newUser = "newUser" + UUID.randomUUID(); + String escapedNewUser = AbstractSQLGenerator.escapeIdentifier(newUserName); try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { - String escapedNewUser = AbstractSQLGenerator.escapeIdentifier(newUser); String password = "password" + UUID.randomUUID(); - stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER" - + escapedNewUser); + TestUtils.dropUserIfExists(newUserName, stmt); + TestUtils.dropLoginIfExists(newUserName, stmt); // create new user and login try { @@ -248,17 +248,17 @@ public void testImpersonateGetUserName() throws SQLException { } DatabaseMetaData databaseMetaData = conn.getMetaData(); - try (CallableStatement asOtherUser = conn.prepareCall("EXECUTE AS USER = '" + newUser + "'")) { + try (CallableStatement asOtherUser = conn.prepareCall("EXECUTE AS USER = '" + newUserName + "'")) { asOtherUser.execute(); - assertTrue(newUser.equalsIgnoreCase(databaseMetaData.getUserName()), + assertTrue(newUserName.equalsIgnoreCase(databaseMetaData.getUserName()), TestResource.getResource("R_userNameNotMatch")); } catch (Exception e) { fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); - } finally { - stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER" - + escapedNewUser); - stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP LOGIN" - + escapedNewUser); + } + } finally { + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + TestUtils.dropUserIfExists(newUserName, stmt); + TestUtils.dropLoginIfExists(newUserName, stmt); } } } @@ -1049,6 +1049,8 @@ public static void terminate() throws SQLException { TestUtils.dropTableWithSchemaIfExists(tableNameWithSchema, stmt); TestUtils.dropProcedureWithSchemaIfExists(sprocWithSchema, stmt); TestUtils.dropSchemaIfExists(schema, stmt); + TestUtils.dropUserIfExists(newUserName, stmt); + TestUtils.dropLoginIfExists(newUserName, stmt); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java index b4ca10c04..2aef2cf43 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java @@ -41,7 +41,7 @@ public static void setupTests() throws Exception { @Test @Tag(Constants.xAzureSQLDW) - public void testLoginFailedError() { + public void testLoginFailedError() { SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setLoginTimeout(loginTimeOutInSeconds); From 1607ef21dc7499906ecc0fe9dd5c16144444ff1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 15:56:37 -0700 Subject: [PATCH 02/12] Bump org.bouncycastle:bcprov-jdk18on from 1.77 to 1.78 (#2403) * Bump org.bouncycastle:bcprov-jdk18on from 1.77 to 1.78 Bumps [org.bouncycastle:bcprov-jdk18on](https://github.com/bcgit/bc-java) from 1.77 to 1.78. - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update bcpkix-jdk18on to 1.78 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Terry Chow <32403408+tkyc@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7a123f3cf..4db973b56 100644 --- a/pom.xml +++ b/pom.xml @@ -60,8 +60,8 @@ 1.1.0 4.9.3 2.10.1 - 1.77 - 1.77 + 1.78 + 1.78 [1.3.2, 1.9.0] 5.8.2 From 357a0b5ccdcb35fc378f39464ecd3c6e67f21171 Mon Sep 17 00:00:00 2001 From: Jeff Wasty Date: Thu, 9 May 2024 10:21:29 -0700 Subject: [PATCH 03/12] Cleanup `writeSQLVariant` (removed unused parameter, remove duplicate code, spelling fixes) (#2411) * Changes to writeSQLVariant * Formatting * I forgot how switches work ... * Too much stuff, just keep the bulkScale change * Revert "Too much stuff, just keep the bulkScale change" This reverts commit 583679da2182dbad1365ec6dd7a56a170d0eebd3. --- .../sqlserver/jdbc/SQLServerBulkCopy.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index e5fa8825e..b868ba5fd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2596,7 +2596,7 @@ else if (4 >= bulkScale) throw new SQLServerException(null, form.format(new Object[] {}), null, 0, false); } writeSqlVariant(tdsWriter, colValue, sourceResultSet, srcColOrdinal, destColOrdinal, bulkJdbcType, - bulkScale, isStreaming); + isStreaming); break; default: MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported")); @@ -2622,9 +2622,10 @@ else if (4 >= bulkScale) * Writes sql_variant data based on the baseType for bulkcopy * * @throws SQLServerException + * an exception */ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sourceResultSet, int srcColOrdinal, - int destColOrdinal, int bulkJdbcType, int bulkScale, boolean isStreaming) throws SQLServerException { + int destColOrdinal, int bulkJdbcType, boolean isStreaming) throws SQLServerException { if (null == colValue) { writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); return; @@ -2633,7 +2634,7 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou int baseType = variantType.getBaseType(); byte[] srcBytes; // for sql variant we normally should return the colvalue for time as time string. but for - // bulkcopy we need it to be timestamp. so we have to retrieve it again once we are in bulkcopy + // bulkcopy we need it to be a timestamp. so we have to retrieve it again once we are in bulkcopy // and make sure that the base type is time. if (TDSType.TIMEN == TDSType.valueOf(baseType)) { variantType.setIsBaseTypeTimeValue(true); @@ -2671,6 +2672,7 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou tdsWriter.writeReal(Float.valueOf(colValue.toString())); break; + case MONEY4: case MONEY8: // For decimalN we right TDSWriter.BIGDECIMAL_MAX_LENGTH as maximum length = 17 // 17 + 2 for basetype and probBytes + 2 for precision and length = 21 the length of data in header @@ -2680,13 +2682,6 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou tdsWriter.writeSqlVariantInternalBigDecimal((BigDecimal) colValue, bulkJdbcType); break; - case MONEY4: - writeBulkCopySqlVariantHeader(21, TDSType.DECIMALN.byteValue(), (byte) 2, tdsWriter); - tdsWriter.writeByte((byte) 38); - tdsWriter.writeByte((byte) 4); - tdsWriter.writeSqlVariantInternalBigDecimal((BigDecimal) colValue, bulkJdbcType); - break; - case BIT1: writeBulkCopySqlVariantHeader(3, TDSType.BIT1.byteValue(), (byte) 0, tdsWriter); tdsWriter.writeByte((byte) (((Boolean) colValue).booleanValue() ? 1 : 0)); @@ -2791,9 +2786,8 @@ private void writeSqlVariant(TDSWriter tdsWriter, Object colValue, ResultSet sou case GUID: length = colValue.toString().length(); writeBulkCopySqlVariantHeader(9 + length, TDSType.BIGCHAR.byteValue(), (byte) 7, tdsWriter); - // since while reading collation from sourceMetaData in guid we don't read collation, cause we are - // reading binary - // but in writing it we are using char, we need to get the collation. + // since while reading collation from sourceMetaData in GUID we don't read collation, because we are + // reading binary, but in writing it we are using char, so we need to get the collation. SQLCollation collation = (null != destColumnMetadata.get(srcColOrdinal).collation) ? destColumnMetadata .get(srcColOrdinal).collation : connection.getDatabaseCollation(); variantType.setCollation(collation); From 561bb4f7677249d49eea0098d814b47efb1f15a5 Mon Sep 17 00:00:00 2001 From: mpwong852 <46591521+mpwong852@users.noreply.github.com> Date: Sat, 11 May 2024 01:41:51 +0800 Subject: [PATCH 04/12] Make enum SQLServerSortOrder become public (#2405) Issue #2404 Make this enum to be public, so that people can create "SQLServerMetaData" like below: new SQLServerMetaData("row_id", java.sql.Types.INTEGER, 0, 0, true, false, SQLServerSortOrder.Unspecified, -1) --- .../java/com/microsoft/sqlserver/jdbc/SQLServerSortOrder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSortOrder.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSortOrder.java index 9f36ef8f9..16ea49043 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSortOrder.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSortOrder.java @@ -10,7 +10,7 @@ * Specifies the sorting order * */ -enum SQLServerSortOrder { +public enum SQLServerSortOrder { ASCENDING(0), DESCENDING(1), UNSPECIFIED(-1); From ae64fed56a07e57b1f5330d14105e77ace55fe4c Mon Sep 17 00:00:00 2001 From: Codegass Date: Mon, 13 May 2024 17:04:58 -0400 Subject: [PATCH 05/12] Add Assertion to the `SharedTimerTest` (#2381) --- .../sqlserver/jdbc/SharedTimerTest.java | 20 ++++++++++++++++--- .../sqlserver/jdbc/TestResource.java | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java index 6aa8bff2e..48a70dd29 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java @@ -1,9 +1,16 @@ package com.microsoft.sqlserver.jdbc; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.ArrayList; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; class SharedTimerTest { @@ -11,8 +18,8 @@ class SharedTimerTest { @Test void getTimer() throws InterruptedException, ExecutionException, TimeoutException { final int iterations = 500; - ExecutorService executor = Executors.newFixedThreadPool(2); + try { ArrayList> futures = new ArrayList<>(iterations); for (int i = 0; i < iterations; i++) { @@ -22,6 +29,13 @@ void getTimer() throws InterruptedException, ExecutionException, TimeoutExceptio CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(2, TimeUnit.MINUTES); } finally { executor.shutdown(); + // 5000ms wait time for the AzureDB connection to close, need full test in the + // test lab for the exact time + if (!executor.awaitTermination(5000, TimeUnit.MILLISECONDS)) { + executor.shutdownNow(); + } } + + assertFalse(SharedTimer.isRunning(), TestResource.getResource("R_sharedTimerStopOnNoRef")); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index d542434fb..a630f5e0d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -212,6 +212,7 @@ protected Object[][] getContents() { {"R_objectNullOrEmpty", "The {0} is null or empty."}, {"R_cekDecryptionFailed", "Failed to decrypt a column encryption key using key store provider: {0}."}, {"R_connectTimedOut", "connect timed out"}, + {"R_sharedTimerStopOnNoRef", "SharedTimer should be stopped after all references are removed."}, {"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"}, {"R_failedFedauth", "Failed to acquire fedauth token: "}, {"R_noLoginModulesConfiguredForJdbcDriver", From eb0b1568b06bf08fa3ec3fc67ebf02b58831bd94 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Wed, 15 May 2024 13:34:18 -0700 Subject: [PATCH 06/12] JDK 22 support (#2414) * JDK 22 support * Updated jacoco --- README.md | 4 ++-- build.gradle | 8 ++++---- pom.xml | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4f3f01687..5142d26ce 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You * Maven: 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. 2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\target` directory. - * Run `mvn install -Pjre21`. This creates JRE 21 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 21). + * Run `mvn install -Pjre22`. This creates JRE 22 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 22). * Run `mvn install -Pjre17`. This creates JRE 17 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 17+). * Run `mvn install -Pjre11`. This creates JRE 11 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 11+). * Run `mvn install -Pjre8`. This creates JRE 8 compatible jar in `\target` directory which is JDBC 4.2 compliant (Build with JDK 11+). @@ -54,7 +54,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You * Gradle: 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. 2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\build\libs` directory. - * Run `gradle build -PbuildProfile=jre21`. This creates JRE 21 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 21). + * Run `gradle build -PbuildProfile=jre22`. This creates JRE 22 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 22). * Run `gradle build -PbuildProfile=jre17`. This creates JRE 17 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 17+). * Run `gradle build -PbuildProfile=jre11`. This creates JRE 11 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 11+). * Run `gradle build -PbuildProfile=jre8`. This creates JRE 8 compatible jar in `\build\libs` directory which is JDBC 4.2 compliant (Build with JDK 11+). diff --git a/build.gradle b/build.gradle index f862b13d4..f7d7bbcd8 100644 --- a/build.gradle +++ b/build.gradle @@ -33,17 +33,17 @@ test { } } -if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile == "jre21")) { +if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile == "jre22")) { - jreVersion = "jre21" + jreVersion = "jre22" excludedFile = 'com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java' jar { manifest { attributes 'Automatic-Module-Name': 'com.microsoft.sqlserver.jdbc' } } - sourceCompatibility = 21 - targetCompatibility = 21 + sourceCompatibility = 22 + targetCompatibility = 22 } if (hasProperty('buildProfile') && buildProfile == "jre17") { diff --git a/pom.xml b/pom.xml index 4db973b56..46395c9a7 100644 --- a/pom.xml +++ b/pom.xml @@ -346,12 +346,12 @@ - jre21 + jre22 true - ${project.artifactId}-${project.version}.jre21${releaseExt} + ${project.artifactId}-${project.version}.jre22${releaseExt} org.apache.maven.plugins @@ -361,8 +361,8 @@ **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java - 21 - 21 + 22 + 22 @@ -522,7 +522,7 @@ org.jacoco jacoco-maven-plugin - 0.8.9 + 0.8.12 pre-test From 2a25d8f5f65fbcfbbd382841f61f05f333160196 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Wed, 15 May 2024 15:25:33 -0700 Subject: [PATCH 07/12] Profiles for new LTS JDK (#2417) --- README.md | 2 ++ build.gradle | 13 +++++++++++++ pom.xml | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/README.md b/README.md index 5142d26ce..6756d0e63 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. 2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\target` directory. * Run `mvn install -Pjre22`. This creates JRE 22 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 22). + * Run `mvn install -Pjre21`. This creates JRE 21 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 21+). * Run `mvn install -Pjre17`. This creates JRE 17 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 17+). * Run `mvn install -Pjre11`. This creates JRE 11 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 11+). * Run `mvn install -Pjre8`. This creates JRE 8 compatible jar in `\target` directory which is JDBC 4.2 compliant (Build with JDK 11+). @@ -55,6 +56,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. 2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\build\libs` directory. * Run `gradle build -PbuildProfile=jre22`. This creates JRE 22 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 22). + * Run `gradle build -PbuildProfile=jre21`. This creates JRE 21 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 21+). * Run `gradle build -PbuildProfile=jre17`. This creates JRE 17 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 17+). * Run `gradle build -PbuildProfile=jre11`. This creates JRE 11 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 11+). * Run `gradle build -PbuildProfile=jre8`. This creates JRE 8 compatible jar in `\build\libs` directory which is JDBC 4.2 compliant (Build with JDK 11+). diff --git a/build.gradle b/build.gradle index f7d7bbcd8..211eff493 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,19 @@ if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile targetCompatibility = 22 } +if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile == "jre21")) { + + jreVersion = "jre21" + excludedFile = 'com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java' + jar { + manifest { + attributes 'Automatic-Module-Name': 'com.microsoft.sqlserver.jdbc' + } + } + sourceCompatibility = 21 + targetCompatibility = 21 +} + if (hasProperty('buildProfile') && buildProfile == "jre17") { jreVersion = "jre17" diff --git a/pom.xml b/pom.xml index 46395c9a7..56f4eec68 100644 --- a/pom.xml +++ b/pom.xml @@ -345,6 +345,42 @@ + + jre21 + + true + + + ${project.artifactId}-${project.version}.jre21${releaseExt} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + + 21 + 21 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + com.microsoft.sqlserver.jdbc + + + + + + + jre22 From 8cb5ed27fdf67343a400a488d5c06d4ee536fa51 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Tue, 21 May 2024 15:27:11 -0700 Subject: [PATCH 08/12] Credential caching (#2415) --- .../jdbc/SQLServerSecurityUtility.java | 120 +++++++++++++++--- .../jdbc/unit/SQLServerErrorTest.java | 2 +- 2 files changed, 105 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java index 3bb2eb32d..734bcf487 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java @@ -6,17 +6,22 @@ package com.microsoft.sqlserver.jdbc; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.util.Arrays; +import java.util.HashMap; import java.util.Optional; import java.util.Iterator; import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; import com.azure.identity.ManagedIdentityCredential; import com.azure.identity.ManagedIdentityCredentialBuilder; @@ -46,6 +51,11 @@ class SQLServerSecurityUtility { // Environment variable for additionally allowed tenants. The tenantIds are comma delimited private static final String ADDITIONALLY_ALLOWED_TENANTS = "ADDITIONALLY_ALLOWED_TENANTS"; + // Credential Cache for ManagedIdentityCredential and DefaultAzureCredential + private static final HashMap CREDENTIAL_CACHE = new HashMap<>(); + + private static final Lock CREDENTIAL_LOCK = new ReentrantLock(); + private SQLServerSecurityUtility() { throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); } @@ -331,16 +341,35 @@ static void verifyColumnMasterKeyMetadata(SQLServerConnection connection, SQLSer */ static SqlAuthenticationToken getManagedIdentityCredAuthToken(String resource, String managedIdentityClientId) throws SQLServerException { - ManagedIdentityCredential mic = null; if (logger.isLoggable(java.util.logging.Level.FINEST)) { logger.finest("Getting Managed Identity authentication token for: " + managedIdentityClientId); } - if (null != managedIdentityClientId && !managedIdentityClientId.isEmpty()) { - mic = new ManagedIdentityCredentialBuilder().clientId(managedIdentityClientId).build(); - } else { - mic = new ManagedIdentityCredentialBuilder().build(); + String key = getHashedSecret( + new String[] {managedIdentityClientId, ManagedIdentityCredential.class.getSimpleName()}); + ManagedIdentityCredential mic = (ManagedIdentityCredential) getCredentialFromCache(key); + + if (null == mic) { + CREDENTIAL_LOCK.lock(); + + try { + mic = (ManagedIdentityCredential) getCredentialFromCache(key); + if (null == mic) { + ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder(); + + if (null != managedIdentityClientId && !managedIdentityClientId.isEmpty()) { + mic = micBuilder.clientId(managedIdentityClientId).build(); + } else { + mic = micBuilder.build(); + } + + Credential credential = new Credential(mic); + CREDENTIAL_CACHE.put(key, credential); + } + } finally { + CREDENTIAL_LOCK.unlock(); + } } TokenRequestContext tokenRequestContext = new TokenRequestContext(); @@ -383,22 +412,49 @@ static SqlAuthenticationToken getDefaultAzureCredAuthToken(String resource, String intellijKeepassPath = System.getenv(INTELLIJ_KEEPASS_PASS); String[] additionallyAllowedTenants = getAdditonallyAllowedTenants(); - DefaultAzureCredentialBuilder dacBuilder = new DefaultAzureCredentialBuilder(); - DefaultAzureCredential dac = null; + int secretsLength = null == additionallyAllowedTenants ? 3 : additionallyAllowedTenants.length + 3; + String[] secrets = new String[secretsLength]; - if (null != managedIdentityClientId && !managedIdentityClientId.isEmpty()) { - dacBuilder.managedIdentityClientId(managedIdentityClientId); + if (null != additionallyAllowedTenants && additionallyAllowedTenants.length != 0) { + System.arraycopy(additionallyAllowedTenants, 0, secrets, 3, additionallyAllowedTenants.length); } - if (null != intellijKeepassPath && !intellijKeepassPath.isEmpty()) { - dacBuilder.intelliJKeePassDatabasePath(intellijKeepassPath); - } + secrets[0] = DefaultAzureCredential.class.getSimpleName(); + secrets[1] = managedIdentityClientId; + secrets[2] = intellijKeepassPath; - if (null != additionallyAllowedTenants && additionallyAllowedTenants.length != 0) { - dacBuilder.additionallyAllowedTenants(additionallyAllowedTenants); - } + String key = getHashedSecret(secrets); + DefaultAzureCredential dac = (DefaultAzureCredential) getCredentialFromCache(key); + + if (null == dac) { + CREDENTIAL_LOCK.lock(); + + try { + dac = (DefaultAzureCredential) getCredentialFromCache(key); + if (null == dac) { + DefaultAzureCredentialBuilder dacBuilder = new DefaultAzureCredentialBuilder(); + + if (null != managedIdentityClientId && !managedIdentityClientId.isEmpty()) { + dacBuilder.managedIdentityClientId(managedIdentityClientId); + } + + if (null != intellijKeepassPath && !intellijKeepassPath.isEmpty()) { + dacBuilder.intelliJKeePassDatabasePath(intellijKeepassPath); + } + + if (null != additionallyAllowedTenants && additionallyAllowedTenants.length != 0) { + dacBuilder.additionallyAllowedTenants(additionallyAllowedTenants); + } + + dac = dacBuilder.build(); - dac = dacBuilder.build(); + Credential credential = new Credential(dac); + CREDENTIAL_CACHE.put(key, credential); + } + } finally { + CREDENTIAL_LOCK.unlock(); + } + } TokenRequestContext tokenRequestContext = new TokenRequestContext(); String scope = resource.endsWith(SQLServerMSAL4JUtils.SLASH_DEFAULT) ? resource : resource @@ -430,4 +486,36 @@ private static String[] getAdditonallyAllowedTenants() { return null; } + + private static TokenCredential getCredentialFromCache(String key) { + Credential credential = CREDENTIAL_CACHE.get(key); + + if (null != credential) { + return credential.tokenCredential; + } + + return null; + } + + private static class Credential { + TokenCredential tokenCredential; + + public Credential(TokenCredential tokenCredential) { + this.tokenCredential = tokenCredential; + } + } + + private static String getHashedSecret(String[] secrets) throws SQLServerException { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + for (String secret : secrets) { + if (null != secret) { + md.update(secret.getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + } + } + return new String(md.digest()); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java index 2aef2cf43..b4ca10c04 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java @@ -41,7 +41,7 @@ public static void setupTests() throws Exception { @Test @Tag(Constants.xAzureSQLDW) - public void testLoginFailedError() { + public void testLoginFailedError() { SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setLoginTimeout(loginTimeOutInSeconds); From a35798e9ecdf23978735f2b275fdffcba13961bb Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Tue, 21 May 2024 15:42:49 -0700 Subject: [PATCH 09/12] Updates for running tests with managed identity (#2416) --- .../jdbc/AlwaysEncrypted/AESetup.java | 6 +-- .../CallableStatementTest.java | 2 +- .../jdbc/SQLServerConnectionTest.java | 37 +++++++++++++++++-- .../sqlserver/jdbc/TestResource.java | 3 +- .../microsoft/sqlserver/jdbc/TestUtils.java | 10 ++++- .../jdbc/connection/PoolingTest.java | 4 ++ .../jdbc/connection/TimeoutTest.java | 27 ++++++++++++++ .../DatabaseMetaDataTest.java | 9 +++-- .../sqlserver/jdbc/fedauth/FedauthWithAE.java | 2 +- .../sqlserver/jdbc/fips/FipsTest.java | 18 +++++++-- .../jdbc/unit/SQLServerErrorTest.java | 5 +++ .../unit/statement/BatchExecutionTest.java | 6 +++ .../unit/statement/PreparedStatementTest.java | 4 +- .../sqlserver/testframework/AbstractTest.java | 28 ++++++++------ 14 files changed, 131 insertions(+), 30 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index 02336f79c..3fbf0d4d8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -339,7 +339,7 @@ protected static void createTable(String tableName, String cekName, String table TestUtils.dropTableIfExists(tableName, stmt); sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } @@ -373,7 +373,7 @@ protected static void createPrecisionTable(String tableName, String table[][], S } sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } @@ -401,7 +401,7 @@ protected static void createScaleTable(String tableName, String table[][], Strin sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 95531d698..d259b35a5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -2201,7 +2201,7 @@ protected static void createDateTableCallableStatement(String cekName) throws SQ SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(DATE_TABLE_AE, stmt); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 12eea46cf..e024e98cb 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -482,7 +482,13 @@ public void testConnectCountInLoginAndCorrectRetryCount() { assertTrue(con == null, TestResource.getResource("R_shouldNotConnect")); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")), e.getMessage()); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && (e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase()) + || e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_MInotAvailable").toLowerCase()))), + e.getMessage()); long totalTime = System.currentTimeMillis() - timerStart; // Maximum is unknown, but is needs to be less than longLoginTimeout or else this is an issue. @@ -795,13 +801,22 @@ public void testIncorrectDatabase() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")), e.getMessage()); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectUserName() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -819,13 +834,22 @@ public void testIncorrectUserName() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectPassword() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -843,7 +867,12 @@ public void testIncorrectPassword() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index a630f5e0d..b98171c23 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -220,5 +220,6 @@ protected Object[][] getContents() { {"R_unexpectedThreadCount", "Thread count is higher than expected."}, {"R_expectedClassDoesNotMatchActualClass", "Expected column class {0} does not match actual column class {1} for column {2}."}, - {"R_loginFailedMSI", "Login failed for user ''"}}; + {"R_loginFailedMI", "Login failed for user ''"}, + {"R_MInotAvailable", "Managed Identity authentication is not available"},}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java index 8db118b4d..b17e86c0d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java @@ -535,7 +535,7 @@ public static void dropDatabaseIfExists(String databaseName, String connectionSt */ public static void dropSchemaIfExists(String schemaName, Statement stmt) throws SQLException { stmt.execute("if EXISTS (SELECT * FROM sys.schemas where name = '" + escapeSingleQuotes(schemaName) - + "') DROP SCHEMA " + AbstractSQLGenerator.escapeIdentifier(schemaName)); + + "') DROP SCHEMA" + AbstractSQLGenerator.escapeIdentifier(schemaName)); } /** @@ -1135,4 +1135,12 @@ public static String getConnectionID( SQLServerConnection conn = (SQLServerConnection) physicalConnection.get(pc); return (String) traceID.get(conn); } + + public static void freeProcCache(Statement stmt) { + try { + stmt.execute("DBCC FREEPROCCACHE"); + } catch (Exception e) { + // ignore error - some tests fails due to permission issues from managed identity, this does not seem to affect tests + } + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java index d99d846ef..57a11a725 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java @@ -152,6 +152,10 @@ public void testConnectionPoolClose() throws SQLException { @Test public void testConnectionPoolClientConnectionId() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + SQLServerXADataSource ds = new SQLServerXADataSource(); ds.setURL(connectionString); PooledConnection pc = null; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index 268fda736..d7290b262 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -68,6 +68,8 @@ public void testDefaultLoginTimeout() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -95,6 +97,8 @@ public void testURLLoginTimeout() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -122,6 +126,8 @@ public void testDMLoginTimeoutApplied() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -150,6 +156,9 @@ public void testDMLoginTimeoutNotApplied() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -181,6 +190,8 @@ public void testConnectRetryDisable() { assertTrue( e.getMessage().matches(TestUtils.formatErrorMsg("R_tcpipConnectionFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -207,6 +218,8 @@ public void testConnectRetryBadServer() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -220,6 +233,10 @@ public void testConnectRetryBadServer() { // Test connect retry for database error @Test public void testConnectRetryServerError() { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long totalTime = 0; long timerStart = System.currentTimeMillis(); int interval = defaultTimeout; // long interval so we can tell if there was a retry @@ -237,6 +254,8 @@ public void testConnectRetryServerError() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -252,6 +271,10 @@ public void testConnectRetryServerError() { // Test connect retry for database error using Datasource @Test public void testConnectRetryServerErrorDS() { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long totalTime = 0; long timerStart = System.currentTimeMillis(); int interval = defaultTimeout; // long interval so we can tell if there was a retry @@ -270,6 +293,8 @@ public void testConnectRetryServerErrorDS() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); @@ -303,6 +328,8 @@ public void testConnectRetryTimeout() { assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index c140c19f8..65b76ae12 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -196,6 +196,10 @@ public void testGetURL() throws SQLException { */ @Test public void testDBUserLogin() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + try (Connection conn = getConnection()) { DatabaseMetaData databaseMetaData = conn.getMetaData(); String connectionString = getConnectionString(); @@ -219,7 +223,8 @@ public void testDBUserLogin() throws SQLException { assertNotNull(userName, TestResource.getResource("R_userNameNull")); assertTrue(userName.equalsIgnoreCase(userFromConnectionString), - TestResource.getResource("R_userNameNotMatch")); + TestResource.getResource("R_userNameNotMatch") + "userName: " + userName + "from connectio string: " + + userFromConnectionString); } catch (Exception e) { fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); } @@ -1049,8 +1054,6 @@ public static void terminate() throws SQLException { TestUtils.dropTableWithSchemaIfExists(tableNameWithSchema, stmt); TestUtils.dropProcedureWithSchemaIfExists(sprocWithSchema, stmt); TestUtils.dropSchemaIfExists(schema, stmt); - TestUtils.dropUserIfExists(newUserName, stmt); - TestUtils.dropLoginIfExists(newUserName, stmt); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java index bfd09d3b9..1e0112f02 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java @@ -307,7 +307,7 @@ private void createCMK(String cmkName, String keyStoreName, String keyPath, Stat private void callDbccFreeProcCache() throws SQLException { try (Connection connection = DriverManager.getConnection(adPasswordConnectionStr); Statement stmt = connection.createStatement()) { - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java index 10853f2f6..2e80407d3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java @@ -50,7 +50,7 @@ public void fipsTrustServerCertificateTest() throws Exception { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidTrustCert")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } @@ -62,13 +62,18 @@ public void fipsTrustServerCertificateTest() throws Exception { */ @Test public void fipsEncryptTest() throws Exception { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + Properties props = buildConnectionProperties(); props.setProperty(Constants.ENCRYPT, Boolean.FALSE.toString()); try (Connection con = PrepUtil.getConnection(connectionString, props)) { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } @@ -118,6 +123,11 @@ public void fipsDataSourcePropertyTest() throws Exception { */ @Test public void fipsDatSourceEncrypt() { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + SQLServerDataSource ds = new SQLServerDataSource(); setDataSourceProperties(ds); ds.setEncrypt(Constants.FALSE); @@ -126,7 +136,7 @@ public void fipsDatSourceEncrypt() { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidEncrypt") + ": " + e.getMessage()); } } @@ -146,7 +156,7 @@ public void fipsDataSourceTrustServerCertificateTest() throws Exception { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidTrustCert")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java index b4ca10c04..7c7dc377e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java @@ -42,6 +42,11 @@ public static void setupTests() throws Exception { @Test @Tag(Constants.xAzureSQLDW) public void testLoginFailedError() { + // test to remove password only valid for password auth + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setLoginTimeout(loginTimeOutInSeconds); 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..47b771dc3 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 @@ -188,6 +188,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep pstmt.setTimestamp(1, timestamp, gmtCal); pstmt.addBatch(); pstmt.executeBatch(); + } catch (Exception e) { + fail(e.getMessage()); } // Insert Timestamp using bulkcopy for batch insert @@ -200,6 +202,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep pstmt.setTimestamp(1, timestamp, gmtCal); pstmt.addBatch(); pstmt.executeBatch(); + } catch (Exception e) { + fail(e.getMessage()); } // Compare Timestamp values inserted, should be the same @@ -225,6 +229,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep assertEquals(ts0, ts1, failureMsg); assertEquals(t0, t1, failureMsg); assertEquals(d0, d1, failureMsg); + } catch (Exception e) { + fail(e.getMessage()); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java index 79862dfcd..b21372c96 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java @@ -258,7 +258,9 @@ public void testBatchedUnprepare() throws SQLException { con.setStatementPoolingCacheSize(0); // Clean-up proc cache - this.executeSQL(con, "DBCC FREEPROCCACHE;"); + try (Statement stmt = con.createStatement()) { + TestUtils.freeProcCache(stmt); + } String lookupUniqueifier = UUID.randomUUID().toString(); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index 3590aa7f2..5078954c9 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -5,6 +5,8 @@ package com.microsoft.sqlserver.testframework; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -299,19 +301,23 @@ protected static void setupConnectionString() { } protected static void setConnection() throws Exception { - setupConnectionString(); + try { + setupConnectionString(); - Assertions.assertNotNull(connectionString, TestResource.getResource("R_ConnectionStringNull")); - Class.forName(Constants.MSSQL_JDBC_PACKAGE + ".SQLServerDriver"); - if (!SQLServerDriver.isRegistered()) { - SQLServerDriver.register(); - } - if (null == connection || connection.isClosed()) { - connection = getConnection(); - } - isSqlAzureOrAzureDW(connection); + Assertions.assertNotNull(connectionString, TestResource.getResource("R_ConnectionStringNull")); + Class.forName(Constants.MSSQL_JDBC_PACKAGE + ".SQLServerDriver"); + if (!SQLServerDriver.isRegistered()) { + SQLServerDriver.register(); + } + if (null == connection || connection.isClosed()) { + connection = getConnection(); + } + isSqlAzureOrAzureDW(connection); - checkSqlOS(connection); + checkSqlOS(connection); + } catch (Exception e) { + fail("setConnection failed, connectionString=" + connectionString + "\nException: " + e.getMessage()); + } } /** From df5bfa6b80ba419042498b343aed697b7807b810 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Wed, 22 May 2024 10:48:20 -0700 Subject: [PATCH 10/12] Execute stored procedures directly for RPC calls (#2410) * RPC fix * Assertion index correction * Removed magic number * Removed login drop command * Code review changes * Formatting --- .../jdbc/SQLServerCallableStatement.java | 16 ++++++++++++---- .../jdbc/SQLServerPreparedStatement.java | 17 +---------------- .../sqlserver/jdbc/SQLServerStatement.java | 4 ++-- .../sqlserver/jdbc/StreamRetValue.java | 6 +++++- .../CallableStatementTest.java | 11 +++++++++++ 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index f98585fe4..270df7ee7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -216,7 +216,7 @@ && callRPCDirectly(inOutParam)) { } if (inOutParam[i - 1].isReturnValue() && bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType - && returnValueStatus != userDefinedFunctionReturnStatus) { + && returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) { return inOutParam[i - 1]; } @@ -269,7 +269,6 @@ final void processOutParameters() throws SQLServerException { // the response stream. for (int index = 0; index < inOutParam.length; ++index) { if (index != outParamIndex && inOutParam[index].isValueGotten()) { - assert inOutParam[index].isOutput(); inOutParam[index].resetOutputValue(); } } @@ -365,7 +364,7 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { OutParamHandler outParamHandler = new OutParamHandler(); if (bReturnValueSyntax && (nOutParamsAssigned == 0) && !isCursorable(executeMethod) && !isTVPType - && callRPCDirectly(inOutParam) && returnValueStatus != userDefinedFunctionReturnStatus) { + && callRPCDirectly(inOutParam) && returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) { nOutParamsAssigned++; } @@ -414,7 +413,7 @@ && callRPCDirectly(inOutParam) && returnValueStatus != userDefinedFunctionReturn outParamIndex = outParamHandler.srv.getOrdinalOrLength(); if (bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType && callRPCDirectly(inOutParam) - && returnValueStatus != userDefinedFunctionReturnStatus) { + && returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) { outParamIndex++; } else { // Statements need to have their out param indices adjusted by the number @@ -424,10 +423,19 @@ && callRPCDirectly(inOutParam) && returnValueStatus != userDefinedFunctionReturn if ((outParamIndex < 0 || outParamIndex >= inOutParam.length) || (!inOutParam[outParamIndex].isOutput())) { + + // For RPC calls with out parameters, the initial return value token will indicate + // it being a RPC. In such case, consume the token as it does not contain the out parameter + // value. The subsequent token will have the value. + if (outParamHandler.srv.getStatus() == USER_DEFINED_FUNCTION_RETURN_STATUS) { + continue; + } + if (getStatementLogger().isLoggable(java.util.logging.Level.INFO)) { getStatementLogger().info(toString() + " Unexpected outParamIndex: " + outParamIndex + "; adjustment: " + outParamIndexAdjustment); } + connection.throwInvalidTDS(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 5f1766d94..f3a441581 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -77,9 +77,6 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS // flag whether is call escape syntax private boolean isCallEscapeSyntax; - // flag whether is four part syntax - private boolean isFourPartSyntax; - /** Parameter positions in processed SQL statement text. */ final int[] userSQLParamPositions; @@ -149,11 +146,6 @@ private void setPreparedStatementHandle(int handle) { */ private static final Pattern execEscapePattern = Pattern.compile("^\\s*(?i)(?:exec|execute)\\b"); - /** - * Regex for four part syntax - */ - private static final Pattern fourPartSyntaxPattern = Pattern.compile("(.+)\\.(.+)\\.(.+)\\.(.+)"); - /** Returns the prepared statement SQL */ @Override public String toString() { @@ -290,7 +282,6 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) { userSQL = parsedSQL.processedSQL; isExecEscapeSyntax = isExecEscapeSyntax(sql); isCallEscapeSyntax = isCallEscapeSyntax(sql); - isFourPartSyntax = isFourPartSyntax(sql); userSQLParamPositions = parsedSQL.parameterPositions; initParams(userSQLParamPositions.length); useBulkCopyForBatchInsert = conn.getUseBulkCopyForBatchInsert(); @@ -1258,10 +1249,8 @@ boolean callRPCDirectly(Parameter[] params) throws SQLServerException { // 4. Compliant CALL escape syntax // If isExecEscapeSyntax is true, EXEC escape syntax is used then use prior behaviour of // wrapping call to execute the procedure - // If isFourPartSyntax is true, sproc is being executed against linked server, then - // use prior behaviour of wrapping call to execute procedure return (null != procedureName && paramCount != 0 && !isTVPType(params) && isCallEscapeSyntax - && !isExecEscapeSyntax && !isFourPartSyntax); + && !isExecEscapeSyntax); } /** @@ -1289,10 +1278,6 @@ private boolean isCallEscapeSyntax(String sql) { return callEscapePattern.matcher(sql).find(); } - private boolean isFourPartSyntax(String sql) { - return fourPartSyntaxPattern.matcher(sql).find(); - } - /** * Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 12432c560..f27e1b1ac 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -72,7 +72,7 @@ public class SQLServerStatement implements ISQLServerStatement { /** Check if statement contains TVP Type */ boolean isTVPType = false; - static int userDefinedFunctionReturnStatus = 2; + protected static final int USER_DEFINED_FUNCTION_RETURN_STATUS = 2; final boolean getIsResponseBufferingAdaptive() { return isResponseBufferingAdaptive; @@ -1676,7 +1676,7 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { // in which case we need to stop parsing and let CallableStatement take over. // A RETVALUE token appearing in the execution results, but before any RETSTATUS // token, is a TEXTPTR return value that should be ignored. - if (moreResults && null == procedureRetStatToken && status != userDefinedFunctionReturnStatus) { + if (moreResults && null == procedureRetStatToken && status != USER_DEFINED_FUNCTION_RETURN_STATUS) { Parameter p = new Parameter( Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); p.skipRetValStatus(tdsReader); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/StreamRetValue.java b/src/main/java/com/microsoft/sqlserver/jdbc/StreamRetValue.java index a4324b6a8..82aa3c639 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/StreamRetValue.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/StreamRetValue.java @@ -18,10 +18,14 @@ final class StreamRetValue extends StreamPacket { */ private int ordinalOrLength; - final int getOrdinalOrLength() { + int getOrdinalOrLength() { return ordinalOrLength; } + int getStatus() { + return status; + } + /* * Status: 0x01 if the return value is an OUTPUT parameter of a stored procedure 0x02 if the return value is from a * User Defined Function 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 67dde1fad..a399e53f7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -1187,6 +1187,8 @@ public void testFourPartSyntaxCallEscapeSyntax() throws SQLException { Statement stmt = linkedServerConnection.createStatement()) { stmt.execute( "create or alter procedure dbo.TestAdd(@Num1 int, @Num2 int, @Result int output) as begin set @Result = @Num1 + @Num2; end;"); + + stmt.execute("create or alter procedure dbo.TestReturn(@Num1 int) as select @Num1 return @Num1*3 "); } try (CallableStatement cstmt = connection @@ -1211,6 +1213,15 @@ public void testFourPartSyntaxCallEscapeSyntax() throws SQLException { cstmt.execute(); assertEquals(sum, cstmt.getInt(3)); } + + try (CallableStatement cstmt = connection + .prepareCall("{? = call [" + linkedServer + "].master.dbo.TestReturn(?)}")) { + int expected = 15; + cstmt.registerOutParameter(1, java.sql.Types.INTEGER); + cstmt.setInt(2, 5); + cstmt.execute(); + assertEquals(expected, cstmt.getInt(1)); + } } /** From 9d503dd326703ca1bd43528b2ff77e8bbbfa6222 Mon Sep 17 00:00:00 2001 From: Kun Huang Date: Thu, 23 May 2024 03:47:33 +0800 Subject: [PATCH 11/12] Fix SqlAuthenticationToken constructor accepting unix epoch (#2425) * Update SqlAuthenticationToken.java * Pass in milliseconds for token object * Minor cleanup --------- Co-authored-by: Terry Chow Co-authored-by: Jeff Wasty --- .../microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java | 4 ++-- .../microsoft/sqlserver/jdbc/SqlAuthenticationToken.java | 6 +++--- src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java index 734bcf487..b49abe9bc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java @@ -387,7 +387,7 @@ static SqlAuthenticationToken getManagedIdentityCredAuthToken(String resource, } else { AccessToken accessToken = accessTokenOptional.get(); sqlFedAuthToken = new SqlAuthenticationToken(accessToken.getToken(), - accessToken.getExpiresAt().toEpochSecond()); + accessToken.getExpiresAt().toInstant().toEpochMilli()); } if (logger.isLoggable(java.util.logging.Level.FINEST)) { @@ -471,7 +471,7 @@ static SqlAuthenticationToken getDefaultAzureCredAuthToken(String resource, } else { AccessToken accessToken = accessTokenOptional.get(); sqlFedAuthToken = new SqlAuthenticationToken(accessToken.getToken(), - accessToken.getExpiresAt().toEpochSecond()); + accessToken.getExpiresAt().toInstant().toEpochMilli()); } return sqlFedAuthToken; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java b/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java index c57cd2268..5cba25cca 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java @@ -24,12 +24,12 @@ public class SqlAuthenticationToken implements Serializable { private final String accessToken; /** - * Contructs a SqlAuthentication token. + * Constructs a SqlAuthentication token. * * @param accessToken * The access token string. * @param expiresOn - * The expiration date in seconds since the unix epoch. + * The expiration date in milliseconds since the unix epoch. */ public SqlAuthenticationToken(String accessToken, long expiresOn) { this.accessToken = accessToken; @@ -37,7 +37,7 @@ public SqlAuthenticationToken(String accessToken, long expiresOn) { } /** - * Contructs a SqlAuthentication token. + * Constructs a SqlAuthentication token. * * @param accessToken * The access token string. diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java index b17e86c0d..928a59490 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java @@ -138,8 +138,8 @@ public SqlAuthenticationToken getAccessToken(String spn, String stsurl) { if (expireTokenToggle) { Date now = new Date(); - long minutesToExpireWithin = TEST_TOKEN_EXPIRY_SECONDS * 1000; // Expire within 2 minutes - return new SqlAuthenticationToken(accessToken, now.getTime() + minutesToExpireWithin); + long millisecondsToExpireWithin = TEST_TOKEN_EXPIRY_SECONDS * 1000; // Expire within 2 minutes + return new SqlAuthenticationToken(accessToken, now.getTime() + millisecondsToExpireWithin); } else { return new SqlAuthenticationToken(accessToken, expiresOn); } From c79befa6f3d7c194a80f1c02b14f8f92228ecedc Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Fri, 24 May 2024 14:33:04 -0700 Subject: [PATCH 12/12] MSAL and Azure Identity version bump (#2433) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 56f4eec68..42040419c 100644 --- a/pom.xml +++ b/pom.xml @@ -55,8 +55,8 @@ 6.0.0 4.7.3 - 1.11.1 - 1.14.1 + 1.12.1 + 1.15.0 1.1.0 4.9.3 2.10.1