Skip to content

fix: CSV empty cells become null; CLI filter on nullable columns - #593

Merged
dfa1 merged 3 commits into
mainfrom
fix/csv-empty-cells-null
Oct 23, 2026
Merged

dfa1 merged 3 commits into
mainfrom
fix/csv-empty-cells-null

Conversation

@dfa1

@dfa1 dfa1 commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Fixes #592. Two bugs, two commits (rebase-merge).

fix(csv): an empty CSV cell in a number or boolean column was stored as 0/0.0/false. Inferred number and boolean columns are now nullable and store an empty cell as null; inferred text keeps "" (Arrow's default). With an explicit schema: nullable → null (text too), non-nullable number/boolean → the import fails on an empty cell instead of storing a value.

Every inferred number/boolean column is nullable, not only those with an empty cell in the first 65 536 rows, as Arrow and DuckDB infer them, so a late empty cell is a null instead of a failed import. Measured on the 2.7 GB CSV (no empty cells): +0.008% file size (+67 KB on 875 MB), a few % write CPU, same wall time.

fix(cli): found while measuring the read side: filter / select --where threw filter not supported for column type: MaskedArray on any nullable column. A comparison now skips null rows (SQL semantics, != included) and compares the rest through the masked values.

Tests:

  • CsvEmptyCellsInteropIntegrationTest: CSV with empty cells → CsvImporter → read with vortex-jni, full and filtered: nulls exactly where cells were empty. Failed before the fix.
  • CsvImporterTest: inference (nullable types, export round-trip of nulls), late empty cell → null, non-nullable schema → fails, nullable text schema → null.
  • FilterCommandTest: nullable column with >, !=, =; failed before the fix.

./mvnw verify green locally.

🤖 Generated with Claude Code

dfa1 and others added 2 commits October 23, 2026 20:02
filter and select --where compared a row only against unmasked arrays, so any
nullable column (a CSV import with an empty cell, a nullable Parquet column)
failed with "filter not supported for column type: MaskedArray". A comparison
now skips a null row, as SQL does (comparing with null is not true, for != as
well), and compares the others through the masked column's values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An empty CSV cell in a number or boolean column was stored as 0, 0.0 or
false, indistinguishable from a real value, and a column empty in every row
became non-nullable I64 zeros. Inferred number and boolean columns are now
nullable and store an empty cell as null; an inferred text column keeps it as
"", as Arrow's CSV reader does by default. With an explicit schema a nullable
column gets null (text too) and a non-nullable number or boolean column fails
on an empty cell instead of storing a value.

Every inferred column is nullable, not only those with an empty cell among
the first chunk's rows, as Arrow and DuckDB infer them: an empty cell later in
the file is then a null instead of a failed import. On a 2.7 GB CSV without
empty cells that costs +0.008% file size and a few percent of write CPU.

CsvEmptyCellsInteropIntegrationTest reads an imported file with vortex-jni,
full and filtered, and finds nulls exactly where the cells were empty.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dfa1
dfa1 force-pushed the fix/csv-empty-cells-null branch from 1402c59 to 1cc4b0f Compare October 23, 2026 18:04
A CSV import schema could only use I64, F64, Bool and Utf8: any other type,
U16 included, failed with UnsupportedOperationException on the first chunk,
after the output file was created. Now every integer type from I8 to U64 and
F32 is accepted. Integers parse through the same byte fast path as I64, are
range-checked against the declared type (a cast would wrap 65536 to 0 in a
U16), and are narrowed into the array the writer takes. A U64 above
Long.MAX_VALUE goes through Long.parseUnsignedLong. F32 uses Float.parseFloat:
rounding a parsed double to float rounds twice and can differ. withSchema
rejects F16 and non-primitive types up front.

CsvEmptyCellsInteropIntegrationTest becomes CsvImportInteropIntegrationTest
and gains a case where vortex-jni reads every narrow type back with Arrow's
unsigned types and the same values, full and filtered on an I16 column.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dfa1
dfa1 merged commit ff4fef4 into main Oct 23, 2026
6 checks passed
@dfa1
dfa1 deleted the fix/csv-empty-cells-null branch October 23, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CsvImporter: empty cells in numeric/boolean columns become 0/false instead of null

1 participant