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")); 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..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,6 +192,8 @@ public void testEscapeColumnDelimitersCSV() throws Exception { i++; } } + + 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 new file mode 100644 index 000000000..4380597d3 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java @@ -0,0 +1,85 @@ +/* + * 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.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Constants; + +/* + * 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 = AbstractSQLGenerator.escapeIdentifier(tableName); + String escapedTriggerName = AbstractSQLGenerator.escapeIdentifier(triggerName); + SQLServerDataSource ds = new SQLServerDataSource(); + ds.setURL(connectionString); + ds.setReplication(false); + + 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 sqlSelect = "SELECT TestReplication FROM " + escapedTableName; + + try (Connection con = ds.getConnection(); Statement stmt = con.createStatement()) { + // drop + TestUtils.dropTableIfExists(escapedTableName, stmt); + // 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."); + } + } + TestUtils.clearTable(con, escapedTableName); + } + + 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."); + } + } + TestUtils.dropTableIfExists(escapedTableName, stmt); + } + } +}