Skip to content

Flow the array element of an explode wrapped in a struct - #4623

Closed
g31pranjal wants to merge 3 commits into
mainfrom
apple/g31pranjal/unnested/explode-wrapped-element
Closed

g31pranjal wants to merge 3 commits into
mainfrom
apple/g31pranjal/unnested/explode-wrapped-element

Conversation

@g31pranjal

@g31pranjal g31pranjal commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

#4696 made a WITH ORDINALITY explode flow its (element, ordinal) struct as a record constructor, but a plain explode still flowed the bare element. That left the two shapes incomparable: MaxMatchMap descends into a record constructor but not into an opaque value, so a plain explode on the query side had nothing below the element for a WITH ORDINALITY explode on the candidate side to be related to. Every explode now flows a record — (element) or (element, ordinal) — and the element is reachable as _0 on either side. The flag #4650 introduced is simply set for both variants.

The rest follows from that:

  • FieldValue.ofFieldNamesAndFuseIfPossible fuses a field access onto an existing field value rather than nesting one inside the other, and the key expression expansion uses it. The pull-up rules require the child of the value being pulled up to be the quantified object value, so a nested access could not be pulled through an explode's quantifier at all.
  • An in-join and an in-union bind an element, not the struct, so the plans they put underneath are translated through ExplodeExpression.elementBindingTranslationMap. Neither can take an explode with ordinality as its source.
  • On the SQL side the element is reached through _0 when building an unnesting's attributes and when resolving an index definition to the base record, a star over an unnesting expands the element's fields, and the AT ordinal becomes ephemeral — nameable, but not part of a star.
  • ExplodePlanTest follows the same way: the skip-and-limit tests read the element out of the record a plain explode flows, the plans that have to flow the element itself ask for that shape explicitly, and the hash a plan flowing the element has always had is pinned alongside the plain explode's new one.

State

Based on #4696, so the diff also shows #4650 and #4696 until they merge. It borrows nothing from #4619 or #4641: rebased straight onto #4696, every source file outside the three that #4650 itself rewrote applies unchanged, both modules compile, and :yaml-tests:quickTest comes back with exactly the failures this branch already had on its previous base — the lower lineage was neither carrying it nor holding it back. The end-to-end tests for an index over an unnesting (unnested-record-type-indexes.yamsql) stay with #4641, which introduces them.

Two things are outstanding, both visible in :yaml-tests:quickTest:

  1. field-index-tests-proto and skipped-field-number-proto fail without Match fields by position rather than field number when deep-copying messages #4631. Wrapping the element makes a record type's field indexes matter where they did not before, and Type.maximumType renumbers them. Verified again on this base: cherry-picking Match fields by position rather than field number when deep-copying messages #4631 on top makes both suites pass, so Match fields by position rather than field number when deep-copying messages #4631 belongs underneath this PR.
  2. aggregate-index-tests.yamsql:703 cannot be planned (UnableToPlanException). The aggregate index mv20 is defined over a fan-out grouping element, and the candidate's select exposes _0 rather than the element's fields, so the query side has nothing to match. The value-index form of the same problem is fixed by the expansion changes on Build a match candidate from an index defined on an unnested record type #4641; the aggregate form is not. That failure also blocks regenerating the suite's planner metrics, which have drifted at :506.

The SQL AT ordinal stays 1-based here; making it 0-based is #4633's change, which this branch does not build on.

@g31pranjal g31pranjal changed the title Apple/g31pranjal/unnested/explode wrapped element explode wrapped element Sep 15, 2026
@g31pranjal
g31pranjal changed the base branch from main to apple/g31pranjal/unnested/explode-ordinality-matching September 15, 2026 14:45
@g31pranjal g31pranjal added the enhancement New feature or request label Sep 15, 2026
@g31pranjal g31pranjal changed the title explode wrapped element Flow the array element of an explode wrapped in a struct Sep 15, 2026
@g31pranjal
g31pranjal added this pull request to stack #4620 September 15, 2026 14:45
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from cd8ca19 to fe9b6d2 Compare September 16, 2026 11:10
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 27e2937 to 6e2f6d2 Compare September 16, 2026 11:10
@g31pranjal
g31pranjal removed this pull request from stack #4620 September 16, 2026 11:12
@g31pranjal
g31pranjal added this pull request to stack #4627 September 16, 2026 11:19
@g31pranjal
g31pranjal removed this pull request from stack #4627 September 16, 2026 13:16
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from fe9b6d2 to 4e19391 Compare September 16, 2026 13:16
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 6e2f6d2 to 39fd5f9 Compare September 16, 2026 13:16
@g31pranjal
g31pranjal added this pull request to stack #4628 September 16, 2026 13:17
@g31pranjal
g31pranjal removed this pull request from stack #4628 September 16, 2026 13:35
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from 4e19391 to 4fb7006 Compare September 16, 2026 13:35
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 39fd5f9 to ae2da85 Compare September 16, 2026 13:36
@g31pranjal
g31pranjal added this pull request to stack #4629 September 16, 2026 13:36
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from 4fb7006 to b7db6bc Compare September 16, 2026 18:55
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from ae2da85 to 084b834 Compare September 16, 2026 20:29
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch 2 times, most recently from 1818a02 to f4a7009 Compare September 16, 2026 21:21
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch 2 times, most recently from 3da0da9 to fb8c044 Compare September 16, 2026 23:11
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch 2 times, most recently from d84e4be to 92aaead Compare September 17, 2026 14:22
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from fb8c044 to 9e9a04d Compare September 17, 2026 14:22
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from 92aaead to 2979d09 Compare September 17, 2026 22:34
@g31pranjal
g31pranjal removed this pull request from stack #4629 September 17, 2026 22:35
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-matching branch from 2979d09 to c781f27 Compare September 17, 2026 22:39
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 9e9a04d to e9867f1 Compare September 17, 2026 22:39
@g31pranjal g31pranjal added the DO NOT MERGE do not merge label Sep 18, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/syn-relational-integration branch from 6db6c25 to 758a824 Compare September 22, 2026 13:13
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 2be5704 to d038eda Compare September 22, 2026 13:13
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from d038eda to 3595415 Compare September 23, 2026 16:31
@g31pranjal
g31pranjal changed the base branch from apple/g31pranjal/unnested/syn-relational-integration to apple/g31pranjal/unnested/explode-ordinality-struct-value September 23, 2026 16:31
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-struct-value branch from f3ab945 to d28361e Compare September 23, 2026 17:33
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 3595415 to 5e9397b Compare September 24, 2026 13:29
g31pranjal added a commit that referenced this pull request Sep 25, 2026
An `EXPLODE WITH ORDINALS`'s value is a single opaque `QueriedValue`,
even though it flows 2 pieces of information - element and its position.
Nothing inside it is reachable, which is fine for evaluation but it
leaves no way to relate the element to anything.

### Why `RecordConstructorValue` is needed:

An index utilizing an `UnnestedRecordType` requires that the
`SyntheticRecordType` is expanded into a candidate. This expansion in
turn requires unnesting with ordinality, because the proto descriptor of
the `UnnestedRecordType`, as well as its primary key, carries the
position of the element. A query that unnests the same collection but
asks for no ordinals must be matched against this candidate, by relating
the query's element to the candidate's element: `MaxMatchMap` has to
find the query's value among the candidate's reachable sub-values.

`MaxMatchMap` descends only into record constructors. An opaque
`QueriedValue` of the struct type cannot be descended into. Having
`RecordConstructorValue`, makes the element a reachable sub-value and
the match findable structurally.

This matching happens in #4619; the shape it needs is turned on in
#4651.

### Why the shape is recorded rather than inferred

The value a plan flows is part of the plan's identity:
`RecordQueryExplodePlan` hashes it, so changing the shape changes the
plan hash of every `WITH ORDINALITY` plan. A plan serialized by an
earlier version flows the opaque value and was hashed as such, and it
has to keep doing both when a newer version reads it back. Deciding the
shape from `withOrdinality` alone would hence not be
backward-compatible.

