From e411e0bb05ef911dfc55af90d66c34f666b24165 Mon Sep 17 00:00:00 2001 From: Jeff Wasty Date: Wed, 22 May 2024 11:51:15 -0700 Subject: [PATCH] Backport 2379/2383 (#2419) --- .../com/microsoft/sqlserver/jdbc/DDC.java | 4 +- .../jdbc/bulkCopy/BulkCopyAllTypesTest.java | 126 ++++++++++++++++-- .../sqlserver/testframework/Constants.java | 5 + 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 57dab6643..03c95f65c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -344,13 +344,14 @@ static final byte[] convertBigDecimalToBytes(BigDecimal bigDecimalVal, int scale return valueBytes; } - static final byte[] convertMoneyToBytes(BigDecimal bigDecimalVal, int bLength) { + static final byte[] convertMoneyToBytes(BigDecimal bigDecimalVal, int bLength) throws SQLServerException { byte[] valueBytes = new byte[bLength]; BigInteger bi = bigDecimalVal.unscaledValue(); if (bLength == 8) { // money + Util.validateMoneyRange(bigDecimalVal, JDBCType.MONEY); byte[] longbArray = new byte[bLength]; Util.writeLong(bi.longValue(), longbArray, 0); /* @@ -362,6 +363,7 @@ static final byte[] convertMoneyToBytes(BigDecimal bigDecimalVal, int bLength) { System.arraycopy(longbArray, 4, valueBytes, 0, 4); } else { // smallmoney + Util.validateMoneyRange(bigDecimalVal, JDBCType.SMALLMONEY); Util.writeInt(bi.intValue(), valueBytes, 0); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java index 660406ce5..fcf4335c2 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java @@ -4,13 +4,33 @@ */ package com.microsoft.sqlserver.jdbc.bulkCopy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.sql.RowSetMetaData; +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetFactory; +import javax.sql.rowset.RowSetMetaDataImpl; +import javax.sql.rowset.RowSetProvider; + +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -20,8 +40,10 @@ import com.microsoft.sqlserver.jdbc.ComparisonUtil; import com.microsoft.sqlserver.jdbc.RandomData; import com.microsoft.sqlserver.jdbc.RandomUtil; +import com.microsoft.sqlserver.jdbc.SQLServerBulkCSVFileRecord; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions; +import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; @@ -31,20 +53,6 @@ import com.microsoft.sqlserver.testframework.DBTable; import com.microsoft.sqlserver.testframework.PrepUtil; -import javax.sql.RowSetMetaData; -import javax.sql.rowset.CachedRowSet; -import javax.sql.rowset.RowSetFactory; -import javax.sql.rowset.RowSetMetaDataImpl; -import javax.sql.rowset.RowSetProvider; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - @RunWith(JUnitPlatform.class) public class BulkCopyAllTypesTest extends AbstractTest { @@ -55,12 +63,26 @@ public class BulkCopyAllTypesTest extends AbstractTest { @BeforeAll public static void setupTests() throws Exception { setConnection(); + setupMoneyTests(); + } + + public static void setupMoneyTests() throws SQLException { + try (Connection con = getConnection(); Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(destTableName, stmt); + TestUtils.dropTableIfExists(destTableName2, stmt); + + String table = "create table " + destTableName + " (c1 smallmoney, c2 money)"; + stmt.execute(table); + table = "create table " + destTableName2 + " (c1 smallmoney, c2 money)"; + stmt.execute(table); + } } /** * Test TVP with result set * * @throws SQLException + * an exception */ @Test @Tag(Constants.xAzureSQLDW) @@ -185,4 +207,80 @@ public void testBulkCopyTimestamp() throws SQLException { private static long getTime(Timestamp time) { return (3 * time.getTime() + 5) / 10; } + + static String encoding = Constants.UTF8; + static String delimiter = Constants.COMMA; + static String destTableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("moneyBulkCopyDest")); + static String destTableName2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("moneyBulkCopyDest")); + + /** + * Test money/smallmoney with BulkCopy + * + * @throws SQLException + * an exception + */ + @Test + public void testMoneyWithBulkCopy() throws Exception { + try (Connection conn = PrepUtil.getConnection(connectionString)) { + testMoneyLimits(Constants.MIN_VALUE_SMALLMONEY - 1, Constants.MAX_VALUE_MONEY - 1, conn); // 1 less than SMALLMONEY MIN + testMoneyLimits(Constants.MAX_VALUE_SMALLMONEY + 1, Constants.MAX_VALUE_MONEY - 1, conn); // 1 more than SMALLMONEY MAX + testMoneyLimits(Constants.MAX_VALUE_SMALLMONEY - 1, Constants.MIN_VALUE_MONEY - 1, conn); // 1 less than MONEY MIN + testMoneyLimits(Constants.MAX_VALUE_SMALLMONEY - 1, Constants.MAX_VALUE_MONEY + 1, conn); // 1 more than MONEY MAX + } + } + + private void testMoneyLimits(double smallMoneyVal, double moneyVal, Connection conn) throws Exception { + SQLServerBulkCSVFileRecord fileRecord = constructFileRecord(smallMoneyVal, moneyVal); + + try { + testMoneyWithBulkCopy(conn, fileRecord); + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } catch (SQLException e) { + assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_valueOutOfRange")), e.getMessage()); + } + } + + private SQLServerBulkCSVFileRecord constructFileRecord(double smallMoneyVal, double moneyVal) throws Exception { + Map data = new HashMap(); + data.put(smallMoneyVal, moneyVal); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("smallmoneycol, moneycol\n"); + + for (Map.Entry entry : data.entrySet()) { + stringBuilder.append(String.format("%s,%s\n", entry.getKey(), entry.getValue())); + } + + byte[] bytes = stringBuilder.toString().getBytes(StandardCharsets.UTF_8); + SQLServerBulkCSVFileRecord fileRecord; + try (InputStream inputStream = new ByteArrayInputStream(bytes)) { + fileRecord = new SQLServerBulkCSVFileRecord(inputStream, encoding, delimiter, true); + } + return fileRecord; + } + + private void testMoneyWithBulkCopy(Connection conn, SQLServerBulkCSVFileRecord fileRecord) throws SQLException { + try (SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn); Statement stmt = conn.createStatement()) { + + fileRecord.addColumnMetadata(1, "c1", java.sql.Types.DECIMAL, 10, 4); // with smallmoney + fileRecord.addColumnMetadata(2, "c2", java.sql.Types.DECIMAL, 19, 4); // with money + + bulkCopy.setDestinationTableName(destTableName); + bulkCopy.writeToServer(fileRecord); + + try (ResultSet rs = stmt.executeQuery("select * FROM " + destTableName + " order by c1"); + SQLServerBulkCopy bcOperation = new SQLServerBulkCopy(conn)) { + bcOperation.setDestinationTableName(destTableName2); + bcOperation.writeToServer(rs); + } + } + } + + @AfterAll + public static void cleanUp() throws Exception { + try (Connection con = getConnection(); Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(destTableName, stmt); + TestUtils.dropTableIfExists(destTableName2, stmt); + } + } } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index 92ba90811..e4338fc92 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -179,6 +179,11 @@ private Constants() {} public static final String SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY = "SENDTEMPORALDATATYPESASSTRINGFORBULKCOPY"; public static final String PREPARE_METHOD = "PREPAREMETHOD"; public static final String CONFIG_PROPERTIES_FILE = "config.properties"; + public static final String UTF8 = "UTF-8"; + public static final double MAX_VALUE_MONEY = 922337203685477.5807; + public static final double MIN_VALUE_MONEY = -922337203685477.5808; + public static final double MAX_VALUE_SMALLMONEY = 214748.3647; + public static final double MIN_VALUE_SMALLMONEY = -214748.3648; public enum LOB { CLOB,