This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new e3cd4df38b [format] Keep parsing remaining fields after a malformed 
one in PERMISSIVE CSV mode (#9556)
e3cd4df38b is described below

commit e3cd4df38beebe13adf3f558fe054c12c400af42
Author: YangJie <[email protected]>
AuthorDate: Thu Sep 3 04:20:26 2026 -0400

    [format] Keep parsing remaining fields after a malformed one in PERMISSIVE 
CSV mode (#9556)
---
 .../org/apache/paimon/format/csv/CsvParser.java    |  3 +-
 .../paimon/format/csv/CsvFileFormatTest.java       | 38 ++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/csv/CsvParser.java 
b/paimon-format/src/main/java/org/apache/paimon/format/csv/CsvParser.java
index 58f7b790c2..c72b72be8e 100644
--- a/paimon-format/src/main/java/org/apache/paimon/format/csv/CsvParser.java
+++ b/paimon-format/src/main/java/org/apache/paimon/format/csv/CsvParser.java
@@ -180,7 +180,8 @@ public class CsvParser {
                 row.setField(i, parseResult.getValue());
             } else if (mode == PERMISSIVE
                     && (parseResult == null || !parseResult.getLeft() || 
exception != null)) {
-                break;
+                // Only this field is malformed, so keep parsing the rest of 
the row.
+                row.setField(i, null);
             } else if (mode == DROPMALFORMED
                     && (parseResult == null || !parseResult.getLeft() || 
exception != null)) {
                 return null;
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/csv/CsvFileFormatTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/csv/CsvFileFormatTest.java
index 47fc367726..a7033bfe2f 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/csv/CsvFileFormatTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/csv/CsvFileFormatTest.java
@@ -639,6 +639,44 @@ public class CsvFileFormatTest extends FormatReadWriteTest 
{
         assertThat(permissiveResult.get(3).getDouble(2)).isEqualTo(400.81);
     }
 
+    @Test
+    public void testCsvPermissiveKeepsFieldsAfterMalformed() throws 
IOException {
+        RowType rowType = DataTypes.ROW(DataTypes.INT(), DataTypes.STRING(), 
DataTypes.DOUBLE());
+        Options options = new Options();
+        options.set(CsvOptions.MODE, CsvOptions.Mode.PERMISSIVE);
+        FileFormat format =
+                new CsvFileFormatFactory().create(new FormatContext(options, 
1024, 1024));
+        Path testFile = new Path(parent, "permissive_first_" + 
UUID.randomUUID() + ".csv");
+
+        // Malformed field in the first position: PERMISSIVE must null only the
+        // offending field and keep the valid fields after it.
+        fileIO.writeFile(testFile, "x,Alice,1.5\n3,Carol,3.5", false);
+        List<InternalRow> result = read(format, rowType, rowType, testFile);
+        assertThat(result).hasSize(2);
+        assertThat(result.get(0).isNullAt(0)).isTrue();
+        assertThat(result.get(0).getString(1)).isEqualTo(fromString("Alice"));
+        assertThat(result.get(0).getDouble(2)).isEqualTo(1.5);
+        assertThat(result.get(1).getInt(0)).isEqualTo(3);
+        assertThat(result.get(1).getString(1)).isEqualTo(fromString("Carol"));
+        assertThat(result.get(1).getDouble(2)).isEqualTo(3.5);
+
+        // PERMISSIVE is the default mode, so this format is built without 
setting csv.mode.
+        // Covers a malformed field in the middle and two malformed fields in 
one row.
+        RowType midRowType = DataTypes.ROW(DataTypes.INT(), 
DataTypes.DOUBLE(), DataTypes.STRING());
+        FileFormat defaultFormat =
+                new CsvFileFormatFactory().create(new FormatContext(new 
Options(), 1024, 1024));
+        Path midFile = new Path(parent, "permissive_middle_" + 
UUID.randomUUID() + ".csv");
+        fileIO.writeFile(midFile, "1,oops,world\ny,bad,keep", false);
+        List<InternalRow> midResult = read(defaultFormat, midRowType, 
midRowType, midFile);
+        assertThat(midResult).hasSize(2);
+        assertThat(midResult.get(0).getInt(0)).isEqualTo(1);
+        assertThat(midResult.get(0).isNullAt(1)).isTrue();
+        
assertThat(midResult.get(0).getString(2)).isEqualTo(fromString("world"));
+        assertThat(midResult.get(1).isNullAt(0)).isTrue();
+        assertThat(midResult.get(1).isNullAt(1)).isTrue();
+        
assertThat(midResult.get(1).getString(2)).isEqualTo(fromString("keep"));
+    }
+
     @Test
     public void testCsvParserParseField() {
         RowType rowType =

Reply via email to