Skip to content

Add outer nullability to Union - #8798

Merged
connortsui20 merged 1 commit into
developfrom
agent/union-outer-nullability
Jul 16, 2026
Merged

Add outer nullability to Union#8798
connortsui20 merged 1 commit into
developfrom
agent/union-outer-nullability

Conversation

@connortsui20

Copy link
Copy Markdown
Member

Tracking issue: #8769

Adds the breaking Union dtype foundation before scalar and array support.

Changes

  • changes DType::Union to carry independent outer nullability
  • keeps outer nullability separate from the nullability of each variant
  • removes runtime-derived Union nullability
  • updates dtype coercion, serialization, display, and downstream matches

This changes the serialized Serde representation of Union dtypes and will require the compatibility override to be reviewed explicitly.

@connortsui20 connortsui20 added the changelog/break A breaking API change label Jul 16, 2026
@connortsui20
connortsui20 force-pushed the agent/union-outer-nullability branch from c5f26f5 to 7db4fac Compare July 16, 2026 17:43
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Polar Signals Profiling Results

Latest Run

Status Commit Job Attempt Link
🟢 Done f04a9d8 1 Explore Profiling Data
Previous Runs (2)
Status Commit Job Attempt Link
🟢 Done 7db4fac 1 Explore Profiling Data
🟢 Done c5f26f5 1 Explore Profiling Data

Powered by Polar Signals Cloud

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Benchmarks: Vortex queries

Verdict: No clear signal (low confidence)
Attributed Vortex impact: +5.5%
Engines: DataFusion Likely regression (+10.4%, medium confidence) · DuckDB No clear signal (+0.8%, low confidence)
Vortex (geomean): 0.993x ➖
Parquet (geomean): 0.966x ➖
Shifts: Parquet (control) -3.4% · Median polish +0.8%

How to read Verdict and Engines
  • Verdict: Overall PR-level signal after subtracting baseline drift estimated from Parquet control rows. It can be Likely improvement, Likely regression, or No clear signal.
  • Engines: Per-engine attribution. DataFusion is compared against DataFusion/Parquet controls; DuckDB is compared against DuckDB/Parquet controls. This answers whether each engine improved or regressed independently.
  • Confidence: Based on directional consistency, share of rows above the noise floor, and control-run noise.

datafusion / vortex-file-compressed (1.027x ➖, 0↑ 0↓)
name PR f04a9d8 (ns) base 17f843a (ns) ratio (PR/base)
vortex_q00/datafusion:vortex-file-compressed 10310966 9877069 1.04
vortex_q01/datafusion:vortex-file-compressed 6415099 6345790 1.01
datafusion / parquet (0.930x ➖, 0↑ 0↓)
name PR f04a9d8 (ns) base 17f843a (ns) ratio (PR/base)
vortex_q00/datafusion:parquet 20508032 21920309 0.94
vortex_q01/datafusion:parquet 4749266 5135545 0.92
duckdb / vortex-file-compressed (1.012x ➖, 0↑ 0↓)
name PR f04a9d8 (ns) base 17f843a (ns) ratio (PR/base)
vortex_q00/duckdb:vortex-file-compressed 10700878 10602435 1.01
vortex_q01/duckdb:vortex-file-compressed 6656265 6559583 1.01
duckdb / parquet (1.004x ➖, 0↑ 0↓)
name PR f04a9d8 (ns) base 17f843a (ns) ratio (PR/base)
vortex_q00/duckdb:parquet 23727070 23596214 1.01
vortex_q01/duckdb:parquet 9568397 9543018 1.00

No file size changes detected.

@codspeed-hq

codspeed-hq Bot commented Jul 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 11.17%

⚡ 1 improved benchmark
✅ 1669 untouched benchmarks
⏩ 52 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation true_count_vortex_buffer[128] 580.6 ns 522.2 ns +11.17%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing agent/union-outer-nullability (f04a9d8) with develop (17f843a)

Open in CodSpeed

Footnotes

  1. 52 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20
connortsui20 force-pushed the agent/union-outer-nullability branch from 7db4fac to f04a9d8 Compare July 16, 2026 18:54
}
_ => {}
if let (DType::Union(lhs, lhs_null), DType::Union(rhs, rhs_null)) = (self, other) {
return (lhs == rhs).then(|| DType::Union(lhs.clone(), *lhs_null | *rhs_null));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so... do we do the fun thing where we try to construct a union more cleverly by looking at the types and type ids / field names?

I think there is a strong argument to do this, but also we can do this another time

@connortsui20
connortsui20 marked this pull request as ready for review July 16, 2026 19:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f04a9d879e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

| List(_, null)
| FixedSizeList(_, _, null)
| Struct(_, null)
| Union(_, null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject union coercions that narrow child nullability

With unions now reporting only their outer nullability here, can_coerce_from's generic eq_ignore_nullability path can accept unsafe union coercions: a target like union(value=i32) and a source like union(value=i32?) are both outer-non-nullable, so other.is_nullable() is false even though source rows may select a null child. That allows child nullability to be narrowed; special-case unions in coercion or otherwise check variant nullability recursively before returning true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually wrong by our new definition of null unions, as we say outer null value for union is not the same as a union of nullable things

}
_ => {}
if let (DType::Union(lhs, lhs_null), DType::Union(rhs, rhs_null)) = (self, other) {
return (lhs == rhs).then(|| DType::Union(lhs.clone(), *lhs_null | *rhs_null));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these rules will go away. They need a replacement but essentially this has to be part of the higher level that integrates with vortex

@connortsui20
connortsui20 merged commit 407720c into develop Jul 16, 2026
125 of 126 checks passed
@connortsui20
connortsui20 deleted the agent/union-outer-nullability branch July 16, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/break A breaking API change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants