From d4630794c728e3e823d2054e272bbc91e05ab826 Mon Sep 17 00:00:00 2001 From: David Engel Date: Wed, 14 Apr 2021 14:59:18 -0700 Subject: [PATCH 1/3] Adding replication connection property --- .../com/microsoft/sqlserver/jdbc/IOBuffer.java | 3 ++- .../sqlserver/jdbc/ISQLServerDataSource.java | 15 +++++++++++++++ .../sqlserver/jdbc/SQLServerConnection.java | 11 +++++++++++ .../sqlserver/jdbc/SQLServerDataSource.java | 12 ++++++++++++ .../microsoft/sqlserver/jdbc/SQLServerDriver.java | 4 ++++ .../sqlserver/jdbc/SQLServerResource.java | 2 ++ .../sqlserver/jdbc/SQLServerConnectionTest.java | 3 +++ 7 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 9ad025677..f29922965 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -340,7 +340,8 @@ static final String getTokenName(int tdsTokenType) { static final byte LOGIN_OPTION2_USER_NORMAL = 0x00; static final byte LOGIN_OPTION2_USER_SERVER = 0x10; static final byte LOGIN_OPTION2_USER_REMUSER = 0x20; - static final byte LOGIN_OPTION2_USER_SQLREPL = 0x30; + static final byte LOGIN_OPTION2_USER_SQLREPL_OFF = 0x00; + static final byte LOGIN_OPTION2_USER_SQLREPL_ON = 0x30; static final byte LOGIN_OPTION2_INTEGRATED_SECURITY_OFF = 0x00; static final byte LOGIN_OPTION2_INTEGRATED_SECURITY_ON = (byte) 0x80; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index 1308a5cd8..6c74e6279 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -284,6 +284,21 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ String getResponseBuffering(); + /** + * Sets the value to enable/disable the replication connection property. + * + * @param replication + * A Boolean value. When true, tells the server that the connection is used for replication. + */ + void setReplication(boolean replication); + + /** + * Returns the value of the replication connection property. + * + * @return true if the connection is to be used for replication. Otherwise false. + */ + boolean getReplication(); + /** * Sets the value to enable/disable the sendTimeAsDatetime connection property. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 5cc89e46c..2d5676bdc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -607,6 +607,7 @@ public void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert) { boolean userSetTNIR = true; + private boolean replication = SQLServerDriverBooleanProperty.REPLICATION.getDefaultValue(); private boolean sendTimeAsDatetime = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue(); private boolean useFmtOnly = SQLServerDriverBooleanProperty.USE_FMT_ONLY.getDefaultValue(); @@ -1803,6 +1804,15 @@ Connection connectInternal(Properties propsIn, applicationIntent = ApplicationIntent.valueOfString(sPropValue); activeConnectionProperties.setProperty(sPropKey, applicationIntent.toString()); + sPropKey = SQLServerDriverBooleanProperty.REPLICATION.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.REPLICATION.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } + + replication = isBooleanPropertyOn(sPropKey, sPropValue); + sPropKey = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null == sPropValue) { @@ -5218,6 +5228,7 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ // fails TDS.LOGIN_OPTION2_ODBC_ON | // Use ODBC defaults (ANSI_DEFAULTS ON, IMPLICIT_TRANSACTIONS OFF, TEXTSIZE // inf, ROWCOUNT inf) + (replication ? TDS.LOGIN_OPTION2_USER_SQLREPL_ON : TDS.LOGIN_OPTION2_USER_SQLREPL_OFF) | (integratedSecurity ? // integrated security if integratedSecurity requested TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_ON : TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_OFF))); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index fded50cfe..91fa788b9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -470,6 +470,18 @@ public String getApplicationIntent() { SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue()); } + @Override + public void setReplication(boolean replication) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.REPLICATION.toString(), + replication); + } + + @Override + public boolean getReplication() { + return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.REPLICATION.toString(), + SQLServerDriverBooleanProperty.REPLICATION.getDefaultValue()); + } + @Override public void setSendTimeAsDatetime(boolean sendTimeAsDatetime) { setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index d5d19566f..dd7faf0bc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -449,6 +449,7 @@ enum SQLServerDriverBooleanProperty { INTEGRATED_SECURITY("integratedSecurity", false), LAST_UPDATE_COUNT("lastUpdateCount", true), MULTI_SUBNET_FAILOVER("multiSubnetFailover", false), + REPLICATION("replication", false), SERVER_NAME_AS_ACE("serverNameAsACE", false), SEND_STRING_PARAMETERS_AS_UNICODE("sendStringParametersAsUnicode", true), SEND_TIME_AS_DATETIME("sendTimeAsDatetime", true), @@ -589,6 +590,9 @@ public final class SQLServerDriver implements java.sql.Driver { SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString(), SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.getDefaultValue(), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.REPLICATION.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.REPLICATION.getDefaultValue()), false, + TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(), Boolean.toString(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue()), false, TRUE_FALSE), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 4a29d5178..24db4ec26 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -245,6 +245,8 @@ protected Object[][] getContents() { "The optional argument to pass to the constructor specified by trustManagerClass."}, {"R_hostNameInCertificatePropertyDescription", "The host name to be used when validating the SQL Server Secure Sockets Layer (SSL) certificate."}, + {"R_replicationPropertyDescription", + "This setting tells the server if the connection is used for replication."}, {"R_sendTimeAsDatetimePropertyDescription", "Determines whether to use the SQL Server datetime data type to send java.sql.Time values to the database."}, {"R_TransparentNetworkIPResolutionPropertyDescription", diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index c700a708e..b9d30e7e1 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -170,6 +170,9 @@ public void testDataSource() { ds.setApplicationIntent(stringPropValue); assertEquals(stringPropValue, ds.getApplicationIntent(), TestResource.getResource("R_valuesAreDifferent")); + ds.setReplication(booleanPropValue); + assertEquals(booleanPropValue, ds.getReplication(), TestResource.getResource("R_valuesAreDifferent")); + ds.setSendTimeAsDatetime(booleanPropValue); assertEquals(booleanPropValue, ds.getSendTimeAsDatetime(), TestResource.getResource("R_valuesAreDifferent")); From c91eaf8b88e02a74bbdc91ead6f5b2a6c346b14a Mon Sep 17 00:00:00 2001 From: David Engel Date: Wed, 14 Apr 2021 20:30:29 -0700 Subject: [PATCH 2/3] Adding test --- .../jdbc/bulkCopy/BulkCopyCSVTest.java | 2 + .../jdbc/connection/ReplicationTest.java | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java 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 d9eaa1c9a..e8bebcb95 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java @@ -192,6 +192,8 @@ public void testEscapeColumnDelimitersCSV() throws Exception { i++; } } + + stmt.execute("DROP TABLE " + tableName); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java new file mode 100644 index 000000000..8d4db0e8e --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java @@ -0,0 +1,89 @@ +/* + * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made + * available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.RandomUtil; +import com.microsoft.sqlserver.jdbc.SQLServerDataSource; +import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Constants; +import com.microsoft.sqlserver.testframework.DBTable; + + +/* + * This test is for testing the replication connection property + */ +@RunWith(JUnitPlatform.class) +@Tag(Constants.xAzureSQLDW) +public class ReplicationTest extends AbstractTest { + + @Test + public void testReplication() throws SQLException { + String tableName = RandomUtil.getIdentifier("repl"); + String triggerName = RandomUtil.getIdentifier("trig"); + String escapedTableName = DBTable.escapeIdentifier(tableName); + String escapedTriggerName = DBTable.escapeIdentifier(triggerName); + SQLServerDataSource ds = new SQLServerDataSource(); + ds.setURL(connectionString); + ds.setReplication(false); + + String sqlDropTable = "IF OBJECT_ID('" + TestUtils.escapeSingleQuotes(escapedTableName) + "', 'U') IS NOT NULL " + + " DROP TABLE " + escapedTableName + ";"; + String sqlCreateTable = "CREATE TABLE " + escapedTableName + " ([TestReplication] [varchar](50) NULL)"; + String sqlCreateTrigger = "CREATE TRIGGER " + escapedTriggerName + " ON " + escapedTableName + " " + + "INSTEAD OF INSERT NOT FOR REPLICATION AS " + + "BEGIN " + + " INSERT INTO " + escapedTableName + " (TestReplication) " + + " SELECT TestReplication + ' - REPLICATION IS OFF' " + + " FROM INSERTED " + + "END"; + String sqlInsert = "INSERT INTO " + escapedTableName + " (TestReplication) values ('Replication test')"; + String sqlDelete = "DELETE FROM " + escapedTableName; + String sqlSelect = "SELECT TestReplication FROM " + escapedTableName; + + try (Connection con = ds.getConnection(); Statement stmt = con.createStatement()) { + // drop + stmt.execute(sqlDropTable); + // create + stmt.execute(sqlCreateTable); + stmt.execute(sqlCreateTrigger); + stmt.executeUpdate(sqlInsert); + try (ResultSet rs = stmt.executeQuery(sqlSelect)) { + if (rs.next()) { + assertEquals(rs.getString(1), "Replication test - REPLICATION IS OFF"); + } else { + assertTrue(false, "Expected row of data was not found."); + } + } + stmt.execute(sqlDelete); + } + + ds.setReplication(true); + try (Connection con = ds.getConnection(); Statement stmt = con.createStatement()) { + stmt.executeUpdate(sqlInsert); + try (ResultSet rs = stmt.executeQuery(sqlSelect)) { + if (rs.next()) { + assertEquals(rs.getString(1), "Replication test"); + } else { + assertTrue(false, "Expected row of data was not found."); + } + } + stmt.execute(sqlDropTable); + } + } +} From ab17676acd9632eb436f289066c0a1a06569fab7 Mon Sep 17 00:00:00 2001 From: David Engel Date: Thu, 15 Apr 2021 14:10:04 -0700 Subject: [PATCH 3/3] Address feedback --- .../sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java | 4 ++-- .../jdbc/connection/ReplicationTest.java | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) 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 e8bebcb95..550b30a65 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java @@ -192,8 +192,8 @@ public void testEscapeColumnDelimitersCSV() throws Exception { i++; } } - - stmt.execute("DROP TABLE " + tableName); + + TestUtils.dropTableIfExists(tableName, stmt); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java index 8d4db0e8e..4380597d3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java @@ -20,10 +20,9 @@ import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerDataSource; import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; -import com.microsoft.sqlserver.testframework.DBTable; - /* * This test is for testing the replication connection property @@ -36,14 +35,12 @@ public class ReplicationTest extends AbstractTest { public void testReplication() throws SQLException { String tableName = RandomUtil.getIdentifier("repl"); String triggerName = RandomUtil.getIdentifier("trig"); - String escapedTableName = DBTable.escapeIdentifier(tableName); - String escapedTriggerName = DBTable.escapeIdentifier(triggerName); + String escapedTableName = AbstractSQLGenerator.escapeIdentifier(tableName); + String escapedTriggerName = AbstractSQLGenerator.escapeIdentifier(triggerName); SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setReplication(false); - String sqlDropTable = "IF OBJECT_ID('" + TestUtils.escapeSingleQuotes(escapedTableName) + "', 'U') IS NOT NULL " - + " DROP TABLE " + escapedTableName + ";"; String sqlCreateTable = "CREATE TABLE " + escapedTableName + " ([TestReplication] [varchar](50) NULL)"; String sqlCreateTrigger = "CREATE TRIGGER " + escapedTriggerName + " ON " + escapedTableName + " " + "INSTEAD OF INSERT NOT FOR REPLICATION AS " @@ -53,12 +50,11 @@ public void testReplication() throws SQLException { + " FROM INSERTED " + "END"; String sqlInsert = "INSERT INTO " + escapedTableName + " (TestReplication) values ('Replication test')"; - String sqlDelete = "DELETE FROM " + escapedTableName; String sqlSelect = "SELECT TestReplication FROM " + escapedTableName; try (Connection con = ds.getConnection(); Statement stmt = con.createStatement()) { // drop - stmt.execute(sqlDropTable); + TestUtils.dropTableIfExists(escapedTableName, stmt); // create stmt.execute(sqlCreateTable); stmt.execute(sqlCreateTrigger); @@ -70,7 +66,7 @@ public void testReplication() throws SQLException { assertTrue(false, "Expected row of data was not found."); } } - stmt.execute(sqlDelete); + TestUtils.clearTable(con, escapedTableName); } ds.setReplication(true); @@ -83,7 +79,7 @@ public void testReplication() throws SQLException { assertTrue(false, "Expected row of data was not found."); } } - stmt.execute(sqlDropTable); + TestUtils.dropTableIfExists(escapedTableName, stmt); } } }