Make index-entry-to-record conversion an explicit contract - #4547
Merged
Conversation
hatyo
marked this pull request as ready for review
September 2, 2026 15:05
hatyo
force-pushed
the
apple/hatyo/index-entry-conversion
branch
2 times, most recently
from
September 2, 2026 16:22
2afe76e to
459503c
Compare
normen662
requested changes
Sep 3, 2026
normen662
left a comment
Contributor
There was a problem hiding this comment.
Nice little improvement! One comment re: naming and/or trait conventions.
| * </p> | ||
| */ | ||
| @API(API.Status.INTERNAL) | ||
| public interface IndexEntryToRecordConversion { |
Contributor
There was a problem hiding this comment.
Can you name this something that makes it explicit it's a plan trait? So something ...Plan.... Also if I look at the existing class IndexKeyValueToPartialRecord, from the name I cannot discern which one does what.
Also, for plans I think we always make the trait extend RecordQueryPlan.
Contributor
Author
There was a problem hiding this comment.
done, there are some exceptions in existing mixin / traits such that they extend nothing, but we could fix that later.
Three plans read an index directly and have to hand back a record rather than the raw entry: the covering, aggregate and Lucene spell-check plans. They shared the mechanism -- IndexKeyValueToPartialRecord -- through a static helper in QueryPlanUtils and a field convention, but not the choice they actually differ on, which is the shape an entry is decoded into. The aggregate plan decodes into the result of its select-having, whose aggregate column exists in no record, so it could not use the helper and grew an inlined copy instead. IndexEntryToRecordConversion states that contract. Implementors convert one entry through one of two named helpers, so decoding into a shape no stored record has is said out loud rather than inferred from a differing record-type lookup and descriptor. The pieces are passed as arguments rather than bundled into an object, since conversion runs once per index entry read. The Lucene spell-check plan is now an implementor too, which leaves QueryPlanUtils with nothing in it, so it is removed. RecordQueryCoveringIndexPlan's function-returning overload goes with it: its one caller in ComposedBitmapIndexQueryPlan converts per entry instead. No behaviour change intended.
hatyo
force-pushed
the
apple/hatyo/index-entry-conversion
branch
from
September 3, 2026 10:03
459503c to
b78b7ff
Compare
normen662
approved these changes
Sep 3, 2026
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.
Three physical plans read an index directly and have to hand back a record rather than the raw index entry: the covering index plan, the aggregate index plan, and the Lucene spell-check plan. They shared the mechanism for doing so —
IndexKeyValueToPartialRecord, reached through a static helper inQueryPlanUtils— but not the thing they actually differ on, which is the shape an entry is decoded into. The covering plan decodes into a partial copy of the base record, resolving the descriptor from the record type. The aggregate plan decodes into the result of its select-having, whose aggregate column exists in no stored record, so it resolves a descriptor from theTypeRepositoryand looks its record type up differently as well. Because of that it could not use the shared helper and carried an inlined copy of it instead. The difference between the two was visible only as a difference between two blocks of code that otherwise look alike, which is an easy thing to get wrong while every individual step reads correctly.IndexEntryToRecordConversionstates that contract. An implementor converts a single entry throughindexEntryToQueriedRecord, and does so via one of two named static helpers:intoStoredRecordShapefor a partial copy of the record type, orintoShapewhen the target is a shape no stored record has. The choice is now said out loud rather than inferred from which lookup a plan happens to use. The pieces are passed as arguments rather than bundled into an object, since conversion runs once per index entry read. Making the Lucene spell-check plan an implementor too leavesQueryPlanUtilsholding nothing, so it is removed, andRecordQueryCoveringIndexPlan's function-returning overload goes with it — its one caller inComposedBitmapIndexQueryPlannow converts per entry, still with an empty evaluation context and a comment explaining why that suffices.No behaviour change is intended, so the existing suites are the safety net:
FDBCoveringIndexQueryTest,BitmapValueIndexTest,GroupByTest,FDBPermutedMinMaxQueryTestand theLuceneIndexTestspell-check cases all pass (116 tests), along with checkstyle and PMD on both touched modules. There is no new unit test — the remaining seam needs a record store, so it is covered by those FDB-backed tests rather than in isolation. Two things worth a reviewer's attention:QueryPlanUtilswaspublicwith no@APIannotation, so if the team treats that as API surface this wants thebreaking changelabel rather thanenhancement; and conversion now resolves its pieces per entry rather than once per cursor, which is a repeated meta-data lookup — and, for the Lucene plan, a rebuilt converter. Nothing is allocated to wrap them, and memoizing is left to implementors with a note on the interface that anything cached must be scoped to one execution, since a plan may run against more than one store or context.