So the shape becomes state the plan records and serializes. This PR adds
that state and leaves it at the value every existing plan flows; #4651
changes what newly planned explodes ask for.

### What this PR does

`ExplodeExpression` and `RecordQueryExplodePlan` gain a
`flowsRecordConstructorValue` flag.

What the flag decides differs between the two variants. With ordinality
the struct is the result type either way, so only the shape of the value
changes: an older version that cannot see the flag still executes such a
plan identically. Plainly, the struct is the flag's doing — the result
type is the element type without it and a one-column struct with it — so
the plan produces that struct rather than the bare element. That is why
each variant needs its own PR to turn the shape on: #4651 for the
ordinality one, and #4623 for the plain one, which reaches into
everything that reads what an explode flows.

**Nothing asks for new shape yet.** That will be enabled in later PRs

### Compatibility

The flag is serialized in porto. Absent means the opaque value, which is
precisely what a plan serialized before the field existed flows, so such
a plan keeps both its shape and its hash.
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-ordinality-struct-value branch 5 times, most recently from 609becf to 65b6627 Compare October 1, 2026 14:55
Base automatically changed from apple/g31pranjal/unnested/explode-ordinality-struct-value to apple/g31pranjal/unnested/synthetic-type-expand October 1, 2026 14:55
g31pranjal added a commit that referenced this pull request Oct 2, 2026
> Supersedes #4651, which GitHub marked as merged when this branch was
messed up in the stack reordering

#4650, now merged, added the `flowsRecordConstructorValue` flag but left
every explode flowing the opaque value. This PR turns it on only for
`EXPLODE WITH ORDINALS`
(`LogicalOperator.generateCorrelatedFieldAccess`). Wrapping the element
of a plain explode in a struct of its own is #4623.

### What changes in plans

Nothing in `fdb-record-layer-core` changes, and no core test needed
updating. What moves is the expected plan of the five `AT` queries that
also carry an `IN`: an `IN` over a value read out of a record is costed
differently than one over an opaque value. The movement goes in both
directions and is worth a look:

- `array-join-at:317`, `WHERE "at" IN (1, 2)`: was an explode of the
`IN` list joined with a scan, and is now a single scan with a residual
`FILTER _._1 + 1 IN @c19`. Cheaper.
- `array-join-at:305` and `in-predicate:402`, both `IN` on the primary
key: same operators, but the unnesting moves across the in-join boundary
— into the in-join in the first, out of it in the second.
- `in-predicate:382` and `in-predicate:410`, both `IN` on the unnested
value: the `IN` list explode becomes the outer loop with the table scan
inside it, i.e. one full scan per `IN` element. This is #4621 — the cost
model scores a data access nested under an in-join the same as an
unnested one.
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from d6852b0 to 30bc80a Compare October 2, 2026 14:49
…ximum

A record type read off a descriptor carries the descriptor's field numbers, and
those numbers may skip. `defineProtoType()` writes them back out as the field
numbers of the descriptor it generates for the type, so two types that disagree
about them generate descriptors that are not wire-compatible with each other.

`maximumType()` rebuilt each field with `Field.of(type, name)`, which has no
index, so `normalizeFields()` renumbered every field by position. The maximum of
two record types that agree on their numbering therefore lost that numbering, and
`MessageHelpers.deepCopyMessage()`, which relates the fields of two descriptors by
number, would then route a value into the field that happens to carry the same
number in the renumbered descriptor -- writing, for instance, a string into a
field declared `int64`.

Carry the index over whenever both sides agree on it, by the same rule already
used for the field name. The indexes are only kept when all of them survive, since
`normalizeFields()` renumbers all fields as soon as a single one is missing.
@g31pranjal
g31pranjal changed the base branch from apple/g31pranjal/unnested/synthetic-type-expand to main October 4, 2026 21:26
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 5e9397b to 3c9426f Compare October 4, 2026 21:37
@g31pranjal
g31pranjal changed the base branch from main to apple/g31pranjal/unnested/preserve-record-field-indexes October 5, 2026 13:33
constructor, but a plain explode still flowed the bare element. That left the two shapes
incomparable: a plain explode on the query side could not be related to a `WITH ORDINALITY`
explode on the candidate side, because `MaxMatchMap` descends into a record constructor but
not into an opaque value, so there was nothing below the element to match. Every explode now
flows a record -- `(element)` or `(element, ordinal)` -- and the element is reachable as `_0`
on either side. The flag #4650 introduced is simply set for both variants.

