diff --git a/CHANGELOG.md b/CHANGELOG.md index 552526457..8c68d7164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## [12.10.0] Stable Release +### Added +- Added provision to set SQLServerBulkCopy options in PreparedStatement [#2555](https://github.com/microsoft/mssql-jdbc/pull/2555) +### Changed +- Changed the scope of BULK_COPY_OPERATION_CACHE to connection [#2594](https://github.com/microsoft/mssql-jdbc/pull/2594) +- Added "requireSecret" exclude tag for tests which require adding a secret to app registration [#2596](https://github.com/microsoft/mssql-jdbc/pull/2596) +- Added com.ibm.security.auth.module and com.sun.security.auth.module as option import [#2609](https://github.com/microsoft/mssql-jdbc/pull/2609) +- Updated driver dependency versions [#2614](https://github.com/microsoft/mssql-jdbc/pull/2614) +### Fixed issues +- Introduced timeouts for MSAL calls [#2562](https://github.com/microsoft/mssql-jdbc/pull/2562) +- Fixed getGeneratedKeys functionality for execute API [#2554](https://github.com/microsoft/mssql-jdbc/pull/2554) +- Fixed ISQLServerConnection java doc reference [#2560](https://github.com/microsoft/mssql-jdbc/pull/2560) +- Fixed OffsetDateTime conversion for pre-Gregorian dates [#2568](https://github.com/microsoft/mssql-jdbc/pull/2568) +- Fix for driver cutting out the question mark from columns labels (aliases) [#2569](https://github.com/microsoft/mssql-jdbc/pull/2569) +- Fixed issue with SQLServerBulkCopy from CSV with setEscapeColumnDelimerts set to true [#2575](https://github.com/microsoft/mssql-jdbc/pull/2575) +- Fixed issue for finding `mssql-jdbc.properties` location in test environments [#2579](https://github.com/microsoft/mssql-jdbc/pull/2579) +- Fixed issue for IBM Semeru Runtime Certified Edition for z/OS and Kerberos [#2581](https://github.com/microsoft/mssql-jdbc/pull/2581) +- Set appropriate value to requestedEncryptionLevel for encrypt=STRICT [#2597](https://github.com/microsoft/mssql-jdbc/pull/2597) +- Add test for ManagedIdentityWithEncryptStrict [#2599](https://github.com/microsoft/mssql-jdbc/pull/2599) +- Check for null when getting DTV values (JDBC spec compliance - getBinaryStream /getAsciiStream will return null when the value is null) [#2600](https://github.com/microsoft/mssql-jdbc/pull/2600) +- Removed scheme from URI before fetching path for CRL path check [#2622](https://github.com/microsoft/mssql-jdbc/pull/2622) + ## [12.9.0] Preview Release ### Added - Added configurable retry logic feature, supporting both statement, and connection, retry [#2396](https://github.com/microsoft/mssql-jdbc/pull/2396)[#2519](https://github.com/microsoft/mssql-jdbc/pull/2519) diff --git a/README.md b/README.md index 7423adff1..a42de7b4c 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to com.microsoft.sqlserver mssql-jdbc - 12.8.1.jre11 + 12.10.0.jre11 ``` The driver can be downloaded from [Microsoft](https://aka.ms/downloadmssqljdbc). For driver version 12.1.0 and greater, please use the jre11 version when using Java 11 or greater, and the jre8 version when using Java 8. @@ -94,7 +94,7 @@ To get the latest version of the driver, add the following to your POM file: com.microsoft.sqlserver mssql-jdbc - 12.8.1.jre11 + 12.10.0.jre11 ``` @@ -129,7 +129,7 @@ Projects that require either of the two features need to explicitly declare the com.microsoft.sqlserver mssql-jdbc - 12.8.1.jre11 + 12.10.0.jre11 compile @@ -147,7 +147,7 @@ Projects that require either of the two features need to explicitly declare the com.microsoft.sqlserver mssql-jdbc - 12.8.1.jre11 + 12.10.0.jre11 compile @@ -174,7 +174,7 @@ When setting 'useFmtOnly' property to 'true' for establishing a connection or cr com.microsoft.sqlserver mssql-jdbc - 12.8.1.jre11 + 12.10.0.jre11 diff --git a/build.gradle b/build.gradle index 648b18895..1d8b6b15a 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,8 @@ apply plugin: 'java' -version = '12.9.0' -def releaseExt = '-preview' +version = '12.10.0' +def releaseExt = '' def jreVersion = "" def testOutputDir = file("build/classes/java/test") def archivesBaseName = 'mssql-jdbc' diff --git a/mssql-jdbc_auth_LICENSE b/mssql-jdbc_auth_LICENSE index b9ebea4ff..642645aeb 100644 --- a/mssql-jdbc_auth_LICENSE +++ b/mssql-jdbc_auth_LICENSE @@ -1,5 +1,5 @@ MICROSOFT SOFTWARE LICENSE TERMS -MICROSOFT JDBC DRIVER 12.9.0 FOR SQL SERVER +MICROSOFT JDBC DRIVER 12.10.0 FOR SQL SERVER These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. diff --git a/pom.xml b/pom.xml index e055b0449..7b4db61de 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.sqlserver mssql-jdbc - 12.9.0 + 12.10.0 jar Microsoft JDBC Driver for SQL Server @@ -51,7 +51,7 @@ Default testing enabled with SQL Server 2019 (SQLv15) --> xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos - -preview + 6.0.0 4.9.2 diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java index 2f76db4f8..b457eaa57 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java @@ -7,7 +7,7 @@ final class SQLJdbcVersion { static final int MAJOR = 12; - static final int MINOR = 9; + static final int MINOR = 10; static final int PATCH = 0; static final int BUILD = 0; /* @@ -15,7 +15,7 @@ final class SQLJdbcVersion { * 1. Set to "-preview" for preview release. * 2. Set to "" (empty String) for official release. */ - static final String RELEASE_EXT = "-preview"; + static final String RELEASE_EXT = ""; private SQLJdbcVersion() { throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index f67a92351..389eb8eb1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -18,8 +18,6 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.sql.Connection; import java.sql.Date; import java.sql.ResultSet; @@ -1859,16 +1857,16 @@ private void getDestinationMetadata() throws SQLServerException { } if (loggerExternal.isLoggable(Level.FINER)) { - loggerExternal.finer(this.toString() + " Acquiring existing destination column metadata " + - "from cache for bulk copy"); + loggerExternal.finer(this.toString() + " Acquiring existing destination column metadata " + + "from cache for bulk copy"); } } else { setDestinationColumnMetadata(escapedDestinationTableName); if (loggerExternal.isLoggable(Level.FINER)) { - loggerExternal.finer(this.toString() + " cacheBulkCopyMetadata=false - Querying server " + - "for destination column metadata"); + loggerExternal.finer(this.toString() + " cacheBulkCopyMetadata=false - Querying server " + + "for destination column metadata"); } } } @@ -2008,7 +2006,7 @@ private void validateColumnMappings() throws SQLServerException { // Generate default column mappings ColumnMapping cm; - for (int i = 1; i <= srcColumnCount; ++i) { + for (Integer i : destColumnMetadata.keySet()) { // Only skip identity column mapping if KEEP IDENTITY OPTION is FALSE if (!(destColumnMetadata.get(i).isIdentity && !copyOptions.isKeepIdentity())) { cm = new ColumnMapping(i, i); @@ -2062,7 +2060,7 @@ private void validateColumnMappings() throws SQLServerException { if (-1 == cm.destinationColumnOrdinal) { boolean foundColumn = false; - for (int j = 1; j <= destColumnCount; ++j) { + for (Integer j : destColumnMetadata.keySet()) { if (destColumnMetadata.get(j).columnName.equals(cm.destinationColumnName)) { foundColumn = true; cm.destinationColumnOrdinal = j; @@ -2223,7 +2221,8 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType, private void writeColumnToTdsWriter(TDSWriter tdsWriter, int bulkPrecision, int bulkScale, int bulkJdbcType, boolean bulkNullable, // should it be destNullable instead? - int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, Calendar cal) throws SQLServerException { + int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, + Calendar cal) throws SQLServerException { SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType; bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, @@ -3140,8 +3139,8 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole /** * Reads the given column from the result set current row and writes the data to tdsWriter. */ - private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, - Object colValue, Calendar cal) throws SQLServerException { + private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, Object colValue, + Calendar cal) throws SQLServerException { String destName = destColumnMetadata.get(destColOrdinal).columnName; int srcPrecision, srcScale, destPrecision, srcJdbcType; SSType destSSType = null; @@ -3793,8 +3792,8 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command, // Loop for each destination column. The mappings is a many to one mapping // where multiple source columns can be mapped to one destination column. for (ColumnMapping columnMapping : columnMappings) { - writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, null, - null // cell + writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, + null, null // cell // value is // retrieved // inside diff --git a/src/samples/adaptive/pom.xml b/src/samples/adaptive/pom.xml index f185669c6..b14b6bae3 100644 --- a/src/samples/adaptive/pom.xml +++ b/src/samples/adaptive/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -74,8 +74,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/alwaysencrypted/pom.xml b/src/samples/alwaysencrypted/pom.xml index d25b975aa..7341ca97d 100644 --- a/src/samples/alwaysencrypted/pom.xml +++ b/src/samples/alwaysencrypted/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -42,8 +42,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/azureactivedirectoryauthentication/pom.xml b/src/samples/azureactivedirectoryauthentication/pom.xml index 8c1acabb3..9279ae457 100644 --- a/src/samples/azureactivedirectoryauthentication/pom.xml +++ b/src/samples/azureactivedirectoryauthentication/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -57,8 +57,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/connections/pom.xml b/src/samples/connections/pom.xml index bbc06a442..d88cc89a8 100644 --- a/src/samples/connections/pom.xml +++ b/src/samples/connections/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -57,8 +57,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/constrained/pom.xml b/src/samples/constrained/pom.xml index 330ab1830..33a35dc25 100644 --- a/src/samples/constrained/pom.xml +++ b/src/samples/constrained/pom.xml @@ -16,7 +16,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -44,8 +44,8 @@ maven-compiler-plugin 3.8.0 - 22 - 22 + 23 + 23 diff --git a/src/samples/dataclassification/pom.xml b/src/samples/dataclassification/pom.xml index c7743021c..3d6c39966 100644 --- a/src/samples/dataclassification/pom.xml +++ b/src/samples/dataclassification/pom.xml @@ -16,7 +16,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -44,8 +44,8 @@ maven-compiler-plugin 3.8.0 - 22 - 22 + 23 + 23 diff --git a/src/samples/datatypes/pom.xml b/src/samples/datatypes/pom.xml index 9052880a9..b19cf898e 100644 --- a/src/samples/datatypes/pom.xml +++ b/src/samples/datatypes/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -74,8 +74,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/resultsets/pom.xml b/src/samples/resultsets/pom.xml index c54636cd4..7597c1f36 100644 --- a/src/samples/resultsets/pom.xml +++ b/src/samples/resultsets/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -73,8 +73,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/samples/sparse/pom.xml b/src/samples/sparse/pom.xml index 3e7b15c94..db9059f33 100644 --- a/src/samples/sparse/pom.xml +++ b/src/samples/sparse/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.8.0.jre11 + 12.10.0.jre11 @@ -41,8 +41,8 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 + 23 + 23 diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java index 057c5068c..b39a6fe2d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java @@ -6,13 +6,13 @@ import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.StackOverflowError; import java.net.URL; import java.sql.Connection; import java.sql.ResultSet; @@ -37,6 +37,7 @@ import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerBulkCSVFileRecord; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; +import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions; import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.TestUtils; import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; @@ -68,6 +69,7 @@ public class BulkCopyCSVTest extends AbstractTest { static String inputFileDelimiterEscape = "BulkCopyCSVTestInputDelimiterEscape.csv"; static String inputFileDelimiterEscapeNoNewLineAtEnd = "BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv"; static String inputFileMultipleDoubleQuotes = "BulkCopyCSVTestInputMultipleDoubleQuotes.csv"; + static String computeColumnCsvFile = "BulkCopyCSVTestInputComputeColumn.csv"; static String encoding = "UTF-8"; static String delimiter = ","; @@ -261,7 +263,7 @@ public void testEscapeColumnDelimitersCSVNoNewLineAtEnd() throws Exception { } } } - + /** * test simple csv file for bulkcopy, for GitHub issue 1391 Tests to ensure that the set returned by * getColumnOrdinals doesn't have to be ordered @@ -446,6 +448,108 @@ public void testCSV2400() { } } + /** + * Test to perform bulk copy with a computed column as the last column in the table. + */ + @Test + @DisplayName("Test bulk copy with computed column as last column") + public void testBulkCopyWithComputedColumnAsLastColumn() { + String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("BulkEscape")); + String fileName = filePath + computeColumnCsvFile; + + try (Connection con = getConnection(); Statement stmt = con.createStatement(); + SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(con); + SQLServerBulkCSVFileRecord fileRecord = new SQLServerBulkCSVFileRecord(fileName, encoding, ",", true)) { + + String createTableSQL = "CREATE TABLE " + tableName + " (" + "[NAME] varchar(50) NOT NULL," + + "[AGE] int NULL," + "[CAL_COL] numeric(17, 2) NULL," + "[ORIGINAL] varchar(50) NOT NULL," + + "[COMPUTED_COL] AS (right([NAME], 8)) PERSISTED" + ")"; + stmt.executeUpdate(createTableSQL); + + fileRecord.addColumnMetadata(1, "NAME", java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(2, "AGE", java.sql.Types.INTEGER, 0, 0); + fileRecord.addColumnMetadata(3, "CAL_COL", java.sql.Types.NUMERIC, 17, 2); + fileRecord.addColumnMetadata(4, "ORIGINAL", java.sql.Types.VARCHAR, 50, 0); + + bulkCopy.setDestinationTableName(tableName); + + bulkCopy.addColumnMapping("NAME", "NAME"); + bulkCopy.addColumnMapping("AGE", "AGE"); + bulkCopy.addColumnMapping("CAL_COL", "CAL_COL"); + bulkCopy.addColumnMapping("ORIGINAL", "ORIGINAL"); + + SQLServerBulkCopyOptions options = new SQLServerBulkCopyOptions(); + options.setKeepIdentity(false); + options.setTableLock(true); + bulkCopy.setBulkCopyOptions(options); + + bulkCopy.writeToServer(fileRecord); + + try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tableName)) { + if (rs.next()) { + int rowCount = rs.getInt(1); + assertTrue(rowCount > 0); + } + } finally { + TestUtils.dropTableIfExists(tableName, stmt); + } + } catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Test to perform bulk copy with a computed column not as the last column in the table. + */ + @Test + @DisplayName("Test bulk copy with computed column not as last column") + public void testBulkCopyWithComputedColumnNotAsLastColumn() { + String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("BulkEscape")); + String fileName = filePath + computeColumnCsvFile; + + try (Connection con = getConnection(); Statement stmt = con.createStatement(); + SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(con); + SQLServerBulkCSVFileRecord fileRecord = new SQLServerBulkCSVFileRecord(fileName, encoding, ",", true)) { + + String createTableSQL = "CREATE TABLE " + tableName + " (" + "[NAME] varchar(50) NOT NULL," + + "[AGE] int NULL," + "[CAL_COL] numeric(17, 2) NULL," + "[ORIGINAL] varchar(50) NOT NULL," + + "[COMPUTED_COL] AS (right([NAME], 8)) PERSISTED," + "[LAST_COL] varchar(50) NULL" + ")"; + stmt.executeUpdate(createTableSQL); + + fileRecord.addColumnMetadata(1, "NAME", java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(2, "AGE", java.sql.Types.INTEGER, 0, 0); + fileRecord.addColumnMetadata(3, "CAL_COL", java.sql.Types.NUMERIC, 17, 2); + fileRecord.addColumnMetadata(4, "ORIGINAL", java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(5, "LAST_COL", java.sql.Types.VARCHAR, 50, 0); + + bulkCopy.setDestinationTableName(tableName); + + bulkCopy.addColumnMapping("NAME", "NAME"); + bulkCopy.addColumnMapping("AGE", "AGE"); + bulkCopy.addColumnMapping("CAL_COL", "CAL_COL"); + bulkCopy.addColumnMapping("ORIGINAL", "ORIGINAL"); + bulkCopy.addColumnMapping("LAST_COL", "LAST_COL"); + + SQLServerBulkCopyOptions options = new SQLServerBulkCopyOptions(); + options.setKeepIdentity(false); + options.setTableLock(true); + bulkCopy.setBulkCopyOptions(options); + + bulkCopy.writeToServer(fileRecord); + + try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tableName)) { + if (rs.next()) { + int rowCount = rs.getInt(1); + assertTrue(rowCount > 0); + } + } finally { + TestUtils.dropTableIfExists(tableName, stmt); + } + } catch (Exception e) { + fail(e.getMessage()); + } + } + /** * drop source table after testing bulk copy * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bvt/BvtTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bvt/BvtTest.java index afa907cb4..e2d3b2ce0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bvt/BvtTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bvt/BvtTest.java @@ -35,7 +35,7 @@ @RunWith(JUnitPlatform.class) public class BvtTest extends AbstractTest { - private static String driverNamePattern = "Microsoft JDBC Driver \\d+.\\d for SQL Server"; + private static String driverNamePattern = "Microsoft JDBC Driver \\d+.\\d+ for SQL Server"; static DBTable table1; static DBTable table2; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java index 7c7f2240d..6842e6115 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java @@ -10,7 +10,6 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicReference; -import com.microsoft.sqlserver.jdbc.TestUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -23,7 +22,6 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) -@Tag(Constants.requireSecret) public class ConcurrentLoginTest extends FedauthCommon { final AtomicReference throwableRef = new AtomicReference(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionEncryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionEncryptionTest.java index b7e307654..d75c5a66f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionEncryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionEncryptionTest.java @@ -11,7 +11,6 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import java.text.MessageFormat; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -29,7 +28,6 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) -@Tag(Constants.requireSecret) public class ConnectionEncryptionTest extends FedauthCommon { static String charTable = TestUtils.escapeSingleQuotes( diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java index ebe87e65c..dd4d95239 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java @@ -25,7 +25,6 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) -@Tag(Constants.requireSecret) public class ErrorMessageTest extends FedauthCommon { String badUserName = "abc" + azureUserName; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java index 1270b1c94..f8cac33ac 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java @@ -7,6 +7,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; import com.microsoft.aad.msal4j.ClientCredentialFactory; import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; @@ -21,6 +25,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Locale; @@ -216,25 +221,21 @@ public static void getConfigs() throws Exception { static void getFedauthInfo() { int retry = 0; long interval = THROTTLE_RETRY_INTERVAL; + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + while (retry <= THROTTLE_RETRY_COUNT) { try { - Set scopes = new HashSet<>(); - scopes.add(spn + "/.default"); - if (null == fedauthClientApp) { - IClientCredential credential = ClientCredentialFactory.createFromSecret(applicationKey); - fedauthClientApp = ConfidentialClientApplication.builder(applicationClientID, credential) - .executorService(Executors.newFixedThreadPool(1)) - .setTokenCacheAccessAspect(FedauthTokenCache.getInstance()).authority(stsurl).build(); - } + TokenRequestContext requestContext = new TokenRequestContext() + .setScopes(Collections.singletonList(spn + "/.default")); - final CompletableFuture future = fedauthClientApp - .acquireToken(ClientCredentialParameters.builder(scopes).build()); + AccessToken token = credential.getToken(requestContext).block(); - final IAuthenticationResult authenticationResult = future.get(); - - secondsBeforeExpiration = TimeUnit.MILLISECONDS - .toSeconds(authenticationResult.expiresOnDate().getTime() - new Date().getTime()); - accessToken = authenticationResult.accessToken(); + if (token != null) { + secondsBeforeExpiration = TimeUnit.MILLISECONDS + .toSeconds(token.getExpiresAt().toInstant().toEpochMilli() - new Date().getTime()); + accessToken = token.getToken(); + } retry = THROTTLE_RETRY_COUNT + 1; } catch (MsalThrottlingException te) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java index 436ca8e17..543f771ef 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java @@ -42,7 +42,6 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) -@Tag(Constants.requireSecret) public class FedauthTest extends FedauthCommon { static String charTable = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBC_FedAuthTest"))); @@ -286,6 +285,7 @@ public void testAADPasswordApplicationName() throws Exception { */ @Deprecated @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuthDeprecated() { String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipal + ";AADSecurePrincipalId=" + applicationClientID @@ -308,6 +308,7 @@ public void testAADServicePrincipalAuthDeprecated() { * encryption. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuth() { String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipal + ";Username=" + applicationClientID + ";Password=" @@ -326,6 +327,7 @@ public void testAADServicePrincipalAuth() { } @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuthFailureOnSubsequentConnectionsWithInvalidatedTokenCacheWithInvalidSecret() throws Exception { String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipal + ";Username=" + applicationClientID + ";Password=" @@ -364,6 +366,7 @@ public void testActiveDirectoryPasswordFailureOnSubsequentConnectionsWithInvalid } @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalCertAuthFailureOnSubsequentConnectionsWithInvalidatedTokenCacheWithInvalidPassword() throws Exception { // Should succeed on valid cert field values String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" @@ -389,6 +392,7 @@ public void testAADServicePrincipalCertAuthFailureOnSubsequentConnectionsWithInv * Test invalid connection property combinations when using AAD Service Principal Authentication. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuthWrong() { String baseUrl = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipal + ";"; @@ -426,6 +430,7 @@ public void testAADServicePrincipalAuthWrong() { * encryption. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalCertAuth() { // certificate from AKV has no password String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" @@ -449,6 +454,7 @@ public void testAADServicePrincipalCertAuth() { * Test invalid connection property combinations when using AAD Service Principal Certificate Authentication. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalCertAuthWrong() { String baseUrl = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipalCertificate + ";userName=" @@ -488,23 +494,6 @@ public void testAccessTokenCallbackClassConnection() throws Exception { try (Connection conn1 = DriverManager.getConnection(cs)) {} } - @Test - public void testAccessTokenCache() { - try { - SilentParameters silentParameters = SilentParameters.builder(Collections.singleton(spn + "/.default")) - .build(); - - // this will fail if not cached - CompletableFuture future = fedauthClientApp.acquireTokenSilently(silentParameters); - IAuthenticationResult authenticationResult = future.get(); - assertNotNull(authenticationResult.accessToken()); - assertTrue(authenticationResult.accessToken().equals(accessToken), accessToken); - } catch (Exception e) { - fail(e.getMessage()); - } - - } - private static void validateException(String url, String resourceKey) { try (Connection conn = DriverManager.getConnection(url)) { fail(TestResource.getResource("R_expectedFailPassed")); 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 e42cc9f56..98896a048 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java @@ -23,6 +23,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.SQLServerColumnEncryptionJavaKeyStoreProvider; @@ -37,7 +39,6 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) -@Tag(Constants.requireSecret) public class FedauthWithAE extends FedauthCommon { static String cmkName1 = Constants.CMK_NAME + "fedauthAE1"; @@ -282,16 +283,17 @@ private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_JKS() th private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_AKV() throws SQLServerException { SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); - return registerAKVProvider( - new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey)); + return registerAKVProvider(); } - private SQLServerColumnEncryptionKeyStoreProvider registerAKVProvider( - SQLServerColumnEncryptionKeyStoreProvider provider) throws SQLServerException { - Map map1 = new HashMap(); - map1.put(provider.getName(), provider); - SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map1); - return provider; + private SQLServerColumnEncryptionKeyStoreProvider registerAKVProvider() throws SQLServerException { + Map map = new HashMap(); + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map); + return akvProvider; } private void createCMK(String cmkName, String keyStoreName, String keyPath, Statement stmt) throws SQLException { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/LobsStreamingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/LobsStreamingTest.java index c8b48a5bc..b7daa9039 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/LobsStreamingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/LobsStreamingTest.java @@ -1,6 +1,10 @@ package com.microsoft.sqlserver.jdbc.unit.lobs; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; @@ -16,9 +20,11 @@ import java.util.Scanner; import java.util.stream.IntStream; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; @@ -280,4 +286,73 @@ public void testNClobsVarcharCHARA() throws SQLException, IOException { } } } -} + + @Nested + public class TestPLP { + private String tableName; + + @AfterEach + public void cleanUp() { + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + } catch (SQLException ex) { + fail(ex.getMessage()); + } + } + + @Test + public void testGetAsciiStreamOnXml() { + tableName = TestUtils.escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TestXmlTable"))); + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate("CREATE TABLE " + tableName + " (col1 XML NULL)"); + stmt.executeUpdate("INSERT INTO " + tableName + " (col1) VALUES ('Hello')"); + stmt.executeUpdate("INSERT INTO " + tableName + " (col1) VALUES (NULL)"); + + try (ResultSet rs = stmt.executeQuery("SELECT col1 FROM " + tableName)) { + while (rs.next()) { + try { + InputStream asciiStream = rs.getAsciiStream(1); + // If no exception is thrown, assert the value is null + assertNull(asciiStream, "Expected null for NULL value, but got a non-null InputStream"); + } catch (SQLException e) { + // Ensure that only expected exceptions occur + assertTrue(e.getMessage().contains("The conversion from xml to AsciiStream is unsupported."), + "Unexpected SQLException message: " + e.getMessage()); + } + } + } + } catch (SQLException e) { + fail("Database setup or execution failed: " + e.getMessage()); + } + } + + @Test + public void testGetBinaryStreamOnVarchar() { + tableName = TestUtils.escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TestPLPTable"))); + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate("CREATE TABLE " + tableName + " (col1 VARCHAR(50) NULL)"); + stmt.executeUpdate("INSERT INTO " + tableName + " (col1) VALUES ('TestValue')"); + stmt.executeUpdate("INSERT INTO " + tableName + " (col1) VALUES (NULL)"); + + try (ResultSet rs = stmt.executeQuery("SELECT col1 FROM " + tableName)) { + while (rs.next()) { + try { + InputStream binaryStream = rs.getBinaryStream(1); + // If no exception is thrown, assert the value is null + assertNull(binaryStream, "Expected null for NULL value, but got a non-null InputStream"); + } catch (SQLException e) { + // Ensure that only expected exceptions occur + assertTrue(e.getMessage().contains("The conversion from varchar to BinaryStream is unsupported."), + "Unexpected SQLException message: " + e.getMessage()); + } + } + } + } catch (SQLException e) { + fail("Database setup or execution failed: " + e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/BulkCopyCSVTestInputComputeColumn.csv b/src/test/resources/BulkCopyCSVTestInputComputeColumn.csv new file mode 100644 index 000000000..d5b7dbc5a --- /dev/null +++ b/src/test/resources/BulkCopyCSVTestInputComputeColumn.csv @@ -0,0 +1,3 @@ +NAME,AGE,CAL_COL,ORIGINAL,LAST_COL +John Doe,30,12345.67,Original,Last +Jane Smith,25,54321.00,Original,Last