Skip to content

feat: Allow null variant scalar - #3287

Open
emkornfield wants to merge 17 commits into
delta-io:mainfrom
emkornfield:stack/variant_null_scalar
Open

feat: Allow null variant scalar#3287
emkornfield wants to merge 17 commits into
delta-io:mainfrom
emkornfield:stack/variant_null_scalar

Conversation

@emkornfield

Copy link
Copy Markdown
Collaborator

What changes are proposed in this pull request?

We allow Variant null scalar to be constructed as a struct.

How was this change tested?

Added unit test. But I'm a little new to variant so not sure if there is more subtlety that needs to be handled here.

emkornfield and others added 17 commits August 6, 2026 21:50
Add stats_schema, which walks a table schema and produces the Adaptive Metadata Tree stats schema (per-column stats structs with field IDs derived from field_id_to_statistics_base). Implemented as a SchemaTransform using the fallible-filtering carrier: primitive/variant columns become stats structs, struct columns recurse, and array/map columns and empty structs are dropped.

Co-authored-by: Isaac
Co-authored-by: emkornfield <emkornfield@gmail.com>
`Scalar::to_array` / `append_null` rejected `DataType::Variant` with
"Variant is not supported as scalar yet", so a null variant literal could
not be materialized. A variant is physically a struct (metadata/value,
plus any shredded fields), and `make_builder` already yields a
StructBuilder for it, so fold `Variant` into the `Struct` null path: a
null variant builds as a null struct. Only the null case is reachable --
there is no non-null `Scalar::Variant`.

This unblocks emitting (initially null) variant column bounds in the
Delta->AMT stats pivot.

Co-authored-by: Isaac <no-reply@databricks.com>
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.29%. Comparing base (d634b50) to head (1267621).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3287   +/-   ##
=======================================
  Coverage   90.28%   90.29%           
=======================================
  Files         251      251           
  Lines       88575    88572    -3     
  Branches    88575    88572    -3     
=======================================
  Hits        79973    79973           
+ Misses       5718     5715    -3     
  Partials     2884     2884           

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Benchmark results: ✅ Pass

Summary: 🚀 0  ·  ✅ 12  ·  ☑️ 3  ·  🚧 0  ·  ❌ 0

Per-benchmark results (15 rows)
Test Change Base PR
clustered/readMetadataLatestPredicate/serial ✅ 1.01x faster 81.4±1.34ms 80.2±1.27ms
crcLatest/snapshotLatest ✅ 1.01x faster 9.5±0.52ms 9.4±0.51ms
crcMissing/snapshotLatest ✅ 1.01x faster 22.2±0.61ms 21.9±0.44ms
crcSlightlyStale/snapshotLatest ✅ 1.02x faster 10.3±0.30ms 10.1±0.30ms
crcVeryStale/snapshotLatest ✅ 1.01x faster 14.9±0.35ms 14.7±0.32ms
partitioned/readMetadataLatestPredicate/serial ✅ 1.01x faster 49.4±1.84ms 48.9±2.13ms
v1Checkpoint/readMetadataLatest/serial ☑️ 1.01x slower 10.8±0.16ms 10.9±0.19ms
v1Checkpoint/snapshotLatest ✅ 1.02x faster 743.3±19.10µs 732.0±19.34µs
v2Checkpoint/readMetadataLatest/parallel2 ☑️ 1.01x slower 8.0±0.27ms 8.1±0.28ms
v2Checkpoint/readMetadataLatest/serial ✅ 1.00x 12.8±0.15ms 12.8±0.16ms
v2Checkpoint/snapshotLatest ✅ 1.01x faster 710.2±23.51µs 703.7±18.74µs
wideSchemaJsonStats/readMetadataLatestPredicate/serial ☑️ 1.01x slower 74.9±1.90ms 75.3±1.84ms
wideSchemaJsonStats/snapshotLatest ✅ 1.00x 2.0±0.07ms 2.0±0.05ms
wideSchemaStructStats/readMetadataLatestPredicate/serial ✅ 1.01x faster 33.9±0.83ms 33.7±0.89ms
wideSchemaStructStats/snapshotLatest ✅ 1.01x faster 1918.3±51.99µs 1906.3±48.33µs

Legend: 🚀 ≥1.15x faster  · ✅ faster or unchanged  · ☑️ ≤1.03x slower  · 🚧 1.03x-1.15x slower  · ❌ ≥1.15x slower
Commit: 1267621 · Trigger: auto-push · Tags: base · Updated: 2026-09-08 21:45 PDT

@Fokko Fokko left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Makes sense to me 👍

@scovich scovich left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this looks like a reasonable approach. But I'm a bit unclear on why it's useful to be able to represent NULL variant values but not actual variant values?

append_nulls_as!(array::Decimal128Builder)
}
DataType::Struct(ref stype) => {
// A variant is physically a struct (`metadata`/`value`, plus any shredded fields), so a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But variant has an extension type that the current builder (shared with normal structs) seems to not apply?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think the extension type is one level up (i.e. the field containing the struct)? That is I believe StructArray only ever contains its child field types, there needs to be another container for the extension type I think.

we also seem to already drop the type in conversion. It looks like to get an actual VariantArray we would need to depend on parquet-variant-compute crate.

@emkornfield
emkornfield requested a review from scovich September 9, 2026 16:29
@emkornfield

emkornfield commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

I think this looks like a reasonable approach. But I'm a bit unclear on why it's useful to be able to represent NULL variant values but not actual variant values?

Sorry missed this. It is a useful placeholder for generating null columns via expression. This is useful for stubbing out parts of follow-on code for AMT.

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.

3 participants