Skip to content

Make SQL AT unnesting use 0-based explode ordinals - #4617

Merged
g31pranjal merged 0 commit into
apple/g31pranjal/unnested/synthetic-type-expandfrom
apple/g31pranjal/unnested/zero-based-ordinality
Sep 17, 2026
Merged

g31pranjal merged 0 commit into
apple/g31pranjal/unnested/synthetic-type-expandfrom
apple/g31pranjal/unnested/zero-based-ordinality

Conversation

@g31pranjal

@g31pranjal g31pranjal commented Sep 14, 2026

Copy link
Copy Markdown
Member

EXPLODE WITH ORDINALITY produced 1-based ordinals, matching what SQL AT exposes. UnnestStoredRecordPlan, inside the record layer that reasons about a position within a repeated field is 0-based though. Hence, this plan, wanting to describe those positions in terms of an explode's ordinal, had to subtract the one back off.

#4625 adds support for 0-based ordinals. This PR enables that for Logical plans, which also then adds the one where it binds the AT alias. AT stays 1-based per the standard (Foundation, Section 4.10.2).

Compatibility

This is a breaking change, change but does not hamper compatibility. Previously generated plans, which have 1-based ordinals, still work since the planner are able to interpret both the variants. Moving forward, we want to move default (unset) case to be 0-based, while support the true case for the flag. Then, we can cleanup the flag.

Follow-ups

@g31pranjal g31pranjal added the breaking change Changes that are not backwards compatible label Sep 14, 2026
@g31pranjal
g31pranjal added this pull request to stack #4620 September 14, 2026 15:01
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch 2 times, most recently from 4316f33 to 56fe3fb Compare September 15, 2026 10:07
Comment on lines +326 to +330
// The explode flows 0-based ordinals. SQL `AT` is 1-based per the standard.
final Value oneBasedOrdinal = new ArithmeticValue(ArithmeticValue.PhysicalOperator.ADD_II,
FieldValue.ofOrdinalNumber(flowedObjectValue, 1),
new LiteralValue<>(Type.primitiveType(Type.TypeCode.INT, false), 1));
attributesBuilder.add(new Expression(atAlias, DataType.Primitives.INTEGER.type(), oneBasedOrdinal));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the return type of the arithmetic expression now nullable or not-nullable? We’d want it to stay not-nullable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it is nullable. ArithmeticValue always produce nullable type. That seems to be true for all operable values it seems. This seems in line with the 3-valued logic, however, we can do a better job at inferring the types correctly based on the inputs. Opened an issue about it #4622

FROM T1, T1."arr1_nn" AS "val" AT "at"
WHERE "at" IN (1, 2)
- explain: "SCAN([IS T1]) | FLATMAP q0 -> { EXPLODE q0.arr1_nn WITH ORDINALITY | FILTER _._1 IN @c19 AS q1 RETURN (q0.id AS id, q1._0 AS val, q1._1 AS at) }"
- explain: "EXPLODE arrayDistinct(@c19) | FLATMAP q0 -> { SCAN([IS T1]) | FLATMAP q1 -> { EXPLODE q1.arr1_nn WITH ORDINALITY | FILTER _._1 + 1 EQUALS q0 AS q2 RETURN (q1.id AS id, q2._0 AS val, q2._1 + 1 AS at) } AS q3 RETURN q3 }"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker for this PR, but this plan change is a regression. Instead of one SCAN+EXPLODE, we’re now doing an EXPLODE and then one scan per EXPLODE element. However, I also see some plan improvements elsewhere where the SCAN/EXPLODE flip exactly the other way round. So this is probably another situation where the cost model doesn’t distinguish what’s better. It would be interesting to figure out which of the tie-breakers causes those plan changes, or if it’s just the hash codes changing. Maybe we can add a tie-breaker then that reliably prefers the plans with the SCAN outside.

Not in this PR of course, but could you please look at it briefly and open a GitHub issue about it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #4621

@g31pranjal
g31pranjal marked this pull request as draft September 16, 2026 10:32
@g31pranjal g31pranjal added the DO NOT MERGE do not merge label Sep 16, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from 23184f2 to 93f1e32 Compare September 16, 2026 11:10
@g31pranjal
g31pranjal removed this pull request from stack #4620 September 16, 2026 11:12
@g31pranjal
g31pranjal changed the base branch from main to apple/g31pranjal/unnested/synthetic-type-expand September 16, 2026 11:18
@g31pranjal g31pranjal changed the title Make EXPLODE WITH ORDINALITY flow 0-based ordinals Make SQL AT unnesting use 0-based explode ordinals Sep 16, 2026
@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

Copy link
Copy Markdown
Member Author

Folded into #4625: the ordinal base is now switched for SQL AT in the same change that introduces it, so this pull request has nothing left of its own. The AT queries stay restricted to the current version there, for the same reason they were here.

@g31pranjal g31pranjal closed this Sep 16, 2026
@g31pranjal g31pranjal reopened this Sep 16, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from 40cfe34 to 28a0ea8 Compare September 16, 2026 13:35
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from 93f1e32 to c597cea Compare September 16, 2026 13:35
@g31pranjal
g31pranjal added this pull request to stack #4629 September 16, 2026 13:36
g31pranjal added a commit that referenced this pull request Sep 16, 2026
The ordinals `EXPLODE ... WITH ORDINALITY` flows are 1-based, as the SQL
standard requires of `WITH ORDINALITY`, so expressing a position from it
means subtracting one from an ordinal.

This PR allows the `RecordQueryExplodePlan` flow its ordinals 0-based
when the caller asks for it. The choice is a property of the plan,
serialized as `zero_based_ordinality`:

- Unset or false means 1-based, which is what every plan serialized
before this field existed flows, so an old plan keeps its meaning when a
newer version reads it — a continuation created by an older server
resumes on a newer one unchanged.
- A plan that flows 1-based ordinals does not set the field at all, so
it serializes to exactly the bytes, and hashes to exactly the plan hash,
that it did before. SQL `AT` keeps asking for 1-based ordinals here, so
no query plan that exists today changes.

#### Future PRs:

1. #4617: Once the release is cut, we can switch the Relational to use
0-based ordinals when providing ordinals. This will be backward
compatible in the sense that old plans continue to work on 1-based
ordinals.
2. TBD: After 1, **_all_** plans have `true` value for
`zero_based_ordinality`. At this point, we change the semantics to treat
unset `zero_based_ordinality` as 0-based. change plan to be unset.
3. TBD: cleanup, planHash still needs to keep the remnants though.
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from 28a0ea8 to f6fb45d Compare September 16, 2026 18:55
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from c597cea to 0848c7b Compare September 16, 2026 18:55
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from f6fb45d to 88397d7 Compare September 16, 2026 20:29
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from 0848c7b to 69399ce Compare September 16, 2026 20:29
@g31pranjal g31pranjal removed the breaking change Changes that are not backwards compatible label Sep 16, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from 88397d7 to 9a8964e Compare September 16, 2026 21:21
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch 2 times, most recently from b1a40b3 to dd4d868 Compare September 16, 2026 23:11
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from 03556e0 to 91b08d7 Compare September 17, 2026 14:22
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from dd4d868 to a8f0925 Compare September 17, 2026 14:22
@g31pranjal g31pranjal added the enhancement New feature or request label Sep 17, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from 91b08d7 to b8c1fb4 Compare September 17, 2026 22:34
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from a8f0925 to 2a64cc4 Compare September 17, 2026 22:34
@g31pranjal
g31pranjal removed this pull request from stack #4629 September 17, 2026 22:35
@g31pranjal
g31pranjal added this pull request to stack #4632 September 17, 2026 22:35
@g31pranjal
g31pranjal removed this pull request from stack #4632 September 17, 2026 22:39
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/zero-based-ordinality branch from 2a64cc4 to d4a521c Compare September 17, 2026 22:39
@g31pranjal
g31pranjal merged commit d4a521c into apple/g31pranjal/unnested/synthetic-type-expand Sep 17, 2026
@g31pranjal
g31pranjal force-pushed the apple/g31pranjal/unnested/synthetic-type-expand branch from b8c1fb4 to 24551d6 Compare September 17, 2026 22:39
@g31pranjal
g31pranjal deleted the apple/g31pranjal/unnested/zero-based-ordinality branch September 17, 2026 22:39
@g31pranjal
g31pranjal restored the apple/g31pranjal/unnested/zero-based-ordinality branch September 17, 2026 22:40
@github-actions

Copy link
Copy Markdown

📊 Metrics Diff Analysis Report

Summary

  • New queries: 0
  • Dropped queries: 0
  • Plan changed + metrics changed: 0
  • Plan unchanged + metrics changed: 0
ℹ️ 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.

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 Run mixed-mode Label to add to Pull Requests to have it run mixed mode tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants