fix(commit): re-parent children when fix_schema renumbers a nested field - #9447
Merged
Merged
Conversation
fix_schema assigns a duplicated field a fresh id but never updates the parent_id its children carry. Field::parent_id is serialized verbatim to the manifest protobuf, so a renumbered struct leaves its children naming a parent id that no longer exists, and every later open of the dataset fails with "references parent id N, which must appear earlier in the protobuf field list". Propagate the new id to the field's direct children. Each mapping entry only touches the children of the field it renumbers, so the result does not depend on the iteration order of the mapping.
Contributor
There was a problem hiding this comment.
✅ Gate recommendation: approve.
This repairs the durable schema corruption from #9424 by re-parenting each remapped field’s direct children to its fresh ID. That matches the serialized schema-tree contract and remains correct when an ancestor and descendant are remapped together. Rejecting duplicate file coverage would prevent the trigger but is a broader behavior change; preserving the existing fixup and making its output readable is the narrower solution.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #9424
Mechanism
fix_schemainrust/lance/src/io/commit.rsremaps duplicate field ids, then applies the mapping to the schema:Only
idis written.Fieldcarries an independentpub parent_id(rust/lance-core/src/datatypes/field.rs), and the protobuf conversion inrust/lance-file/src/datatypes.rswritesparent_id: field.parent_idverbatim rather than recomputing it from the tree.Schema::mut_field_by_idrecurses into children, so nested fields are reachable targets of this loop.The consequence is durable. Once a struct field is renumbered, its children still name the old parent id, that value is persisted into the manifest, and the read path in
rust/lance-file/src/datatypes.rshard-errors on reopen:The tolerant branch immediately above only covers duplicate parent ids that still exist, not a parent id that was renumbered away. The commit succeeds and the table can never be read back.
fix_schemais called unconditionally on both live commit paths; the check at the top of the function is a fast-path short-circuit, not a guard.Fix
Propagate the new id to the field's direct children alongside the id write.
Field::set_idalready establishes this convention (it setsparent_idand recurses), but it is not directly reusable here because it also assigns ids from a seed for fields withid < 0, which is not what this loop wants.The update is order-independent. A grandchild names its own parent's id, which that parent's own mapping entry handles, so no entry depends on another having run first. New ids start at
max_field_id() + 1, so a new id can never collide with an old id still pending in the mapping.Why #8658 does not cover this
PR #8658 (
feat: stabilize field IDs across schema evolution) touchesfix_schema, but only to make itpub(crate), to early-return when stable field ids are active, and to widen the id arithmetic to i64. It does not re-parent children, so it neither fixes nor obsoletes this. It is also currently CONFLICTING with CHANGES_REQUESTED.On the validation alternative
The issue notes that the trigger requires a writer committing duplicate field coverage across two data files in one fragment, and concedes that such a writer is itself buggy. It offers an alternative of rejecting duplicate coverage at validation instead. That alternative is reasonable and this change does not preclude it. But under either approach, a fixup that runs and then emits an unopenable manifest is indefensible: if duplicate coverage is later rejected outright, this loop becomes unreachable and harmless, and until then it should not corrupt the manifest it is meant to repair.
Tests
New
test_fix_schema_nestedcoversstruct s { text, struct inner { ordinal } }with duplicate coverage on both structs, asserting children and grandchildren follow their renumbered parents, plus a closing sweep that every non-root field names a parent id that still exists.Before, on unmodified source:
After:
cargo fmt --all --checkandcargo clippy -p lance --tests -- -D warningsare both clean on the pinned 1.97.0 toolchain. Note thatcargo clippy --allfails onlance-coreon current main independently of this change (threechunks_exactfindings inutils/bloomfilter/sbbf.rsandutils/stable_partition.rs), but only under a newer clippy than the pin, so it should not affect CI.