Make SQL AT unnesting use 0-based explode ordinals - #4617
Conversation
4316f33 to
56fe3fb
Compare
| // 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)); |
There was a problem hiding this comment.
Is the return type of the arithmetic expression now nullable or not-nullable? We’d want it to stay not-nullable.
There was a problem hiding this comment.
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 }" |
There was a problem hiding this comment.
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?
56fe3fb to
23184f2
Compare
23184f2 to
93f1e32
Compare
|
Folded into #4625: the ordinal base is now switched for SQL |
40cfe34 to
28a0ea8
Compare
93f1e32 to
c597cea
Compare
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.
28a0ea8 to
f6fb45d
Compare
c597cea to
0848c7b
Compare
f6fb45d to
88397d7
Compare
0848c7b to
69399ce
Compare
88397d7 to
9a8964e
Compare
b1a40b3 to
dd4d868
Compare
03556e0 to
91b08d7
Compare
dd4d868 to
a8f0925
Compare
91b08d7 to
b8c1fb4
Compare
a8f0925 to
2a64cc4
Compare
2a64cc4 to
d4a521c
Compare
b8c1fb4 to
24551d6
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. |
EXPLODE WITH ORDINALITYproduced 1-based ordinals, matching what SQLATexposes.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
ATalias.ATstays 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
truecase for the flag. Then, we can cleanup the flag.Follow-ups
ArithmeticValuereports a nullable result type even when both operands are not nullable, so theATordinal is nullable while the column is declared not-nullable.INplan inarray-join-at.yamsqlflip to scanning the table once per IN-list element.