From 03cfcfd3f298a56f690d0a4d298fb6d91eaae635 Mon Sep 17 00:00:00 2001 From: Mahendra Chavan Date: Mon, 27 Jan 2025 16:38:13 +0530 Subject: [PATCH] Fixed issue with SQLServerBulkCopy from CSV with setEscapeColumnDelimerts set to true (#2575) * Fixed issue with SQLServerBulkCopy from CSV for setEscapeColumnDelimitersCSV * Fixed scenario with last line having blank content * Simplied if condition * Changed sb.isEmpty to sb.toString().isEmpty() * Use length instead of toString * Added dropTable in finally block. --- .../jdbc/SQLServerBulkCSVFileRecord.java | 2 +- .../jdbc/bulkCopy/BulkCopyCSVTest.java | 59 +++++++++++++++++++ ...TestInputDelimiterEscapeNoNewLineAtEnd.csv | 15 +++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java index 2db4339fa..1b21544f9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java @@ -246,7 +246,7 @@ private String readLineEscapeDelimiters() throws SQLServerException { if (c == -1 && quoteCount % 2 != 0) { // stream ended, but we are within quotes -- data problem throw new SQLServerException(SQLServerException.getErrString("R_InvalidCSVQuotes"), null, 0, null); } - if (c == -1) { // keep semantics of readLine() by returning a null when there is no more data + if ((c == -1) && (sb.length() == 0)) { // keep semantics of readLine() by returning a null when there is no more data return null; } } catch (IOException e) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java index 2a8fdb0a0..057c5068c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java @@ -66,6 +66,7 @@ public class BulkCopyCSVTest extends AbstractTest { static String inputFile = "BulkCopyCSVTestInput.csv"; static String inputFileNoColumnName = "BulkCopyCSVTestInputNoColumnName.csv"; static String inputFileDelimiterEscape = "BulkCopyCSVTestInputDelimiterEscape.csv"; + static String inputFileDelimiterEscapeNoNewLineAtEnd = "BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv"; static String inputFileMultipleDoubleQuotes = "BulkCopyCSVTestInputMultipleDoubleQuotes.csv"; static String encoding = "UTF-8"; static String delimiter = ","; @@ -197,12 +198,70 @@ public void testEscapeColumnDelimitersCSV() throws Exception { assertEquals(expectedEscaped[i][3], rs.getString("c4")); i++; } + assertEquals(i, 12, "Expected to load 12 records, but loaded " + i + " records"); } TestUtils.dropTableIfExists(tableName, stmt); } } + @Test + @DisplayName("Test setEscapeColumnDelimitersCSVNoNewLineAtEnd") + public void testEscapeColumnDelimitersCSVNoNewLineAtEnd() throws Exception { + String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("BulkEscape")); + String fileName = filePath + inputFileDelimiterEscapeNoNewLineAtEnd; + /* + * The list below is the copy of inputFileDelimiterEsc ape with quotes removed. + */ + String[][] expectedEscaped = new String[12][4]; + expectedEscaped[0] = new String[] {"test", " test\"", "no@split", " testNoQuote", ""}; + expectedEscaped[1] = new String[] {null, null, null, null, ""}; + expectedEscaped[2] = new String[] {"\"", "test\"test", "test@\" test", null, ""}; + expectedEscaped[3] = new String[] {"testNoQuote ", " testSpaceAround ", " testSpaceInside ", + " testSpaceQuote\" ", ""}; + expectedEscaped[4] = new String[] {null, null, null, " testSpaceInside ", ""}; + expectedEscaped[5] = new String[] {"1997", "Ford", "E350", "E63", ""}; + expectedEscaped[6] = new String[] {"1997", "Ford", "E350", "E63", ""}; + expectedEscaped[7] = new String[] {"1997", "Ford", "E350", "Super@ luxurious truck", ""}; + expectedEscaped[8] = new String[] {"1997", "Ford", "E350", "Super@ \"luxurious\" truck", ""}; + expectedEscaped[9] = new String[] {"1997", "Ford", "E350", "E63", ""}; + expectedEscaped[10] = new String[] {"1997", "Ford", "E350", " Super luxurious truck ", ""}; + expectedEscaped[11] = new String[] {"1997", "F\r\no\r\nr\r\nd", "E350", "\"Super\" \"luxurious\" \"truck\"", + ""}; + + try (Connection con = getConnection(); Statement stmt = con.createStatement(); + SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(con); + SQLServerBulkCSVFileRecord fileRecord = new SQLServerBulkCSVFileRecord(fileName, encoding, "@", + false)) { + bulkCopy.setDestinationTableName(tableName); + fileRecord.setEscapeColumnDelimitersCSV(true); + fileRecord.addColumnMetadata(1, null, java.sql.Types.INTEGER, 0, 0); + fileRecord.addColumnMetadata(2, null, java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(3, null, java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(4, null, java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(5, null, java.sql.Types.VARCHAR, 50, 0); + fileRecord.addColumnMetadata(6, null, java.sql.Types.VARCHAR, 50, 0); + stmt.executeUpdate("CREATE TABLE " + tableName + + " (id INT IDENTITY(1,1), c1 VARCHAR(50), c2 VARCHAR(50), c3 VARCHAR(50), c4 VARCHAR(50), c5 VARCHAR(50))"); + bulkCopy.writeToServer(fileRecord); + + int i = 0; + try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id"); + BufferedReader br = new BufferedReader(new FileReader(fileName));) { + while (rs.next()) { + assertEquals(expectedEscaped[i][0], rs.getString("c1")); + assertEquals(expectedEscaped[i][1], rs.getString("c2")); + assertEquals(expectedEscaped[i][2], rs.getString("c3")); + assertEquals(expectedEscaped[i][3], rs.getString("c4")); + i++; + } + assertEquals(i, 12, "Expected to load 12 records, but loaded " + i + " records"); + } finally { + TestUtils.dropTableIfExists(tableName, stmt); + } + } + } + /** * test simple csv file for bulkcopy, for GitHub issue 1391 Tests to ensure that the set returned by * getColumnOrdinals doesn't have to be ordered diff --git a/src/test/resources/BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv b/src/test/resources/BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv new file mode 100644 index 000000000..98ac9f114 --- /dev/null +++ b/src/test/resources/BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv @@ -0,0 +1,15 @@ +1@"test"@ " test"""@ "no@split" @ testNoQuote@ +2@""@ ""@ ""@ ""@ +3@""""@ "test""test"@ "test@"" test"@ ""@ +4@testNoQuote @ testSpaceAround @ " testSpaceInside "@ " testSpaceQuote"" "@ +5@""@ ""@ ""@ " testSpaceInside "@ +6@1997@Ford@E350@E63@ +7@"1997"@"Ford"@"E350"@"E63"@ +8@1997@Ford@E350@"Super@ luxurious truck"@ +9@1997@Ford@E350@"Super@ ""luxurious"" truck"@ +10@1997@ "Ford" @E350@ "E63"@ +11@1997@Ford@E350@" Super luxurious truck "@ +12@1997@"F +o +r +d"@"E350"@"""Super"" ""luxurious"" ""truck"""@ \ No newline at end of file