From 66ee80888d7ab79a844424ae8c3454f39c13fbb9 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 29 Jan 2021 14:24:33 -0800 Subject: [PATCH] Merge dev to master for 9.2.0 release (#1506) Merge dev to master for 9.2.0 release (#1506) --- CHANGELOG.md | 7 +++ README.md | 10 ++-- azure-pipelines.yml | 2 +- build.gradle | 4 +- mssql-jdbc_auth_LICENSE | 2 +- pom.xml | 4 +- .../microsoft/sqlserver/jdbc/IOBuffer.java | 21 +++---- .../jdbc/KeyVaultTokenCredential.java | 12 ++-- .../sqlserver/jdbc/SQLJdbcVersion.java | 6 +- .../sqlserver/jdbc/SQLServerBulkCopy.java | 54 ++++++++---------- ...ColumnEncryptionAzureKeyVaultProvider.java | 14 +++-- .../sqlserver/jdbc/SQLServerConnection.java | 56 +++++++++---------- .../sqlserver/jdbc/SQLServerMSAL4JUtils.java | 17 +++--- src/samples/adaptive/pom.xml | 8 +-- src/samples/alwaysencrypted/pom.xml | 8 +-- .../pom.xml | 8 +-- src/samples/connections/pom.xml | 8 +-- src/samples/constrained/pom.xml | 8 +-- src/samples/dataclassification/pom.xml | 8 +-- src/samples/datatypes/pom.xml | 8 +-- src/samples/resultsets/pom.xml | 8 +-- src/samples/sparse/pom.xml | 8 +-- .../sqlserver/jdbc/MaxResultBufferTest.java | 4 ++ .../sqlserver/jdbc/TestResource.java | 2 +- .../microsoft/sqlserver/jdbc/TestUtils.java | 19 +++++++ .../bulkCopy/BulkCopyColumnMappingTest.java | 39 +++++++++++++ .../DatabaseMetaDataTest.java | 40 ++++++++++--- .../jdbc/fedauth/ErrorMessageTest.java | 9 +-- 28 files changed, 240 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217f2d7a8..869b9de15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## [9.2.0] Stable Release +### Added +- Added logic to handle multi-factor authentication timeouts during ActiveDirectoryInteractive authentication [#1488](https://github.com/microsoft/mssql-jdbc/pull/1488) + +### Fixed issues +- Fixed an issue with high memory allocation during bulk copy [#1475](https://github.com/microsoft/mssql-jdbc/pull/1475) + ## [9.1.1] Preview Release ### Added - Added maxResultBuffer connection property [1431](https://github.com/microsoft/mssql-jdbc/pull/1431) diff --git a/README.md b/README.md index c08f6b5c7..73f21b934 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to com.microsoft.sqlserver mssql-jdbc - 8.4.1.jre14 + 9.2.0.jre15 ``` The driver can be downloaded from the [Microsoft Download Center](https://go.microsoft.com/fwlink/?linkid=868287). @@ -127,7 +127,7 @@ Projects that require either of the two features need to explicitly declare the com.microsoft.sqlserver mssql-jdbc - 9.1.1.jre15-preview + 9.2.0.jre15-preview compile @@ -145,7 +145,7 @@ Projects that require either of the two features need to explicitly declare the com.microsoft.sqlserver mssql-jdbc - 9.1.1.jre15-preview + 9.2.0.jre15-preview compile @@ -172,7 +172,7 @@ When setting 'useFmtOnly' property to 'true' for establishing a connection or cr com.microsoft.sqlserver mssql-jdbc - 9.1.1.jre15-preview + 9.2.0.jre15-preview @@ -212,7 +212,7 @@ Preview releases happen approximately monthly between stable releases. This give You can see what is going into a future release by monitoring [Milestones](https://github.com/Microsoft/mssql-jdbc/milestones) in the repository. ### Version conventions -Starting with 6.0, stable versions have an even minor version. For example, 6.0, 6.2, 6.4, 7.0, 7.2, 7.4, 8.2, 8.4. Preview versions have an odd minor version. For example, 6.1, 6.3, 6.5, 7.1, 7.3, 8.1 and so on +Starting with 6.0, stable versions have an even minor version. For example, 6.0, 6.2, 6.4, 7.0, 7.2, 7.4, 8.2, 8.4, 9.2. Preview versions have an odd minor version. For example, 6.1, 6.3, 6.5, 7.1, 7.3, 8.1 and so on ## Contributors Special thanks to everyone who has contributed to the project. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 977851c76..4cb42ff51 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,7 +38,7 @@ jobs: name: authDLL displayName: 'Download mssql-jdbc_auth DLL' inputs: - secureFile: 'mssql-jdbc_auth-9.1.1.x64-preview.dll' + secureFile: 'mssql-jdbc_auth-9.2.0.x64.dll' - task: Maven@3 displayName: 'Maven build jre15' inputs: diff --git a/build.gradle b/build.gradle index dc223da6b..02fa0916d 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ apply plugin: 'java' -version = '9.1.1' +version = '9.2.0' def jreVersion = "" def testOutputDir = file("build/classes/java/test") def archivesBaseName = 'mssql-jdbc' @@ -75,7 +75,7 @@ if(hasProperty('buildProfile') && buildProfile == "jre8") { } } -jar.archiveFileName = "${archivesBaseName}-${version}.${jreVersion}-preview.jar" +jar.archiveFileName = "${archivesBaseName}-${version}.${jreVersion}.jar" jar { manifest { attributes 'Title': "Microsoft JDBC Driver ${version} for SQL Server", diff --git a/mssql-jdbc_auth_LICENSE b/mssql-jdbc_auth_LICENSE index f35f9d80e..29dd404a3 100644 --- a/mssql-jdbc_auth_LICENSE +++ b/mssql-jdbc_auth_LICENSE @@ -1,5 +1,5 @@ MICROSOFT SOFTWARE LICENSE TERMS -MICROSOFT JDBC DRIVER 9.1 FOR SQL SERVER +MICROSOFT JDBC DRIVER 9.2 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 a93874f60..0eed1fe2a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.microsoft.sqlserver mssql-jdbc - 9.1.1 + 9.2.0 jar Microsoft JDBC Driver for SQL Server @@ -57,7 +57,7 @@ xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth - -preview + 4.2.1 diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 4e0752aeb..87bd5e532 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -3151,6 +3151,11 @@ boolean isEOMSent() { private ByteBuffer socketBuffer; private ByteBuffer logBuffer; + // Intermediate arrays + // It is assumed, startMessage is called before use, to alloc arrays + private char[] streamCharBuffer; + private byte[] streamByteBuffer; + private CryptoMetadata cryptoMeta = null; TDSWriter(TDSChannel tdsChannel, SQLServerConnection con) { @@ -3247,6 +3252,8 @@ void startMessage(TDSCommand command, byte tdsMessageType) throws SQLServerExcep stagingBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN); logBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN); currentPacketSize = negotiatedPacketSize; + streamCharBuffer = new char[2 * currentPacketSize]; + streamByteBuffer = new byte[4 * currentPacketSize]; } ((Buffer) socketBuffer).position(((Buffer) socketBuffer).limit()); @@ -4015,10 +4022,6 @@ void writeNonUnicodeReader(Reader reader, long advertisedLength, boolean isDestB assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0; long actualLength = 0; - char[] streamCharBuffer = new char[currentPacketSize]; - // The unicode version, writeReader() allocates a byte buffer that is 4 times the currentPacketSize, not sure - // why. - byte[] streamByteBuffer = new byte[currentPacketSize]; int charsRead = 0; int charsToWrite; int bytesToWrite; @@ -4026,10 +4029,10 @@ void writeNonUnicodeReader(Reader reader, long advertisedLength, boolean isDestB do { // Read in next chunk - for (charsToWrite = 0; -1 != charsRead && charsToWrite < streamCharBuffer.length; + for (charsToWrite = 0; -1 != charsRead && charsToWrite < currentPacketSize; charsToWrite += charsRead) { try { - charsRead = reader.read(streamCharBuffer, charsToWrite, streamCharBuffer.length - charsToWrite); + charsRead = reader.read(streamCharBuffer, charsToWrite, currentPacketSize - charsToWrite); } catch (IOException e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream")); Object[] msgArgs = {e.toString()}; @@ -4040,7 +4043,7 @@ void writeNonUnicodeReader(Reader reader, long advertisedLength, boolean isDestB break; // Check for invalid bytesRead returned from Reader.read - if (charsRead < 0 || charsRead > streamCharBuffer.length - charsToWrite) { + if (charsRead < 0 || charsRead > currentPacketSize - charsToWrite) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream")); Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")}; error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET); @@ -4070,7 +4073,7 @@ void writeNonUnicodeReader(Reader reader, long advertisedLength, boolean isDestB if (0 != charsToWrite) bytesToWrite = charsToWrite / 2; - streamString = new String(streamCharBuffer); + streamString = new String(streamCharBuffer, 0, currentPacketSize); byte[] bytes = ParameterUtils.HexToBin(streamString.trim()); writeInt(bytesToWrite); writeBytes(bytes, 0, bytesToWrite); @@ -4096,8 +4099,6 @@ void writeReader(Reader reader, long advertisedLength, boolean writeChunkSizes) assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0; long actualLength = 0; - char[] streamCharBuffer = new char[2 * currentPacketSize]; - byte[] streamByteBuffer = new byte[4 * currentPacketSize]; int charsRead = 0; int charsToWrite; do { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultTokenCredential.java b/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultTokenCredential.java index 06c09be46..3bedc589e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultTokenCredential.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultTokenCredential.java @@ -29,6 +29,8 @@ */ @Immutable class KeyVaultTokenCredential implements TokenCredential { + private static final String NULL_VALUE = "R_NullValue"; + private final String clientId; private final String clientSecret; private final SQLServerKeyVaultAuthenticationCallback authenticationCallback; @@ -48,13 +50,13 @@ class KeyVaultTokenCredential implements TokenCredential { */ KeyVaultTokenCredential(String clientId, String clientSecret) throws SQLServerException { if (null == clientId || clientId.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client ID"}; throw new SQLServerException(form.format(msgArgs1), null); } if (null == clientSecret || clientSecret.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client Secret"}; throw new SQLServerException(form.format(msgArgs1), null); } @@ -116,19 +118,19 @@ KeyVaultTokenCredential setAuthorization(String authorization) { */ private ConfidentialClientApplication getConfidentialClientApplication() { if (null == clientId) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client ID"}; throw new IllegalArgumentException(form.format(msgArgs1), null); } if (null == authorization) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Authorization"}; throw new IllegalArgumentException(form.format(msgArgs1), null); } if (null == clientSecret) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client Secret"}; throw new IllegalArgumentException(form.format(msgArgs1), null); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java index 207f24bd9..11edf4db3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java @@ -7,13 +7,13 @@ final class SQLJdbcVersion { static final int major = 9; - static final int minor = 1; - static final int patch = 1; + static final int minor = 2; + static final int patch = 0; static final int build = 0; /* * Used to load mssql-jdbc_auth DLL. * 1. Set to "-preview" for preview release. * 2. Set to "" (empty String) for official release. */ - static final String releaseExt = "-preview"; + static final String releaseExt = ""; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 53f6c27c0..13d14465b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -369,7 +369,7 @@ public void addColumnMapping(int sourceColumn, String destinationColumn) throws } else if (null == destinationColumn || destinationColumn.isEmpty()) { throwInvalidArgument("destinationColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn.trim())); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -396,7 +396,7 @@ public void addColumnMapping(String sourceColumn, int destinationColumn) throws } else if (null == sourceColumn || sourceColumn.isEmpty()) { throwInvalidArgument("sourceColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn)); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -422,7 +422,7 @@ public void addColumnMapping(String sourceColumn, String destinationColumn) thro } else if (null == destinationColumn || destinationColumn.isEmpty()) { throwInvalidArgument("destinationColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn.trim())); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -929,11 +929,10 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i case java.sql.Types.NUMERIC: case java.sql.Types.DECIMAL: /* - * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, but + * Azure DW only accepts money types for money column. To make the code compatible against both SQL + * Server and Azure DW, always send decimal and numeric as money/smallmoney if the destination column is + * money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { tdsWriter.writeByte(TDSType.MONEYN.byteValue()); @@ -944,7 +943,8 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i tdsWriter.writeByte((byte) 4); break; } - byte byteType = (java.sql.Types.DECIMAL == srcJdbcType) ? TDSType.DECIMALN.byteValue() : TDSType.NUMERICN.byteValue(); + byte byteType = (java.sql.Types.DECIMAL == srcJdbcType) ? TDSType.DECIMALN.byteValue() + : TDSType.NUMERICN.byteValue(); tdsWriter.writeByte(byteType); tdsWriter.writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length tdsWriter.writeByte((byte) srcPrecision); // unsigned byte @@ -1296,11 +1296,10 @@ private String getDestTypeFromSrcType(int srcColIndx, int destColIndx, return "smallmoney"; case java.sql.Types.DECIMAL: /* - * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, but + * Azure DW only accepts money types for money column. To make the code compatible against both SQL + * Server and Azure DW, always send decimal and numeric as money/smallmoney if the destination column is + * money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { return "money"; @@ -1842,8 +1841,7 @@ private void validateColumnMappings() throws SQLServerException { Set columnOrdinals = serverBulkData.getColumnOrdinals(); Iterator columnsIterator = columnOrdinals.iterator(); int j = 1; - outerWhileLoop: - while (columnsIterator.hasNext()) { + outerWhileLoop : while (columnsIterator.hasNext()) { int currentOrdinal = columnsIterator.next(); if (j != currentOrdinal) { /* @@ -2067,8 +2065,7 @@ else if (null != sourceCryptoMeta) { } else if (null != serverBulkData && connection.getSendTemporalDataTypesAsStringForBulkCopy()) { /* * Bulk copy from CSV and destination is not encrypted. In this case, we send the temporal types as varchar - * and - * SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as + * and SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as * varchar. */ switch (bulkJdbcType) { @@ -2219,10 +2216,9 @@ else if (null != sourceCryptoMeta) { } /* * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * but Azure DW only accepts money types for money column. To make the code compatible against + * both SQL Server and Azure DW, always send decimal and numeric as money/smallmoney if the + * destination column is money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { tdsWriter.writeMoney((BigDecimal) colValue, microsoft.sql.Types.MONEY); @@ -2490,14 +2486,12 @@ else if (4 >= bulkScale) tdsWriter.writeByte((byte) 0x05); if (colValue instanceof String) { /* - * if colValue is an instance of String, this means the data is coming from a CSV file. - * Time string is expected to come in with this pattern: hh:mm:ss[.nnnnnnn] - * First, look for the '.' character to determine if the String has the optional nanoseconds - * component. - * Next, create a java.sql.Time instance with the hh:mm:ss part we extracted, then set that - * time as the timestamp's time. - * Then, add the nanoseconds (optional, 0 if not provided) to the timestamp value. - * Finally, provide the timestamp value to writeTime method. + * if colValue is an instance of String, this means the data is coming from a CSV file. Time + * string is expected to come in with this pattern: hh:mm:ss[.nnnnnnn] First, look for the + * '.' character to determine if the String has the optional nanoseconds component. Next, + * create a java.sql.Time instance with the hh:mm:ss part we extracted, then set that time + * as the timestamp's time. Then, add the nanoseconds (optional, 0 if not provided) to the + * timestamp value. Finally, provide the timestamp value to writeTime method. */ java.sql.Timestamp ts = new java.sql.Timestamp(0); int nanos = 0; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java index d5f0f237a..3b31812ac 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java @@ -58,6 +58,8 @@ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerCol private static final int KEY_NAME_INDEX = 4; private static final int KEY_URL_SPLIT_LENGTH_WITH_VERSION = 6; private static final String KEY_URL_DELIMITER = "/"; + private static final String NULL_VALUE = "R_NullValue"; + private HttpPipeline keyVaultPipeline; private KeyVaultTokenCredential keyVaultTokenCredential; @@ -102,12 +104,12 @@ public String getName() { */ public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, String clientKey) throws SQLServerException { if (null == clientId || clientId.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client ID"}; throw new SQLServerException(form.format(msgArgs1), null); } if (null == clientKey || clientKey.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client Key"}; throw new SQLServerException(form.format(msgArgs1), null); } @@ -141,7 +143,7 @@ public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, String cl */ SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId) throws SQLServerException { if (null == clientId || clientId.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Client ID"}; throw new SQLServerException(form.format(msgArgs1), null); } @@ -160,7 +162,7 @@ public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, String cl */ public SQLServerColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential) throws SQLServerException { if (null == tokenCredential) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Token Credential"}; throw new SQLServerException(form.format(msgArgs1), null); } @@ -183,7 +185,7 @@ public SQLServerColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCrede public SQLServerColumnEncryptionAzureKeyVaultProvider( SQLServerKeyVaultAuthenticationCallback authenticationCallback) throws SQLServerException { if (null == authenticationCallback) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"SQLServerKeyVaultAuthenticationCallback"}; throw new SQLServerException(form.format(msgArgs1), null); } @@ -202,7 +204,7 @@ public SQLServerColumnEncryptionAzureKeyVaultProvider( */ private void setCredential(TokenCredential credential) throws SQLServerException { if (null == credential) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + MessageFormat form = new MessageFormat(SQLServerException.getErrString(NULL_VALUE)); Object[] msgArgs1 = {"Credential"}; throw new SQLServerException(form.format(msgArgs1), null); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 0b65ff8a5..cca90a0a4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -2366,6 +2366,10 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu + " Timeout Unit Interval: " + timeoutUnitInterval); } + // Returns false if authenticationString is null + boolean isInteractive = SqlAuthentication.ActiveDirectoryInteractive.toString() + .equalsIgnoreCase(authenticationString); + // Initialize loop variables int attemptNumber = 0; @@ -2477,33 +2481,21 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu } else break; // leave the while loop -- we've successfully connected } catch (SQLServerException sqlex) { - if ((SQLServerException.LOGON_FAILED == sqlex.getErrorCode()) // actual logon failed, i.e. bad password - || (SQLServerException.PASSWORD_EXPIRED == sqlex.getErrorCode()) // actual logon failed, i.e. - // password isExpired - || (SQLServerException.USER_ACCOUNT_LOCKED == sqlex.getErrorCode()) // actual logon failed, i.e. - // user account locked - || (SQLServerException.DRIVER_ERROR_INVALID_TDS == sqlex.getDriverErrorCode()) // invalid TDS - // received from - // server - || (SQLServerException.DRIVER_ERROR_SSL_FAILED == sqlex.getDriverErrorCode()) // failure - // negotiating SSL - || (SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED == sqlex.getDriverErrorCode()) // failure - // TLS1.2 - || (SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG == sqlex.getDriverErrorCode()) // unsupported - // configuration - // (e.g. - // Sphinx, - // invalid - // packet - // size, - // etc.) - || (SQLServerException.ERROR_SOCKET_TIMEOUT == sqlex.getDriverErrorCode()) // socket timeout - // ocurred - || timerHasExpired(timerExpire)// no more time to try again - || (state.equals(State.Connected) && !isDBMirroring) - // for non-dbmirroring cases, do not retry after tcp socket connection succeeds - ) { - + int errorCode = sqlex.getErrorCode(); + int driverErrorCode = sqlex.getDriverErrorCode(); + if (SQLServerException.LOGON_FAILED == errorCode // logon failed, ie bad password + || SQLServerException.PASSWORD_EXPIRED == errorCode // password expired + || SQLServerException.USER_ACCOUNT_LOCKED == errorCode // user account locked + || SQLServerException.DRIVER_ERROR_INVALID_TDS == driverErrorCode // invalid TDS + || SQLServerException.DRIVER_ERROR_SSL_FAILED == driverErrorCode // SSL failure + || SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED == driverErrorCode // TLS1.2 failure + || SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG == driverErrorCode // unsupported config + // (eg Sphinx, invalid + // packetsize, etc) + || SQLServerException.ERROR_SOCKET_TIMEOUT == driverErrorCode // socket timeout + || (timerHasExpired(timerExpire) && !isInteractive) // no time to try again and not interactive + // for non-dbmirroring cases, do not retry after tcp socket connection succeeds + || (state.equals(State.Connected) && !isDBMirroring)) { // close the connection and throw the error back close(); throw sqlex; @@ -2521,7 +2513,7 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu // Check sleep interval to make sure we won't exceed the timeout // Do this in the catch block so we can re-throw the current exception long remainingMilliseconds = timerRemaining(timerExpire); - if (remainingMilliseconds <= sleepInterval) { + if (remainingMilliseconds <= sleepInterval && !isInteractive) { throw sqlex; } } @@ -2552,6 +2544,14 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * (attemptNumber + 1)); } else if (isDBMirroring) { intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * ((attemptNumber / 2) + 1)); + } else if (isInteractive) { + // Interactive auth may involve MFA which will take longer and timeout. Reset timeout and retry silently + timerStart = System.currentTimeMillis(); + timeout = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue(); + timerTimeout = timeout * 1000L; // ConnectTimeout is in seconds, we need timer millis + timerExpire = timerStart + timerTimeout; + intervalExpire = timerStart + timeoutUnitInterval; + intervalExpireFullTimeout = timerStart + timerTimeout; } else if (useTnir) { long timeSlice = timeoutUnitInterval * (1 << attemptNumber); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java index b1bd17ebb..2f7933e93 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java @@ -40,8 +40,9 @@ class SQLServerMSAL4JUtils { static final String REDIRECTURI = "http://localhost"; + private static final String SLASH_DEFAULT = "/.default"; - static final private java.util.logging.Logger logger = java.util.logging.Logger + private static final java.util.logging.Logger logger = java.util.logging.Logger .getLogger("com.microsoft.sqlserver.jdbc.SQLServerMSAL4JUtils"); static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, String user, String password, @@ -53,7 +54,7 @@ static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, String use .builder(ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID).executorService(executorService) .authority(fedAuthInfo.stsurl).build(); final CompletableFuture future = pca.acquireToken(UserNamePasswordParameters - .builder(Collections.singleton(fedAuthInfo.spn + "/.default"), user, password.toCharArray()) + .builder(Collections.singleton(fedAuthInfo.spn + SLASH_DEFAULT), user, password.toCharArray()) .build()); final IAuthenticationResult authenticationResult = future.get(); @@ -71,10 +72,10 @@ static SqlFedAuthToken getSqlFedAuthTokenPrincipal(SqlFedAuthInfo fedAuthInfo, S String aadPrincipalSecret, String authenticationString) throws SQLServerException { ExecutorService executorService = Executors.newSingleThreadExecutor(); try { - String defaultScopeSuffix = "/.default"; + String defaultScopeSuffix = SLASH_DEFAULT; String scope = fedAuthInfo.spn.endsWith(defaultScopeSuffix) ? fedAuthInfo.spn : fedAuthInfo.spn + defaultScopeSuffix; - Set scopes = new HashSet(); + Set scopes = new HashSet<>(); scopes.add(scope); IClientCredential credential = ClientCredentialFactory.createFromSecret(aadPrincipalSecret); ConfidentialClientApplication clientApplication = ConfidentialClientApplication @@ -114,7 +115,7 @@ static SqlFedAuthToken getSqlFedAuthTokenIntegrated(SqlFedAuthInfo fedAuthInfo, .authority(fedAuthInfo.stsurl).build(); final CompletableFuture future = pca .acquireToken(IntegratedWindowsAuthenticationParameters - .builder(Collections.singleton(fedAuthInfo.spn + "/.default"), user).build()); + .builder(Collections.singleton(fedAuthInfo.spn + SLASH_DEFAULT), user).build()); final IAuthenticationResult authenticationResult = future.get(); return new SqlFedAuthToken(authenticationResult.accessToken(), authenticationResult.expiresOnDate()); @@ -135,7 +136,7 @@ static SqlFedAuthToken getSqlFedAuthTokenInteractive(SqlFedAuthInfo fedAuthInfo, PublicClientApplication pca = PublicClientApplication .builder(ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID).executorService(executorService) .setTokenCacheAccessAspect(PersistentTokenCacheAccessAspect.getInstance()) - .authority(fedAuthInfo.stsurl).logPii((logger.isLoggable(Level.FINE)) ? true : false).build(); + .authority(fedAuthInfo.stsurl).logPii((logger.isLoggable(Level.FINE))).build(); CompletableFuture future = null; IAuthenticationResult authenticationResult = null; @@ -150,7 +151,7 @@ static SqlFedAuthToken getSqlFedAuthTokenInteractive(SqlFedAuthInfo fedAuthInfo, logger.fine(logger.toString() + "Silent authentication for user:" + user); } SilentParameters silentParameters = SilentParameters - .builder(Collections.singleton(fedAuthInfo.spn + "/.default"), account).build(); + .builder(Collections.singleton(fedAuthInfo.spn + SLASH_DEFAULT), account).build(); future = pca.acquireTokenSilently(silentParameters); } @@ -169,7 +170,7 @@ static SqlFedAuthToken getSqlFedAuthTokenInteractive(SqlFedAuthInfo fedAuthInfo, InteractiveRequestParameters parameters = InteractiveRequestParameters.builder(new URI(REDIRECTURI)) .systemBrowserOptions(SystemBrowserOptions.builder() .htmlMessageSuccess(SQLServerResource.getResource("R_MSALAuthComplete")).build()) - .loginHint(user).scopes(Collections.singleton(fedAuthInfo.spn + "/.default")).build(); + .loginHint(user).scopes(Collections.singleton(fedAuthInfo.spn + SLASH_DEFAULT)).build(); future = pca.acquireToken(parameters); authenticationResult = future.get(); diff --git a/src/samples/adaptive/pom.xml b/src/samples/adaptive/pom.xml index dc9450b10..c998e5e49 100644 --- a/src/samples/adaptive/pom.xml +++ b/src/samples/adaptive/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -74,14 +74,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/alwaysencrypted/pom.xml b/src/samples/alwaysencrypted/pom.xml index aa764957d..26bb8ddf7 100644 --- a/src/samples/alwaysencrypted/pom.xml +++ b/src/samples/alwaysencrypted/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -42,14 +42,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/azureactivedirectoryauthentication/pom.xml b/src/samples/azureactivedirectoryauthentication/pom.xml index c51324ef6..d25a98884 100644 --- a/src/samples/azureactivedirectoryauthentication/pom.xml +++ b/src/samples/azureactivedirectoryauthentication/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -41,14 +41,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/connections/pom.xml b/src/samples/connections/pom.xml index e80665f3a..f9191e558 100644 --- a/src/samples/connections/pom.xml +++ b/src/samples/connections/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -57,14 +57,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/constrained/pom.xml b/src/samples/constrained/pom.xml index 2c374ec1c..d810c540d 100644 --- a/src/samples/constrained/pom.xml +++ b/src/samples/constrained/pom.xml @@ -16,7 +16,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -44,14 +44,14 @@ maven-compiler-plugin 3.6.0 - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.1.0 diff --git a/src/samples/dataclassification/pom.xml b/src/samples/dataclassification/pom.xml index 5ca668006..00c34bff0 100644 --- a/src/samples/dataclassification/pom.xml +++ b/src/samples/dataclassification/pom.xml @@ -16,7 +16,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -44,14 +44,14 @@ maven-compiler-plugin 3.6.0 - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.1.0 diff --git a/src/samples/datatypes/pom.xml b/src/samples/datatypes/pom.xml index f364cbec3..e19ee7753 100644 --- a/src/samples/datatypes/pom.xml +++ b/src/samples/datatypes/pom.xml @@ -15,7 +15,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -74,14 +74,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/resultsets/pom.xml b/src/samples/resultsets/pom.xml index a2960b72e..b53bb5ace 100644 --- a/src/samples/resultsets/pom.xml +++ b/src/samples/resultsets/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -73,14 +73,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/samples/sparse/pom.xml b/src/samples/sparse/pom.xml index 4d557cf05..d10d2dc7e 100644 --- a/src/samples/sparse/pom.xml +++ b/src/samples/sparse/pom.xml @@ -14,7 +14,7 @@ com.microsoft.sqlserver mssql-jdbc - 7.0.0.jre10 + 9.2.0.jre15 @@ -41,14 +41,14 @@ org.apache.maven.plugins maven-compiler-plugin - 10 - 10 + 15 + 15 org.apache.maven.plugins maven-resources-plugin - 2.5 + 3.1.0 diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferTest.java index 594e4f709..5fed5e24b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferTest.java @@ -7,11 +7,14 @@ import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Constants; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -104,6 +107,7 @@ static void teardownTestTable() throws SQLException { * @param concurrencyMode * type of ResultSet's concurrency provided by source method */ + @Tag(Constants.xAzureSQLDW) @ParameterizedTest( name = "[{index}] maxResultBuffer = {0}, responseBuffering = {1}, resultSetType = {2}, concurrencyMode = {3}") @MethodSource("linearResultSetData") diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index 6d131ca2e..f0198d85b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -195,5 +195,5 @@ protected Object[][] getContents() { {"R_socketClosed", "Socket closed"}, {"R_aeStreamReadError", "The multi-part identifier"}, {"R_dataClassificationNotSupported", "Data Classification is not supported on this server."}, {"R_maxResultBufferExceeded", "MaxResultBuffer exceeded {0}."}, - {"R_noAuthorizationCode", "No Authorization code was returned from the server"}}; + {"R_databaseNotFound", "Database {0} not found."}}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java index b293b5a87..054c08e77 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java @@ -840,6 +840,25 @@ public static String removeProperty(String connectionString, String property) { return connectionString.replace(propertyStr, ""); } + /** + * Get the given connection property in the connection string + * + * @param connectionString + * connection string + * @param property + * name of the property + * @return The the value of the connection property or null if not found + */ + public static String getProperty(String connectionString, String property) { + int start = connectionString.indexOf(property); + if (-1 == start) { + return null; + } + start = connectionString.indexOf("=", start) + 1; + int end = connectionString.indexOf(";", start); + return connectionString.substring(start, -1 != end ? end : connectionString.length()); + } + /** * Creates a truststore and returns the path of it. * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java index 1ec4de537..493eff751 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java @@ -26,6 +26,7 @@ import com.microsoft.sqlserver.jdbc.ComparisonUtil; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; +import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; @@ -377,6 +378,44 @@ public void testUnicodeCharToNchar() throws SQLException, ClassNotFoundException validateMapping("VARCHAR(5)", "NVARCHAR(max)", "фщыab"); } + @Tag(Constants.xAzureSQLDW) + @Test + @DisplayName("BulkCopy:test column name with trailing space") + public void testTrailingSpace() throws SQLException, ClassNotFoundException { + String sourceTable = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("sourceTable"))); + String destTable = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("destTable"))); + + try (Connection conn = DriverManager.getConnection(connectionString);) { + try (Statement sourceStmt = conn.createStatement(); Statement destStmt = conn.createStatement(); + SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) { + + sourceStmt.executeUpdate("CREATE TABLE " + sourceTable + " ([colTrailingSpace ] char(5) null);"); + sourceStmt.executeUpdate("INSERT INTO " + sourceTable + " VALUES('" + "data" + "');"); + destStmt.executeUpdate("CREATE TABLE " + destTable + " ([colTrailingSpace ] char(5) null);"); + ResultSet sourceRs = sourceStmt.executeQuery("SELECT * FROM " + sourceTable); + + ResultSet destRs = destStmt.executeQuery("SELECT * FROM " + destTable); + destRs.next(); + + ResultSetMetaData rsmd = sourceRs.getMetaData(); + String name = rsmd.getColumnName(1); + bulkCopy.addColumnMapping(name, name); + + bulkCopy.setDestinationTableName(destTable); + bulkCopy.writeToServer(sourceRs); + } catch (Exception e) { + fail(e.getMessage()); + } finally { + try (Statement stmt = conn.createStatement();) { + TestUtils.dropTableIfExists(destTable, stmt); + TestUtils.dropTableIfExists(sourceTable, stmt); + } + } + } + } + /** * Validate if same values are in both source and destination table taking into account 1 extra column in * destination which should be a copy of first column of source. 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 a16fc8152..644087b87 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -317,19 +317,41 @@ public void testDBTables() throws SQLException { try (Connection con = getConnection()) { DatabaseMetaData databaseMetaData = con.getMetaData(); try (ResultSet rsCatalog = databaseMetaData.getCatalogs()) { - - MessageFormat form1 = new MessageFormat(TestResource.getResource("R_atLeastOneFound")); Object[] msgArgs1 = {"catalog"}; - assertTrue(rsCatalog.next(), form1.format(msgArgs1)); + assertTrue(rsCatalog.next(), + (new MessageFormat(TestResource.getResource("R_atLeastOneFound"))).format(msgArgs1)); + String dbNameFromCatalog = rsCatalog.getString("TABLE_CAT"); String[] types = {"TABLE"}; - try (ResultSet rs = databaseMetaData.getTables(rsCatalog.getString("TABLE_CAT"), null, "%", types)) { - - MessageFormat form2 = new MessageFormat(TestResource.getResource("R_nameEmpty")); - Object[] msgArgs2 = {"Table"}; - while (rs.next()) { - assertTrue(!StringUtils.isEmpty(rs.getString("TABLE_NAME")), form2.format(msgArgs2)); + Object[] msgArgs2 = {"Table"}; + String dbNameFromConnectionString = TestUtils.getProperty(connectionString, "databaseName"); + if (null == dbNameFromConnectionString || (null != dbNameFromConnectionString + && dbNameFromConnectionString.equals(dbNameFromCatalog))) { + try (ResultSet rs = databaseMetaData.getTables(dbNameFromCatalog, null, "%", types)) { + while (rs.next()) { + assertTrue(!StringUtils.isEmpty(rs.getString("TABLE_NAME")), + (new MessageFormat(TestResource.getResource("R_nameEmpty"))).format(msgArgs2)); + } } + } else { + // try to find the databaseName specified + while (rsCatalog.next()) { + dbNameFromCatalog = rsCatalog.getString("TABLE_CAT"); + if (null != dbNameFromCatalog && !dbNameFromCatalog.isEmpty() + && dbNameFromConnectionString.equals(dbNameFromCatalog)) { + try (ResultSet rs = databaseMetaData.getTables(dbNameFromCatalog, null, "%", types)) { + while (rs.next()) { + assertTrue(!StringUtils.isEmpty(rs.getString("TABLE_NAME")), + (new MessageFormat(TestResource.getResource("R_nameEmpty"))) + .format(msgArgs2)); + } + return; + } + } + } + + Object[] msgArgs3 = {dbNameFromConnectionString}; + fail((new MessageFormat(TestResource.getResource("R_databaseNotFound"))).format(msgArgs3)); } } } 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 ce92d6916..0f39c17f7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java @@ -17,10 +17,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; -import com.microsoft.sqlserver.jdbc.SQLServerConnectionTest; import com.microsoft.sqlserver.jdbc.SQLServerDataSource; import com.microsoft.sqlserver.jdbc.SQLServerException; -import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.testframework.Constants; @@ -837,11 +835,8 @@ public void testInteractiveAuthTimeout() throws SQLException { fail(EXPECTED_EXCEPTION_NOT_THROWN); } assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage() + "," + e.getCause(), - e.getMessage() - .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName - + " in Active Directory (Authentication=ActiveDirectoryInteractive).") - && (isWindows ? e.getCause().getMessage() - .contains(TestResource.getResource("R_noAuthorizationCode")) : true)); + e.getMessage().contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + + " in Active Directory (Authentication=ActiveDirectoryInteractive).")); } } }