feat(format): separate index keys from covering fields - #9159
Ali2Arslan wants to merge 1 commit into
Conversation
|
Important Format specification voteThis PR modifies the Lance format specification, so it requires 3 binding +1 votes from PMC members (excluding the proposer), at least one of them on the latest commit, and a minimum 72-hour voting period, weekends excluded, before it can merge. Vote by approving this PR (+1) or requesting changes (−1, a veto). See the voting process. Approvals carry over across pushes, so a rebase or a typo fix does not send everyone back to re-vote. Whoever approves the latest commit is vouching that nothing substantive has changed since the earlier approvals; if something has, ask for fresh votes. Status: ✅ Vote passed — 3 PMC approvals, voting period elapsed
Updated automatically by the format-spec vote gate, which re-checks every 15 minutes — just voted? Re-check now (press Run workflow; leave the input blank to re-check every open format PR). A PMC member may apply the |
wjones127
left a comment
There was a problem hiding this comment.
Need to validate my assumption, but if we can I'm fine with making a breaking change to the existing spec so we avoid having these two separate flags.
0888867 to
fbf5f0f
Compare
fbf5f0f to
0888867
Compare
0888867 to
f3ea036
Compare
64ab4d4 to
8e6c8f6
Compare
Thanks @westonpace , I updated the wordage if you guys want to take another look. |
8e6c8f6 to
b8adf6c
Compare
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The rebase preserves the accepted independent-declaration contract while fitting it after newly occupied feature-flag bits 9 and 10: bit 11 remains explicitly rejected by current readers and writers, and the unknown boundary moves to bit 12. The atomic activation, current-index retry, and sticky-retention rules remain intact, so released bit-7 manifests and #8856ʼs carried-column storage contract stay unambiguous.
wjones127
left a comment
There was a problem hiding this comment.
I'm a little sad about the two flags, but I'll accept it.
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An index can carry a column's values without being keyed on it. The action vocabulary had no way to say so, so an action set that built such an index would have presented every field as indexed, and the manifest the action path assembles would have published it without the fence that stops an older library from answering a query from a carried column. `AddIndexSegment` gains a `covering_fields` list of the same references as `fields`. The two are independent -- `fields` means the keyed columns only and the segment's dependency set is the union -- which is the contract in #9159 and the one the action should be born with: unlike `IndexMetadata`, it has no historical encoding to stay compatible with. A manifest cannot express that contract yet, because FLAG_INDEPENDENT_COVERING_FIELDS is reserved but unimplemented. So apply lowers a disjoint declaration to the legacy form -- keys followed by the carried columns, `covering_fields` naming that trailing subset -- and rejects an overlapping one rather than publishing the weaker claim that the column is merely keyed. Both come out when a release implements the flag; the action's own shape does not change. The split also joins the index identity two concurrent writers compare: disagreeing about which columns are merely carried is disagreeing about what the index answers for, not just about what it holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BubbleCal
left a comment
There was a problem hiding this comment.
The independent key/covering declarations read much clearer to me, and since nothing writes covering indexes yet this is a good time to make the switch. Left a few small notes, mainly the bit collision after rebase. Approving assuming those get sorted out.
Co-authored-by: Cursor <cursoragent@cursor.com>
b8adf6c to
7657b56
Compare
Summary
IndexMetadatacontract sofieldscontains key fields only andcovering_fieldsis an independent declaration that may overlap itfieldsandcovering_fieldsFLAG_INDEPENDENT_COVERING_FIELDS(bit 11 / 2048) as a paired reader/writer fence and keep it unsupported until the implementation landsCompatibility
The subset contract for covering fields (added in #8535, refined by the now-merged #8856) shipped in Lance 11.0.0, so the same protobuf bytes cannot be reinterpreted unconditionally. A manifest using the new contract must set both
FLAG_COVERED_INDEX_METADATAandFLAG_INDEPENDENT_COVERING_FIELDS. A manifest with onlyFLAG_COVERED_INDEX_METADATAretains the stable legacy interpretation, wherecovering_fieldsis a subset offieldswith no id repeated.The new bit makes older releases reject independent declarations instead of treating covering fields as keys. No protobuf field number or wire type changes. Bit 11 avoids the reserved bit 9 and the bit 10
FLAG_FRAGMENT_REUSE_INDEXfrom #9136.This PR intentionally reserves and specifies the contract without implementing readers or writers, following the format-change process. Bit 11 sits above
FLAG_UNKNOWN(1 << 9) andsupported_flags_whenstarts fromFLAG_UNKNOWN - 1, so no build can open or write a manifest that sets it;FLAG_FRAGMENT_REUSE_INDEXis in the same reserved state onmaintoday. Consequently the existingkeyed_fieldsderivation, which subtracts the carried count fromfields, can never observe an independent declaration and is left for the implementation PR. This PR layers on the carried-column storage contract from #8856 so physical storage and query behavior can follow the approved metadata contract.Testing
cargo fmt --allcargo test -p lance-table feature_flags --libcargo clippy --all --tests --benches -- -D warningsRUSTDOCFLAGS='-D warnings' cargo doc -p lance-table --no-depsuv run --project docs python ci/check_proto_comments.pycd docs && uv run mkdocs build