Repository: microsoft/mssql-jdbc
Pull request #2250 (2023-11-09 21:49:40)

Added new named logger for connection open retries and idle connection resiliency reconnects
Added new logger that logs details related to connection open retries and ICR reconnects only.


Patch:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
index 8025410c..314e6443 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
@@ -481,4 +481,20 @@ public interface ISQLServerConnection extends java.sql.Connection {
      * @param accessTokenCallbackClass
      */
     void setAccessTokenCallbackClass(String accessTokenCallbackClass);
+
+    /**
+     * Returns the current flag for calcBigDecimalScale.
+     *
+     * @return calcBigDecimalScale
+     *        Whether calculating big decimal scale from input values is enabled.
+     */
+    boolean getCalcBigDecimalScale();
+
+    /**
+     * Specifies whether to calculate scale from inputted big decimal values.
+     *
+     * @param calcBigDecimalScale
+     *        A boolean that indicates if the driver should calculate scale from inputted big decimal values.
+     */
+    void setCalcBigDecimalScale(boolean computeBigDecimal);
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
index 3bfc279a..1e623875 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
@@ -1313,4 +1313,19 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
      * @param accessTokenCallbackClass
      */
     void setAccessTokenCallbackClass(String accessTokenCallbackClass);
+
+    /**
+     * Returns value of 'calcBigDecimalScale' from Connection String.
+     *
+     * @param calcBigDecimalScale
+     *         indicates whether the driver should attempt to calculate scale from inputted big decimal values
+     */
+    void setCalcBigDecimalScale(boolean calcBigDecimalScale);
+
+    /**
+     * Sets the value for 'calcBigDecimalScale' property
+     *
+     * @return calcBigDecimalScale boolean value
+     */
+    boolean getCalcBigDecimalScale();
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java b/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java
index b6182a68..abc15bf5 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java
@@ -11,6 +11,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Level;
 
+import static com.microsoft.sqlserver.jdbc.SQLServerConnection.loggerResiliency;
+
 
 class IdleConnectionResiliency {
     private static final java.util.logging.Logger loggerExternal = java.util.logging.Logger
@@ -423,8 +425,8 @@ final class ReconnectThread extends Thread {
         connectRetryCount = con.getRetryCount();
         eReceived = null;
         stopRequested = false;
-        if (loggerExternal.isLoggable(Level.FINER)) {
-            loggerExternal.finer("ReconnectThread initialized. Connection retry count = " + connectRetryCount
+        if (loggerResiliency.isLoggable(Level.FINER)) {
+            loggerResiliency.finer("Idle connection resiliency - ReconnectThread initialized. Connection retry count = " + connectRetryCount
                     + "; Command = " + cmd.toString());
         }
 
@@ -432,8 +434,8 @@ final class ReconnectThread extends Thread {
 
     @Override
     public void run() {
-        if (loggerExternal.isLoggable(Level.FINER)) {
-            loggerExternal.finer("Starting ReconnectThread for command: " + command.toString());
+        if (loggerResiliency.isLoggable(Level.FINER)) {
+            loggerResiliency.finer("Idle connection resiliency - starting ReconnectThread for command: " + command.toString());
         }
         boolean interruptsEnabled = command.getInterruptsEnabled();
         /*
@@ -457,18 +459,34 @@ final class ReconnectThread extends Thread {
         long connectRetryInterval = TimeUnit.SECONDS.toMillis(con.getRetryInterval());
 
         while ((connectRetryCount >= 0) && (!stopRequested) && keepRetrying) {
-            if (loggerExternal.isLoggable(Level.FINER)) {
-                loggerExternal.finer("Running reconnect for command: " + command.toString() + " ; ConnectRetryCount = "
+            if (loggerResiliency.isLoggable(Level.FINER)) {
+                loggerResiliency.finer("Idle connection resiliency - running reconnect for command: " + command.toString() + " ; connectRetryCount = "
                         + connectRetryCount);
             }
+
             try {
                 eReceived = null;
                 con.connect(null, con.getPooledConnectionParent());
                 keepRetrying = false;
+
+                if (loggerResiliency.isLoggable(Level.FINE)) {
+                    loggerResiliency.fine("Idle connection resiliency - reconnect attempt succeeded ; connectRetryCount = " + connectRetryCount);
+                }
+
             } catch (SQLServerException e) {
+
+                if (loggerResiliency.isLoggable(Level.FINE)) {
+                    loggerResiliency.fine("Idle connection resiliency - reconnect attempt failed ; connectRetryCount = " + connectRetryCount);
+                }
+
                 if (!stopRequested) {
                     eReceived = e;
                     if (con.isFatalError(e)) {
+
+                        if (loggerResiliency.isLoggable(Level.FINER)) {
+                            loggerResiliency.finer("Idle connection resiliency - reconnect for command: " + command.toString() + " encountered fatal error: " + e.getMessage() + " - stopping reconnect attempt.");
+                        }
+
                         keepRetrying = false;
                     } else {
                         try {
@@ -476,6 +494,10 @@ final class ReconnectThread extends Thread {
                                 Thread.sleep(connectRetryInterval);
                             }
                         } catch (InterruptedException ie) {
+                            if (loggerResiliency.isLoggable(Level.FINER)) {
+                                loggerResiliency.finer("Idle connection resiliency - query timed out for command: " + command.toString() + ". Stopping reconnect attempt.");
+                            }
+
                             // re-interrupt thread
                             Thread.currentThread().interrupt();
 
@@ -490,6 +512,9 @@ final class ReconnectThread extends Thread {
                 try {
                     command.checkForInterrupt();
                 } catch (SQLServerException e) {
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer("Idle connection resiliency - timeout occurred on reconnect: " + command.toString() + ". Stopping reconnect attempt.");
+                    }
                     // Interrupted, timeout occurred. Stop retrying.
                     keepRetrying = false;
                     eReceived = e;
@@ -504,8 +529,8 @@ final class ReconnectThread extends Thread {
 
         command.setInterruptsEnabled(interruptsEnabled);
 
-        if (loggerExternal.isLoggable(Level.FINER)) {
-            loggerExternal.finer("ReconnectThread exiting for command: " + command.toString());
+        if (loggerResiliency.isLoggable(Level.FINER)) {
+            loggerResiliency.finer("Idle connection resiliency - ReconnectThread exiting for command: " + command.toString());
         }
 
         if (timeout != null) {
@@ -520,8 +545,8 @@ final class ReconnectThread extends Thread {
     }
 
     void stop(boolean blocking) {
-        if (loggerExternal.isLoggable(Level.FINER)) {
-            loggerExternal.finer("ReconnectThread stop requested for command: " + command.toString());
+        if (loggerResiliency.isLoggable(Level.FINER)) {
+            loggerResiliency.finer("ReconnectThread stop requested for command: " + command.toString());
         }
         stopRequested = true;
         if (blocking && this.isAlive()) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
index aa64c87b..fe098500 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
@@ -531,8 +531,35 @@ final class Parameter {
                             param.typeDefinition = SSType.DECIMAL.toString() + "(" + valueLength + "," + scale + ")";
                         }
                     } else {
-                        param.typeDefinition = SSType.DECIMAL.toString() + "("
-                                + SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")";
+                        if (con.getCalcBigDecimalScale() && dtv.getJavaType() == JavaType.BIGDECIMAL
+                                && null != dtv.getSetterValue()) {
+                            String[] plainValueArray
+                                    = ((BigDecimal) dtv.getSetterValue()).abs().toPlainString().split("\\.");
+
+                            // Precision is computed as opposed to using BigDecimal.precision(). This is because the
+                            // BigDecimal method can lead to inaccurate results.
+                            int calculatedPrecision;
+
+                            // If the string array has two parts, e.g .the input was a decimal, check if the first
+                            // part is a 0. For BigDecimals with leading zeroes, the leading zero does not count towards
+                            // precision. For all other decimals, we include the integer portion as part of the precision
+                            // When the string array has just one part, we only look at that part to compute precision.
+                            if (plainValueArray.length == 2) {
+                                if (plainValueArray[0].length() == 1 && (Integer.parseInt(plainValueArray[0]) == 0)) {
+                                    calculatedPrecision = plainValueArray[1].length();
+                                } else {
+                                    calculatedPrecision = plainValueArray[0].length() + plainValueArray[1].length();
+                                }
+                            } else  {
+                                calculatedPrecision = plainValueArray[0].length();
+                            }
+
+                            param.typeDefinition = SSType.DECIMAL.toString() + "(" + calculatedPrecision + "," +
+                                    (plainValueArray.length == 2 ? plainValueArray[1].length() : 0) + ")";
+                        } else {
+                            param.typeDefinition = SSType.DECIMAL.toString() + "("
+                                    + SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")";
+                        }
                     }
                     break;
 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index d8d19a8b..4ffc8f32 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -988,6 +988,18 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
         this.ignoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
     }
 
+    private boolean calcBigDecimalScale = SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue();
+
+    @Override
+    public boolean getCalcBigDecimalScale() {
+        return calcBigDecimalScale;
+    }
+
+    @Override
+    public void setCalcBigDecimalScale(boolean calcBigDecimalScale) {
+        this.calcBigDecimalScale = calcBigDecimalScale;
+    }
+
     /** Session Recovery Object */
     private transient IdleConnectionResiliency sessionRecovery = new IdleConnectionResiliency(this);
 
@@ -1580,7 +1592,10 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerConnection");
     static final private java.util.logging.Logger loggerExternal = java.util.logging.Logger
             .getLogger("com.microsoft.sqlserver.jdbc.Connection");
-
+    static final java.util.logging.Logger loggerResiliency = java.util.logging.Logger
+            .getLogger("com.microsoft.sqlserver.jdbc.Resiliency");
+    static final java.util.logging.Logger loggerRedirection = java.util.logging.Logger
+            .getLogger("com.microsoft.sqlserver.jdbc.Redirection");
     private static String loggingClassNameBase = "com.microsoft.sqlserver.jdbc.SQLServerConnection";
 
     /** Instance-specific loggingClassName to identity the connection in logs */
@@ -2078,6 +2093,15 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                             IPAddressPreference.valueOfString(sPropValue).toString());
                 }
 
+                sPropKey = SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.toString();
+                sPropValue = activeConnectionProperties.getProperty(sPropKey);
+                if (null == sPropValue) {
+                    sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue());
+                    activeConnectionProperties.setProperty(sPropKey, sPropValue);
+                }
+
+                calcBigDecimalScale = isBooleanPropertyOn(sPropKey, sPropValue);
+
                 sPropKey = SQLServerDriverStringProperty.APPLICATION_NAME.toString();
                 sPropValue = activeConnectionProperties.getProperty(sPropKey);
                 if (null != sPropValue)
@@ -3072,9 +3096,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
         // and the original timeout is used instead of the timeout slice.
         long intervalExpireFullTimeout = loginStartTime + loginTimeoutMs;
 
-        if (connectionlogger.isLoggable(Level.FINER)) {
-            connectionlogger.finer(toString() + " Start time: " + loginStartTime + " Time out time: " + timerExpire
-                    + " Timeout Unit Interval: " + timeoutUnitInterval);
+        if (loggerResiliency.isLoggable(Level.FINER)) {
+            loggerResiliency.finer(toString() + " Connection open - start time: " + loginStartTime + " time out time: " + timerExpire
+                    + " timeout unit interval: " + timeoutUnitInterval);
         }
 
         // Initialize loop variables
@@ -3106,6 +3130,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                     currentConnectPlaceHolder = currentFOPlaceHolder;
                 } else {
                     if (routingInfo != null) {
+                        if (loggerRedirection.isLoggable(Level.FINE)) {
+                            loggerRedirection.fine(toString() + " Connection open - redirecting to server and instance: " + routingInfo.getFullServerName());
+                        }
                         currentPrimaryPlaceHolder = routingInfo;
                         routingInfo = null;
                     } else if (null == currentPrimaryPlaceHolder) {
@@ -3115,13 +3142,17 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                     currentConnectPlaceHolder = currentPrimaryPlaceHolder;
                 }
 
-                if (connectionlogger.isLoggable(Level.FINE)) {
-                    connectionlogger
-                            .fine(toString() + " This attempt server name: " + currentConnectPlaceHolder.getServerName()
+                if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) {
+                    loggerResiliency.fine(toString() + " Connection open - starting connection retry attempt number: " + retryAttempt);
+                }
+
+                if (loggerResiliency.isLoggable(Level.FINER)) {
+                    loggerResiliency
+                            .finer(toString() + " Connection open - attempt server name: " + currentConnectPlaceHolder.getServerName()
                                     + " port: " + currentConnectPlaceHolder.getPortNumber() + " InstanceName: "
                                     + currentConnectPlaceHolder.getInstanceName() + " useParallel: " + useParallel);
-                    connectionlogger.fine(toString() + " This attempt endtime: " + intervalExpire);
-                    connectionlogger.fine(toString() + " This attempt No: " + retryAttempt);
+                    loggerResiliency.finer(toString() + " Connection open - attempt end time: " + intervalExpire);
+                    loggerResiliency.finer(toString() + " Connection open - attempt number: " + retryAttempt);
                 }
 
                 // Attempt login. Use Place holder to make sure that the failoverdemand is done.
@@ -3144,6 +3175,10 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
 
                     noOfRedirections++;
 
+                    if (loggerRedirection.isLoggable(Level.FINE)) {
+                        loggerRedirection.fine(toString() + " Connection open - redirection count: " + noOfRedirections);
+                    }
+
                     if (noOfRedirections > 1) {
                         String msg = SQLServerException.getErrString("R_multipleRedirections");
                         terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
@@ -3188,9 +3223,23 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
 
                         continue;
                     }
-                } else
+                } else {
+                    if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) {
+                        loggerResiliency.fine(toString() + " Connection open - connection retry succeeded on attempt number: " + retryAttempt);
+                    }
+
                     break; // leave the while loop -- we've successfully connected
+                }
             } catch (SQLServerException e) {
+
+                if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) {
+                    loggerResiliency.fine(toString() + " Connection open - connection retry failed on attempt number: " + retryAttempt);
+                }
+
+                if (loggerResiliency.isLoggable(Level.FINER) && (retryAttempt >= connectRetryCount)) {
+                    loggerResiliency.finer(toString() + " Connection open - connection failed. Maximum connection retry count " + connectRetryCount + " reached.");
+                }
+
                 int errorCode = e.getErrorCode();
                 driverErrorCode = e.getDriverErrorCode();
                 sqlServerError = e.getSQLServerError();
@@ -3208,15 +3257,26 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                         || timerHasExpired(timerExpire)
                 // for non-dbmirroring cases, do not retry after tcp socket connection succeeds
                 ) {
-                    if (connectionlogger.isLoggable(Level.FINE) && (retryAttempt >= connectRetryCount)) {
-                        connectionlogger.fine(
-                                "Connection failed. Maximum connection retry count " + connectRetryCount + " reached.");
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + ".");
+                        loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode);
+                        if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) {
+                            loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage());
+                        }
                     }
 
                     // close the connection and throw the error back
                     close();
                     throw e;
                 } else {
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + ".");
+                        loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode);
+                        if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) {
+                            loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage());
+                        }
+                    }
+
                     // Close the TDS channel from the failed connection attempt so that we don't
                     // hold onto network resources any longer than necessary.
                     if (null != tdsChannel)
@@ -3231,6 +3291,14 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                 if ((!isDBMirroring || 1 == retryAttempt % 2
                         || TimeUnit.SECONDS.toMillis(connectRetryInterval) >= remainingTime)
                         && (remainingTime <= TimeUnit.SECONDS.toMillis(connectRetryInterval))) {
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + ".");
+                        loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode);
+                        if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) {
+                            loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage());
+                        }
+                    }
+
                     throw e;
                 }
             }
@@ -3244,19 +3312,19 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                     && tlsRetryAttempt < INTERMITTENT_TLS_MAX_RETRY && !timerHasExpired(timerExpire)) {
                 // special case for TLS intermittent failures: no wait retries
                 tlsRetryAttempt++;
-                if (connectionlogger.isLoggable(Level.FINE)) {
-                    connectionlogger.fine(
-                            "Connection failed during SSL handshake. Retry due to an intermittent TLS 1.2 failure issue. Retry attempt = "
+                if (loggerResiliency.isLoggable(Level.FINER)) {
+                    loggerResiliency.finer(toString() +
+                            " Connection open - connection failed during SSL handshake. Retry due to an intermittent TLS 1.2 failure issue. Retry attempt = "
                                     + tlsRetryAttempt + " of " + INTERMITTENT_TLS_MAX_RETRY);
                 }
             } else {
                 if (retryAttempt++ >= connectRetryCount && TransientError.isTransientError(sqlServerError)
                         && !timerHasExpired(timerExpire) && (!isDBMirroring || (1 == retryAttempt % 2))) {
-                    if (connectionlogger.isLoggable(Level.FINE)) {
-                        connectionlogger.fine(toString() + " sleeping milisec: " + connectRetryInterval);
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer(toString() + " Connection open - sleeping milisec: " + connectRetryInterval);
                     }
-                    if (connectionlogger.isLoggable(Level.FINEST)) {
-                        connectionlogger.finest(toString() + "Connection failed on transient error "
+                    if (loggerResiliency.isLoggable(Level.FINER)) {
+                        loggerResiliency.finer(toString() + " Connection open - connection failed on transient error "
                                 + (sqlServerError != null ? sqlServerError.getErrorNumber() : "")
                                 + ". Wait for connectRetryInterval(" + connectRetryInterval + ")s before retry #"
                                 + retryAttempt);
@@ -4081,7 +4149,12 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                                     preparedStatementHandleCache.clear();
                                 }
 
+                                if (loggerResiliency.isLoggable(Level.FINE)) {
+                                    loggerResiliency.fine(toString() + " Idle connection resiliency - starting idle connection resiliency reconnect.");
+                                }
+
                                 sessionRecovery.reconnect(newCommand);
+
                             } catch (InterruptedException e) {
                                 // re-interrupt thread
                                 Thread.currentThread().interrupt();
@@ -4090,10 +4163,11 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                                 SQLServerException.makeFromDriverError(this, sessionRecovery, e.getMessage(), null,
                                         false);
                             }
+
                             if (sessionRecovery.getReconnectException() != null) {
-                                if (connectionlogger.isLoggable(Level.FINER)) {
-                                    connectionlogger.finer(
-                                            this.toString() + "Connection is broken and recovery is not possible.");
+                                if (loggerResiliency.isLoggable(Level.FINER)) {
+                                    loggerResiliency.finer(
+                                            this.toString() + " Idle connection resiliency - connection is broken and recovery is not possible.");
                                 }
                                 throw sessionRecovery.getReconnectException();
                             }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
index 248957b3..ca9e773d 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
@@ -691,4 +691,25 @@ class SQLServerConnectionPoolProxy implements ISQLServerConnection, java.io.Seri
     public void setAccessTokenCallbackClass(String accessTokenCallbackClass) {
         wrappedConnection.setAccessTokenCallbackClass(accessTokenCallbackClass);
     }
+
+    /**
+     * Returns the current value for 'calcBigDecimalScale'.
+     *
+     * @return calcBigDecimalScale
+     *          a boolean
+     */
+    @Override
+    public boolean getCalcBigDecimalScale() {
+        return wrappedConnection.getCalcBigDecimalScale();
+    }
+
+    /**
+     * Sets the current value of 'calculateBigDecimalScale' for the driver.
+     *
+     * @param calcBigDecimalScale
+     */
+    @Override
+    public void setCalcBigDecimalScale(boolean calcBigDecimalScale) {
+        wrappedConnection.setCalcBigDecimalScale(calcBigDecimalScale);
+    }
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
index 52182bbf..626dca78 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
@@ -1326,6 +1326,29 @@ public class SQLServerDataSource
                 null);
     }
 
+    /**
+     * Sets the 'calcBigDecimalScale' setting.
+     *
+     * @param calcBigDecimalScale
+     *         boolean property to have the driver calculate a big decimal's scale from input
+     */
+    @Override
+    public void setCalcBigDecimalScale(boolean calcBigDecimalScale) {
+        setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.toString(),
+                calcBigDecimalScale);
+    }
+
+    /**
+     * Returns the value for 'calcBigDecimalScale'.
+     *
+     * @return computeBigDecimal boolean value
+     */
+    @Override
+    public boolean getCalcBigDecimalScale() {
+        return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.toString(),
+                SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue());
+    }
+
     /**
      * Sets a property string value.
      *
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
index d5f40111..3eac09e9 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
@@ -696,7 +696,8 @@ enum SQLServerDriverBooleanProperty {
     DELAY_LOADING_LOBS("delayLoadingLobs", true),
     IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION("ignoreOffsetOnDateTimeOffsetConversion", false),
     USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false),
-    USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false);
+    USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false),
+    CALC_BIG_DECIMAL_SCALE("calcBigDecimalScale", false);
 
     private final String name;
     private final boolean defaultValue;
@@ -901,6 +902,9 @@ public final class SQLServerDriver implements java.sql.Driver {
             new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
                     Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue()), false,
                     TRUE_FALSE),
+            new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.toString(),
+                    Boolean.toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue()), false,
+                    TRUE_FALSE),
             new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(),
                     SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false,
                     new String[] {SSLProtocol.TLS.toString(), SSLProtocol.TLS_V10.toString(),
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index 0b4e3628..f4fd2a25 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -515,6 +515,7 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_unassignableError", "The class specified by the {0} property must be assignable to {1}."},
         {"R_InvalidCSVQuotes", "Failed to parse the CSV file, verify that the fields are correctly enclosed in double quotes."},
         {"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."},
+        {"R_calcBigDecimalScalePropertyDescription", "Indicates whether the driver should calculate scale for big decimal values."},
         {"R_maxResultBufferPropertyDescription", "Determines maximum amount of bytes that can be read during retrieval of result set"},
         {"R_maxResultBufferInvalidSyntax", "Invalid syntax: {0} in maxResultBuffer parameter."},
         {"R_maxResultBufferNegativeParameterValue", "MaxResultBuffer must have positive value: {0}."},


Test patch:
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
index 37880115..f4496236 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
@@ -205,6 +205,9 @@ public class SQLServerConnectionTest extends AbstractTest {
         assertEquals(booleanPropValue, ds.getUseDefaultGSSCredential(),
                 TestResource.getResource("R_valuesAreDifferent"));
 
+        ds.setCalcBigDecimalScale(booleanPropValue);
+        assertEquals(booleanPropValue, ds.getCalcBigDecimalScale(), TestResource.getResource("R_valuesAreDifferent"));
+
         ds.setServerCertificate(stringPropValue);
         assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent"));
 
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 da2d4d73..bb524b67 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
@@ -509,6 +509,8 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
         verifiedMethodNames.add("setMsiTokenCacheTtl");
         verifiedMethodNames.add("getAccessTokenCallbackClass");
         verifiedMethodNames.add("setAccessTokenCallbackClass");
+        verifiedMethodNames.add("getCalcBigDecimalScale");
+        verifiedMethodNames.add("setCalcBigDecimalScale");
         return verifiedMethodNames;
     }
 }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
index 76f764e4..deb7abc0 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
@@ -41,6 +41,7 @@ import org.junit.platform.runner.JUnitPlatform;
 import org.junit.runner.RunWith;
 
 import com.microsoft.sqlserver.jdbc.RandomUtil;
+import com.microsoft.sqlserver.jdbc.SQLServerConnection;
 import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
 import com.microsoft.sqlserver.jdbc.SQLServerException;
 import com.microsoft.sqlserver.jdbc.SQLServerResultSet;
@@ -1426,6 +1427,234 @@ public class StatementTest extends AbstractTest {
             }
         }
 
+        /**
+         * Tests that big decimal values with a precision less than 38 hold their precision. Tests cases where scale is
+         * 38 (integer part is a 0) and less than 38 (integer part is a non-zero).
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testSmallBigDecimalValuesForLossOfPrecision() throws SQLException {
+            try (SQLServerConnection con = getConnection();
+                 Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) {
+                con.setCalcBigDecimalScale(true);
+                double bigDecimalLessThanOne = 0.1235;
+                double bigDecimalGreaterThanOne = 1.1235;
+                String query = "CREATE PROCEDURE " + procName
+                        + " @col1Value decimal(4,4) OUTPUT, @col2Value decimal(5,4) OUTPUT AS BEGIN SET @col1Value = "
+                        + bigDecimalLessThanOne + " SET @col2Value = " + bigDecimalGreaterThanOne + " END";
+                stmt.execute(query);
+
+                try (CallableStatement cstmt = con.prepareCall("{CALL " + procName + "(?, ?)}")) {
+                    cstmt.registerOutParameter("col1Value", java.sql.Types.DECIMAL, "DECIMAL");
+                    cstmt.registerOutParameter("col2Value", java.sql.Types.DECIMAL, "DECIMAL");
+                    cstmt.execute();
+
+                    // Previously, the leading 0 would be counted as part of the precision. This would lead to the actual
+                    // value being stored as 0.123.
+                    assertEquals(0,
+                            cstmt.getObject("col1Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalLessThanOne)));
+                    assertEquals(0,
+                            cstmt.getObject("col2Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalGreaterThanOne)));
+                }
+            }
+        }
+
+        /**
+         * Tests that big decimal values with a precision equal to 38 hold their precision. Tests cases where scale is
+         * 38 (integer part is a 0) and less than 38 (integer part is a non-zero).
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testLongBigDecimalValuesForLossOfPrecision() throws SQLException {
+            try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
+                con.setCalcBigDecimalScale(true);
+                stmt.executeUpdate("CREATE TABLE " + tableName + " (col1 decimal(38,38), col2 decimal(38,37))");
+
+                // col1 has maximum scale (38) with a leading zero, for a precision of 38. col2 has maximum scale (37) when
+                // using a lead integer other than zero, also resulting in a precision of 38.
+                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(0.98432319763138435186412316842316874322, 1.9843231976313843518641231684231687432)");
+
+                try (PreparedStatement pstmt = con.prepareStatement("SELECT * FROM " + tableName)) {
+
+                    try (ResultSet rs = pstmt.executeQuery()) {
+                        rs.next();
+                        assertEquals(new BigDecimal("0.98432319763138435186412316842316874322"), rs.getObject(1));
+                        assertEquals(new BigDecimal("1.9843231976313843518641231684231687432"), rs.getObject(2));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Tests result of math operation in prepared statement using subtraction
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testMathBigDecimalSubtraction() throws SQLException {
+            try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
+                con.setCalcBigDecimalScale(true);
+                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
+                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
+                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column - ?), "
+                        + "(test_column - ?), (test_column - ?), (test_column - ?) FROM " + tableName)) {
+                    BigDecimal value1 = new BigDecimal("1.5");
+                    pstmt.setObject(1, value1);
+                    BigDecimal value2 = new BigDecimal("0");
+                    pstmt.setObject(2, value2);
+                    BigDecimal value3 = new BigDecimal("99999.12345");
+                    pstmt.setObject(3, value3);
+                    BigDecimal value4 = new BigDecimal("99999.2");
+                    pstmt.setObject(4, value4);
+
+                    BigDecimal base = new BigDecimal("99999.12345");
+                    BigDecimal expected1 = base.subtract(value1);
+                    BigDecimal expected2 = base.subtract(value2);
+                    BigDecimal expected3 = base.subtract(value3);
+                    BigDecimal expected4 = base.subtract(value4);
+
+                    try (ResultSet rs = pstmt.executeQuery()) {
+                        rs.next();
+                        assertEquals(expected1, rs.getObject(1));
+                        assertEquals(expected2, rs.getObject(2));
+                        assertEquals(expected3, rs.getObject(3));
+                        assertEquals(expected4, rs.getObject(4));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Tests result of math operation in prepared statement using addition
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testMathBigDecimalAddition() throws SQLException {
+            try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
+                con.setCalcBigDecimalScale(true);
+                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
+                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
+                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column + ?), "
+                        + "(test_column + ?), (test_column + ?), (test_column + ?) FROM " + tableName)) {
+                    BigDecimal value1 = new BigDecimal("1.5");
+                    pstmt.setObject(1, value1);
+                    BigDecimal value2 = new BigDecimal("0");
+                    pstmt.setObject(2, value2);
+                    BigDecimal value3 = new BigDecimal("99999.12345");
+                    pstmt.setObject(3, value3);
+                    BigDecimal value4 = new BigDecimal("99999.2");
+                    pstmt.setObject(4, value4);
+
+                    BigDecimal base = new BigDecimal("99999.12345");
+                    BigDecimal expected1 = base.add(value1);
+                    BigDecimal expected2 = base.add(value2);
+                    BigDecimal expected3 = base.add(value3);
+                    BigDecimal expected4 = base.add(value4);
+
+                    try (ResultSet rs = pstmt.executeQuery()) {
+                        rs.next();
+                        assertEquals(expected1, rs.getObject(1));
+                        assertEquals(expected2, rs.getObject(2));
+                        assertEquals(expected3, rs.getObject(3));
+                        assertEquals(expected4, rs.getObject(4));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Tests result of math operation in prepared statement using multiplication
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testMathBigDecimalMultiplication() throws SQLException {
+            try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
+                con.setCalcBigDecimalScale(true);
+                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
+                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
+                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column * ?), "
+                        + "(test_column * ?), (test_column * ?), (test_column * ?) FROM " + tableName)) {
+                    BigDecimal value1 = new BigDecimal("1.5");
+                    pstmt.setObject(1, value1);
+                    BigDecimal value2 = new BigDecimal("0");
+                    pstmt.setObject(2, value2);
+                    BigDecimal value3 = new BigDecimal("99999.12345");
+                    pstmt.setObject(3, value3);
+                    BigDecimal value4 = new BigDecimal("99999.2");
+                    pstmt.setObject(4, value4);
+
+                    BigDecimal base = new BigDecimal("99999.12345");
+                    BigDecimal expected1 = base.multiply(value1);
+                    BigDecimal expected2 = base.multiply(value2);
+                    BigDecimal expected3 = base.multiply(value3);
+                    BigDecimal expected4 = base.multiply(value4);
+
+                    try (ResultSet rs = pstmt.executeQuery()) {
+                        rs.next();
+                        assertEquals(expected1, rs.getObject(1));
+                        assertEquals(expected2, rs.getObject(2));
+                        assertEquals(expected3, rs.getObject(3));
+                        assertEquals(expected4, rs.getObject(4));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Tests result of math operation in prepared statement using division
+         *
+         * @throws SQLException
+         *         when an error occurs
+         */
+        @Test
+        public void testMathBigDecimalDivision() throws SQLException {
+            try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
+                con.setCalcBigDecimalScale(true);
+                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
+                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
+                try (PreparedStatement pstmt = con.prepareStatement("select (test_column / ?), "
+                        + "(test_column / ?), (test_column / ?), (test_column / ?) FROM " + tableName)) {
+
+                    /*
+                     * Division has some unique properties in sql server math operations.
+                     * Notably in this case we cannot compare a result with an infinite trailing decimal
+                     * and the returned value has an expanded precision.
+                     */
+                    BigDecimal value1 = new BigDecimal("1.5");
+                    pstmt.setObject(1, value1);
+                    BigDecimal value2 = new BigDecimal("0.1");
+                    pstmt.setObject(2, value2);
+                    BigDecimal value3 = new BigDecimal("99999.12345");
+                    pstmt.setObject(3, value3);
+                    BigDecimal value4 = new BigDecimal("1");
+                    pstmt.setObject(4, value4);
+
+                    BigDecimal base = new BigDecimal("99999.12345");
+                    BigDecimal expected1 = base.divide(value1, RoundingMode.HALF_UP);
+                    BigDecimal expected2 = base.divide(value2, RoundingMode.HALF_UP);
+                    BigDecimal expected3 = base.divide(value3, RoundingMode.HALF_UP);
+                    BigDecimal expected4 = base.divide(value4, RoundingMode.HALF_UP);
+
+                    try (ResultSet rs = pstmt.executeQuery()) {
+                        rs.next();
+                        assertEquals(0, expected1.compareTo((BigDecimal) rs.getObject(1)));
+                        assertEquals(0, expected2.compareTo((BigDecimal) rs.getObject(2)));
+                        assertEquals(0, expected3.compareTo((BigDecimal) rs.getObject(3)));
+                        assertEquals(0, expected4.compareTo((BigDecimal) rs.getObject(4)));
+                    }
+                }
+            }
+        }
+
         /**
          * 
          * @throws Exception