Repository navigation
Expand an unnested record type into a Cascades graph - #4626
Conversation
4d2d403 to
9b5aca6
Compare
51f14cb to
40cfe34
Compare
9b5aca6 to
f1509aa
Compare
40cfe34 to
28a0ea8
Compare
03556e0 to
91b08d7
Compare
91b08d7 to
b8c1fb4
Compare
b8c1fb4 to
24551d6
Compare
a74faf7 to
da06b69
Compare
da06b69 to
3fa86c3
Compare
An index defined on an `UnnestedRecordType` had no match candidate, so the Cascades planner could never use it. Build one from the graph #4626 expands the type into, and teach the relational layer to declare such a type from SQL: a synthetic table whose constituents come from unnesting a repeated field, the metadata to serialize it, and the DDL to define an index over it. The query side is a plain explode of the same repeated field, and it matches the candidate's explode with ordinality through #4619, whose mapping reaches the element because the candidate flows it as a column of a record constructor. `unnested-record-type-indexes.yamsql` covers the whole path end to end, from declaring the type in a schema template to a covering scan of the index. Squashed from the pre-split branch and re-rooted onto #4619, so that #4626 -> #4651 -> #4619 -> #4641 is linear.
3fa86c3 to
109de81
Compare
An index defined on an `UnnestedRecordType` had no match candidate, so the Cascades planner could never use it. Build one from the graph #4626 expands the type into, and teach the relational layer to declare such a type from SQL: a synthetic table whose constituents come from unnesting a repeated field, the metadata to serialize it, and the DDL to define an index over it. The query side is a plain explode of the same repeated field, and it matches the candidate's explode with ordinality through #4619, whose mapping reaches the element because the candidate flows it as a column of a record constructor. `unnested-record-type-indexes.yamsql` covers the whole path end to end, from declaring the type in a schema template to a covering scan of the index. Squashed from the pre-split branch and re-rooted onto #4619, so that #4626 -> #4651 -> #4619 -> #4641 is linear.
109de81 to
d6852b0
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. Plan and Metrics ChangedThese queries experienced both plan and metrics changes. This generally indicates that there was some planner change Total: 2 queries Statistical Summary (Plan and Metrics Changed)
Significant Regressions (Plan and 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).
|
d6852b0 to
30bc80a
Compare
| } | ||
|
|
||
| @Nonnull | ||
| private static List<String> arrayFieldPath(@Nonnull final KeyExpression nestingExpression) { |
There was a problem hiding this comment.
Can you check if you like this better in MessageHelpers?
There was a problem hiding this comment.
Doesn't go with MessageHelpers since it doesn't have to do anything with Message. What about in KeyExpression or NullableArrayTypeUtils? I am not fully convinced though
There was a problem hiding this comment.
KeyExpression is probably better if this were to be reused.
There was a problem hiding this comment.
or leave it here. your call
| final NestedConstituent parentConstituent = getParentConstituent(); | ||
| final RecordType parentRecordType = parentConstituent.getRecordType(); | ||
| final Quantifier.ForEach parentQuantifier = | ||
| Quantifier.forEach(ExpansionVisitor.createBaseRef(metaData.getRecordTypes().keySet(), |
There was a problem hiding this comment.
wouldn't you want to call a child's record types expand here?
There was a problem hiding this comment.
added public GraphExpansion expand(@Nonnull final AccessHint accessHint) to RecordType and call it here.
Adds `SyntheticRecordType.expand`, which turns a synthetic record type into the graph that assembles its records, and implements it for `UnnestedRecordType`: a select over the parent record's scan joined with one explode per nested constituent, whose result columns are the constituents plus the `__positions` field the synthetic primary key needs. The explodes are created `WITH ORDINALITY`, because a stored position is exactly the ordinal of the element within its owner's array, and there is no other way to recover it. They are asked for 0-based ordinals, so that an ordinal *is* the position, and for the `(element, ordinal)` struct to be flowed as a record constructor, so that both are sub-values a query's values can be matched against. The ordinals only need widening from the `INT` the explode flows to the `LONG` the `Positions` message declares -- the types have to agree, or the record the expansion flows is not of the type's own planner type. `RecordMetaData` gains `getPlannerTypeForRecordType(s)` overloads that take already-resolved `RecordType`s. The existing name-based overloads resolve against stored record types only, so they cannot describe a synthetic type, whereas `recordTypesForIndex` hands back the type itself for an index defined on one. The tests cover the graph `UnnestedRecordType.expand` builds -- the parent scan, one explode per nested constituent, the `__positions` record, chained constituents and nullable-array wrappers -- and the record-type-taking planner type overloads, including that they describe a synthetic type, which the name-taking ones cannot. `test_records_nested_chain.proto` is added for the chained case: the existing two-level proto, `test_records_double_nested.proto`, is deliberately recursive, and the planner cannot build a type for a recursive descriptor, so a chain of constituents needs a non-recursive record type to unnest. This is groundwork: nothing calls `expand` yet. It is what lets a value index defined on a synthetic type become a match candidate, so that a query performing the same unnesting by hand can be answered from the index.
25492b0 to
0a23c13
Compare
|
|
||
| @Nonnull | ||
| @API(API.Status.INTERNAL) | ||
| public GraphExpansion expand(@Nonnull final AccessHint accessHint) { |
| public GraphExpansion expand(@Nonnull final AccessHint accessHint) { | ||
| final GraphExpansion parentExpansion = getParentConstituent().getRecordType().expand(accessHint); | ||
| final Map<String, Value> elementValuesByConstituent = new HashMap<>(); | ||
| final GraphExpansion.Builder builder = parentExpansion.toBuilder().removeAllResultColumns(); |
There was a problem hiding this comment.
Why do you remove all columns here? you should leave them and add the ones from here to it.
There was a problem hiding this comment.
Sorry, this is something that confused me as well :D - This is taking the child's expansion and using its builder (without resultValue) to add explode quantifiers and the "synthetic" resultValue. Tbh, it's as good as adding quantifiers of the child expansion (as of now). Changed to that!
Implements `SyntheticRecordType.expand` for `JoinedRecordType`, which until now threw. #4626 added `expand` and implemented it for `UnnestedRecordType`; this does the same for joins. A joined record type expands to one scan per constituent and one equality predicate per join: - Each constituent is the expansion of its stored record type, a type-filtered scan, flowed whole as a result column named after the constituent. A type joined to itself gets one scan per copy, each with its own quantifier, so the join can relate the copies. - Each join becomes `left = right`, with each side's key expression translated into a value over its own constituent's quantifier. Nested join fields become nested field values. - A constituent with no join leaves the pair unconstrained, which is the same cross product the joined record type itself produces. Outer-joined constituents are rejected with `UnsupportedOperationException`. An outer join keeps a record that has no partner, and an inner equality between two scans would drop it. This is groundwork: nothing calls `expand` yet. It is what lets an index on a joined record type become a match candidate, so that a query performing the same join by hand can be answered from the index. `UnnestedRecordTypeExpansionTest` is renamed to `SyntheticRecordTypeExpansionTest`, which now tests the expansion of both kinds of synthetic type. Its test expecting a joined type to throw is replaced by tests of a two-way join, a self-join, a three-way cyclic join (one equality per join), a join whose constituents are named in the opposite order to their declaration, a nested join field, a constituent with no join, and outer-join rejection.
Adds
SyntheticRecordType.expand, which turns a synthetic record type into the graph that assembles its records, and implements it forUnnestedRecordType: a select over the parent record's scan joined with one explode per nested constituent, whose result columns are the constituents plus the__positionsfield the synthetic primary key needs.The explodes are created
WITH ORDINALITY, because a stored position is exactly the ordinal of the element within its owner's array, and there is no other way to recover it. They ask for 0-based ordinals (the pull request below this one); it then only needs widening from theINTthe explode flows to theLONGthePositionsmessage declares.This is groundwork: nothing calls
expandyet. It is what lets a value index defined on a synthetic type become a match candidate, so that a query performing the same unnesting by hand can be answered from the index.