Build a match candidate from an index defined on an unnested record type - #4641
Conversation
…tional-integration
An index on an `UnnestedRecordType` could not be matched, because nothing led the planner to it. Three things were missing, and each is a precondition of the next. `MetaDataPlanContext` collected the indexes of the queried stored types and the universal ones, so an index defined on a synthetic type was never in the list a candidate could be expanded from. It now also collects the indexes of every synthetic type all of whose stored constituents the query reads. `IndexExpansionInfo` described the base with the name-taking `getPlannerType`, which resolves against stored record types only, so a synthetic type could not be described at all. It now uses `getPlannerTypeForRecordTypes`, which takes the already-resolved types `recordTypesForIndex` hands back. `ValueIndexExpansionVisitor` always built the base as a scan of the stored types plus a type filter. For an index on a synthetic type the base is instead that type's own expansion -- the graph that assembles its records -- so `SyntheticRecordType.expand` is finally what the candidate ranges over. Matching and compensating against such a candidate then needs the rest of the change: the select the expansion builds carries the constituents' quantifiers and the index's placeholders together, which is what lets a predicate over the unnesting bind the scan. A covering scan over such an index also has to read the `__positions` tuple, which a `ListKeyExpression` places one level deeper in the entry's key rather than flattening. `computeIndexEntryToLogicalRecord` therefore takes the positions of the full key and derives the path to each datum from them. The planner metrics of two yamsql files move with the new rule applications.
Seven queries over one index defined on an unnested synthetic type: the query the index was defined for, the same unnesting without the ordering, a prefix of the key, the PartiQL spelling of the unnesting, and equalities that bind one or both of the key's leading columns. Each asserts the covering plan as well as the rows, since answering these from a fan-out is what the index is meant to avoid.
The plans these two files assert move once the relational unnesting work is merged in: a join over two unnestings picks the other order, and an IN over an ordinal stays a filter instead of becoming an explode joined to the scan. These expectations belong to the branches the files came from, so the change is kept in its own commit, to be dropped when this branch is rebased onto them.
c781f27 to
f3be10b
Compare
9a0ac5f to
6db6c25
Compare
📊 Metrics Diff Analysis ReportSummary
ℹ️ About this analysisThis automated analysis compares query planner metrics between the base branch and this PR. It categorizes changes into:
The last category in particular may indicate planner regressions that should be investigated. New QueriesCount of new queries by file:
Plan and Metrics ChangedThese queries experienced both plan and metrics changes. This generally indicates that there was some planner change Total: 7 queries Statistical Summary (Plan and Metrics Changed)
There were no queries with significant regressions detected. Minor Changes (Plan and Metrics Changed)In addition, there were 7 queries with minor changes. Only Metrics ChangedThese queries experienced only metrics changes without any plan changes. If these metrics have substantially changed, Total: 2 queries Statistical Summary (Only Metrics Changed)
Significant Regressions (Only Metrics Changed)There were 2 outliers detected. Outlier queries have a significant regression in at least one field. Statistically, this represents either an increase of more than two standard deviations above the mean or a large absolute increase (e.g., 100).
|
Makes an index defined on an
UnnestedRecordTypeusable by the Cascades planner, and covers it end to end.Three preconditions were missing, each one gating the next:
MetaDataPlanContextnever collected the indexes of a synthetic record type, so such an index was not in the list a candidate could be expanded from.IndexExpansionInfodescribed the base with the name-takinggetPlannerType, which resolves stored record types only.ValueIndexExpansionVisitoralways built the base as a scan of the stored types plus a type filter, soSyntheticRecordType.expandwas never called.With those in place, matching and compensating against such a candidate works, and a covering scan can read the
__positionstuple, which aListKeyExpressionplaces one level deeper in the index entry's key.unnested-record-type-indexes.yamsqlasserts the covering plan and the rows for seven queries over one such index: the query the index was defined for, the same unnesting without the ordering, a prefix of the key, the PartiQL spelling of the unnesting, and equalities binding one or both leading key columns.Why this is marked do-not-merge
The branch carries the relational-side work of #4467 as a merge, because that is what generates the synthetic table from an index definition. #4467 lives on a fork, so this PR cannot be based on it. It is based on #4619 instead, and the diff therefore shows #4467's changes as well. Once #4467 merges, this branch will be rebased and the diff will narrow to its own commits.
The second commit refreshes plan expectations in
array-join-at.yamsqlandin-predicate.yamsql. Those mismatches are present on this branch without the candidate change, and belong to the branches those files came from; the commit is kept separate so it can be dropped on that rebase.