From 5b2cd71bf08bdca906faa8ef37519debeeb1c440 Mon Sep 17 00:00:00 2001 From: Mahendra Chavan Date: Fri, 24 Jan 2025 10:10:00 +0530 Subject: [PATCH] Incident#567732673 - Add provision to set SQLServerBulkCopy options in PreparedStatement (#2555) * Add provision to set SQLServerBulkCopy options in PreparedStatement * Reanmed test * Renamed bcCopyOptions to bcOptions * Fixed indentation * Fail the test that enables consraint checking when exception is not thrown * Added connection string options for all the available bulk copy options * Added test case for bulkCopyOptionDefaultsBatchSize, bulkCopyOptionDefaultsKeepIdentity and bulkCopyOptionDefaultsTableLock * Added test case for bulkCopty options in Prepared Statement * Updated failures in test case * Added test case testBulkCopyOptionDefaultsTimeoutLowerValue * Added test case for bulkCopyOptionDefaultsAllowEncryptedValueModifications * Fixed failures * Added test scenario for bulkCopyOptionDefaultsAllowEncryptedValueModifications * Improved messages. * Remove the setBulkCopyOptions method from PreparedStatement -Since the change has added connection string options for setting various bulk copy options, this non-standard API is not needed. * Fixed indent issue. * Update name for bulk copy batchSize option * Update name for bulk copy timeout option * Update name for bulk copy check constraints option * Update name for bulk copy fireTrigger, keepIdentity, keepNulls, tableLock, internalTrnsaction, encrptedValue options * Remove timeout option as configured already in bulk copy option * Updated value of bulkCopyForBatchInsertTimeout to 60s * Removed timeout and internal transaction option * removed local changes --------- Co-authored-by: muskan124947 Co-authored-by: Divang Sharma --- .../sqlserver/jdbc/ISQLServerConnection.java | 105 +++ .../sqlserver/jdbc/ISQLServerDataSource.java | 105 +++ .../jdbc/SQLServerBulkCopyOptions.java | 14 + .../sqlserver/jdbc/SQLServerConnection.java | 273 ++++++- .../jdbc/SQLServerConnectionPoolProxy.java | 139 ++++ .../sqlserver/jdbc/SQLServerDataSource.java | 90 +++ .../sqlserver/jdbc/SQLServerDriver.java | 26 +- .../jdbc/SQLServerPreparedStatement.java | 4 +- .../sqlserver/jdbc/SQLServerResource.java | 9 +- .../RequestBoundaryMethodsTest.java | 134 ++-- .../BatchExecutionWithBCOptionsTest.java | 683 ++++++++++++++++++ 11 files changed, 1533 insertions(+), 49 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBCOptionsTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 52f1b67e6..f8959a9b1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java @@ -547,4 +547,109 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * @return cacheBulkCopyMetadata boolean value */ boolean getcacheBulkCopyMetadata(); + + /** + * Specifies the default batch size for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertBatchSize + * integer value for bulkCopyForBatchInsertBatchSize. + */ + void setBulkCopyForBatchInsertBatchSize(int bulkCopyForBatchInsertBatchSize); + + /** + * Returns the default batch size for bulk copy operations created from batch insert operations. + * + * @return integer value for bulkCopyForBatchInsertBatchSize. + */ + int getBulkCopyForBatchInsertBatchSize(); + + /** + * Specifies the default check constraints for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertCheckConstraints + * boolean value for bulkCopyForBatchInsertCheckConstraints. + */ + void setBulkCopyForBatchInsertCheckConstraints(boolean bulkCopyForBatchInsertCheckConstraints); + + /** + * Returns the default check constraints for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertCheckConstraints. + */ + boolean getBulkCopyForBatchInsertCheckConstraints(); + + /** + * Specifies the default fire triggers for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertFireTriggers + * boolean value for bulkCopyForBatchInsertFireTriggers. + */ + void setBulkCopyForBatchInsertFireTriggers(boolean bulkCopyForBatchInsertFireTriggers); + + /** + * Returns the default fire triggers for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertFireTriggers. + */ + boolean getBulkCopyForBatchInsertFireTriggers(); + + /** + * Specifies the default keep identity for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertKeepIdentity + * boolean value for bulkCopyForBatchInsertKeepIdentity. + */ + void setBulkCopyForBatchInsertKeepIdentity(boolean bulkCopyForBatchInsertKeepIdentity); + + /** + * Returns the default keep identity for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertKeepIdentity. + */ + boolean getBulkCopyForBatchInsertKeepIdentity(); + + /** + * Specifies the default keep nulls for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertKeepNulls + * boolean value for bulkCopyForBatchInsertKeepNulls. + */ + void setBulkCopyForBatchInsertKeepNulls(boolean bulkCopyForBatchInsertKeepNulls); + + /** + * Returns the default keep nulls for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertKeepNulls. + */ + boolean getBulkCopyForBatchInsertKeepNulls(); + + /** + * Specifies the default table lock for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertTableLock + * boolean value for bulkCopyForBatchInsertTableLock. + */ + void setBulkCopyForBatchInsertTableLock(boolean bulkCopyForBatchInsertTableLock); + + /** + * Returns the default table lock for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertTableLock. + */ + boolean getBulkCopyForBatchInsertTableLock(); + + /** + * Specifies the default allow encrypted value modifications for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertAllowEncryptedValueModifications + * boolean value for bulkCopyForBatchInsertAllowEncryptedValueModifications. + */ + void setBulkCopyForBatchInsertAllowEncryptedValueModifications(boolean bulkCopyForBatchInsertAllowEncryptedValueModifications); + + /** + * Returns the default allow encrypted value modifications for bulk copy operations created from batch insert operations. + * + * @return boolean value for bulkCopyForBatchInsertAllowEncryptedValueModifications. + */ + boolean getBulkCopyForBatchInsertAllowEncryptedValueModifications(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index ec7067220..62fdeda8b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -980,6 +980,111 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert); + /** + * Sets the default batch size for bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertBatchSize + * the default batch size for bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertBatchSize(int bulkCopyForBatchInsertBatchSize); + + /** + * Returns the default batch size for bulk copy operations created from batch insert operations. + * + * @return the default batch size for bulk copy operations created from batch insert operations. + */ + int getBulkCopyForBatchInsertBatchSize(); + + /** + * Sets whether to check constraints during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertCheckConstraints + * indicates whether to check constraints during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertCheckConstraints(boolean bulkCopyForBatchInsertCheckConstraints); + + /** + * Returns whether to check constraints during bulk copy operations created from batch insert operations. + * + * @return whether to check constraints during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertCheckConstraints(); + + /** + * Sets whether to fire triggers during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertFireTriggers + * indicates whether to fire triggers during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertFireTriggers(boolean bulkCopyForBatchInsertFireTriggers); + + /** + * Returns whether to fire triggers during bulk copy operations created from batch insert operations. + * + * @return whether to fire triggers during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertFireTriggers(); + + /** + * Sets whether to keep identity values during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertKeepIdentity + * indicates whether to keep identity values during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertKeepIdentity(boolean bulkCopyForBatchInsertKeepIdentity); + + /** + * Returns whether to keep identity values during bulk copy operations created from batch insert operations. + * + * @return whether to keep identity values during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertKeepIdentity(); + + /** + * Sets whether to keep null values during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertKeepNulls + * indicates whether to keep null values during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertKeepNulls(boolean bulkCopyForBatchInsertKeepNulls); + + /** + * Returns whether to keep null values during bulk copy operations created from batch insert operations. + * + * @return whether to keep null values during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertKeepNulls(); + + /** + * Sets whether to use table lock during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertTableLock + * indicates whether to use table lock during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertTableLock(boolean bulkCopyForBatchInsertTableLock); + + /** + * Returns whether to use table lock during bulk copy operations created from batch insert operations. + * + * @return whether to use table lock during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertTableLock(); + + /** + * Sets whether to allow encrypted value modifications during bulk copy operations created from batch insert operations. + * + * @param bulkCopyForBatchInsertAllowEncryptedValueModifications + * indicates whether to allow encrypted value modifications during bulk copy operations created from batch insert operations. + */ + void setBulkCopyForBatchInsertAllowEncryptedValueModifications(boolean bulkCopyForBatchInsertAllowEncryptedValueModifications); + + /** + * Returns whether to allow encrypted value modifications during bulk copy operations created from batch insert operations. + * + * @return whether to allow encrypted value modifications during bulk copy operations created from batch insert operations. + */ + boolean getBulkCopyForBatchInsertAllowEncryptedValueModifications(); + /** * Sets the client id to be used to retrieve the access token for a user-assigned Managed Identity. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java index 24e145a26..7756add0e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java @@ -103,6 +103,20 @@ public SQLServerBulkCopyOptions() { useInternalTransaction = false; allowEncryptedValueModifications = false; } + + /** + * Constructs a SQLServerBulkCopySettings class using defaults from given connection + */ + SQLServerBulkCopyOptions(SQLServerConnection conn) { + batchSize = conn.getBulkCopyForBatchInsertBatchSize(); + checkConstraints = conn.getBulkCopyForBatchInsertCheckConstraints(); + fireTriggers = conn.getBulkCopyForBatchInsertFireTriggers(); + keepIdentity = conn.getBulkCopyForBatchInsertKeepIdentity(); + keepNulls = conn.getBulkCopyForBatchInsertKeepNulls(); + tableLock = conn.getBulkCopyForBatchInsertTableLock(); + allowEncryptedValueModifications = conn.getBulkCopyForBatchInsertAllowEncryptedValueModifications(); + } + /** * Returns the number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 5f076f9af..be4df3139 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -833,7 +833,7 @@ final int getSocketTimeoutMilliseconds() { * boolean value for deciding if the driver should use bulk copy API for batch inserts. */ private boolean useBulkCopyForBatchInsert; - + /** * Returns the useBulkCopyForBatchInsert value. * @@ -843,7 +843,7 @@ final int getSocketTimeoutMilliseconds() { public boolean getUseBulkCopyForBatchInsert() { return useBulkCopyForBatchInsert; } - + /** * Specifies the flag for using Bulk Copy API for batch insert operations. * @@ -855,6 +855,175 @@ public void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert) { this.useBulkCopyForBatchInsert = useBulkCopyForBatchInsert; } + /** + * The default batch size for bulk copy operations created from batch insert operations. + */ + private int bulkCopyForBatchInsertBatchSize = 0; + + /** + * Returns the bulkCopyForBatchInsertBatchSize value. + * + * @return the bulkCopyForBatchInsertBatchSize value. + */ + public int getBulkCopyForBatchInsertBatchSize() { + return bulkCopyForBatchInsertBatchSize; + } + + /** + * Sets the bulkCopyForBatchInsertBatchSize value. + * + * @param bulkCopyForBatchInsertBatchSize + * the bulkCopyForBatchInsertBatchSize value to set. + */ + public void setBulkCopyForBatchInsertBatchSize(int bulkCopyForBatchInsertBatchSize) { + this.bulkCopyForBatchInsertBatchSize = bulkCopyForBatchInsertBatchSize; + } + + /** + * Whether to check constraints during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertCheckConstraints = false; + + /** + * Returns the bulkCopyForBatchInsertCheckConstraints value. + * + * @return the bulkCopyForBatchInsertCheckConstraints value. + */ + public boolean getBulkCopyForBatchInsertCheckConstraints() { + return bulkCopyForBatchInsertCheckConstraints; + } + + /** + * Sets the bulkCopyForBatchInsertCheckConstraints value. + * + * @param bulkCopyForBatchInsertCheckConstraints + * the bulkCopyForBatchInsertCheckConstraints value to set. + */ + public void setBulkCopyForBatchInsertCheckConstraints(boolean bulkCopyForBatchInsertCheckConstraints) { + this.bulkCopyForBatchInsertCheckConstraints = bulkCopyForBatchInsertCheckConstraints; + } + + /** + * Returns the bulkCopyForBatchInsertFireTriggers value. + * + * @return the bulkCopyForBatchInsertFireTriggers value. + */ + public boolean getBulkCopyForBatchInsertFireTriggers() { + return bulkCopyForBatchInsertFireTriggers; + } + + /** + * Whether to fire triggers during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertFireTriggers = false; + + /** + * Sets the bulkCopyForBatchInsertFireTriggers value. + * + * @param bulkCopyForBatchInsertFireTriggers + * the bulkCopyForBatchInsertFireTriggers value to set. + */ + public void setBulkCopyForBatchInsertFireTriggers(boolean bulkCopyForBatchInsertFireTriggers) { + this.bulkCopyForBatchInsertFireTriggers = bulkCopyForBatchInsertFireTriggers; + } + + /** + * Whether to keep identity values during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertKeepIdentity = false; + + /** + * Returns the bulkCopyForBatchInsertKeepIdentity value. + * + * @return the bulkCopyForBatchInsertKeepIdentity value. + */ + public boolean getBulkCopyForBatchInsertKeepIdentity() { + return bulkCopyForBatchInsertKeepIdentity; + } + + /** + * Sets the bulkCopyForBatchInsertKeepIdentity value. + * + * @param bulkCopyForBatchInsertKeepIdentity + * the bulkCopyForBatchInsertKeepIdentity value to set. + */ + public void setBulkCopyForBatchInsertKeepIdentity(boolean bulkCopyForBatchInsertKeepIdentity) { + this.bulkCopyForBatchInsertKeepIdentity = bulkCopyForBatchInsertKeepIdentity; + } + + /** + * Whether to keep null values during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertKeepNulls = false; + + /** + * Returns the bulkCopyForBatchInsertKeepNulls value. + * + * @return the bulkCopyForBatchInsertKeepNulls value. + */ + public boolean getBulkCopyForBatchInsertKeepNulls() { + return bulkCopyForBatchInsertKeepNulls; + } + + /** + * Sets the bulkCopyForBatchInsertKeepNulls value. + * + * @param bulkCopyForBatchInsertKeepNulls + * the bulkCopyForBatchInsertKeepNulls value to set. + */ + public void setBulkCopyForBatchInsertKeepNulls(boolean bulkCopyForBatchInsertKeepNulls) { + this.bulkCopyForBatchInsertKeepNulls = bulkCopyForBatchInsertKeepNulls; + } + + /** + * Whether to use table lock during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertTableLock = false; + + /** + * Returns the bulkCopyForBatchInsertTableLock value. + * + * @return the bulkCopyForBatchInsertTableLock value. + */ + public boolean getBulkCopyForBatchInsertTableLock() { + return bulkCopyForBatchInsertTableLock; + } + + /** + * Sets the bulkCopyForBatchInsertTableLock value. + * + * @param bulkCopyForBatchInsertTableLock + * the bulkCopyForBatchInsertTableLock value to set. + */ + public void setBulkCopyForBatchInsertTableLock(boolean bulkCopyForBatchInsertTableLock) { + this.bulkCopyForBatchInsertTableLock = bulkCopyForBatchInsertTableLock; + } + + /** + * Whether to allow encrypted value modifications during bulk copy operations. + */ + private boolean bulkCopyForBatchInsertAllowEncryptedValueModifications = false; + + + /** + * Returns the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + * + * @return the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + */ + public boolean getBulkCopyForBatchInsertAllowEncryptedValueModifications() { + return bulkCopyForBatchInsertAllowEncryptedValueModifications; + } + + /** + * Sets the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + * + * @param bulkCopyForBatchInsertAllowEncryptedValueModifications + * the bulkCopyForBatchInsertAllowEncryptedValueModifications value to set. + */ + public void setBulkCopyForBatchInsertAllowEncryptedValueModifications(boolean bulkCopyForBatchInsertAllowEncryptedValueModifications) { + this.bulkCopyForBatchInsertAllowEncryptedValueModifications = bulkCopyForBatchInsertAllowEncryptedValueModifications; + } + /** user set TNIR flag */ boolean userSetTNIR = true; @@ -3118,6 +3287,48 @@ else if (0 == requestedPacketSize) useBulkCopyForBatchInsert = isBooleanPropertyOn(sPropKey, sPropValue); } + sPropKey = SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertBatchSize = Integer.parseInt(sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertCheckConstraints = isBooleanPropertyOn(sPropKey, sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertFireTriggers = isBooleanPropertyOn(sPropKey, sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertKeepIdentity = isBooleanPropertyOn(sPropKey, sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertKeepNulls = isBooleanPropertyOn(sPropKey, sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertTableLock = isBooleanPropertyOn(sPropKey, sPropValue); + } + + sPropKey = SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + bulkCopyForBatchInsertAllowEncryptedValueModifications = isBooleanPropertyOn(sPropKey, sPropValue); + } + sPropKey = SQLServerDriverBooleanProperty.ENABLE_BULK_COPY_CACHE.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null != sPropValue) { @@ -7630,6 +7841,27 @@ public T unwrap(Class iface) throws SQLException { /** original useBulkCopyForBatchInsert flag */ private boolean originalUseBulkCopyForBatchInsert; + /** original bulkCopyForBatchInsertBatchSize */ + private int originalBulkCopyForBatchInsertBatchSize; + + /** original bulkCopyForBatchInsertCheckConstraints flag */ + private boolean originalBulkCopyForBatchInsertCheckConstraints; + + /** original bulkCopyForBatchInsertFireTriggers flag */ + private boolean originalBulkCopyForBatchInsertFireTriggers; + + /** original bulkCopyForBatchInsertKeepIdentity flag */ + private boolean originalBulkCopyForBatchInsertKeepIdentity; + + /** original bulkCopyForBatchInsertKeepNulls flag */ + private boolean originalBulkCopyForBatchInsertKeepNulls; + + /** original bulkCopyForBatchInsertTableLock flag */ + private boolean originalBulkCopyForBatchInsertTableLock; + + /** original bulkCopyForBatchInsertAllowEncryptedValueModifications flag */ + private boolean originalBulkCopyForBatchInsertAllowEncryptedValueModifications; + /** original SqlWarnings */ private volatile SQLWarning originalSqlWarnings; @@ -7665,6 +7897,13 @@ void beginRequestInternal() throws SQLException { originalEnablePrepareOnFirstPreparedStatementCall = getEnablePrepareOnFirstPreparedStatementCall(); originalSCatalog = sCatalog; originalUseBulkCopyForBatchInsert = getUseBulkCopyForBatchInsert(); + originalBulkCopyForBatchInsertBatchSize = getBulkCopyForBatchInsertBatchSize(); + originalBulkCopyForBatchInsertCheckConstraints = getBulkCopyForBatchInsertCheckConstraints(); + originalBulkCopyForBatchInsertFireTriggers = getBulkCopyForBatchInsertFireTriggers(); + originalBulkCopyForBatchInsertKeepIdentity = getBulkCopyForBatchInsertKeepIdentity(); + originalBulkCopyForBatchInsertKeepNulls = getBulkCopyForBatchInsertKeepNulls(); + originalBulkCopyForBatchInsertTableLock = getBulkCopyForBatchInsertTableLock(); + originalBulkCopyForBatchInsertAllowEncryptedValueModifications = getBulkCopyForBatchInsertAllowEncryptedValueModifications(); originalSqlWarnings = sqlWarnings; openStatements = new LinkedList<>(); originalUseFmtOnly = useFmtOnly; @@ -7722,9 +7961,39 @@ void endRequestInternal() throws SQLException { if (!sCatalog.equals(originalSCatalog)) { setCatalog(originalSCatalog); } + if (getUseBulkCopyForBatchInsert() != originalUseBulkCopyForBatchInsert) { setUseBulkCopyForBatchInsert(originalUseBulkCopyForBatchInsert); } + + if (getBulkCopyForBatchInsertBatchSize() != originalBulkCopyForBatchInsertBatchSize) { + setBulkCopyForBatchInsertBatchSize(originalBulkCopyForBatchInsertBatchSize); + } + + if (getBulkCopyForBatchInsertCheckConstraints() != originalBulkCopyForBatchInsertCheckConstraints) { + setBulkCopyForBatchInsertCheckConstraints(originalBulkCopyForBatchInsertCheckConstraints); + } + + if (getBulkCopyForBatchInsertFireTriggers() != originalBulkCopyForBatchInsertFireTriggers) { + setBulkCopyForBatchInsertFireTriggers(originalBulkCopyForBatchInsertFireTriggers); + } + + if (getBulkCopyForBatchInsertKeepIdentity() != originalBulkCopyForBatchInsertKeepIdentity) { + setBulkCopyForBatchInsertKeepIdentity(originalBulkCopyForBatchInsertKeepIdentity); + } + + if (getBulkCopyForBatchInsertKeepNulls() != originalBulkCopyForBatchInsertKeepNulls) { + setBulkCopyForBatchInsertKeepNulls(originalBulkCopyForBatchInsertKeepNulls); + } + + if (getBulkCopyForBatchInsertTableLock() != originalBulkCopyForBatchInsertTableLock) { + setBulkCopyForBatchInsertTableLock(originalBulkCopyForBatchInsertTableLock); + } + + if (getBulkCopyForBatchInsertAllowEncryptedValueModifications() != originalBulkCopyForBatchInsertAllowEncryptedValueModifications) { + setBulkCopyForBatchInsertAllowEncryptedValueModifications(originalBulkCopyForBatchInsertAllowEncryptedValueModifications); + } + if (delayLoadingLobs != originalDelayLoadingLobs) { setDelayLoadingLobs(originalDelayLoadingLobs); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java index d9eb047ea..9f67a8321 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java @@ -753,4 +753,143 @@ public boolean getUseBulkCopyForBatchInsert() { public void setUseBulkCopyForBatchInsert(boolean useBulkCopyForBatchInsert) { wrappedConnection.setUseBulkCopyForBatchInsert(useBulkCopyForBatchInsert); } + + /** + * The default batch size for bulk copy operations created from batch insert operations. + */ + private int bulkCopyForBatchInsertBatchSize = 0; + + /** + * Returns the bulkCopyForBatchInsertBatchSize value. + * + * @return the bulkCopyForBatchInsertBatchSize value. + */ + public int getBulkCopyForBatchInsertBatchSize() { + return wrappedConnection.getBulkCopyForBatchInsertBatchSize(); + } + + /** + * Sets the bulkCopyForBatchInsertBatchSize value. + * + * @param bulkCopyForBatchInsertBatchSize + * the bulkCopyForBatchInsertBatchSize value to set. + */ + public void setBulkCopyForBatchInsertBatchSize(int bulkCopyForBatchInsertBatchSize) { + wrappedConnection.setBulkCopyForBatchInsertBatchSize(bulkCopyForBatchInsertBatchSize); + } + + /** + * Returns the bulkCopyForBatchInsertCheckConstraints value. + * + * @return the bulkCopyForBatchInsertCheckConstraints value. + */ + public boolean getBulkCopyForBatchInsertCheckConstraints() { + return wrappedConnection.getBulkCopyForBatchInsertCheckConstraints(); + } + + /** + * Sets the bulkCopyForBatchInsertCheckConstraints value. + * + * @param bulkCopyForBatchInsertCheckConstraints + * the bulkCopyForBatchInsertCheckConstraints value to set. + */ + public void setBulkCopyForBatchInsertCheckConstraints(boolean bulkCopyForBatchInsertCheckConstraints) { + wrappedConnection.setBulkCopyForBatchInsertCheckConstraints(bulkCopyForBatchInsertCheckConstraints); + } + + /** + * Returns the bulkCopyForBatchInsertFireTriggers value. + * + * @return the bulkCopyForBatchInsertFireTriggers value. + */ + public boolean getBulkCopyForBatchInsertFireTriggers() { + return wrappedConnection.getBulkCopyForBatchInsertFireTriggers(); + } + + /** + * Sets the bulkCopyForBatchInsertFireTriggers value. + * + * @param bulkCopyForBatchInsertFireTriggers + * the bulkCopyForBatchInsertFireTriggers value to set. + */ + public void setBulkCopyForBatchInsertFireTriggers(boolean bulkCopyForBatchInsertFireTriggers) { + wrappedConnection.setBulkCopyForBatchInsertFireTriggers(bulkCopyForBatchInsertFireTriggers); + } + + /** + * Returns the bulkCopyForBatchInsertKeepIdentity value. + * + * @return the bulkCopyForBatchInsertKeepIdentity value. + */ + public boolean getBulkCopyForBatchInsertKeepIdentity() { + return wrappedConnection.getBulkCopyForBatchInsertKeepIdentity(); + } + + /** + * Sets the bulkCopyForBatchInsertKeepIdentity value. + * + * @param bulkCopyForBatchInsertKeepIdentity + * the bulkCopyForBatchInsertKeepIdentity value to set. + */ + public void setBulkCopyForBatchInsertKeepIdentity(boolean bulkCopyForBatchInsertKeepIdentity) { + wrappedConnection.setBulkCopyForBatchInsertKeepIdentity(bulkCopyForBatchInsertKeepIdentity); + } + + /** + * Returns the bulkCopyForBatchInsertKeepNulls value. + * + * @return the bulkCopyForBatchInsertKeepNulls value. + */ + public boolean getBulkCopyForBatchInsertKeepNulls() { + return wrappedConnection.getBulkCopyForBatchInsertKeepNulls(); + } + + /** + * Sets the bulkCopyForBatchInsertKeepNulls value. + * + * @param bulkCopyForBatchInsertKeepNulls + * the bulkCopyForBatchInsertKeepNulls value to set. + */ + public void setBulkCopyForBatchInsertKeepNulls(boolean bulkCopyForBatchInsertKeepNulls) { + wrappedConnection.setBulkCopyForBatchInsertKeepNulls(bulkCopyForBatchInsertKeepNulls); + } + + /** + * Returns the bulkCopyForBatchInsertTableLock value. + * + * @return the bulkCopyForBatchInsertTableLock value. + */ + public boolean getBulkCopyForBatchInsertTableLock() { + return wrappedConnection.getBulkCopyForBatchInsertTableLock(); + } + + /** + * Sets the bulkCopyForBatchInsertTableLock value. + * + * @param bulkCopyForBatchInsertTableLock + * the bulkCopyForBatchInsertTableLock value to set. + */ + public void setBulkCopyForBatchInsertTableLock(boolean bulkCopyForBatchInsertTableLock) { + wrappedConnection.setBulkCopyForBatchInsertTableLock(bulkCopyForBatchInsertTableLock); + } + + /** + * Returns the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + * + * @return the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + */ + public boolean getBulkCopyForBatchInsertAllowEncryptedValueModifications() { + return wrappedConnection.getBulkCopyForBatchInsertAllowEncryptedValueModifications(); + } + + /** + * Sets the bulkCopyForBatchInsertAllowEncryptedValueModifications value. + * + * @param bulkCopyForBatchInsertAllowEncryptedValueModifications + * the bulkCopyForBatchInsertAllowEncryptedValueModifications value to set. + */ + public void setBulkCopyForBatchInsertAllowEncryptedValueModifications(boolean bulkCopyForBatchInsertAllowEncryptedValueModifications) { + wrappedConnection.setBulkCopyForBatchInsertAllowEncryptedValueModifications(bulkCopyForBatchInsertAllowEncryptedValueModifications); + } + } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 480f36ba3..1f3c7993b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -1025,6 +1025,96 @@ public boolean getUseBulkCopyForBatchInsert() { SQLServerDriverBooleanProperty.USE_BULK_COPY_FOR_BATCH_INSERT.getDefaultValue()); } + @Override + public void setBulkCopyForBatchInsertBatchSize(int bulkCopyForBatchInsertBatchSize) { + setIntProperty(connectionProps, SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.toString(), + bulkCopyForBatchInsertBatchSize); + } + + @Override + public int getBulkCopyForBatchInsertBatchSize() { + return getIntProperty(connectionProps, + SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.toString(), + SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertCheckConstraints(boolean bulkCopyForBatchInsertCheckConstraints) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.toString(), + bulkCopyForBatchInsertCheckConstraints); + } + + @Override + public boolean getBulkCopyForBatchInsertCheckConstraints() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertFireTriggers(boolean bulkCopyForBatchInsertFireTriggers) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.toString(), + bulkCopyForBatchInsertFireTriggers); + } + + @Override + public boolean getBulkCopyForBatchInsertFireTriggers() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertKeepIdentity(boolean bulkCopyForBatchInsertKeepIdentity) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.toString(), + bulkCopyForBatchInsertKeepIdentity); + } + + @Override + public boolean getBulkCopyForBatchInsertKeepIdentity() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertKeepNulls(boolean bulkCopyForBatchInsertKeepNulls) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.toString(), + bulkCopyForBatchInsertKeepNulls); + } + + @Override + public boolean getBulkCopyForBatchInsertKeepNulls() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertTableLock(boolean bulkCopyForBatchInsertTableLock) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.toString(), + bulkCopyForBatchInsertTableLock); + } + + @Override + public boolean getBulkCopyForBatchInsertTableLock() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.getDefaultValue()); + } + + @Override + public void setBulkCopyForBatchInsertAllowEncryptedValueModifications(boolean bulkCopyForBatchInsertAllowEncryptedValueModifications) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.toString(), + bulkCopyForBatchInsertAllowEncryptedValueModifications); + } + + @Override + public boolean getBulkCopyForBatchInsertAllowEncryptedValueModifications() { + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.toString(), + SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.getDefaultValue()); + } /** * @deprecated */ diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 1ffffa6f0..17264ce59 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -644,7 +644,8 @@ enum SQLServerDriverIntProperty { STATEMENT_POOLING_CACHE_SIZE("statementPoolingCacheSize", SQLServerConnection.DEFAULT_STATEMENT_POOLING_CACHE_SIZE), CANCEL_QUERY_TIMEOUT("cancelQueryTimeout", -1), CONNECT_RETRY_COUNT("connectRetryCount", 1, 0, 255), - CONNECT_RETRY_INTERVAL("connectRetryInterval", 10, 1, 60); + CONNECT_RETRY_INTERVAL("connectRetryInterval", 10, 1, 60), + BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE("bulkCopyForBatchInsertBatchSize", 0); private final String name; private final int defaultValue; @@ -694,6 +695,12 @@ enum SQLServerDriverBooleanProperty { ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT("enablePrepareOnFirstPreparedStatementCall", SQLServerConnection.DEFAULT_ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT_CALL), ENABLE_BULK_COPY_CACHE("cacheBulkCopyMetadata", false), USE_BULK_COPY_FOR_BATCH_INSERT("useBulkCopyForBatchInsert", false), + BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS("bulkCopyForBatchInsertCheckConstraints", false), + BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS("bulkCopyForBatchInsertFireTriggers", false), + BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY("bulkCopyForBatchInsertKeepIdentity", false), + BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS("bulkCopyForBatchInsertKeepNulls", false), + BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK("bulkCopyForBatchInsertTableLock", false), + BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS("bulkCopyForBatchInsertAllowEncryptedValueModifications", false), USE_FMT_ONLY("useFmtOnly", false), SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true), DELAY_LOADING_LOBS("delayLoadingLobs", true), @@ -946,9 +953,22 @@ static String getAppName() { new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_BULK_COPY_FOR_BATCH_INSERT.toString(), Boolean.toString(SQLServerDriverBooleanProperty.USE_BULK_COPY_FOR_BATCH_INSERT.getDefaultValue()), false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.toString(), + Integer.toString(SQLServerDriverIntProperty.BULK_COPY_FOR_BATCH_INSERT_BATCH_SIZE.getDefaultValue()),false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_CHECK_CONSTRAINTS.getDefaultValue()),false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_FIRE_TRIGGERS.getDefaultValue()),false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_IDENTITY.getDefaultValue()),false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_KEEP_NULLS.getDefaultValue()),false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_TABLE_LOCK.getDefaultValue()),false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.BULK_COPY_FOR_BATCH_INSERT_ALLOW_ENCRYPTED_VALUE_MODIFICATIONS.getDefaultValue()),false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.ENABLE_BULK_COPY_CACHE.toString(), - Boolean.toString(SQLServerDriverBooleanProperty.ENABLE_BULK_COPY_CACHE.getDefaultValue()), - false, TRUE_FALSE), + Boolean.toString(SQLServerDriverBooleanProperty.ENABLE_BULK_COPY_CACHE.getDefaultValue()),false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.MSI_CLIENT_ID.toString(), SQLServerDriverStringProperty.MSI_CLIENT_ID.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_VAULT_PROVIDER_CLIENT_ID.toString(), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 244cfb696..96ce9858f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -2224,7 +2224,7 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL if (null == bcOperation) { bcOperation = new SQLServerBulkCopy(connection); - SQLServerBulkCopyOptions option = new SQLServerBulkCopyOptions(); + SQLServerBulkCopyOptions option = new SQLServerBulkCopyOptions(connection); option.setBulkCopyTimeout(queryTimeout); bcOperation.setBulkCopyOptions(option); bcOperation.setDestinationTableName(bcOperationTableName); @@ -2405,7 +2405,7 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio if (null == bcOperation) { bcOperation = new SQLServerBulkCopy(connection); - SQLServerBulkCopyOptions option = new SQLServerBulkCopyOptions(); + SQLServerBulkCopyOptions option = new SQLServerBulkCopyOptions(connection); option.setBulkCopyTimeout(queryTimeout); bcOperation.setBulkCopyOptions(option); bcOperation.setDestinationTableName(bcOperationTableName); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index c9d875e58..9d09e1edd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -465,7 +465,14 @@ protected Object[][] getContents() { {"R_invalidSSLProtocol", "SSL Protocol {0} label is not valid. Only TLS, TLSv1, TLSv1.1, and TLSv1.2 are supported."}, {"R_cancelQueryTimeoutPropertyDescription", "The number of seconds to wait to cancel sending a query timeout."}, {"R_invalidCancelQueryTimeout", "The cancel timeout value {0} is not valid."}, - {"R_useBulkCopyForBatchInsertPropertyDescription", "Whether the driver will use bulk copy API for batch insert operations"}, + {"R_useBulkCopyForBatchInsertPropertyDescription", "Determines whether the driver will use bulk copy API for batch insert operations."}, + {"R_bulkCopyForBatchInsertBatchSizePropertyDescription", "The default batch size for bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertCheckConstraintsPropertyDescription", "Determines whether to check constraints during bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertFireTriggersPropertyDescription", "Determines whether to fire triggers during bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertKeepIdentityPropertyDescription", "Determines whether to keep identity values during bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertKeepNullsPropertyDescription", "Determines whether to keep null values during bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertTableLockPropertyDescription", "Determines whether to use table lock during bulk copy operations created from batch insert operations."}, + {"R_bulkCopyForBatchInsertAllowEncryptedValueModificationsPropertyDescription", "Determines whether to allow encrypted value modifications during bulk copy operations created from batch insert operations."}, {"R_UnknownDataClsTokenNumber", "Unknown token for Data Classification."}, // From Server {"R_InvalidDataClsVersionNumber", "Invalid version number {0} for Data Classification."}, // From Server {"R_unknownUTF8SupportValue", "Unknown value for UTF8 support."}, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java index 402cccc8c..89df6595a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -73,6 +73,13 @@ public void testModifiableConnectionProperties() throws SQLException { boolean enablePrepareOnFirstPreparedStatementCall1 = false; String sCatalog1 = "master"; boolean useBulkCopyForBatchInsert1 = true; + int bulkCopyForBatchInsertBatchSize1 = 1000; + boolean bulkCopyForBatchInsertCheckConstraints1 = true; + boolean bulkCopyForBatchInsertFireTriggers1 = true; + boolean bulkCopyForBatchInsertKeepIdentity1 = true; + boolean bulkCopyForBatchInsertKeepNulls1 = true; + boolean bulkCopyForBatchInsertTableLock1 = true; + boolean bulkCopyForBatchInsertAllowEncryptedValueModifications1 = true; boolean useFmtOnly1 = true; boolean delayLoadingLobs1 = false; boolean ignoreOffsetOnDateTimeOffsetConversion1 = true; @@ -88,6 +95,13 @@ public void testModifiableConnectionProperties() throws SQLException { boolean enablePrepareOnFirstPreparedStatementCall2 = true; String sCatalog2 = RandomUtil.getIdentifier("RequestBoundaryDatabase"); boolean useBulkCopyForBatchInsert2 = false; + int bulkCopyForBatchInsertBatchSize2 = 0; + boolean bulkCopyForBatchInsertCheckConstraints2 = false; + boolean bulkCopyForBatchInsertFireTriggers2 = false; + boolean bulkCopyForBatchInsertKeepIdentity2 = false; + boolean bulkCopyForBatchInsertKeepNulls2 = false; + boolean bulkCopyForBatchInsertTableLock2 = false; + boolean bulkCopyForBatchInsertAllowEncryptedValueModifications2 = false; boolean useFmtOnly2 = false; boolean delayLoadingLobs2 = true; boolean ignoreOffsetOnDateTimeOffsetConversion2 = false; @@ -101,62 +115,78 @@ public void testModifiableConnectionProperties() throws SQLException { setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, - ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1, + bulkCopyForBatchInsertBatchSize1, bulkCopyForBatchInsertCheckConstraints1, + bulkCopyForBatchInsertFireTriggers1, bulkCopyForBatchInsertKeepIdentity1, bulkCopyForBatchInsertKeepNulls1, + bulkCopyForBatchInsertTableLock1,bulkCopyForBatchInsertAllowEncryptedValueModifications1); con.beginRequest(); // Call setters with the second set of values inside beginRequest()/endRequest() block. setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, - ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2, + bulkCopyForBatchInsertBatchSize2, bulkCopyForBatchInsertCheckConstraints2, + bulkCopyForBatchInsertFireTriggers2, bulkCopyForBatchInsertKeepIdentity2, bulkCopyForBatchInsertKeepNulls2, + bulkCopyForBatchInsertTableLock2, bulkCopyForBatchInsertAllowEncryptedValueModifications2); con.endRequest(); // Test if endRequest() resets the SQLServerConnection properties back to the first set of values. compareValuesAgainstConnection(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, - ignoreOffsetOnDateTimeOffsetConversion1); - + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1, + bulkCopyForBatchInsertBatchSize1, bulkCopyForBatchInsertCheckConstraints1, + bulkCopyForBatchInsertFireTriggers1, bulkCopyForBatchInsertKeepIdentity1, bulkCopyForBatchInsertKeepNulls1, + bulkCopyForBatchInsertTableLock1, bulkCopyForBatchInsertAllowEncryptedValueModifications1); // Multiple calls to beginRequest() without an intervening call to endRequest() are no-op. setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, - ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2, + bulkCopyForBatchInsertBatchSize2, bulkCopyForBatchInsertCheckConstraints2, + bulkCopyForBatchInsertFireTriggers2, bulkCopyForBatchInsertKeepIdentity2, bulkCopyForBatchInsertKeepNulls2, + bulkCopyForBatchInsertTableLock2, bulkCopyForBatchInsertAllowEncryptedValueModifications2); con.beginRequest(); setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, - ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1, + bulkCopyForBatchInsertBatchSize1, bulkCopyForBatchInsertCheckConstraints1, + bulkCopyForBatchInsertFireTriggers1, bulkCopyForBatchInsertKeepIdentity1, bulkCopyForBatchInsertKeepNulls1, + bulkCopyForBatchInsertTableLock1, bulkCopyForBatchInsertAllowEncryptedValueModifications1); con.beginRequest(); con.endRequest(); // Same values as before the first beginRequest() compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, - ignoreOffsetOnDateTimeOffsetConversion2); - + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2, + bulkCopyForBatchInsertBatchSize2, bulkCopyForBatchInsertCheckConstraints2, + bulkCopyForBatchInsertFireTriggers2, bulkCopyForBatchInsertKeepIdentity2, bulkCopyForBatchInsertKeepNulls2, + bulkCopyForBatchInsertTableLock2, bulkCopyForBatchInsertAllowEncryptedValueModifications2); // A call to endRequest() without an intervening call to beginRequest() is no-op. setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, - ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1, + bulkCopyForBatchInsertBatchSize1, bulkCopyForBatchInsertCheckConstraints1, + bulkCopyForBatchInsertFireTriggers1, bulkCopyForBatchInsertKeepIdentity1, bulkCopyForBatchInsertKeepNulls1, + bulkCopyForBatchInsertTableLock1, bulkCopyForBatchInsertAllowEncryptedValueModifications1); + setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, - ignoreOffsetOnDateTimeOffsetConversion2); - con.endRequest(); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2, + bulkCopyForBatchInsertBatchSize2, bulkCopyForBatchInsertCheckConstraints2, + bulkCopyForBatchInsertFireTriggers2, bulkCopyForBatchInsertKeepIdentity2, bulkCopyForBatchInsertKeepNulls2, + bulkCopyForBatchInsertTableLock2, bulkCopyForBatchInsertAllowEncryptedValueModifications2); con.endRequest(); // No change. compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, - ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2, + bulkCopyForBatchInsertBatchSize2, bulkCopyForBatchInsertCheckConstraints2, + bulkCopyForBatchInsertFireTriggers2, bulkCopyForBatchInsertKeepIdentity2, bulkCopyForBatchInsertKeepNulls2, + bulkCopyForBatchInsertTableLock2, bulkCopyForBatchInsertAllowEncryptedValueModifications2); } finally { TestUtils.dropDatabaseIfExists(sCatalog2, connectionString); } @@ -400,8 +430,12 @@ private void setConnectionFields(SQLServerConnection con, boolean autoCommitMode int networkTimeout, int holdability, boolean sendTimeAsDatetime, int statementPoolingCacheSize, boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold, boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert, - boolean useFmtOnly, boolean delayLoadingLobs, - boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { + boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion, + int bulkCopyForBatchInsertBatchSize, boolean bulkCopyForBatchInsertCheckConstraints, + boolean bulkCopyForBatchInsertFireTriggers, boolean bulkCopyForBatchInsertKeepIdentity, + boolean bulkCopyForBatchInsertKeepNulls, boolean bulkCopyForBatchInsertTableLock, + boolean bulkCopyForBatchInsertAllowEncryptedValueModifications) throws SQLException { + con.setAutoCommit(autoCommitMode); con.setTransactionIsolation(transactionIsolationLevel); con.setNetworkTimeout(null, networkTimeout); @@ -416,38 +450,49 @@ private void setConnectionFields(SQLServerConnection con, boolean autoCommitMode con.setUseFmtOnly(useFmtOnly); con.setDelayLoadingLobs(delayLoadingLobs); con.setIgnoreOffsetOnDateTimeOffsetConversion(ignoreOffsetOnDateTimeOffsetConversion); + con.setBulkCopyForBatchInsertBatchSize(bulkCopyForBatchInsertBatchSize); + con.setBulkCopyForBatchInsertCheckConstraints(bulkCopyForBatchInsertCheckConstraints); + con.setBulkCopyForBatchInsertFireTriggers(bulkCopyForBatchInsertFireTriggers); + con.setBulkCopyForBatchInsertKeepIdentity(bulkCopyForBatchInsertKeepIdentity); + con.setBulkCopyForBatchInsertKeepNulls(bulkCopyForBatchInsertKeepNulls); + con.setBulkCopyForBatchInsertTableLock(bulkCopyForBatchInsertTableLock); + con.setBulkCopyForBatchInsertAllowEncryptedValueModifications(bulkCopyForBatchInsertAllowEncryptedValueModifications); } - + private void compareValuesAgainstConnection(SQLServerConnection con, boolean autoCommitMode, int transactionIsolationLevel, int networkTimeout, int holdability, boolean sendTimeAsDatetime, int statementPoolingCacheSize, boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold, boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert, - boolean useFmtOnly, boolean delayLoadingLobs, - boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { + boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion, + int bulkCopyForBatchInsertBatchSize, boolean bulkCopyForBatchInsertCheckConstraints, + boolean bulkCopyForBatchInsertFireTriggers, boolean bulkCopyForBatchInsertKeepIdentity, + boolean bulkCopyForBatchInsertKeepNulls, boolean bulkCopyForBatchInsertTableLock, + boolean bulkCopyForBatchInsertAllowEncryptedValueModifications) throws SQLException { + final String description = " values do not match."; assertEquals(autoCommitMode, con.getAutoCommit(), "autoCommitmode" + description); - assertEquals(transactionIsolationLevel, con.getTransactionIsolation(), - "transactionIsolationLevel" + description); + assertEquals(transactionIsolationLevel, con.getTransactionIsolation(), "transactionIsolationLevel" + description); assertEquals(networkTimeout, con.getNetworkTimeout(), "networkTimeout" + description); assertEquals(holdability, con.getHoldability(), "holdability" + description); assertEquals(sendTimeAsDatetime, con.getSendTimeAsDatetime(), "sendTimeAsDatetime" + description); - assertEquals(statementPoolingCacheSize, con.getStatementPoolingCacheSize(), - "statementPoolingCacheSize" + description); - assertEquals(disableStatementPooling, con.getDisableStatementPooling(), - "disableStatementPooling" + description); - assertEquals(serverPreparedStatementDiscardThreshold, con.getServerPreparedStatementDiscardThreshold(), - "serverPreparedStatementDiscardThreshold" + description); - assertEquals(enablePrepareOnFirstPreparedStatementCall, con.getEnablePrepareOnFirstPreparedStatementCall(), - "enablePrepareOnFirstPreparedStatementCall" + description); + assertEquals(statementPoolingCacheSize, con.getStatementPoolingCacheSize(), "statementPoolingCacheSize" + description); + assertEquals(disableStatementPooling, con.getDisableStatementPooling(), "disableStatementPooling" + description); + assertEquals(serverPreparedStatementDiscardThreshold, con.getServerPreparedStatementDiscardThreshold(), "serverPreparedStatementDiscardThreshold" + description); + assertEquals(enablePrepareOnFirstPreparedStatementCall, con.getEnablePrepareOnFirstPreparedStatementCall(), "enablePrepareOnFirstPreparedStatementCall" + description); assertEquals(sCatalog, con.getCatalog(), "sCatalog" + description); - assertEquals(useBulkCopyForBatchInsert, con.getUseBulkCopyForBatchInsert(), - "useBulkCopyForBatchInsert" + description); + assertEquals(useBulkCopyForBatchInsert, con.getUseBulkCopyForBatchInsert(), "useBulkCopyForBatchInsert" + description); assertEquals(useFmtOnly, con.getUseFmtOnly(), "useFmtOnly" + description); assertEquals(delayLoadingLobs, con.getDelayLoadingLobs(), "delayLoadingLobs" + description); - assertEquals(ignoreOffsetOnDateTimeOffsetConversion, con.getIgnoreOffsetOnDateTimeOffsetConversion(), - "ignoreOffsetOnDateTimeOffsetConversion" + description); + assertEquals(ignoreOffsetOnDateTimeOffsetConversion, con.getIgnoreOffsetOnDateTimeOffsetConversion(), "ignoreOffsetOnDateTimeOffsetConversion" + description); + assertEquals(bulkCopyForBatchInsertBatchSize, con.getBulkCopyForBatchInsertBatchSize(), "bulkCopyForBatchInsertBatchSize" + description); + assertEquals(bulkCopyForBatchInsertCheckConstraints, con.getBulkCopyForBatchInsertCheckConstraints(), "bulkCopyForBatchInsertCheckConstraints" + description); + assertEquals(bulkCopyForBatchInsertFireTriggers, con.getBulkCopyForBatchInsertFireTriggers(), "bulkCopyForBatchInsertFireTriggers" + description); + assertEquals(bulkCopyForBatchInsertKeepIdentity, con.getBulkCopyForBatchInsertKeepIdentity(), "bulkCopyForBatchInsertKeepIdentity" + description); + assertEquals(bulkCopyForBatchInsertKeepNulls, con.getBulkCopyForBatchInsertKeepNulls(), "bulkCopyForBatchInsertKeepNulls" + description); + assertEquals(bulkCopyForBatchInsertTableLock, con.getBulkCopyForBatchInsertTableLock(), "bulkCopyForBatchInsertTableLock" + description); + assertEquals(bulkCopyForBatchInsertAllowEncryptedValueModifications, con.getBulkCopyForBatchInsertAllowEncryptedValueModifications(), "bulkCopyForBatchInsertAllowEncryptedValueModifications" + description); } - + private void generateWarning(Connection con) throws SQLException { con.setClientInfo("name", "value"); } @@ -476,6 +521,13 @@ private List getVerifiedMethodNames() { verifiedMethodNames.add("setDisableStatementPooling"); verifiedMethodNames.add("setTransactionIsolation"); verifiedMethodNames.add("setUseBulkCopyForBatchInsert"); + verifiedMethodNames.add("setBulkCopyForBatchInsertBatchSize"); + verifiedMethodNames.add("setBulkCopyForBatchInsertCheckConstraints"); + verifiedMethodNames.add("setBulkCopyForBatchInsertFireTriggers"); + verifiedMethodNames.add("setBulkCopyForBatchInsertKeepIdentity"); + verifiedMethodNames.add("setBulkCopyForBatchInsertKeepNulls"); + verifiedMethodNames.add("setBulkCopyForBatchInsertTableLock"); + verifiedMethodNames.add("setBulkCopyForBatchInsertAllowEncryptedValueModifications"); verifiedMethodNames.add("commit"); verifiedMethodNames.add("clearWarnings"); verifiedMethodNames.add("prepareStatement"); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBCOptionsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBCOptionsTest.java new file mode 100644 index 000000000..836d57a52 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBCOptionsTest.java @@ -0,0 +1,683 @@ +/* + * 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.preparedStatement; + +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +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.SQLServerBulkCopyOptions; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.TestResource; +import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.PrepUtil; + +@RunWith(JUnitPlatform.class) +public class BatchExecutionWithBCOptionsTest extends AbstractTest { + + private static final String tableName = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("BatchInsertWithBCOptions")); + + /** + * Test with useBulkCopyBatchInsert=true without passing + * bulkCopyForBatchInsertCheckConstraints + * + * @throws SQLException + */ + @Test + public void testBulkInsertNoConnStrOptions() throws Exception { + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertCheckConstraints=true + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithConnStrConstraintCheckEnabled() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertCheckConstraints=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + + pstmt.setInt(1, 1); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + + } + } catch (SQLException e) { + if (!e.getMessage().contains("CHECK")) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertCheckConstraints=false + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithConnStrCheckConstraintsDisabled() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertCheckConstraints=false")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + + pstmt.setInt(1, 1); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and bulkCopyForBatchInsertBatchSize set + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithBatchSize() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertBatchSize=2")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertKeepIdentity=true + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithKeepIdentity() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertKeepIdentity=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertKeepIdentity=true where identity insert fails + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithKeepIdentityFailure() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertKeepIdentity=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 1); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.executeBatch(); + + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } + } catch (SQLException e) { + if (!e.getMessage().contains("Violation of PRIMARY KEY constraint")) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + } + + /** + * Test with useBulkCopyBatchInsert=true without passing + * SQLServerBulkCopyOptions + * + * @throws SQLException + */ + @Test + public void testBulkInsertNoOptions() throws Exception { + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertTableLock=true + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithTableLock() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertTableLock=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertTableLock=true where insert fails + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithTableLockFailure() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertTableLock=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + // Start a transaction and acquire a table lock + connection.setAutoCommit(false); + try (Statement stmt = connection.createStatement()) { + String lockTableSQL = "SELECT * FROM " + tableName + " WITH (TABLOCKX)"; + stmt.execute(lockTableSQL); + + try (Connection connection2 = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertTableLock=true"); + PreparedStatement pstmt2 = connection2 + .prepareStatement("insert into " + tableName + " values(?, ?)")) { + + pstmt2.setInt(1, 5); + pstmt2.setInt(2, 5); + pstmt2.addBatch(); + + // Set a query timeout to prevent the test from running indefinitely + pstmt2.setQueryTimeout(5); + + pstmt2.executeBatch(); // This should fail due to the table lock + fail("Expected exception due to table lock was not thrown"); + } catch (SQLException e) { + System.out.println("Bulk insert failed as expected: " + e.getMessage()); + } + // Release the lock + connection.rollback(); + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertFireTriggers=true + * + * @throws SQLException + */ + @Test + public void testBulkCopyOptionDefaultsFireTriggers() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertFireTriggers=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "Row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertFireTriggers=true where insert fails + * + * @throws SQLException + */ + @Test + public void testBulkCopyOptionDefaultsFireTriggersFailure() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertFireTriggers=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 3); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + // Created a trigger that will cause the batch insert to fail + try (Statement stmt = connection.createStatement()) { + String createTriggerSQL = "CREATE TRIGGER trgFailInsert ON " + tableName + + " AFTER INSERT AS BEGIN " + + "RAISERROR('Trigger failure', 16, 1); " + + "ROLLBACK TRANSACTION; END"; + stmt.execute(createTriggerSQL); + } + + try { + pstmt.executeBatch(); + fail("Expected trigger failure exception was not thrown"); + } catch (SQLException e) { + System.out.println("Batch execution failed as expected: " + e.getMessage()); + } + + // Cleaning up by dropping the trigger + try (Statement stmt = connection.createStatement()) { + String dropTriggerSQL = "DROP TRIGGER trgFailInsert"; + stmt.execute(dropTriggerSQL); + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertKeepNulls=true + * + * @throws SQLException + */ + @Test + public void testBulkCopyOptionDefaultsKeepNulls() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertKeepNulls=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt + .executeQuery("select count(*) from " + tableName + " where b is null")) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "Row count with null values should have been 4"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertKeepNulls=false + * + * @throws SQLException + */ + @Test + public void testBulkCopyOptionDefaultsKeepNullsFalse() throws Exception { + try (Connection connection = PrepUtil.getConnection( + connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertKeepNulls=false")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setNull(2, java.sql.Types.INTEGER); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt + .executeQuery("select count(*) from " + tableName + " where b is not null")) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 0, "Row count with non-null values should have been 0"); + } + } + } + } + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + + /** + * Test with useBulkCopyBatchInsert=true and + * bulkCopyForBatchInsertAllowEncryptedValueModifications=true + * + * @throws SQLException + */ + @Test + public void testBulkInsertWithEncryptedValueModifications() throws Exception { + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;bulkCopyForBatchInsertAllowEncryptedValueModifications=true")) { + try (PreparedStatement pstmt = connection.prepareStatement("insert into " + tableName + " values(?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 2); + pstmt.setInt(2, 2); + pstmt.addBatch(); + + pstmt.setInt(1, 3); + pstmt.setInt(2, 0); + pstmt.addBatch(); + + pstmt.setInt(1, 4); + pstmt.setInt(2, 4); + pstmt.addBatch(); + + pstmt.executeBatch(); + + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select count(*) from " + tableName)) { + if (rs.next()) { + int cnt = rs.getInt(1); + assertEquals(cnt, 4, "row count should have been 4"); + } + } + } + } + } catch (SQLException e) { + if (e.getMessage().contains("Invalid column type from bcp client for colid 1")) { + return; + } else { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + fail("Expected exception 'Invalid column type from bcp client for colid 1' was not thrown."); + } + + @BeforeEach + public void init() throws Exception { + try (Connection con = getConnection()) { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + String sql1 = "create table " + tableName + "(a INT PRIMARY KEY, b INT CHECK (b > 0))"; + stmt.executeUpdate(sql1); + } + con.commit(); + } + } + + @AfterEach + public void terminate() throws Exception { + try (Connection con = getConnection()) { + try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + } + } + } + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + +}