Skip to content

feat: enable the semantic type contract on new 2.3 tables - #9488

Draft
Xuanwo wants to merge 4 commits into
xuanwo/semantic-type-modelfrom
xuanwo/semantic-type-write
Draft

Xuanwo wants to merge 4 commits into
xuanwo/semantic-type-modelfrom
xuanwo/semantic-type-write

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Do not merge before #9485, the format specification this implements, is voted and merged. Stacked on #9486.

This turns on the semantic type contract of #7073 for new tables. Today a table's logical_type names one exact Arrow type. So appending LargeUtf8, Utf8View, or dictionary data to a string column fails even though every value fits, and view input is squeezed into 32-bit offsets.

A table created with data storage version 2.3 or later now sets FLAG_SEMANTIC_TYPES (bit 2048) in both flag words. The flag is sticky across later versions and restores, and it is never set on an existing table, so every existing table and every table on 2.2 or earlier keeps its exact behavior. Older builds refuse flagged tables through the unknown-flag check.

On a flagged table:

  • The table schema records canonical names (string, decimal:10:2) and a lance-schema:output-encoding entry when a created column's layout is not the default. A LargeUtf8 or Utf8View column reads back as it was created. Appends never change the entry. Setting an entry that is not an output encoding of the column's type fails the schema update. An unknown entry read from the manifest is kept but ignored.
  • Compatibility compares semantic types, so appends and merge-inserts accept any layout of a column's type: Decimal256(10, 2) appends to decimal:10:2, and Decimal128(12, 2) is rejected.
  • Writers encode representation-only input unchanged, and each data file schema records the layout that file actually holds, so files of one column may differ. View input is stored as LargeUtf8/LargeBinary, which holds every value, instead of being cast to 32-bit offsets. The single write boundary from feat: check write inputs against the data file schema at a single boundary #9478 still holds because file schemas and arrays agree.
  • Readers ask each file for the column's output layout, and the decoders convert. Every page already describes its own offset width, value width, and dictionary key width, so DataBlock::into_arrow decodes the stored layout and converts to the requested one of the same values: offset widths, views, dictionary and plain layouts, dictionary key widths, and decimal widths. Constant pages read their scalar the same way. A value that does not fit, such as utf8 offsets past 2 GiB, fails the read with the field, the byte size, and the requested layout instead of truncating.
  • Only data files of version 2.1 or later can join the table. The frozen 2.0 decoders cannot produce another layout, and a targeted test showed they return wrong values when asked to.

Tradeoffs:

  • Decoding into the requested layout keeps the dataset read path exactly as it is for legacy tables, including the perf: speed up cold read up to 8x by lazily load column metadata #7375 shortcut that skips reading each file's schema. Converting after decoding instead would need every file's schema, which costs a schema decode per file and an extra object store request on cold reads of wide tables.
  • Before this change, a page whose offset width or dictionary key width differed from the requested type was reported as a corrupt file. It now converts when the values fit, so such a mismatch is no longer reported on its own. Legacy tables never request another width.
  • In memory, every field of a flagged table carries its output encoding in a new Field::output_encoding, including the default. Legacy and data file fields never carry one. This lets data_type(), projections, and exact comparisons follow the layout without consulting the table's flags. The manifest records only non-default encodings. Data file writers normalize their schema with Schema::to_data_file_schema().
  • Binary-copy compaction only runs when every input file already holds the table's output layouts; otherwise it re-encodes.

@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer enhancement New feature or request labels Sep 23, 2026
@github-actions github-actions Bot added the A-python Python bindings label Sep 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-encoding Encoding, IO, file reader/writer A-python Python bindings enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant