Repository: microsoft/mssql-jdbc
Pull request #2248 (2023-11-07 23:50:16)

Allow old BigDecimal behavior via connection prop
This adds a connection prop to allow old big decimal calculation behavior, which was present in the driver between versions 12.2 and 12.4. This behavior has been known to cause performance issues, but allows the driver to calculate a big decimal's scale and input from the inputted value.


Patch:
diff --git a/pom.xml b/pom.xml
index eb0d5167..3052d34f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
 		<releaseExt>-preview</releaseExt>
 		<!-- Driver Dependencies -->
 		<org.osgi.core.version>6.0.0</org.osgi.core.version>
-		<azure-security-keyvault-keys.version>4.6.4</azure-security-keyvault-keys.version>
+		<azure-security-keyvault-keys.version>4.7.1</azure-security-keyvault-keys.version>
 		<azure-identity.version>1.9.0</azure-identity.version>
 		<msal.version>1.13.8</msal.version>
 		<osgi.jdbc.version>1.1.0</osgi.jdbc.version>
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/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..751ddd7d 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);
 
@@ -2078,6 +2090,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)
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