feat(csv-scan): keep a numeric column's type when one of its cells is blank - #7568
feat(csv-scan): keep a numeric column's type when one of its cells is blank#7568kz930 wants to merge 1 commit into
Conversation
Backport auto-label reportThis
|
… blank
Schema inference and execution disagreed about what a blank cell is, and the
schema side was the one that lost information. Inference set nullValue("") on its
parser, so a blank read as an empty string: tryParseDouble("") fails,
tryParseBoolean("") fails, and inferField lands on tryParseString(). One empty
cell was enough to type a whole numeric column as STRING. Execution builds its
parser without nullValue, so the same blank read as null there, which is what
AttributeTypeUtils.parseField is written to pass through.
The effect reaches well past the scan. Every downstream operator that does
arithmetic on such a column then receives strings and fails on rows whose values
are perfectly good numbers, not on the blank one. Hugging Face Iris Logistic
Regression on a three-row file fails at the first row, where numpy is handed
array([['2.6', '0.75']], dtype='<U32').
Dropping the setting leaves both sides reading a blank as null, and
tryParseDouble(null) already answers DOUBLE, so the column keeps the type its
values give it. One corner changes with it: a column that is blank in every
sampled row now infers as INTEGER rather than STRING. Its values are null either
way, so this renames the empty rather than reinterpreting anything.
CSVScanSourceOpDescSpec gains the case; it fails on the previous behavior. The
module's 2187 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2b08597 to
9f49d90
Compare
Automated Reviewer SuggestionsBased on the
|
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 402 | 0.245 | 23,705/32,572/32,572 us | 🔴 +11.4% / 🔴 +103.7% |
| 🟢 | bs=100 sw=10 sl=64 | 912 | 0.556 | 108,227/131,485/131,485 us | 🟢 -6.7% / 🔴 +21.7% |
| 🟢 | bs=1000 sw=10 sl=64 | 1,076 | 0.656 | 927,480/972,320/972,320 us | 🟢 -5.6% / 🟢 -9.0% |
Baseline details
Latest main e878df3 from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 402 tuples/sec | 424 tuples/sec | 784.3 tuples/sec | -5.2% | -48.7% |
| bs=10 sw=10 sl=64 | MB/s | 0.245 MB/s | 0.259 MB/s | 0.479 MB/s | -5.4% | -48.8% |
| bs=10 sw=10 sl=64 | p50 | 23,705 us | 21,276 us | 12,590 us | +11.4% | +88.3% |
| bs=10 sw=10 sl=64 | p95 | 32,572 us | 36,545 us | 15,991 us | -10.9% | +103.7% |
| bs=10 sw=10 sl=64 | p99 | 32,572 us | 36,545 us | 18,694 us | -10.9% | +74.2% |
| bs=100 sw=10 sl=64 | throughput | 912 tuples/sec | 946 tuples/sec | 1,002 tuples/sec | -3.6% | -9.0% |
| bs=100 sw=10 sl=64 | MB/s | 0.556 MB/s | 0.578 MB/s | 0.612 MB/s | -3.8% | -9.1% |
| bs=100 sw=10 sl=64 | p50 | 108,227 us | 103,584 us | 101,285 us | +4.5% | +6.9% |
| bs=100 sw=10 sl=64 | p95 | 131,485 us | 140,904 us | 108,068 us | -6.7% | +21.7% |
| bs=100 sw=10 sl=64 | p99 | 131,485 us | 140,904 us | 118,235 us | -6.7% | +11.2% |
| bs=1000 sw=10 sl=64 | throughput | 1,076 tuples/sec | 1,075 tuples/sec | 1,030 tuples/sec | +0.1% | +4.5% |
| bs=1000 sw=10 sl=64 | MB/s | 0.656 MB/s | 0.656 MB/s | 0.629 MB/s | 0.0% | +4.4% |
| bs=1000 sw=10 sl=64 | p50 | 927,480 us | 918,309 us | 991,882 us | +1.0% | -6.5% |
| bs=1000 sw=10 sl=64 | p95 | 972,320 us | 1,029,692 us | 1,038,496 us | -5.6% | -6.4% |
| bs=1000 sw=10 sl=64 | p99 | 972,320 us | 1,029,692 us | 1,068,265 us | -5.6% | -9.0% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,497.25,200,128000,402,0.245,23704.76,32571.79,32571.79
1,100,10,64,20,2194.03,2000,1280000,912,0.556,108226.81,131485.04,131485.04
2,1000,10,64,20,18594.59,20000,12800000,1076,0.656,927479.97,972319.66,972319.66
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7568 +/- ##
============================================
- Coverage 87.88% 87.87% -0.02%
+ Complexity 4276 4273 -3
============================================
Files 1176 1176
Lines 47018 47017 -1
Branches 5245 5245
============================================
- Hits 41323 41317 -6
- Misses 3972 3974 +2
- Partials 1723 1726 +3
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@aglinxinyuan May you take a look at this? |
What changes were proposed in this PR?
Schema inference and execution disagreed about what a blank cell is, and the schema side was the one that lost information. Inference set
nullValue("")on its parser, so a blank read as an empty string:tryParseDouble("")fails,tryParseBoolean("")fails, andinferFieldlands ontryParseString(). One empty cell was enough to type a whole numeric column as STRING. Execution builds its parser withoutnullValue, so the same blank read as null there, which is whatAttributeTypeUtils.parseFieldis written to pass through.The effect reaches well past the scan. Every downstream operator that does arithmetic on such a column then receives strings and fails on rows whose values are perfectly good numbers, not on the blank one. Hugging Face Iris Logistic Regression on a three-row file fails at the first row, where numpy is handed
array([['2.6', '0.75']], dtype='<U32').Dropping the setting leaves both sides reading a blank as null, and
tryParseDouble(null)already answers DOUBLE, so the column keeps the type its values give it.One corner changes with it: a column that is blank in every sampled row now infers as INTEGER rather than STRING. Its values are null either way, so this renames the empty rather than reinterpreting anything.
Any related issues, documentation, discussions?
Closes #7550
How was this PR tested?
CSVScanSourceOpDescSpecgains a case that writes a two-column file whose numeric column is blank on one row and asserts the inferred type is DOUBLE. It fails on the previous behavior, 16 passed / 1 failed before the change and 17 / 0 after. Since this touches inference every scan goes through, the whole module was run as well: 2187 passed, 0 failed.Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 5)