Skip to content

ORC: fill initial defaults for missing id-bound fields - #263

Open
cbb330 wants to merge 1 commit into
chbush/oh120-orc-id-bindingfrom
chbush/oh120-orc-default-fill
Open

ORC: fill initial defaults for missing id-bound fields#263
cbb330 wants to merge 1 commit into
chbush/oh120-orc-id-bindingfrom
chbush/oh120-orc-default-fill

Conversation

@cbb330

@cbb330 cbb330 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fills a field's initial-default when it is absent from an id-bearing ORC file, using the same shape as the Parquet reader's default resolution.

  • buildOrcProjection omits an absent field that declares a default, but only when the file's embedded ids are trustworthy.
  • The id-binding StructReader from ORC: bind struct fields by Iceberg field id in generic and Spark 3.1 readers #265 then finds no column for that field and materializes the declared default as a per-file constant, consuming no column vector.
  • A present column always wins — including one holding an explicit null. Id-less / name-mapped files keep legacy null synthesis, so a default is never name-matched onto a legacy or migrated file.

Provenance: this is net new, not a backport

Unlike #265, this has no upstream counterpart. apache/main's ORC reader refuses to read defaults outright:

if (field.initialDefault() != null) {
  throw new UnsupportedOperationException(
      String.format("ORC cannot read default value for field %s (%s): %s", ...));
}

Upstream has the schema model for defaults (#9502, merged here as #250) but never wired the ORC reader to honor them. So we are not diverging from working upstream behavior — we are implementing something upstream declined to implement.

The design deliberately mirrors the Parquet reader, which is the engine upstream did implement defaults for. BaseParquetReaders.defaultReader resolves per field: file reader → declared default → null → fail. This PR adds the same fallback chain to the ORC StructReader, keyed off fileReader == null.

How it works

Two phases of one read, communicating through the projection:

  1. ORCSchemaUtil.buildOrcProjection (schema planning) — isOmittableDefault skips a field when it declares a default and is absent from the file and hasTrustedIds. OrcIterable passes true only on the id-bearing path; the name-mapped path keeps the 2-arg overload, which defaults to false.
  2. OrcValueReaders.StructReader (reader assembly) — for a field with no file reader, supplies constants(convertConstant.apply(field.type(), field.initialDefault())).

Default filling is opt-in per engine: it activates only when a constant converter is supplied. GenericOrcReaders.StructReader passes IdentityPartitionConverters::convertConstant in its own constructor, so the visitor call site in GenericOrcReader is unchanged and byte-identical to upstream. Engines that have not opted in keep the strict missing-reader failure, and the deprecated positional path is untouched.

Supported scope

Generic ORC row reader; scalar defaults at any struct nesting level, including structs inside list elements and map values.

Vectorized ORC and predicate pushdown on omitted default columns remain deferred. Restricting which engines may read a defaults-declaring table is handled outside this change.

Stack

  1. ORC: bind struct fields by Iceberg field id in generic and Spark 3.1 readers #265 — ID-based StructReader backport (base: openhouse-1.2.0)
  2. This PR — Generic ORC initial-default fill
  3. Spark 3.1: fill ORC initial defaults on the row reader #264 — Spark 3.1 ORC initial-default fill

Testing Done

  • Projection tests: trusted-id omission, id-less null synthesis, required defaults, nested defaults, struct emptied by omission
  • Generic ORC end-to-end: all scalar types, required/nested/list/map defaults, present values and explicit nulls, empty/null parent structs, id-less files
  • Negative cases: testReadDoesNotApplyDefaultToIdLessFile, testReadDoesNotOverridePresentColumn
  • Full forced rerun across the stack: 481 tests, 0 failures (iceberg-orc 66, iceberg-data 393, Spark 3.1 ORC 22)
  • Spotless, checkstyle, and iceberg-orc:revapi pass

Tests generated with unit-tests plugin

Made with Cursor

Fill a field's initial-default when it is absent from an id-bearing ORC
file, mirroring how the Parquet reader resolves defaults.

buildOrcProjection omits an absent field that declares a default when the
file's embedded ids are trustworthy, so the id-binding StructReader finds
no column for it and materializes the declared default as a per-file
constant. This reuses the reader's existing constant path, so a defaulted
field consumes no column vector.

A present column always wins, including one holding an explicit null.
Id-less and name-mapped files keep legacy null synthesis, so a default is
never name-matched onto a legacy or migrated file. Engines that have not
opted into id binding are unaffected: default filling is enabled only by
passing a constant converter, and the positional path keeps its strict
missing-reader failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cbb330
cbb330 force-pushed the chbush/oh120-orc-default-fill branch from 1115244 to 0e7b860 Compare July 28, 2026 07:13
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.

1 participant