Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert #2051 (Big decimal precision) / #2116 (Fix for bigDecimal values between 0 and 1 having too high of a precision) #2176

Merged
merged 4 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -531,36 +531,9 @@ private void setTypeDefinition(DTV dtv) {
param.typeDefinition = SSType.DECIMAL.toString() + "(" + valueLength + "," + scale + ")";
}
} else {
if (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 + ")";
}
param.typeDefinition = SSType.DECIMAL.toString() + "("
+ SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")";
}

break;

case MONEY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1426,228 +1426,6 @@ 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 (Connection con = getConnection();
Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) {
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 (Connection con = getConnection(); Statement stmt = con.createStatement()) {
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 (Connection con = getConnection(); Statement stmt = con.createStatement()) {
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 (Connection con = getConnection(); Statement stmt = con.createStatement()) {
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 (Connection con = getConnection(); Statement stmt = con.createStatement()) {
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 (Connection con = getConnection(); Statement stmt = con.createStatement()) {
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
Expand Down
Loading