From 729520d05c02e22c75be5f0e168f7cd4d454958c Mon Sep 17 00:00:00 2001 From: Jeffery Wasty Date: Fri, 3 Nov 2023 13:47:04 -0700 Subject: [PATCH] Check for maximum scale in IOBuffer before array copy (#2239) * Simple check to see if the input exceeds scale of 38. * Added test * Update failing tests * Cleanup * More cleanup * Move to TVPTypesTest * The scale check was somehow failing coco, instead moved to just catch the IndexOutOfBoundsException * ArrayIndexOutOfBoundsException not IndexOutOfBoundsException * More general * Revert * This should have been rolled back in the retry PR * This test will be removed in a separate PR * ... * Changed the error message * Forgot to change the test as well * Better error --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 14 ++++++++-- .../sqlserver/jdbc/SQLServerResource.java | 3 +- .../sqlserver/jdbc/tvp/TVPTypesTest.java | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 89fab7c785..6a2f5a8de1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5028,7 +5028,8 @@ void writeTVPRows(TVP value) throws SQLServerException { } private void writeInternalTVPRowValues(JDBCType jdbcType, String currentColumnStringValue, Object currentObject, - Map.Entry columnPair, boolean isSqlVariant) throws SQLServerException { + Map.Entry columnPair, boolean isSqlVariant) + throws SQLServerException, IllegalArgumentException { boolean isShortValue, isNull; int dataLength; switch (jdbcType) { @@ -5100,9 +5101,18 @@ private void writeInternalTVPRowValues(JDBCType jdbcType, String currentColumnSt /* * setScale of all BigDecimal value based on metadata as scale is not sent separately for individual - * value. Use the rounding used in Server. Say, for BigDecimal("0.1"), if scale in metdadata is 0, + * value. Use the rounding used in Server. Say, for BigDecimal("0.1"), if scale in metadata is 0, * then ArithmeticException would be thrown if RoundingMode is not set + * + * Additionally, we should check here if the scale is within the bounds of SQLServer as it is + * possible for a number with a scale larger than 38 to be passed in. */ + if (columnPair.getValue().scale > SQLServerConnection.MAX_DECIMAL_PRECISION) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidScale")); + Object[] msgArgs = {columnPair.getValue().scale}; + throw new IllegalArgumentException(form.format(msgArgs)); + } + bdValue = bdValue.setScale(columnPair.getValue().scale, RoundingMode.HALF_UP); byte[] val = DDC.convertBigDecimalToBytes(bdValue, bdValue.scale()); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index effb8ae6d1..0b4e362858 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -540,7 +540,8 @@ protected Object[][] getContents() { {"R_serverError", "An error occurred during the current command (Done status {0}). {1}"}, {"R_ManagedIdentityTokenAcquisitionFail", "Failed to acquire managed identity token. Request for the token succeeded, but no token was returned. The token is null."}, {"R_AmbiguousRowUpdate", "Failed to execute updateRow(). The update is attempting an ambiguous update on tables \"{0}\" and \"{1}\". Ensure all columns being updated prior to the updateRow() call belong to the same table."}, - {"R_InvalidSqlQuery", "Invalid SQL Query: {0}"} + {"R_InvalidSqlQuery", "Invalid SQL Query: {0}"}, + {"R_InvalidScale", "Scale of input value is larger than the maximum allowed by SQL Server."} }; } // @formatter:on diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java index e56ec220f2..5239d2fff9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -573,6 +574,33 @@ public String toString() { } } + /** + * Numeric (bigdecimal) with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPNumericStoredProcedure() throws SQLException { + createTables("numeric(10,2)"); + createTVPS("numeric(38,10)"); + createProcedure(); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.NUMERIC); + tvp.addRow(new BigDecimal(0.222)); + + final String sql = "{call " + AbstractSQLGenerator.escapeIdentifier(procedureName) + "(?)}"; + + try (SQLServerCallableStatement callableStmt = (SQLServerCallableStatement) connection.prepareCall(sql)) { + callableStmt.setStructured(1, tvpName, tvp); + callableStmt.execute(); + + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_InvalidScale")), e.getMessage()); + } + } + /** * Negative test cases for testing Boolean with TVP *