From c01814b514b5b2749515009a0b487a0d8b52f0cb Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Mon, 24 Feb 2025 12:33:31 -0800 Subject: [PATCH 1/4] Re-enable AE Tests (#2611) --- ...pySendTemporalDataTypesAsStringAETest.java | 1 - .../CallableStatementTest.java | 1 - .../JDBCEncryptionDecryptionTest.java | 18 +++---- .../jdbc/AlwaysEncrypted/MSITest.java | 19 +++++++- .../AlwaysEncrypted/MultiUserAKVTest.java | 48 +++++-------------- .../ParameterMetaDataCacheTest.java | 1 - .../AlwaysEncrypted/PrecisionScaleTest.java | 1 - .../RegressionAlwaysEncryptedTest.java | 1 - .../sqlserver/jdbc/EnclavePackageTest.java | 1 - .../sqlserver/testframework/AbstractTest.java | 18 ++++++- 10 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java index 47f717f54..6dd177ab5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java @@ -58,7 +58,6 @@ @Tag(Constants.xAzureSQLDB) @Tag(Constants.xAzureSQLDW) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class BulkCopySendTemporalDataTypesAsStringAETest extends AESetup { static String inputFile = "BulkCopyCSVSendTemporalDataTypesAsStringForBulkCopy.csv"; static String encoding = "UTF-8"; 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 8215250ee..d259b35a5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -54,7 +54,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class CallableStatementTest extends AESetup { private static String multiStatementsProcedure = AbstractSQLGenerator diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index b7cfac547..3c2fbe81c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -29,10 +29,6 @@ import com.azure.identity.ClientSecretCredentialBuilder; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -65,7 +61,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class JDBCEncryptionDecryptionTest extends AESetup { private boolean nullable = false; @@ -107,8 +102,7 @@ public void testJksName(String serverName, String url, String protocol) throws E public void testAkvName(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = new SQLServerColumnEncryptionAzureKeyVaultProvider( - applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; String keystoreName = "keystoreName"; akv.setName(keystoreName); assertTrue(akv.getName().equals(keystoreName), "AKV name: " + akv.getName() + " keystoreName: " + keystoreName); @@ -138,6 +132,7 @@ public void testBadJks(String serverName, String url, String protocol) throws Ex @SuppressWarnings("unused") @ParameterizedTest @MethodSource("enclaveParams") + @Tag(Constants.requireSecret) public void testBadAkvCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -211,8 +206,7 @@ public void testJksBadEncryptColumnEncryptionKey(String serverName, String url, public void testAkvBadEncryptColumnEncryptionKey(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = null; - akv = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; // null encryptedColumnEncryptionKey try { @@ -290,8 +284,7 @@ public void testJksDecryptColumnEncryptionKey(String serverName, String url, Str public void testAkvDecryptColumnEncryptionKey(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = null; - akv = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; // null akvpath try { @@ -2270,6 +2263,7 @@ void testNumerics(SQLServerStatement stmt, String cekName, String[][] table, Str @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvNameWithAuthCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -2288,6 +2282,7 @@ public void testAkvNameWithAuthCallback(String serverName, String url, String pr @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvNameWithTokenCredential(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -2309,6 +2304,7 @@ public void testAkvNameWithTokenCredential(String serverName, String url, String @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvBadEncryptColumnEncryptionKeyWithAuthCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java index 360e172a1..ee4c2707e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java @@ -17,6 +17,9 @@ import java.util.Properties; import com.azure.identity.CredentialUnavailableException; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -42,7 +45,6 @@ */ @RunWith(JUnitPlatform.class) @Tag(Constants.MSI) -@Tag(Constants.requireSecret) public class MSITest extends AESetup { /* @@ -458,11 +460,20 @@ private void testNumericAKV(String connStr) throws SQLException { @BeforeEach public void registerAKVProvider() throws Exception { + try { // unregister the custom providers registered in AESetup SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); Map map = new HashMap(); - if (null != applicationClientID && null != applicationKey) { + if (null != akvProviderManagedClientId) { + System.out.println("ManagedIdentityCredential: registering akvProvider"); + + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + System.out.println("ManagedIdentityCredential: registered akvProvider"); + } else if (null != applicationClientID && null != applicationKey) { File file = null; try { file = new File(Constants.MSSQL_JDBC_PROPERTIES); @@ -479,8 +490,12 @@ public void registerAKVProvider() throws Exception { file.delete(); } } + System.out.println("applicationClientID: registered akvProvider"); } SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map); + } catch (Exception e) { + System.out.println("MSITest registerAKVProvider exception: " +e.getMessage()); + } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java index 614895608..17b5c4bee 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java @@ -35,6 +35,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionKeyStoreProvider; @@ -59,7 +61,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class MultiUserAKVTest extends AESetup { private static Map requiredKeyStoreProvider = new HashMap<>(); @@ -101,7 +102,7 @@ public static void testCleanUp() throws Exception { tempMap.put(Constants.CUSTOM_KEYSTORE_NAME, jksProvider); } - if (null != akvProvider && null != applicationClientID && null != applicationKey) { + if (null != akvProvider) { tempMap.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); } @@ -113,7 +114,7 @@ public static void testCleanUp() throws Exception { @Test @Tag(Constants.reqExternalSetup) public void decryptedCekIsCachedDuringDecryption() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (null == provider) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -153,8 +154,9 @@ public void decryptedCekIsCachedDuringDecryption() throws Exception { @Test @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void signatureVerificationResultIsCachedDuringVerification() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -186,7 +188,7 @@ public void signatureVerificationResultIsCachedDuringVerification() throws Excep @Test @Tag(Constants.reqExternalSetup) public void cekCacheEntryIsEvictedAfterTtlExpires() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -214,7 +216,7 @@ public void cekCacheEntryIsEvictedAfterTtlExpires() throws Exception { @Test @Tag(Constants.reqExternalSetup) public void cekCacheShouldBeDisabledWhenAkvProviderIsRegisteredGlobally() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -258,8 +260,9 @@ public void cekCacheShouldBeDisabledWhenAkvProviderIsRegisteredGlobally() throws @Test @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testLocalCekCacheIsScopedToProvider() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -270,6 +273,8 @@ public void testLocalCekCacheIsScopedToProvider() throws Exception { fail((new MessageFormat(TestResource.getResource("R_objectNullOrEmpty"))).format(msgArg)); } + SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); + SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); Map providerMap = new HashMap(); providerMap.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); @@ -327,7 +332,7 @@ public void testLocalCekCacheIsScopedToProvider() throws Exception { } fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLServerException ex) { - assertTrue(ex.getMessage().contains("AADSTS700016")); + org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("AADSTS700016"), ex.getMessage()); } } finally { dropObject(AETestConnectionString, "TABLE", customProviderTableName); @@ -610,31 +615,4 @@ private int getCacheSize(String methodName, return (int) method.invoke(provider); } - - private SQLServerColumnEncryptionAzureKeyVaultProvider createAKVProvider() throws Exception { - - SQLServerColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = null; - - if (null != applicationClientID && null != applicationKey) { - File file = null; - try { - file = new File(Constants.MSSQL_JDBC_PROPERTIES); - try (OutputStream os = new FileOutputStream(file);) { - Properties props = new Properties(); - // Append to the list of hardcoded endpoints - props.setProperty(Constants.AKV_TRUSTED_ENDPOINTS_KEYWORD, ";vault.azure.net"); - props.store(os, ""); - } - azureKeyVaultProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, - applicationKey); - - } finally { - if (null != file) { - file.delete(); - } - } - } - - return azureKeyVaultProvider; - } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java index ca4162154..cef219d4a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java @@ -33,7 +33,6 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xSQLv14) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class ParameterMetaDataCacheTest extends AESetup { @BeforeAll diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java index dcbe907b5..162fe8cef 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java @@ -43,7 +43,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class PrecisionScaleTest extends AESetup { private static java.util.Date date = null; private static int offsetFromGMT = 0; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java index c8f33573a..d8725cdbe 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java @@ -30,7 +30,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class RegressionAlwaysEncryptedTest extends AESetup { static String numericTable[][] = {{"Bit", "bit"}, {"Tinyint", "tinyint"}, {"Smallint", "smallint"},}; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index 174755d4b..5f3c1165c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -33,7 +33,6 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) -@Tag(Constants.requireSecret) public class EnclavePackageTest extends AbstractTest { private static String connectionStringEnclave = null; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index eda7b8847..178fd9f7f 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -34,6 +34,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.sqlserver.jdbc.ISQLServerDataSource; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider; @@ -64,6 +68,7 @@ public abstract class AbstractTest { protected static String servicePrincipalCertificateApplicationClientId = null; protected static String tenantID; protected static String[] keyIDs = null; + protected static String akvProviderManagedClientId = null; protected static String linkedServer = null; protected static String linkedServerUser = null; @@ -162,8 +167,12 @@ public static void setup() throws Exception { applicationClientID = getConfiguredProperty("applicationClientID"); applicationKey = getConfiguredProperty("applicationKey"); + + akvProviderManagedClientId = getConfiguredProperty("akvProviderManagedClientId"); + tenantID = getConfiguredProperty("tenantID"); - servicePrincipalCertificateApplicationClientId = getConfiguredProperty("servicePrincipalCertificateApplicationClientId"); + servicePrincipalCertificateApplicationClientId = getConfiguredProperty( + "servicePrincipalCertificateApplicationClientId"); accessTokenClientId = getConfiguredProperty("accessTokenClientId"); accessTokenSecret = getConfiguredProperty("accessTokenSecret"); @@ -235,7 +244,12 @@ public static void setup() throws Exception { map.put(Constants.CUSTOM_KEYSTORE_NAME, jksProvider); } - if (null == akvProvider && null != applicationClientID && null != applicationKey) { + if (null == akvProvider && null != akvProviderManagedClientId) { + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + } else if (null == akvProvider && null != applicationClientID && null != applicationKey) { File file = null; try { file = new File(Constants.MSSQL_JDBC_PROPERTIES); From 7e795d4fe495a4d4fad99544e6b6e09f70d54d6f Mon Sep 17 00:00:00 2001 From: Mahendra Chavan Date: Wed, 26 Feb 2025 20:48:15 +0530 Subject: [PATCH 2/4] Changed the scope of BULK_COPY_OPERATION_CACHE to connection. (#2594) --- .../microsoft/sqlserver/jdbc/SQLServerBulkCopy.java | 9 ++++----- .../microsoft/sqlserver/jdbc/SQLServerConnection.java | 10 +++++++++- .../jdbc/unit/statement/BatchExecutionTest.java | 8 ++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 35f239608..7dcc1469a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -5,7 +5,6 @@ package com.microsoft.sqlserver.jdbc; -import static com.microsoft.sqlserver.jdbc.SQLServerConnection.BULK_COPY_OPERATION_CACHE; import static com.microsoft.sqlserver.jdbc.Util.getHashedSecret; import static java.nio.charset.StandardCharsets.UTF_16LE; import static java.nio.charset.StandardCharsets.UTF_8; @@ -1731,19 +1730,19 @@ private void getDestinationMetadata() throws SQLServerException { String escapedDestinationTableName = Util.escapeSingleQuotes(destinationTableName); String key = null; - + HashMap> bulkCopyOperationCache = connection.getBulkCopyOperationCache(); if (connection.getcacheBulkCopyMetadata()) { String databaseName = connection.activeConnectionProperties .getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString()); key = getHashedSecret(new String[] {escapedDestinationTableName, databaseName}); - destColumnMetadata = BULK_COPY_OPERATION_CACHE.get(key); + destColumnMetadata = bulkCopyOperationCache.get(key); } if (null == destColumnMetadata || destColumnMetadata.isEmpty()) { if (connection.getcacheBulkCopyMetadata()) { DESTINATION_COL_METADATA_LOCK.lock(); try { - destColumnMetadata = BULK_COPY_OPERATION_CACHE.get(key); + destColumnMetadata = bulkCopyOperationCache.get(key); if (null == destColumnMetadata || destColumnMetadata.isEmpty()) { setDestinationColumnMetadata(escapedDestinationTableName); @@ -1758,7 +1757,7 @@ private void getDestinationMetadata() throws SQLServerException { // driver will not be aware of this and the inserted data will likely be corrupted. In such // scenario, we can't detect this without making an additional metadata query, which would // defeat the purpose of caching. - BULK_COPY_OPERATION_CACHE.put(key, destColumnMetadata); + bulkCopyOperationCache.put(key, destColumnMetadata); } } finally { DESTINATION_COL_METADATA_LOCK.unlock(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index c5d8a8a8e..ed7f21efc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1763,7 +1763,7 @@ public static void clearUserTokenCache() { /** transaction descriptor */ private byte[] transactionDescriptor = new byte[8]; - static final HashMap> BULK_COPY_OPERATION_CACHE = new HashMap<>(); + final HashMap> bulkCopyOperationCache = new HashMap<>(); /** * Flag (Yukon and later) set to true whenever a transaction is rolled back..The flag's value is reset to false when @@ -1782,6 +1782,9 @@ private void setState(State state) { this.state = state; } + final HashMap> getBulkCopyOperationCache() { + return bulkCopyOperationCache; + } /** * This function actually represents whether a database session is not open. The session is not available before the * session is established and after the session is closed. @@ -2040,6 +2043,11 @@ final void resetPooledConnection() { if (null != preparedStatementHandleCache) { preparedStatementHandleCache.clear(); } + + //clear bulk copy operation cache for this connection + if (null != bulkCopyOperationCache) { + bulkCopyOperationCache.clear(); + } } /** 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 ba3f6e70c..f271af8d2 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,9 +170,9 @@ public void testSqlServerBulkCopyCachingConnectionLevel() throws Exception { if (con.getClass().getName().equals("com.microsoft.sqlserver.jdbc.SQLServerConnection43")) { bulkcopyMetadataCacheField = con.getClass().getSuperclass() - .getDeclaredField("BULK_COPY_OPERATION_CACHE"); + .getDeclaredField("bulkCopyOperationCache"); } else { - bulkcopyMetadataCacheField = con.getClass().getDeclaredField("BULK_COPY_OPERATION_CACHE"); + bulkcopyMetadataCacheField = con.getClass().getDeclaredField("bulkCopyOperationCache"); } bulkcopyMetadataCacheField.setAccessible(true); @@ -233,9 +233,9 @@ public void testSqlServerBulkCopyCachingConnectionLevelMultiThreaded() throws Ex if (con.getClass().getName().equals("com.microsoft.sqlserver.jdbc.SQLServerConnection43")) { bulkcopyMetadataCacheField = con.getClass().getSuperclass() - .getDeclaredField("BULK_COPY_OPERATION_CACHE"); + .getDeclaredField("bulkCopyOperationCache"); } else { - bulkcopyMetadataCacheField = con.getClass().getDeclaredField("BULK_COPY_OPERATION_CACHE"); + bulkcopyMetadataCacheField = con.getClass().getDeclaredField("bulkCopyOperationCache"); } bulkcopyMetadataCacheField.setAccessible(true); From 8a77b15bebf56a53a6f8a37f1846d6b2711c8fca Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Mon, 3 Mar 2025 11:13:48 -0800 Subject: [PATCH 3/4] Update dependency versions (#2614) --- build.gradle | 42 ++++++++-------- pom.xml | 140 +++++++++++++++++---------------------------------- 2 files changed, 68 insertions(+), 114 deletions(-) diff --git a/build.gradle b/build.gradle index 3e5d87a9e..648b18895 100644 --- a/build.gradle +++ b/build.gradle @@ -143,29 +143,29 @@ repositories { dependencies { implementation 'org.osgi:org.osgi.core:6.0.0', 'org.osgi:org.osgi.service.jdbc:1.1.0' - compileOnly 'com.azure:azure-security-keyvault-keys:4.7.3', - 'com.azure:azure-identity:1.12.2', - 'org.antlr:antlr4-runtime:4.9.3', - 'com.google.code.gson:gson:2.10.1', - 'org.bouncycastle:bcprov-jdk18on:1.78', - 'org.bouncycastle:bcpkix-jdk18on:1.78' - testImplementation 'org.junit.platform:junit-platform-console:1.5.2', - 'org.junit.platform:junit-platform-commons:1.5.2', - 'org.junit.platform:junit-platform-engine:1.5.2', - 'org.junit.platform:junit-platform-launcher:1.5.2', - 'org.junit.platform:junit-platform-runner:1.5.2', + compileOnly 'com.azure:azure-security-keyvault-keys:4.9.2', + 'com.azure:azure-identity:1.15.3', + 'org.antlr:antlr4-runtime:4.9.1', + 'com.google.code.gson:gson:2.11.0', + 'org.bouncycastle:bcprov-jdk18on:1.79', + 'org.bouncycastle:bcpkix-jdk18on:1.79' + testImplementation 'org.junit.platform:junit-platform-console:1.11.4', + 'org.junit.platform:junit-platform-commons:1.11.4', + 'org.junit.platform:junit-platform-engine:1.11.4', + 'org.junit.platform:junit-platform-launcher:1.11.4', + 'org.junit.platform:junit-platform-runner:1.11.4', 'org.junit.platform:junit-platform-surefire-provider:1.3.2', - 'org.junit.jupiter:junit-jupiter-api:5.8.2', - 'org.junit.jupiter:junit-jupiter-engine:5.8.2', - 'org.junit.jupiter:junit-jupiter-params:5.8.2', + 'org.junit.jupiter:junit-jupiter-api:5.11.4', + 'org.junit.jupiter:junit-jupiter-engine:5.11.4', + 'org.junit.jupiter:junit-jupiter-params:5.11.4', 'com.zaxxer:HikariCP:3.4.2', - 'org.apache.commons:commons-dbcp2:2.7.0', - 'org.slf4j:slf4j-nop:1.7.30', + 'org.apache.commons:commons-dbcp2:2.13.0', + 'org.slf4j:slf4j-nop:1.7.36', 'org.antlr:antlr4-runtime:4.9.3', - 'org.eclipse.gemini.blueprint:gemini-blueprint-mock:2.1.0.RELEASE', - 'com.google.code.gson:gson:2.10.1', - 'org.bouncycastle:bcprov-jdk18on:1.78', - 'com.azure:azure-security-keyvault-keys:4.7.3', - 'com.azure:azure-identity:1.12.2', + 'org.eclipse.gemini.blueprint:gemini-blueprint-mock:3.0.0.M01', + 'com.google.code.gson:gson:2.11.0', + 'org.bouncycastle:bcprov-jdk18on:1.79', + 'com.azure:azure-security-keyvault-keys:4.9.2', + 'com.azure:azure-identity:1.15.3', 'com.h2database:h2:2.2.220' } diff --git a/pom.xml b/pom.xml index 284c17582..e055b0449 100644 --- a/pom.xml +++ b/pom.xml @@ -42,35 +42,32 @@ xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance - NTLM - - - For tests using NTLM Authentication mode (excluded by default) - kerberos - - - - For tests using Kerberos authentication (excluded by default) + NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default) + kerberos - - - - - For tests using Kerberos authentication (excluded by default) reqExternalSetup - For tests requiring external setup (excluded by default) clientCertAuth - - For tests requiring client certificate authentication - setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - - requireSecret - For tests requiring setting up secrets manually + setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default testing enabled with SQL Server 2019 (SQLv15) --> - - xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos,requireSecret + xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos -preview 6.0.0 - 4.7.3 - 1.12.2 - 1.15.1 + 4.9.2 + 1.15.3 1.1.0 4.9.3 - 2.10.1 - 1.78 - 1.78 + 2.11.0 + 1.79 + 1.79 - [1.3.2, 1.9.0] - 5.8.2 + [1.3.2, 1.11.4] + 5.11.4 3.4.2 - 2.7.0 - 1.7.30 - 2.1.0.RELEASE + 2.13.0 + 1.7.36 + 3.0.0.M01 2.2.220 UTF-8 ${project.build.sourceEncoding} @@ -95,12 +92,6 @@ - - com.microsoft.azure - msal4j - ${msal.version} - true - org.antlr @@ -230,8 +221,7 @@ central - - https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 + https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 true @@ -243,8 +233,7 @@ central - - https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 + https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 true @@ -257,8 +246,7 @@ jre8 - - ${project.artifactId}-${project.version}.jre8${releaseExt} + ${project.artifactId}-${project.version}.jre8${releaseExt} org.apache.maven.plugins @@ -266,20 +254,14 @@ 3.8.0 - - **/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java - - **/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java - - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java + **/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java + **/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java - - **/com/microsoft/sqlserver/jdbc/connection/ConnectionWrapper43Test.java - - **/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java - - **/com/microsoft/sqlserver/jdbc/JDBC43Test.java + **/com/microsoft/sqlserver/jdbc/connection/ConnectionWrapper43Test.java + **/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java + **/com/microsoft/sqlserver/jdbc/JDBC43Test.java 1.8 1.8 @@ -298,8 +280,7 @@ 3.1.1 - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF + ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -308,8 +289,7 @@ maven-surefire-plugin 3.0.0-M1 - + ${excludedGroups}, xJDBC42 @@ -319,8 +299,7 @@ jre11 - - ${project.artifactId}-${project.version}.jre11${releaseExt} + ${project.artifactId}-${project.version}.jre11${releaseExt} org.apache.maven.plugins @@ -328,8 +307,7 @@ 3.8.0 - - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java 11 11 @@ -341,11 +319,9 @@ 3.1.1 - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF + ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - com.microsoft.sqlserver.jdbc + com.microsoft.sqlserver.jdbc @@ -356,8 +332,7 @@ jre17 - - ${project.artifactId}-${project.version}.jre17${releaseExt} + ${project.artifactId}-${project.version}.jre17${releaseExt} org.apache.maven.plugins @@ -365,8 +340,7 @@ 3.8.0 - - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java 17 17 @@ -378,11 +352,9 @@ 3.1.1 - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF + ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - com.microsoft.sqlserver.jdbc + com.microsoft.sqlserver.jdbc @@ -393,8 +365,7 @@ jre21 - - ${project.artifactId}-${project.version}.jre21${releaseExt} + ${project.artifactId}-${project.version}.jre21${releaseExt} org.apache.maven.plugins @@ -402,8 +373,7 @@ 3.8.0 - - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java 21 21 @@ -415,11 +385,9 @@ 3.1.1 - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF + ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - com.microsoft.sqlserver.jdbc + com.microsoft.sqlserver.jdbc @@ -433,8 +401,7 @@ true - - ${project.artifactId}-${project.version}.jre23${releaseExt} + ${project.artifactId}-${project.version}.jre23${releaseExt} org.apache.maven.plugins @@ -442,8 +409,7 @@ 3.8.0 - - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java 23 23 @@ -455,11 +421,9 @@ 3.1.1 - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF + ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - com.microsoft.sqlserver.jdbc + com.microsoft.sqlserver.jdbc @@ -506,16 +470,12 @@ - + WARN - - org.apache.maven.plugins:maven-verifier-plugin + org.apache.maven.plugins:maven-verifier-plugin - - Please consider using the - maven-invoker-plugin + Please consider using the maven-invoker-plugin (http://maven.apache.org/plugins/maven-invoker-plugin/)! @@ -568,13 +528,8 @@ com.microsoft.sqlserver.jdbc.dataclassification, microsoft.sql - - !microsoft.sql, - com.ibm.security.auth.module;resolution:=optional, - com.sun.security.auth.module;resolution:=optional, - jdk.net;resolution:=optional,* - - com.microsoft.sqlserver.jdbc.osgi.Activator + !microsoft.sql,jdk.net;resolution:=optional,* + com.microsoft.sqlserver.jdbc.osgi.Activator @@ -611,8 +566,7 @@ true outdated-dependencies.txt - - file:///${session.executionRootDirectory}/maven-version-rules.xml + file:///${session.executionRootDirectory}/maven-version-rules.xml From b8be6434f67702ae9f87157e08930dd46588bbc7 Mon Sep 17 00:00:00 2001 From: Jeff Wasty Date: Mon, 3 Mar 2025 15:55:21 -0800 Subject: [PATCH 4/4] Remove scheme from URI before fetching path for CRL path check (#2622) * Remove scheme from URI before fetching path for CRL path check * Update src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Committed nitpick mentioned by copilot * Added additional check + greater clarity * The issue was with jar:file:/ NOT jar:/, corrected. * Forgot a case where there would be no scheme --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../jdbc/ConfigurableRetryLogic.java | 25 ++++++++++++++++--- .../sqlserver/jdbc/SQLServerResource.java | 3 ++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java index da8facdd8..0a1bd4e44 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java @@ -13,6 +13,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.Collections; @@ -39,9 +40,11 @@ public class ConfigurableRetryLogic { private static final String SEMI_COLON = ";"; private static final String COMMA = ","; private static final String EQUALS_SIGN = "="; + private static final String FORWARD_SLASH = "/"; private static final String RETRY_EXEC = "retryExec"; private static final String RETRY_CONN = "retryConn"; private static final String STATEMENT = "statement"; + private static final String CLASS_FILES_SUFFIX = "target/classes/"; private static boolean replaceFlag = false; // Are we replacing the list of transient errors? /** * The time the properties file was last modified. @@ -284,20 +287,36 @@ private static void createConnectionRules(LinkedList listOfRules) throws private static String getCurrentClassPath() throws SQLServerException { String location = ""; String className = ""; + String uriToString = ""; try { className = new Object() {}.getClass().getEnclosingClass().getName(); location = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath(); + URI uri = ConfigurableRetryLogic.class.getProtectionDomain().getCodeSource().getLocation() + .toURI(); + + uriToString = uri.toString(); + + int initialIndexOfForwardSlash = uriToString.indexOf(FORWARD_SLASH); + + if (!uri.getScheme().isEmpty() && initialIndexOfForwardSlash > 0) { + // If the URI has a scheme, i.e. jar:file:, jar:, or file: then we create a substring from the + // forward slash onwards. + uriToString = uriToString.substring(initialIndexOfForwardSlash + 1); + } - if (Files.isDirectory(Paths - .get(ConfigurableRetryLogic.class.getProtectionDomain().getCodeSource().getLocation().toURI()))) { + if (Files.isDirectory(Paths.get(uriToString))) { // We check if the Path we get from the CodeSource location is a directory. If so, we are running // from class files and should remove a suffix (i.e. the props file is in a different location from the // location returned) - location = location.substring(0, location.length() - ("target/classes/").length()); + location = location.substring(0, location.length() - CLASS_FILES_SUFFIX.length()); } return new URI(location).getPath() + DEFAULT_PROPS_FILE; // TODO: Allow custom paths + } catch (InvalidPathException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_PathInvalid")); + Object[] msgArgs = {uriToString}; + throw new SQLServerException(form.format(msgArgs), null, 0, e); } catch (URISyntaxException e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_URLInvalid")); Object[] msgArgs = {location}; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 942bfdc26..435dd286b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -369,7 +369,8 @@ protected Object[][] getContents() { {"R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumn", "Cannot execute statement or procedure {0} because Force Encryption was set as true for parameter {1} and the database expects this parameter to be sent as plaintext. This may be due to a configuration error."}, {"R_ForceEncryptionTrue_HonorAEFalseRS", "Cannot set Force Encryption to true for parameter {0} because encryption is not enabled for the statement or procedure."}, {"R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumnRS", "Cannot execute update because Force Encryption was set as true for parameter {0} and the database expects this parameter to be sent as plaintext. This may be due to a configuration error."}, - {"R_NullValue", "{0} cannot be null."}, + {"R_NullValue", "{0} cannot be null."}, + {"R_PathInvalid", "Invalid path specified: {0}."}, {"R_URLInvalid", "Invalid URL specified: {0}."}, {"R_AKVPathNull", "Azure Key Vault key path cannot be null."}, {"R_AKVMasterKeyPathInvalid", "Invalid Azure Key Vault key path specified: {0}."},