From 1f69f481228f52a3c4c76b914b284cc579563542 Mon Sep 17 00:00:00 2001 From: Jeffery Wasty Date: Wed, 15 Nov 2023 10:00:53 -0800 Subject: [PATCH] Allow old BigDecimal behavior via connection prop (#2248) * Ini * More * tests * ds stuff * Junit was added to pom for some reason * Resolve build fails * A change * SQLServerResource * RequestBoundaryMethodsTest * Why is fx failing? * Fix pom * Fix pom again * Fix pom some more * Ok * Rename + fixes * whoops * one more thing * wth keep messing this up --- .../sqlserver/jdbc/ISQLServerConnection.java | 16 ++ .../sqlserver/jdbc/ISQLServerDataSource.java | 15 ++ .../microsoft/sqlserver/jdbc/Parameter.java | 31 ++- .../sqlserver/jdbc/SQLServerConnection.java | 21 ++ .../jdbc/SQLServerConnectionPoolProxy.java | 21 ++ .../sqlserver/jdbc/SQLServerDataSource.java | 23 ++ .../sqlserver/jdbc/SQLServerDriver.java | 6 +- .../sqlserver/jdbc/SQLServerResource.java | 1 + .../jdbc/SQLServerConnectionTest.java | 3 + .../RequestBoundaryMethodsTest.java | 2 + .../jdbc/unit/statement/StatementTest.java | 229 ++++++++++++++++++ 11 files changed, 365 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 8025410c9..314e64435 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 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * @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 3bfc279a5..1e6238756 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 aa64c87b8..fe0985003 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 @@ private void setTypeDefinition(DTV dtv) { 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 d8d19a8be..751ddd7d7 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 void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDate 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 @@ Connection connectInternal(Properties propsIn, 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 248957b3a..ca9e773df 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 @@ public String getAccessTokenCallbackClass() { 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 52182bbfd..626dca789 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 String getAccessTokenCallbackClass() { 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 d5f401115..3eac09e9f 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 0b4e36285..f4fd2a253 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 @@ protected Object[][] getContents() { {"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}."}, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 37880115a..f44962367 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 void testDataSource() throws SQLServerException { 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 da2d4d73f..bb524b67f 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 @@ private List getVerifiedMethodNames() { 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 76f764e41..deb7abc02 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.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 void testFailedToResumeTransaction() throws Exception { } } + /** + * 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