diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index bb709307d..7a64afea3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5032,7 +5032,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) { @@ -5104,9 +5105,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 effb8ae6d..0b4e36285 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 e56ec220f..5239d2fff 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 *