Skip to content

Commit

Permalink
Check that money/small money are within correct range for bulkcopy (#…
Browse files Browse the repository at this point in the history
…2379)

* Fixed 2309

* CHANGELOG fix

* Review fixes

* More review + format

* More changes
  • Loading branch information
Jeffery-Wasty authored Apr 4, 2024
1 parent 78bdefd commit 5b3f32e
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
- Fixed ClassLoader leak of ActivityCorrelator ThreadLocal [#2366] (https://github.com/microsoft/mssql-jdbc/pull/2366)
- Check if TDSCommand counter is null before incrementing. [#2368] (https://github.com/microsoft/mssql-jdbc/pull/2368)
- Escape schema for getProcedures and getProcedureColumns in SQLServerDatabaseMetaData [#2369] (https://github.com/microsoft/mssql-jdbc/pull/2369)
- Fix to properly validate money and small money values for BulkCopy [#2379] (https://github.com/microsoft/mssql-jdbc/pull/2379)

## [12.6.0] Stable Release
### Changed
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/microsoft/sqlserver/jdbc/DDC.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
/*
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.microsoft.sqlserver.jdbc.bulkCopy;

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.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import com.microsoft.sqlserver.jdbc.RandomUtil;
import com.microsoft.sqlserver.jdbc.SQLServerBulkCSVFileRecord;
import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy;
import com.microsoft.sqlserver.jdbc.TestResource;
import com.microsoft.sqlserver.jdbc.TestUtils;
import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
import com.microsoft.sqlserver.testframework.AbstractTest;
import com.microsoft.sqlserver.testframework.Constants;
import com.microsoft.sqlserver.testframework.PrepUtil;


/**
* Tests money/smallmoney limits with BulkCopy
*/
@RunWith(JUnitPlatform.class)
public class BulkCopyMoneyTest extends AbstractTest {
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
/**
* Tests money and smallmoney with bulkcopy using minimum and maximum values of each
*/
public void testMoneyWithBulkCopy() throws Exception {
try (Connection conn = PrepUtil.getConnection(connectionString)) {
testMoneyLimits(-214799.3648, 922337203685387.5887, conn); // SMALLMONEY MIN
testMoneyLimits(214799.3698, 922337203685387.5887, conn); // SMALLMONEY MAX
testMoneyLimits(214719.3698, -922337203685497.5808, conn); // MONEY MIN
testMoneyLimits(214719.3698, 922337203685478.5807, conn); // 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<Object, Object> 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 = null;
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);
}
}
}

@BeforeAll
public static void setupTests() 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);
}
}

@AfterAll
public static void cleanUp() throws Exception {
try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
TestUtils.dropTableIfExists(destTableName, stmt);
TestUtils.dropTableIfExists(destTableName2, stmt);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ 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 enum LOB {
CLOB,
Expand Down

0 comments on commit 5b3f32e

Please sign in to comment.