Jorge Molina created SPARK-58458:
------------------------------------

             Summary: No test covers the CSV record split caused by a quoted 
line break when multiLine is disabled
                 Key: SPARK-58458
                 URL: https://issues.apache.org/jira/browse/SPARK-58458
             Project: Spark
          Issue Type: Test
          Components: SQL
    Affects Versions: 5.0.0
            Reporter: Jorge Molina


A line break inside a quoted value is valid CSV (RFC 4180 §2.6). With the 
default
`multiLine=false`, the CSV reader cuts such a record at the break: the head 
keeps its
leading fields with the rest of the schema nulled, and the remainder of the 
quoted
value starts a new record. The row count rises by one.

`CSVSuite` does not cover this. The suite contains exactly one input literal 
with a
newline inside quotes, in {{lineSep with 2 chars when multiLine set to ...}}, 
and it is
confined to the {{multiLine = true}} branch:

{code:scala}
val inputData = if (multiLine) {
  s"""name,"i am the${newLine} column1"${newLine}jack,30${newLine}tom,18"""
} else {
  s"name,age${newLine}jack,30${newLine}tom,18"
}
{code}

The {{multiLine = false}} branch drops the quoted break, so the default path is 
never
exercised with the input that distinguishes it. `UnivocityParserSuite` does not 
cover
it either.

h3. Why it is worth a test

The behaviour follows from the non-multiLine read path being line-oriented by 
design:
`HadoopFileLinesReader` splits the input on the line separator before the 
tokenizer
sees it, and `UnivocityParser.parseLine` then receives one physical line at a 
time.
That is what makes the file splittable, and this ticket does *not* propose 
changing it.
It proposes pinning what the current design produces, so a future change to 
that path
cannot alter it silently.

Two consequences are worth locking down in particular:

* {{DROPMALFORMED}} discards *both* halves, so the whole source record is lost 
rather
  than the damaged half — the opposite of what the mode's name suggests for one 
bad
  line.
* {{PERMISSIVE}} without {{columnNameOfCorruptRecord}} — the default — retains 
both
  halves with no signal. The split *adds* a row while rejecting the fragment 
*removes*
  one, so a row-count reconciliation can pass over a record that lost its 
trailing
  fields.

h3. Measured behaviour to be asserted

Measured on `pyspark` 3.5.9 — three records with a quoted line break in the 
second,
explicit three-column schema. *These are the values to be asserted, not yet 
confirmed
on `master`* (5.0.0-SNAPSHOT); the fork's CI run is what confirms them, and the 
PR will
not be opened before it is green.

|| read || rows ||
| default ({{multiLine=false}}) | 4: {{("1","ACME LTDA","SP")}}, 
{{("2","EMPRESA COM",null)}}, {{("QUEBRA DE LINHA\"","RJ",null)}}, 
{{("3","OUTRA EMPRESA","MG")}} |
| {{multiLine=true}} | 3, break preserved inside the value |
| {{mode=DROPMALFORMED}} | 2: records 1 and 3; record 2 lost entirely |
| {{columnNameOfCorruptRecord}} declared | 4, both halves carrying their raw 
text |

PR to follow, test-only, one test added to `CSVSuite`.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to