Everything that reads what an explode flows follows from that.

`FieldValue.ofFieldNamesAndFuseIfPossible` fuses a field access onto an existing
field value instead of nesting one inside the other, and the key expression
expansion uses it. The pull-up rules require the child of the value being pulled up
to be the quantified object value itself, so a nested access could not be pulled
through the explode's quantifier at all.

An in-join and an in-union bind an element of the collection under
`__corr_«alias»`, not the struct the explode flows, so the plans they put
underneath have to be translated: `ExplodeExpression.elementBindingTranslationMap`
makes each such alias stand for the element alone, which composes `«alias»._0` away
to a plain reference to the binding. Neither rule can take an explode with
ordinality as a source, since there is no place to put the ordinal.

On the SQL side, a quantifier over an unnesting stands for the struct, so the
element is reached through `_0`: `LogicalOperator` unwraps it when it builds the
unnesting's attributes, `QuantifierValues` sees through it when resolving an index
definition down to the base record, and a star expansion over an unnesting expands
the element's fields rather than the struct's. The `AT` ordinal is a property of the
unnesting rather than a column of the row, so it is ephemeral: nameable, but not
part of a star.

`ExplodePlanTest` follows the same way: a plain explode flows a record, so the skip-and-limit
tests read the element out of it, and the plans that have to flow the element itself ask for
that shape explicitly. The hash of a plain explode moves accordingly; the hash a plan that
flows the element itself has always had is pinned alongside it.
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/explode-wrapped-element branch from 3c9426f to 515201a Compare October 5, 2026 13:34
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

📊 Metrics Diff Analysis Report

Summary

  • New queries: 0
  • Dropped queries: 0
  • Plan changed + metrics changed: 48
  • Plan unchanged + metrics changed: 27
ℹ️ About this analysis

This automated analysis compares query planner metrics between the base branch and this PR. It categorizes changes into:

  • New queries: Queries added in this PR
  • Dropped queries: Queries removed in this PR. These should be reviewed to ensure we are not losing coverage.
  • Plan changed + metrics changed: The query plan has changed along with planner metrics.
  • Metrics only changed: Same plan but different metrics

The last category in particular may indicate planner regressions that should be investigated.

Plan and Metrics Changed

These queries experienced both plan and metrics changes. This generally indicates that there was some planner change
that means the planning for this query may be substantially different. Some amount of query plan metrics change is expected,
but the reviewer should still validate that these changes are not excessive.

Total: 48 queries

Statistical Summary (Plan and Metrics Changed)

task_count:

  • Average change: -4.8
  • Average regression: +24.8
  • Median change: +14
  • Median regression: +23
  • Standard deviation: 63.8
  • Standard deviation of regressions: 7.9
  • Range: -285 to +45
  • Range of regressions: +14 to +45
  • Queries changed: 22
  • Queries regressed: 11

