From 961da9c0e771331631aaf40fc61c74f11533b473 Mon Sep 17 00:00:00 2001 From: Pranjal Gupta Date: Tue, 22 Sep 2026 18:24:35 +0100 Subject: [PATCH 1/2] Flow the `WITH ORDINALITY` struct as a record constructor value #4650 gave an explode the flag that decides whether it flows the element and the ordinal as a `RecordConstructorValue` or as one opaque value of the struct type, but left every explode flowing the opaque value. Ask for the record constructor where the SQL layer builds an `AT` unnesting, which is the only place that produces an explode with ordinality. The record-layer constructors keep defaulting to the opaque value, so nothing else changes shape. Both shapes stand for the same data and evaluate identically -- the plan builds its struct from the protobuf descriptor its declared type names -- but the element and the ordinal are now reachable sub-values, which is what lets anything be related to them. The expected plans of the five `AT` queries that also carry an `IN` move, since an `IN` over a value read out of a record is costed differently than one over an opaque value. They move in both directions: `array-join-at`'s `WHERE "at" IN (1, 2)` drops its join over the `IN` list for a single scan with a residual filter, two queries with an `IN` on the primary key keep their operators but move the unnesting across the in-join boundary, and `in-predicate`'s two queries with an `IN` on the unnested value start scanning the table once per `IN` element, which is #4621. No index match is lost, and all results still verify. A plan serialized before the flag existed still deserializes to the opaque value, so it keeps the shape, and the hash, it was planned with. A release that predates #4650 drops the flag instead, so this cannot be released before #4650 is. --- .../recordlayer/query/LogicalOperator.java | 10 ++-- .../resources/array-join-at.metrics.binpb | Bin 47499 -> 46280 bytes .../test/resources/array-join-at.metrics.yaml | 22 ++++----- .../src/test/resources/array-join-at.yamsql | 4 +- .../test/resources/in-predicate.metrics.binpb | 43 +++++++++--------- .../test/resources/in-predicate.metrics.yaml | 38 ++++++++-------- .../src/test/resources/in-predicate.yamsql | 6 +-- 7 files changed, 64 insertions(+), 59 deletions(-) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java index e6ba226c97c..effaec9a5bb 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java @@ -312,9 +312,13 @@ private static LogicalOperator generateCorrelatedFieldAccess(@Nonnull Expression ErrorCode.INVALID_COLUMN_REFERENCE, () -> String.format(Locale.ROOT, "join correlation can occur only on column of repeated type, not %s type", expression.getDataType())); final boolean withOrdinality = atAlias.isPresent(); - // Ask for 0-based ordinals: `AT` is 1-based per the SQL standard, but the one is added below, where the `AT` - // alias is bound, so that the ordinal an index candidate matches against is the position it stores. - final var explode = new ExplodeExpression(expression.getUnderlying(), withOrdinality, withOrdinality); + // An `AT` alias binds the ordinal of each element, so ask the explode, in the order of its arguments, for + // ordinals; for 0-based ones, since the one is added below, where the `AT` alias is bound, so that the ordinal an + // index candidate matches against is the position it stores; and for the element and the ordinal to be flowed as + // a record constructor rather than as one opaque value, which is what makes each of them a sub-value that + // anything can be related to. + final var explode = new ExplodeExpression(expression.getUnderlying(), withOrdinality, withOrdinality, + withOrdinality); final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(explode)); final QuantifiedObjectValue flowedObjectValue = resultingQuantifier.getFlowedObjectValue(); final Type flowedObjectType = resultingQuantifier.getFlowedObjectType(); diff --git a/yaml-tests/src/test/resources/array-join-at.metrics.binpb b/yaml-tests/src/test/resources/array-join-at.metrics.binpb index 3a297698c70f12014ec7d447becd591d5f8eaf7f..d914cd5199e55289477f840475d8315ca27edb38 100644 GIT binary patch delta 151 zcmeDF%yi-<(}wwTCSQt?p1fyH=w|-80z6F1#V0GSEKu4i#wE=$l|x|4@h6*qR8@ZY$PwtY zlyXmgt;09@Zj1oXaH+}iWjy?b3fc;W3a){njy}N(g$9!^&#@J81hR}3f?PvFgZx0O zTB+Z%lQ(kSU@o*U+PsmAhjH?Pg%(0sEIG*7JlTVdck*uL_{j<8k&``EZ=HP7Fmdt% zk#CbbZ4xIZxZCm|EHgElypgME@&r$r$<|?sn>REA4GyXYs$S^973CV_ui)eu?CLVv z(01kIY%Os { SCAN([IS - T1, EQUALS q0]) } | FLATMAP q1 -> { EXPLODE q1.arr1_nn WITH ORDINALITY AS - q2 RETURN (q1.id AS id, q2._0 AS val, q2._1 + 1 AS at) }' + T1, EQUALS q0]) | FLATMAP q1 -> { EXPLODE q1.arr1_nn WITH ORDINALITY AS q2 + RETURN (q1.id AS id, q2._0 AS val, q2._1 + 1 AS at) } }' task_count: 1162 task_total_time_ms: 14 transform_count: 452 @@ -196,14 +196,14 @@ array-join-at: - query: EXPLAIN SELECT "id", "val", "at" FROM T1, T1."arr1_nn" AS "val" AT "at" WHERE "at" IN (1, 2) ref: array-join-at.yamsql:320 - 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 } - task_count: 1056 - task_total_time_ms: 31 - transform_count: 421 - transform_time_ms: 11 - transform_yield_count: 52 + explain: SCAN([IS T1]) | FLATMAP q0 -> { EXPLODE q0.arr1_nn WITH ORDINALITY | + FILTER _._1 + 1 IN @c19 AS q1 RETURN (q0.id AS id, q1._0 AS val, q1._1 + 1 + AS at) } + task_count: 1045 + task_total_time_ms: 27 + transform_count: 420 + transform_time_ms: 10 + transform_yield_count: 51 insert_time_ms: 2 - insert_new_count: 115 + insert_new_count: 114 insert_reused_count: 6 diff --git a/yaml-tests/src/test/resources/array-join-at.yamsql b/yaml-tests/src/test/resources/array-join-at.yamsql index d57d544fc76..e8e39e0cfd3 100644 --- a/yaml-tests/src/test/resources/array-join-at.yamsql +++ b/yaml-tests/src/test/resources/array-join-at.yamsql @@ -305,7 +305,7 @@ test_block: - query: SELECT "id", "val", "at" FROM T1, T1."arr1_nn" AS "val" AT "at" WHERE T1."id" IN (1, 2) - - explain: "[IN arrayDistinct(promote(@c21 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS T1, EQUALS q0]) } | FLATMAP q1 -> { EXPLODE q1.arr1_nn WITH ORDINALITY AS q2 RETURN (q1.id AS id, q2._0 AS val, q2._1 + 1 AS at) }" + - explain: "[IN arrayDistinct(promote(@c21 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS T1, EQUALS q0]) | FLATMAP q1 -> { EXPLODE q1.arr1_nn WITH ORDINALITY AS q2 RETURN (q1.id AS id, q2._0 AS val, q2._1 + 1 AS at) } }" - unorderedResult: [ {id: 1, val: 101, at: 1}, {id: 2, val: 201, at: 1}, @@ -317,7 +317,7 @@ test_block: - query: SELECT "id", "val", "at" FROM T1, T1."arr1_nn" AS "val" AT "at" WHERE "at" IN (1, 2) - - 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 }" + - explain: "SCAN([IS T1]) | FLATMAP q0 -> { EXPLODE q0.arr1_nn WITH ORDINALITY | FILTER _._1 + 1 IN @c19 AS q1 RETURN (q0.id AS id, q1._0 AS val, q1._1 + 1 AS at) }" - resultMetadata: [{id: BIGINT}, {val: INTEGER}, {at: INTEGER}] - unorderedResult: [ {id: 1, val: 101, at: 1}, diff --git a/yaml-tests/src/test/resources/in-predicate.metrics.binpb b/yaml-tests/src/test/resources/in-predicate.metrics.binpb index 0eeed8b04e7..96f90f2dd94 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.binpb +++ b/yaml-tests/src/test/resources/in-predicate.metrics.binpb @@ -921,7 +921,7 @@ U }¨" i unnamed-2\EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE n IN (10, 20)º! -¤”·™'§ ‡Óé(60¼Ò¸8s@öSCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(promote(@c23 AS ARRAY(LONG))) | FLATMAP q1 -> { EXPLODE q0.NUMBERS WITH ORDINALITY | FILTER _._0 EQUALS q1 AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) }¡digraph G { +¤”·™'§ ‡Óé(60¼Ò¸8s@öEXPLODE arrayDistinct(promote(@c23 AS ARRAY(LONG))) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY | FILTER _._0 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }¡digraph G { fontname=courier; rankdir=BT; splines=line; @@ -957,49 +957,50 @@ i rankDir=LR; 4 -> 5 [ color="red" style="invis" ]; } -}™" +}ƒ" i - unnamed-2\EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE idx IN (1, 3)«! -¤�ü†.§ À­€(60ïåö8s@íEXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ZERO BASED ORDINALITY | FILTER _._1 + 1 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }›digraph G { + unnamed-2\EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE idx IN (1, 3)•! +™è’–"¦ €ÁÈ +(50„ñð8r@âEXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY | FILTER _._1 + 1 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }�digraph G { fontname=courier; rankdir=BT; splines=line; 1 [ label=<
Nested Loop Join
FLATMAP q92
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS N, INT AS IDX)" ]; 2 [ label=<
Value Computation
EXPLODE arrayDistinct(@c23)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT)" ]; 3 [ label=<
Nested Loop Join
FLATMAP (q2.ID AS ID, q4._0 AS N, q4._1 + 1 AS IDX)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS N, INT AS IDX)" ]; - 4 [ label=<
Scan
comparisons: [IS ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; - 5 [ label=<
Predicate Filter
WHERE q4._1 + 1 EQUALS q62
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, INT AS _1)" ]; - 6 [ label=<
Primary Storage
record types: [T_CHILD, ARRAY_TABLE, T_PARENT, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; - 7 [ label=<
Value Computation
EXPLODE q2.NUMBERS WITH ZERO BASED ORDINALITY
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, INT AS _1)" ]; + 4 [ label=<
Predicate Filter
WHERE q4._1 + 1 EQUALS q62
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, INT AS _1)" ]; + 5 [ label=<
Scan
comparisons: [IS ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 6 [ label=<
Value Computation
EXPLODE q2.NUMBERS WITH ORDINALITY
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, INT AS _1)" ]; + 7 [ label=<
Primary Storage
record types: [T_CHILD, ARRAY_TABLE, T_PARENT, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; 2 -> 1 [ label=< q62> label="q62" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 4 -> 3 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 5 -> 3 [ label=< q4> label="q4" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 6 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 7 -> 5 [ label=< q4> label="q4" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q4> label="q4" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 3 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 4 [ label=< q4> label="q4" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 3 -> 1 [ label=< q92> label="q92" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; { - 5 -> 1 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; + 6 -> 3 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; } { - rank=same; - rankDir=LR; - 2 -> 3 [ color="red" style="invis" ]; + 4 -> 3 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; } { - 7 -> 3 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; + rank=same; + rankDir=LR; + 5 -> 4 [ color="red" style="invis" ]; } { - 5 -> 3 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; + 4 -> 1 [ color="blue" style="dotted" arrowhead="none" tailport="nw" headport="s" constraint="false" ]; } { rank=same; rankDir=LR; - 4 -> 5 [ color="red" style="invis" ]; + 2 -> 3 [ color="red" style="invis" ]; } }¸ j unnamed-2]EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE t.id IN (2, 4)É -Ž ©òü1Æ ¨·—(F0ÞÁ¿8Œ@ Õ[IN arrayDistinct(promote(@c25 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS ARRAY_TABLE, EQUALS q0]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } }Ðdigraph G { +Ž ©òü1Æ ¨·—(F0ÞÁ¿8Œ@ Õ[IN arrayDistinct(promote(@c25 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS ARRAY_TABLE, EQUALS q0]) } | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) }Ðdigraph G { fontname=courier; rankdir=BT; splines=line; @@ -1030,7 +1031,7 @@ j }‰" r unnamed-2eEXPLAIN SELECT t.id, f, idx FROM array_table AS t, t.fruits AS f AT idx WHERE f IN ('apple', 'mango')’! -¤Äó›%§ Ô¾Õ(60Æýš8s@ÝSCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(@c23) | FLATMAP q1 -> { EXPLODE q0.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q1 AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) }’digraph G { +¤Äó›%§ Ô¾Õ(60Æýš8s@ÝEXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }’digraph G { fontname=courier; rankdir=BT; splines=line; diff --git a/yaml-tests/src/test/resources/in-predicate.metrics.yaml b/yaml-tests/src/test/resources/in-predicate.metrics.yaml index 18a258d54f7..26901c1ac31 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.yaml +++ b/yaml-tests/src/test/resources/in-predicate.metrics.yaml @@ -488,10 +488,10 @@ unnamed-2: - query: EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE n IN (10, 20) ref: in-predicate.yamsql:385 - explain: SCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(promote(@c23 - AS ARRAY(LONG))) | FLATMAP q1 -> { EXPLODE q0.NUMBERS WITH ORDINALITY | FILTER - _._0 EQUALS q1 AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS N, q2._1 - + 1 AS IDX) } + explain: EXPLODE arrayDistinct(promote(@c23 AS ARRAY(LONG))) | FLATMAP q0 -> { + SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY + | FILTER _._0 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS + IDX) } AS q3 RETURN q3 } task_count: 1060 task_total_time_ms: 82 transform_count: 423 @@ -504,23 +504,23 @@ unnamed-2: WHERE idx IN (1, 3) ref: in-predicate.yamsql:394 explain: EXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) - | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ZERO BASED ORDINALITY | FILTER _._1 - + 1 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS - q3 RETURN q3 } - task_count: 1060 - task_total_time_ms: 96 - transform_count: 423 - transform_time_ms: 29 - transform_yield_count: 54 - insert_time_ms: 4 - insert_new_count: 115 + | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY | FILTER _._1 + 1 EQUALS + q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS q3 RETURN + q3 } + task_count: 1049 + task_total_time_ms: 71 + transform_count: 422 + transform_time_ms: 22 + transform_yield_count: 53 + insert_time_ms: 3 + insert_new_count: 114 insert_reused_count: 6 - query: EXPLAIN SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE t.id IN (2, 4) ref: in-predicate.yamsql:405 explain: '[IN arrayDistinct(promote(@c25 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS - ARRAY_TABLE, EQUALS q0]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY - AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } }' + ARRAY_TABLE, EQUALS q0]) } | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY + AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) }' task_count: 1166 task_total_time_ms: 104 transform_count: 454 @@ -532,9 +532,9 @@ unnamed-2: - query: EXPLAIN SELECT t.id, f, idx FROM array_table AS t, t.fruits AS f AT idx WHERE f IN ('apple', 'mango') ref: in-predicate.yamsql:413 - explain: SCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(@c23) - | FLATMAP q1 -> { EXPLODE q0.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q1 - AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) + explain: EXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) + | FLATMAP q1 -> { EXPLODE q1.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q0 + AS q2 RETURN (q1.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) } AS q3 RETURN q3 } task_count: 1060 task_total_time_ms: 78 diff --git a/yaml-tests/src/test/resources/in-predicate.yamsql b/yaml-tests/src/test/resources/in-predicate.yamsql index a5628fdb630..415a5732987 100644 --- a/yaml-tests/src/test/resources/in-predicate.yamsql +++ b/yaml-tests/src/test/resources/in-predicate.yamsql @@ -382,7 +382,7 @@ test_block: # The ordinal (IDX) reflects the position in the original array, not the rank among filtered rows. - query: SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE n IN (10, 20) - supported_version: 4.12.5.0 - - explain: "SCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(promote(@c23 AS ARRAY(LONG))) | FLATMAP q1 -> { EXPLODE q0.NUMBERS WITH ORDINALITY | FILTER _._0 EQUALS q1 AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) }" + - explain: "EXPLODE arrayDistinct(promote(@c23 AS ARRAY(LONG))) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY | FILTER _._0 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }" - unorderedResult: [ {ID: 1, N: 10, IDX: 1}, {ID: 1, N: 20, IDX: 2}, {ID: 3, N: 10, IDX: 1}, @@ -402,7 +402,7 @@ test_block: # PartiQL join AT on bigint array with IN predicate on the primary key. - query: SELECT t.id, n, idx FROM array_table AS t, t.numbers AS n AT idx WHERE t.id IN (2, 4) - supported_version: 4.12.5.0 - - explain: "[IN arrayDistinct(promote(@c25 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS ARRAY_TABLE, EQUALS q0]) | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) } }" + - explain: "[IN arrayDistinct(promote(@c25 AS ARRAY(LONG)))] | INJOIN q0 -> { SCAN([IS ARRAY_TABLE, EQUALS q0]) } | FLATMAP q1 -> { EXPLODE q1.NUMBERS WITH ORDINALITY AS q2 RETURN (q1.ID AS ID, q2._0 AS N, q2._1 + 1 AS IDX) }" - unorderedResult: [ {ID: 2, N: 40, IDX: 1}, {ID: 2, N: 50, IDX: 2}, {ID: 4, N: 80, IDX: 1}] @@ -410,7 +410,7 @@ test_block: # PartiQL join AT on string array with IN predicate on the unnested value. - query: SELECT t.id, f, idx FROM array_table AS t, t.fruits AS f AT idx WHERE f IN ('apple', 'mango') - supported_version: 4.12.5.0 - - explain: "SCAN([IS ARRAY_TABLE]) | FLATMAP q0 -> { EXPLODE arrayDistinct(@c23) | FLATMAP q1 -> { EXPLODE q0.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q1 AS q2 RETURN q2 } AS q2 RETURN (q0.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) }" + - explain: "EXPLODE arrayDistinct(@c23) | FLATMAP q0 -> { SCAN([IS ARRAY_TABLE]) | FLATMAP q1 -> { EXPLODE q1.FRUITS WITH ORDINALITY | FILTER _._0 EQUALS q0 AS q2 RETURN (q1.ID AS ID, q2._0 AS F, q2._1 + 1 AS IDX) } AS q3 RETURN q3 }" - unorderedResult: [ {ID: 1, F: 'apple', IDX: 1}, {ID: 3, F: 'apple', IDX: 1}, {ID: 3, F: 'mango', IDX: 3}] From 65b6627b3a5b165551979cff8e7252db988a5b9f Mon Sep 17 00:00:00 2001 From: Pranjal Gupta Date: Fri, 25 Sep 2026 17:14:00 +0100 Subject: [PATCH 2/2] changes --- .../relational/recordlayer/query/LogicalOperator.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java index effaec9a5bb..8fdf3e34c58 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java @@ -312,11 +312,8 @@ private static LogicalOperator generateCorrelatedFieldAccess(@Nonnull Expression ErrorCode.INVALID_COLUMN_REFERENCE, () -> String.format(Locale.ROOT, "join correlation can occur only on column of repeated type, not %s type", expression.getDataType())); final boolean withOrdinality = atAlias.isPresent(); - // An `AT` alias binds the ordinal of each element, so ask the explode, in the order of its arguments, for - // ordinals; for 0-based ones, since the one is added below, where the `AT` alias is bound, so that the ordinal an - // index candidate matches against is the position it stores; and for the element and the ordinal to be flowed as - // a record constructor rather than as one opaque value, which is what makes each of them a sub-value that - // anything can be related to. + // for the case when `withOrdinality` is true, we by default ask for 0-based and with RecordConstructor + // value. final var explode = new ExplodeExpression(expression.getUnderlying(), withOrdinality, withOrdinality, withOrdinality); final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(explode));