Skip to content

feat(csv-scan): keep a numeric column's type when one of its cells is blank - #7568

Open
kz930 wants to merge 1 commit into
apache:mainfrom
kz930:fix/7550-csv-blank-cell-type
Open

feat(csv-scan): keep a numeric column's type when one of its cells is blank#7568
kz930 wants to merge 1 commit into
apache:mainfrom
kz930:fix/7550-csv-blank-cell-type

Conversation

@kz930

@kz930 kz930 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

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, 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.

Any related issues, documentation, discussions?

Closes #7550

How was this PR tested?

CSVScanSourceOpDescSpec gains 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)

@Yicong-Huang Yicong-Huang added the release/v1.2 back porting to release/v1.2 label Aug 11, 2026
@github-actions
github-actions Bot requested a review from xuang7 August 11, 2026 20:45
@github-actions

Copy link
Copy Markdown
Contributor

Backport auto-label report

This fix: PR was checked against each actively-supported release branch. release/* labels drive the post-merge backport, so add or remove one to change where this fix lands.

Release branch Analysis
release/v1.2 Change detected on this branch — label added; this fix is queued to backport here. Requested review from @xuang7.

Auto-label run.

… 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>
@kz930
kz930 force-pushed the fix/7550-csv-blank-cell-type branch from 2b08597 to 9f49d90 Compare August 11, 2026 20:51
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • No candidates found from git blame history.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 6 better · 🔴 3 worse · ⚪ 6 noise (<±5%) · 0 without baseline

Compared against main e878df3 benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

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

@kz930 kz930 changed the title fix(csv-scan): keep a numeric column's type when one of its cells is blank feat(csv-scan): keep a numeric column's type when one of its cells is blank Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.87%. Comparing base (e878df3) to head (9f49d90).

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     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø)
agent-service 98.62% <ø> (ø) Carriedforward from e878df3
amber 82.85% <ø> (-0.03%) ⬇️
computing-unit-managing-service 60.38% <ø> (ø)
config-service 65.97% <ø> (ø)
file-service 69.05% <ø> (ø)
frontend 89.94% <ø> (ø) Carriedforward from e878df3
notebook-migration-service 78.89% <ø> (ø)
pyamber 97.52% <ø> (ø) Carriedforward from e878df3
workflow-compiling-service 26.31% <ø> (ø)

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@xuang7 xuang7 removed fix release/v1.2 back porting to release/v1.2 labels Aug 11, 2026
@xuang7
xuang7 removed their request for review August 11, 2026 21:04
@kz930

kz930 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@aglinxinyuan May you take a look at this?

@aglinxinyuan
aglinxinyuan added this pull request to the merge queue Aug 13, 2026
@aglinxinyuan
aglinxinyuan removed this pull request from the merge queue due to a manual request Aug 13, 2026
@aglinxinyuan
aglinxinyuan added this pull request to the merge queue Aug 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A blank cell makes CSV File Scan infer a numeric column as STRING, breaking every downstream numeric operator

5 participants