Keep an escaped value that equals the null string#626
Conversation
garydgregory
left a comment
There was a problem hiding this comment.
Hello @saleemno1
Please see my comment.
| @Test | ||
| void testEscapedNullStringIsAValue() throws Exception { | ||
| // "\N" is the MySQL and PostgreSQL null marker, "\\N" is the value "\N", which is what the printer writes for it. | ||
| for (final CSVFormat format : new CSVFormat[] { CSVFormat.MYSQL, CSVFormat.POSTGRESQL_TEXT, CSVFormat.ORACLE }) { |
There was a problem hiding this comment.
Use a JUnit @ParameterizedTest instead of a loop.
There was a problem hiding this comment.
Done, it's an @ParameterizedTest with @EnumSource now. I also folded PostgreSQLCsv into the list since it has the same \N null string and was missing from the loop.
garydgregory
left a comment
There was a problem hiding this comment.
Hello @saleemno1
Thank you for your update. Please see my comment.
| final CSVFormat format = predefined.getFormat(); | ||
| final StringWriter writer = new StringWriter(); | ||
| try (CSVPrinter printer = new CSVPrinter(writer, format)) { | ||
| printer.printRecord("\\N", null); |
There was a problem hiding this comment.
Refactor the magic string \\N into a constant or a local variable with a name that explains its intent. Since there are 2 such instances in this method, it will also make it obvious that both magic strings are indeed intended to be the same.
|
@saleemno1 Ping on #626 (comment) 👋 |
There was a problem hiding this comment.
Pull request overview
Fixes a null-marker ambiguity when parsing formats that use a null string (for example \N): if a literal value equal to the null string was emitted with escape processing (e.g., \\N on output), the parser previously unescaped it back to \N and incorrectly returned null. The lexer now tracks whether any escape sequence was actually translated for the current token, and the parser only treats an untranslated nullString token as a null marker.
Changes:
- Add
Token.isEscapedand set it in the lexer when escape handling translates input. - Update
CSVParser.handleNull()to preserve values equal tonullStringwhen the token involved escape translation. - Add/adjust unit tests to cover escaped-null-string round-tripping across relevant predefined formats.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/org/apache/commons/csv/Token.java |
Adds a per-token flag to record whether escape translation occurred while building token content. |
src/main/java/org/apache/commons/csv/Lexer.java |
Sets the new flag when an escape sequence is translated into token content. |
src/main/java/org/apache/commons/csv/CSVParser.java |
Uses the new lexer signal to avoid treating escaped literals as null markers. |
src/test/java/org/apache/commons/csv/CSVParserTest.java |
Adds a parameterized regression test for escaped nullString values. |
src/test/java/org/apache/commons/csv/CSVPrinterTest.java |
Updates random round-trip expectations to account for when nullString is printed verbatim vs escaped/quoted. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Updated comment for clarity on handling null strings in CSV formats.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
src/test/java/org/apache/commons/csv/CSVPrinterTest.java:168
expectNullscurrently assumes that any non-verbatim rendering of the null string means the parser will read the value back as a literal. This is not true whenQuoteMode.ALLis used: bothnulland a literal value equal tonullStringare printed quoted, so they are indistinguishable and the parser will still producenull. Consider treatingQuoteMode.ALLas indistinguishable from verbatim printing for this helper.
final String nullString = csvFormat.getNullString();
if (nullString == null || !printsVerbatim(csvFormat, nullString)) {
return fixed;
}
src/test/java/org/apache/commons/csv/CSVPrinterTest.java:161
- This JavaDoc implies that quoting the null string is sufficient for the parser to read it back as a literal value. In this codebase, quoted null markers are only distinguished in strict quote modes (ALL_NON_NULL / NON_NUMERIC); otherwise a quoted value equal to the null string is still parsed as null.
* Converts an input CSV array into expected output values, including NULLs. A value equal to the null string is converted to null only when the printer
* writes that value unchanged; when the printer escapes or quotes it, the parser reads it back as the value it is.
| // An escaped value is the null string itself, not the null marker: printing null writes the null string | ||
| // verbatim, so "\\N" for nullString "\N" can only have come from a value that really is "\N". |
Updated comment for clarity on null string handling.
garydgregory
left a comment
There was a problem hiding this comment.
@saleemno1
Please review the copilot comments and at least improve the code comments.
TY!
|
@saleemno1 ping 🔔 |
CSVParser.handleNullcompares the fully unescaped token against the format's null string, so a value that really is\Nand that the printer correctly wrote as\\NforMYSQL,POSTGRESQL_TEXT,POSTGRESQL_CSVandORACLEcomes back asnull; the lexer now records whether an escape sequence was actually translated, and only an untranslated token can be the null marker.mvn; that'smvnon the command line by itself.