task_count % change distribution (22 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
[-32%, -30%)  ███                             1
[-30%, -28%)                                  0
[-28%, -26%)                                  0
[-26%, -24%)                                  0
[-24%, -22%)                                  0
[-22%, -20%)                                  0
[-20%, -18%)                                  0
[-18%, -16%)                                  0
[-16%, -14%)                                  0
[-14%, -12%)                                  0
[-12%, -10%)                                  0
 [-10%, -8%)                                  0
  [-8%, -6%)                                  0
  [-6%, -4%)                                  0
  [-4%, -2%)                                  0
  [-2%, +0%)  ██████████████████████████████  10
  [+0%, +2%)                                  0
  [+2%, +4%)  ██████                          2
  [+4%, +6%)                                  0
  [+6%, +8%)                                  0
 [+8%, +10%)  ███                             1
[+10%, +12%)  ███                             1
[+12%, +14%)                                  0
[+14%, +16%)                                  0
[+16%, +18%)  ███                             1
[+18%, +20%)                                  0
[+20%, +22%)  █████████                       3
[+22%, +24%)                                  0
[+24%, +26%)                                  0
[+26%, +28%)  ██████                          2
[+28%, +30%)                                  0
[+30%, +32%)  ███                             1

transform_count:

  • Average change: -3.8
  • Average regression: +5.2
  • Median change: +3
  • Median regression: +5
  • Standard deviation: 24.3
  • Standard deviation of regressions: 2.0
  • Range: -108 to +11
  • Range of regressions: +3 to +11
  • Queries changed: 20
  • Queries regressed: 11

transform_count % change distribution (20 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
[-34%, -32%)  █████                           1
[-32%, -30%)                                  0
[-30%, -28%)                                  0
[-28%, -26%)                                  0
[-26%, -24%)                                  0
[-24%, -22%)                                  0
[-22%, -20%)                                  0
[-20%, -18%)                                  0
[-18%, -16%)                                  0
[-16%, -14%)                                  0
[-14%, -12%)                                  0
[-12%, -10%)                                  0
 [-10%, -8%)                                  0
  [-8%, -6%)                                  0
  [-6%, -4%)  ██████████                      2
  [-4%, -2%)                                  0
  [-2%, +0%)  ██████████████████████████████  6
  [+0%, +2%)                                  0
  [+2%, +4%)  ██████████                      2
  [+4%, +6%)  █████                           1
  [+6%, +8%)  █████                           1
 [+8%, +10%)                                  0
[+10%, +12%)  ██████████                      2
[+12%, +14%)                                  0
[+14%, +16%)  █████                           1
[+16%, +18%)                                  0
[+18%, +20%)  ███████████████                 3
[+20%, +22%)                                  0
[+22%, +24%)                                  0
[+24%, +26%)                                  0
[+26%, +28%)                                  0
[+28%, +30%)                                  0
[+30%, +32%)                                  0
[+32%, +34%)  █████                           1

transform_yield_count:

  • Average change: -0.6
  • Average regression: +2.3
  • Median change: +2
  • Median regression: +2
  • Standard deviation: 6.5
  • Standard deviation of regressions: 0.9
  • Range: -27 to +5
  • Range of regressions: +2 to +5
  • Queries changed: 19
  • Queries regressed: 11

transform_yield_count % change distribution (19 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
[-40%, -38%)  ██████                          1
[-38%, -36%)                                  0
[-36%, -34%)                                  0
[-34%, -32%)                                  0
[-32%, -30%)                                  0
[-30%, -28%)                                  0
[-28%, -26%)                                  0
[-26%, -24%)                                  0
[-24%, -22%)                                  0
[-22%, -20%)                                  0
[-20%, -18%)                                  0
[-18%, -16%)                                  0
[-16%, -14%)                                  0
[-14%, -12%)                                  0
[-12%, -10%)                                  0
 [-10%, -8%)                                  0
  [-8%, -6%)                                  0
  [-6%, -4%)  ████████████                    2
  [-4%, -2%)                                  0
  [-2%, +0%)  ██████████████████████████████  5
  [+0%, +2%)                                  0
  [+2%, +4%)                                  0
  [+4%, +6%)  ████████████                    2
  [+6%, +8%)                                  0
 [+8%, +10%)  ██████                          1
[+10%, +12%)                                  0
[+12%, +14%)                                  0
[+14%, +16%)  ██████                          1
[+16%, +18%)                                  0
[+18%, +20%)                                  0
[+20%, +22%)                                  0
[+22%, +24%)                                  0
[+24%, +26%)  ██████                          1
[+26%, +28%)                                  0
[+28%, +30%)                                  0
[+30%, +32%)                                  0
[+32%, +34%)  ██████████████████████████████  5
[+34%, +36%)                                  0
[+36%, +38%)                                  0
[+38%, +40%)                                  0
[+40%, +42%)                                  0
[+42%, +44%)                                  0
[+44%, +46%)                                  0
[+46%, +48%)                                  0
[+48%, +50%)                                  0
[+50%, +52%)                                  0
[+52%, +54%)                                  0
[+54%, +56%)                                  0
[+56%, +58%)                                  0
[+58%, +60%)                                  0
[+60%, +62%)                                  0
[+62%, +64%)  ██████                          1

insert_new_count:

  • Average change: +2.5
  • Average regression: +3.6
  • Median change: +2
  • Median regression: +2
  • Standard deviation: 7.3
  • Standard deviation of regressions: 4.9
  • Range: -33 to +32
  • Range of regressions: +1 to +32
  • Queries changed: 43
  • Queries regressed: 40

insert_new_count % change distribution (43 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
[-36%, -34%)  █                               1
[-34%, -32%)                                  0
[-32%, -30%)                                  0
[-30%, -28%)                                  0
[-28%, -26%)                                  0
[-26%, -24%)                                  0
[-24%, -22%)                                  0
[-22%, -20%)                                  0
[-20%, -18%)                                  0
[-18%, -16%)                                  0
[-16%, -14%)                                  0
[-14%, -12%)                                  0
[-12%, -10%)                                  0
 [-10%, -8%)                                  0
  [-8%, -6%)                                  0
  [-6%, -4%)                                  0
  [-4%, -2%)                                  0
  [-2%, +0%)  ██                              2
  [+0%, +2%)  ██████████████████████████████  28
  [+2%, +4%)  █                               1
  [+4%, +6%)                                  0
  [+6%, +8%)                                  0
 [+8%, +10%)  ██                              2
[+10%, +12%)                                  0
[+12%, +14%)                                  0
[+14%, +16%)                                  0
[+16%, +18%)  █                               1
[+18%, +20%)                                  0
[+20%, +22%)                                  0
[+22%, +24%)                                  0
[+24%, +26%)                                  0
[+26%, +28%)                                  0
[+28%, +30%)  █                               1
[+30%, +32%)  █                               1
[+32%, +34%)                                  0
[+34%, +36%)                                  0
[+36%, +38%)                                  0
[+38%, +40%)                                  0
[+40%, +42%)                                  0
[+42%, +44%)                                  0
[+44%, +46%)  ███                             3
[+46%, +48%)                                  0
[+48%, +50%)                                  0
[+50%, +52%)                                  0
[+52%, +54%)                                  0
[+54%, +56%)                                  0
[+56%, +58%)                                  0
[+58%, +60%)                                  0
[+60%, +62%)                                  0
[+62%, +64%)  ██                              2
[+64%, +66%)                                  0
[+66%, +68%)                                  0
[+68%, +70%)                                  0
[+70%, +72%)                                  0
[+72%, +74%)                                  0
[+74%, +76%)                                  0
[+76%, +78%)                                  0
[+78%, +80%)  █                               1

insert_reused_count:

  • Average change: -2.0
  • Median change: -2
  • Standard deviation: 0.0
  • Range: -2 to -2
  • Queries changed: 1
  • No regressions! 🎉

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).

  • yaml-tests/src/test/resources/in-predicate.metrics.yaml:450: EXPLAIN SELECT t_child.id AS child_id, t_child.num AS num, t_parent.id AS parent_id FROM t_parent, t_child WHERE get_key(t_child.t_link) = t_parent.id AND 'tag_a' IN t_parent.lst AND t_parent.id = 'p1' AND t_child.num IN (1, 2, 3)
    • old explain: SCAN([IS T_PARENT, EQUALS promote(@c44 AS STRING)]) | FLATMAP q0 -> { EXPLODE arrayDistinct(q0.LST) | FLATMAP q1 -> { [IN arrayDistinct(promote(@c50 AS ARRAY(LONG)))] | INJOIN q2 -> { SCAN([IS T_CHILD]) | FILTER _.T_LINK.NAME EQUALS q0.ID AND _.NUM EQUALS q2 } | FILTER @c34 EQUALS q1 AS q3 RETURN q3 } AS q4 RETURN (q4.ID AS CHILD_ID, q4.NUM AS NUM, q0.ID AS PARENT_ID) }
    • new explain: SCAN([IS T_PARENT, EQUALS promote(@c44 AS STRING)]) | FLATMAP q0 -> { EXPLODE arrayDistinct(q0.LST) | FLATMAP q1 -> { [IN arrayDistinct(promote(@c50 AS ARRAY(LONG)))] | INJOIN q2 -> { SCAN([IS T_CHILD]) | FILTER _.T_LINK.NAME EQUALS q0.ID AND _.NUM EQUALS q2 } | FILTER @c34 EQUALS q1._0 AS q3 RETURN q3 } AS q4 RETURN (q4.ID AS CHILD_ID, q4.NUM AS NUM, q0.ID AS PARENT_ID) }
    • insert_new_count: 1394 -> 1426 (+32)
  • yaml-tests/src/test/resources/table-functions.metrics.yaml:72: EXPLAIN select * from (select A.B, C as Q, W.X from values (1, 2.0, (3, 4, 'foo')), (10, 90.2, (5, 6.0, 'bar')) as A(B, C, W(X, Y, Z))) as u where b < 8
    • old explain: EXPLODE array((@c19 AS B, @c21 AS C, (@c24 AS X, promote(@c26 AS DOUBLE) AS Y, @c28 AS Z) AS W), (@c33 AS B, @c35 AS C, (@c38 AS X, @c40 AS Y, @c42 AS Z) AS W)) | FILTER _.B LESS_THAN @c67 | MAP (_.B AS B, _.C AS Q, _.W.X AS X)
    • new explain: EXPLODE array((@c19 AS B, @c21 AS C, (@c24 AS X, promote(@c26 AS DOUBLE) AS Y, @c28 AS Z) AS W), (@c33 AS B, @c35 AS C, (@c38 AS X, @c40 AS Y, @c42 AS Z) AS W)) | FILTER _._0.B LESS_THAN promote(@c67 AS INT) | MAP (_._0.B AS B, _._0.C AS Q, _._0.W.X AS X)
    • task_count: 148 -> 193 (+45)
    • transform_count: 34 -> 45 (+11)
    • transform_yield_count: 8 -> 13 (+5)
    • insert_new_count: 14 -> 25 (+11)

Minor Changes (Plan and Metrics Changed)

In addition, there were 46 queries with minor changes.

Only Metrics Changed

These queries experienced only metrics changes without any plan changes. If these metrics have substantially changed,
then a planner change has been made which affects planner performance but does not correlate with any new outcomes,
which could indicate a regression.

Total: 27 queries

Statistical Summary (Only Metrics Changed)

task_count:

  • Average change: +21.0
  • Average regression: +21.0
  • Median change: +23
  • Median regression: +23
  • Standard deviation: 4.5
  • Standard deviation of regressions: 4.5
  • Range: +11 to +23
  • Range of regressions: +11 to +23
  • Queries changed: 12
  • Queries regressed: 12

task_count % change distribution (12 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
  [+0%, +2%)  ██████                          2
  [+2%, +4%)                                  0
  [+4%, +6%)                                  0
  [+6%, +8%)                                  0
 [+8%, +10%)                                  0
[+10%, +12%)                                  0
[+12%, +14%)                                  0
[+14%, +16%)                                  0
[+16%, +18%)                                  0
[+18%, +20%)                                  0
[+20%, +22%)  ██████████████████████████████  10

transform_count:

  • Average change: +4.3
  • Average regression: +4.3
  • Median change: +5
  • Median regression: +5
  • Standard deviation: 1.5
  • Standard deviation of regressions: 1.5
  • Range: +1 to +5
  • Range of regressions: +1 to +5
  • Queries changed: 12
  • Queries regressed: 12

transform_count % change distribution (12 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
  [+0%, +2%)  ██████                          2
  [+2%, +4%)                                  0
  [+4%, +6%)                                  0
  [+6%, +8%)                                  0
 [+8%, +10%)                                  0
[+10%, +12%)                                  0
[+12%, +14%)                                  0
[+14%, +16%)                                  0
[+16%, +18%)                                  0
[+18%, +20%)  ██████████████████████████████  10

transform_yield_count:

  • Average change: +2.0
  • Average regression: +2.0
  • Median change: +2
  • Median regression: +2
  • Standard deviation: 0.0
  • Standard deviation of regressions: 0.0
  • Range: +2 to +2
  • Range of regressions: +2 to +2
  • Queries changed: 10
  • Queries regressed: 10

transform_yield_count % change distribution (10 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
[+32%, +34%)  ██████████████████████████████  10

insert_new_count:

  • Average change: +9.1
  • Average regression: +9.1
  • Median change: +4
  • Median regression: +4
  • Standard deviation: 12.0
  • Standard deviation of regressions: 12.0
  • Range: +1 to +62
  • Range of regressions: +1 to +62
  • Queries changed: 27
  • Queries regressed: 27

insert_new_count % change distribution (27 queries, bin = 2%):

       Range                                  n
------------  ------------------------------  ---
  [+0%, +2%)  ███████████████████████████     9
  [+2%, +4%)  ██████████████████              6
  [+4%, +6%)  ██████                          2
  [+6%, +8%)                                  0
 [+8%, +10%)                                  0
[+10%, +12%)                                  0
[+12%, +14%)                                  0
[+14%, +16%)                                  0
[+16%, +18%)                                  0
[+18%, +20%)                                  0
[+20%, +22%)                                  0
[+22%, +24%)                                  0
[+24%, +26%)                                  0
[+26%, +28%)                                  0
[+28%, +30%)                                  0
[+30%, +32%)                                  0
[+32%, +34%)                                  0
[+34%, +36%)                                  0
[+36%, +38%)                                  0
[+38%, +40%)                                  0
[+40%, +42%)                                  0
[+42%, +44%)                                  0
[+44%, +46%)  ██████████████████████████████  10

Significant Regressions (Only Metrics Changed)

There were 3 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).

  • yaml-tests/src/test/resources/join-tests.metrics.yaml:186: EXPLAIN select dept.id, dept.name, project.name from emp, dept, project where emp.dept_id = dept.id and project.emp_id = emp.id and dept.id in (1, 2)
    • explain: [IN arrayDistinct(promote(@c39 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS DEPT, EQUALS q0]) | FLATMAP q1 -> { SCAN([IS PROJECT]) | FLATMAP q2 -> { COVERING(EMPDEPT [EQUALS q1.ID] -> [DEPT_ID: KEY:[0], ID: KEY:[2]]) | FILTER q2.EMP_ID EQUALS _.ID | MAP (1 AS _0) AS q3 RETURN q2 } AS q2 RETURN (q1.ID AS ID, q1.NAME AS _1, q2.NAME AS _2) } }
    • insert_new_count: 3823 -> 3885 (+62)
  • yaml-tests/src/test/resources/valid-identifiers.metrics.yaml:2: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20)
    • explain: COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c24 AS LONG)] -> [ID: KEY:[2]]) | MAP (_.ID AS ID)
    • task_count: 641 -> 652 (+11)
    • transform_count: 203 -> 204 (+1)
    • insert_new_count: 66 -> 67 (+1)
  • yaml-tests/src/test/resources/valid-identifiers.metrics.yaml:15: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91)
    • explain: COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c26 AS LONG)] -> [ID: KEY:[2]]) | MAP (_.ID AS ID)
    • task_count: 641 -> 652 (+11)
    • transform_count: 203 -> 204 (+1)
    • insert_new_count: 66 -> 67 (+1)

Minor Changes (Only Metrics Changed)

In addition, there were 24 queries with minor changes.

@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/preserve-record-field-indexes branch from 7f4ede7 to 6f6111a Compare November 2, 2026 00:32
Base automatically changed from apple/g31pranjal/unnested/preserve-record-field-indexes to main November 2, 2026 17:00
@g31pranjal g31pranjal closed this Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE do not merge enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant