This is an automated email from the ASF dual-hosted git repository.

englefly pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new f2117db7497 [fix](fe) Collect ARRAY_MAP container metadata on the 
input array (#69340)
f2117db7497 is described below

commit f2117db74978b2bf04f92c11cc2b349936ff9607
Author: minghong <[email protected]>
AuthorDate: Fri Oct 23 11:49:37 2026 +0800

    [fix](fe) Collect ARRAY_MAP container metadata on the input array (#69340)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Problem Summary:
    
    With nested column pruning enabled, `CARDINALITY(ARRAY_MAP(x -> x, a))`
    over an
    `ARRAY<STRUCT<...>>` column fails at execution with
    
    ```
    [E-3110][INTERNAL_ERROR]Invalid META access path for column 'item': OFFSET 
metadata is not supported at this level
    ```
    
    `EXPLAIN VERBOSE` shows the access path `a.*.OFFSET`: the OFFSET
    metadata that `cardinality()` needs has been
    pushed down to the struct element layer. Disabling the pruning makes the
    same query succeed.
    
    Root cause: `visitCardinality` creates a META/OFFSET context for the
    ARRAY_MAP result, and `visitArrayMap`
    forwarded that context unchanged into `collectArrayPathInLambda`. The
    lambda body reaches the input array
    through an array item, and `visitArrayItemSlot` adds the element-level
    `*`, so the OFFSET suffix ends up on
    the element (struct) layer. The BE only supports OFFSET metadata at
    array/map/string level.
    
    ARRAY_MAP keeps the container shape of its inputs: the result offsets
    come from the first input array and the
    result null map is the union of all inputs' null maps. A metadata
    requirement on the ARRAY_MAP result is
    therefore a requirement on the input arrays' containers, not on the
    elements.
    
    Fix: when ARRAY_MAP is collected with a root META context, route the
    NULL/OFFSET requirement to the input
    array containers (`a.OFFSET`, `s.arr.OFFSET`, `m.VALUES.OFFSET`,
    `a.NULL`) and collect the lambda body with a
    fresh DATA context, so the element paths the lambda actually reads are
    still kept. Every input array gets the
    container metadata, because the result offsets come from the first input
    while the result null map is the
    union of all inputs' null maps (equal lengths do not imply equal
    nullness: `cardinality(array_map((x, y) ->
    x + y, [1, 2], NULL))` is NULL).
    
    An input array produced by the map (`map_values(m)` / `map_keys(m)`) is
    normalized to the map column: the
    produced array's container is the map itself (its length is the map's
    entry count and its nullness is the
    map's nullness), so `cardinality(array_map(x -> x.v + 1,
    map_values(m)))` collects `m.OFFSET` instead of the
    invalid `m.VALUES.OFFSET` that the VALUES sub-column of a struct-valued
    map cannot serve.
    
    Only an unprefixed `[OFFSET]` / `[NULL]` request describes the ARRAY_MAP
    result's own container. A request
    that addresses an element of the result, such as the `[*, OFFSET]` of
    `cardinality(element_at(array_map(x ->
    array(x.v), a), 1))`, keeps the previous lambda traversal instead: that
    requirement is produced by the lambda
    and cannot be mapped to the input array's element, so only the valid
    `a.*.v` payload is read and no
    element-level metadata path is emitted.
    
    Before: the plan shows `[a.*.OFFSET]` and the query fails with E-3110.
    After: the plan shows `[a.*, a.OFFSET]` and the query returns `2 / 0 /
    NULL / 2` for a two-element array, an
    empty array, a NULL array and an array holding a NULL element.
    
    Nested variants are fixed the same way:
    
    | expression | before | after |
    | --- | --- | --- |
    | `cardinality(array_map(x -> x, a))` | `a.*.OFFSET` (fails) | `a.*,
    a.OFFSET` |
    | struct field `s.arr` | `s.arr.*.OFFSET` (fails) | `s.arr.*,
    s.arr.OFFSET` |
    | map value `m['k']` | `m.*.*.OFFSET` (fails) | `m.KEYS, m.VALUES.*,
    m.VALUES.OFFSET` |
    | `array_map(x -> x, a) IS NULL` | element-level `a.*.NULL` | `a.*,
    a.NULL` |
    | result element: `cardinality(element_at(array_map(x -> array(x.v), a),
    1))` | `a.*.v` | `a.*.v` (descendant metadata not routed) |
    | multi-array `array_map((x, y) -> ..., a, b)` | `a.*.OFFSET,
    b.*.OFFSET` (fails) | `a.*, a.OFFSET, b.*, b.OFFSET` |
    
    ### Release note
    
    Fix `[E-3110] Invalid META access path ... OFFSET metadata is not
    supported at this level` for
    `CARDINALITY(ARRAY_MAP(...))` when nested column pruning is enabled on
    array-of-struct columns.
    
    ### Check List (For Author)
    
    - Test
    - [x] Regression test: new suite
    `nereids_rules_p0/column_pruning/array_map_container_metadata_pruning`
    (10 result sections, 9 `explain` guards including `notContains
    "a.*.OFFSET"`); the whole
          `nereids_rules_p0/column_pruning` directory (16 suites) passes.
    - [x] Unit Test: `PruneNestedColumnTest`, 7 new cases; 80/80 tests pass.
    - [x] Manual test: the reproduction SQL fails with E-3110 before the fix
    and returns the expected
          `2 / 0 / NULL / 2` after it on a local FE + BE cluster.
        - [ ] No need to test.
    - Behavior changed:
        - [ ] No.
    - [x] Yes. `CARDINALITY(ARRAY_MAP(...))` and `ARRAY_MAP(...) IS NULL`
    now collect the input array's
    OFFSET/NULL metadata instead of an element-level metadata path, and keep
    the element data that the
          lambda reads.
    - Does this need documentation?
        - [x] No.
---
 .../rewrite/AccessPathExpressionCollector.java     |  61 +++++-
 .../rules/rewrite/PruneNestedColumnTest.java       | 100 ++++++++++
 .../array_map_container_metadata_pruning.out       |  57 ++++++
 .../array_map_container_metadata_pruning.groovy    | 220 +++++++++++++++++++++
 4 files changed, 435 insertions(+), 3 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java
index b3440c5a8bd..9e98a1c8819 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java
@@ -292,14 +292,25 @@ public class AccessPathExpressionCollector extends 
DefaultExpressionVisitor<Void
             offsetContext.setType(ColumnAccessPathType.META);
             // cardinality(map_keys(m)) == cardinality(m) == 
cardinality(map_values(m)):
             // all three count map entries, so emit the same [map_col, OFFSET] 
path.
-            Expression effectiveArg = (arg instanceof MapKeys || arg 
instanceof MapValues)
-                    ? arg.child(0) : arg;
+            Expression effectiveArg = normalizeMapArrayProducer(arg);
             return effectiveArg.accept(this, offsetContext);
         }
         // fall through to default
         return visit(cardinality, context);
     }
 
+    /**
+     * map_keys / map_values produce an array whose container shape (length 
and nullness) is the
+     * map's own: their metadata requirement belongs to the map column, not to 
the KEYS / VALUES
+     * sub-column, which has no offsets of its own (the BE rejects e.g. 
`m.VALUES.OFFSET` when the
+     * value is a struct). Returns the map expression for such an array 
producer, the expression
+     * itself otherwise.
+     */
+    private static Expression normalizeMapArrayProducer(Expression expression) 
{
+        return (expression instanceof MapKeys || expression instanceof 
MapValues)
+                ? expression.child(0) : expression;
+    }
+
     @Override
     public Void visitArrayItemSlot(ArrayItemSlot arrayItemSlot, 
CollectorContext context) {
         if (exprIdToLambdaArguments.isEmpty()) {
@@ -576,11 +587,55 @@ public class AccessPathExpressionCollector extends 
DefaultExpressionVisitor<Void
 
         Expression argument = arrayMap.getArgument(0);
         if ((argument instanceof Lambda)) {
-            return collectArrayPathInLambda((Lambda) argument, context);
+            Lambda lambda = (Lambda) argument;
+            return collectArrayMapPathInLambda(lambda, context);
         }
         return visit(arrayMap, context);
     }
 
+    private Void collectArrayMapPathInLambda(Lambda lambda, CollectorContext 
context) {
+        if (isRootContainerMetadata(context)) {
+            // A metadata requirement on the ARRAY_MAP result itself (OFFSET 
for cardinality(arr),
+            // NULL for arr IS NULL) describes the output ARRAY container, not 
the lambda elements.
+            // The output container is shaped by the input arrays: array_map 
copies the first
+            // input's offsets and its null map is the union of all inputs' 
null maps. Pushing that
+            // context into the lambda body would attach the metadata to the 
element layer instead,
+            // producing e.g. `arr.*.OFFSET`, which the BE rejects for a 
struct element (OFFSET
+            // metadata is only supported at array/map/string level). Route 
the container metadata
+            // to the input arrays, and collect the element paths the lambda 
actually reads with a
+            // fresh data context so that the elements stay available to the 
lambda body.
+            // A map_keys / map_values input is normalized to its map column: 
the produced array's
+            // container is the map itself, so the metadata must not be 
attached to the KEYS /
+            // VALUES sub-column (which has no offsets of its own, e.g. for a 
struct value).
+            for (ArrayItemReference lambdaArgument : 
lambda.getLambdaArguments()) {
+                continueCollectAccessPath(
+                        
normalizeMapArrayProducer(lambdaArgument.getArrayExpression()),
+                        copyContext(context));
+            }
+            return collectArrayPathInLambda(lambda,
+                    new CollectorContext(context.statementContext, 
context.bottomFilter));
+        }
+        // Only a root request is routed to the input containers. A prefixed 
request such as the
+        // `[*, OFFSET]` of cardinality(element_at(array_map(...), 1)) 
addresses an element of the
+        // ARRAY_MAP result, whose shape is produced by the lambda and is 
unrelated to the input
+        // array's containers. Keep the lambda traversal for it: the 
unrepresentable metadata is
+        // dropped and the element data the lambda reads is preserved.
+        return collectArrayPathInLambda(lambda, context);
+    }
+
+    /**
+     * Whether the pending metadata requirement addresses the current 
expression's own container,
+     * e.g. the `[OFFSET]` of cardinality(arr) or the `[NULL]` of arr IS NULL, 
rather than one of
+     * its descendants such as the `[*, OFFSET]` produced by 
cardinality(element_at(&lt;array&gt;, 1)).
+     * Every metadata request is created together with exactly one OFFSET / 
NULL suffix (see
+     * visitCardinality / visitLength / visitMapSize / visitIsNull), and the 
visitors only prepend
+     * the components they descend through, so a single-component metadata 
path is a root request.
+     */
+    private static boolean isRootContainerMetadata(CollectorContext context) {
+        return context.getType() == ColumnAccessPathType.META
+                && context.getAccessPathBuilder().getPathList().size() == 1;
+    }
+
     @Override
     public Void visitArraySort(ArraySort arraySort, CollectorContext context) {
         // ARRAY_SORT(lambda, <arr>)
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
index 18e0b48be1b..ce5e499ebdc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
@@ -144,6 +144,16 @@ public class PruneNestedColumnTest extends 
TestWithFeService implements MemoPatt
                 + "  map_arr_col map<string, array<int>>\n"
                 + ") properties ('replication_num'='1')");
 
+        createTable("create table array_map_meta_tbl(\n"
+                + "  id int,\n"
+                + "  a array<struct<v: int>>\n"
+                + ") properties ('replication_num'='1')");
+
+        createTable("create table array_map_map_producer_tbl(\n"
+                + "  id int,\n"
+                + "  m map<string, struct<v: int>>\n"
+                + ") properties ('replication_num'='1')");
+
         createTable("create table map_array_value_tbl(\n"
                 + "  id int,\n"
                 + "  s struct<\n"
@@ -250,6 +260,96 @@ public class PruneNestedColumnTest extends 
TestWithFeService implements MemoPatt
                 ImmutableList.of(path("a", "*")));
     }
 
+    @Test
+    public void testCardinalityArrayMapCollectsContainerOffsetPath() throws 
Exception {
+        // ARRAY_MAP keeps the container shape of its inputs, so 
cardinality(array_map(...)) must
+        // collect the input array's OFFSET metadata. Passing the OFFSET 
requirement into the lambda
+        // body instead yields `a.*.OFFSET`, an OFFSET at the struct element 
level that the BE
+        // rejects; the element data the lambda reads must stay collected as 
well.
+        assertColumn("select cardinality(array_map(x -> x, a)) from 
array_map_meta_tbl",
+                "array<struct<v:int>>",
+                ImmutableList.of(path("a", "*"), metaPath("a", "OFFSET")),
+                ImmutableList.of());
+    }
+
+    @Test
+    public void 
testCardinalityArrayMapOnNestedArrayCollectsContainerOffsetPath() throws 
Exception {
+        assertColumn("select cardinality(array_map(x -> x, element_at(s, 
'arr'))) "
+                        + "from nested_container_tbl",
+                "struct<arr:array<struct<str_field:text,int_field:int>>>",
+                ImmutableList.of(
+                        path("s", "arr", "*"),
+                        metaPath("s", "arr", "OFFSET")),
+                ImmutableList.of());
+    }
+
+    @Test
+    public void testCardinalityArrayMapOnMapValueCollectsValueOffsetPath() 
throws Exception {
+        assertAllAccessPathsContain(
+                "select cardinality(array_map(x -> x, element_at(element_at(s, 
'm'), 'k'))) "
+                        + "from map_array_value_tbl",
+                ImmutableList.of(
+                        path("s", "m", "KEYS"),
+                        path("s", "m", "VALUES", "*"),
+                        metaPath("s", "m", "VALUES", "OFFSET")),
+                ImmutableList.of(
+                        metaPath("s", "m", "VALUES", "*", "OFFSET")));
+    }
+
+    @Test
+    public void testIsNullArrayMapCollectsContainerNullPath() throws Exception 
{
+        assertAllAccessPathsContain(
+                "select array_map(x -> x, a) is null from array_map_meta_tbl",
+                ImmutableList.of(metaPath("a", "NULL")),
+                ImmutableList.of(metaPath("a", "*", "NULL")));
+    }
+
+    @Test
+    public void testCardinalityArrayMapKeepsElementFieldTheLambdaReads() 
throws Exception {
+        assertColumn("select cardinality(array_map(x -> x.v + 1, a)) from 
array_map_meta_tbl",
+                "array<struct<v:int>>",
+                ImmutableList.of(path("a", "*", "v"), metaPath("a", "OFFSET")),
+                ImmutableList.of());
+    }
+
+    @Test
+    public void testArrayMapResultElementMetadataKeepsLambdaPayloadOnly() 
throws Exception {
+        // cardinality() of a selected ARRAY_MAP element addresses an element 
of the ARRAY_MAP
+        // result, whose shape is produced by the lambda, not the input array 
container. That
+        // metadata cannot be routed to the input array's element: doing so 
emitted the invalid
+        // `a.*.OFFSET` on a struct element, so only the element data the 
lambda reads is kept.
+        assertColumn("select cardinality(element_at(array_map(x -> array(x.v), 
a), 1)) "
+                        + "from array_map_meta_tbl",
+                "array<struct<v:int>>",
+                ImmutableList.of(path("a", "*", "v")),
+                ImmutableList.of());
+
+        assertColumn("select element_at(array_map(x -> array(x.v), a), 1) is 
null "
+                        + "from array_map_meta_tbl",
+                "array<struct<v:int>>",
+                ImmutableList.of(path("a", "*", "v")),
+                ImmutableList.of());
+    }
+
+    @Test
+    public void testArrayMapOnMapProducerUsesMapContainerMetadata() throws 
Exception {
+        // map_values / map_keys produce an array whose container is the map 
itself: its length is
+        // the map's entry count. The OFFSET requirement must be collected on 
the map column, as
+        // cardinality(map_values(m)) does, instead of the VALUES / KEYS 
sub-column, which has no
+        // offsets of its own when the value is a struct (`m.VALUES.OFFSET` 
fails in the BE).
+        assertColumn("select cardinality(array_map(x -> x.v + 1, 
map_values(m))) "
+                        + "from array_map_map_producer_tbl",
+                "map<text,struct<v:int>>",
+                ImmutableList.of(path("m", "VALUES", "v"), metaPath("m", 
"OFFSET")),
+                ImmutableList.of());
+
+        assertColumn("select cardinality(array_map(x -> 1, map_keys(m))) "
+                        + "from array_map_map_producer_tbl",
+                "map<text,struct<v:int>>",
+                ImmutableList.of(path("m", "KEYS"), metaPath("m", "OFFSET")),
+                ImmutableList.of());
+    }
+
     @Test
     public void testComparatorArraySortKeepsPayloadPath() throws Exception {
         assertColumn("select array_sort((x, y) -> if(cardinality(x) < 
cardinality(y), -1, "
diff --git 
a/regression-test/data/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.out
 
b/regression-test/data/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.out
new file mode 100644
index 00000000000..4402d6ad189
--- /dev/null
+++ 
b/regression-test/data/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.out
@@ -0,0 +1,57 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !root_array_map_cardinality --
+1      2       2
+2      0       0
+3      \N      \N
+4      2       2
+
+-- !struct_field_array_map_cardinality --
+1      2       2
+2      0       0
+3      \N      \N
+4      2       2
+
+-- !map_value_array_map_cardinality --
+1      2       2
+2      0       0
+3      \N      \N
+4      2       2
+
+-- !multi_array_array_map_cardinality --
+1      2
+2      0
+3      \N
+4      2
+
+-- !array_map_is_null --
+1      false   false
+2      false   false
+3      true    true
+4      false   false
+
+-- !lambda_reads_element_field --
+1      2
+2      0
+3      \N
+4      2
+
+-- !array_map_result_element_cardinality --
+1      1
+2      \N
+3      \N
+4      1
+
+-- !array_map_result_element_is_null --
+1      false
+2      true
+3      true
+4      false
+
+-- !map_values_array_map --
+1      [{"v":1}, {"v":3}]      [2, 4]  2
+2      [{"v":10}]      [11]    1
+
+-- !map_keys_array_map --
+1      2
+2      1
+
diff --git 
a/regression-test/suites/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.groovy
 
b/regression-test/suites/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.groovy
new file mode 100644
index 00000000000..c0b73b1de6f
--- /dev/null
+++ 
b/regression-test/suites/nereids_rules_p0/column_pruning/array_map_container_metadata_pruning.groovy
@@ -0,0 +1,220 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("array_map_container_metadata_pruning") {
+    sql """ DROP TABLE IF EXISTS array_map_container_metadata_pruning_tbl """
+    sql """
+        CREATE TABLE array_map_container_metadata_pruning_tbl (
+            id INT,
+            a  ARRAY<STRUCT<v: INT>>,
+            s  STRUCT<arr: ARRAY<STRUCT<v: INT>>>,
+            m  MAP<STRING, ARRAY<STRUCT<v: INT>>>
+        ) ENGINE = OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_allocation" = "tag.location.default: 1")
+    """
+    sql """
+        INSERT INTO array_map_container_metadata_pruning_tbl VALUES
+            (1,
+             array(named_struct('v', 1), named_struct('v', 2)),
+             named_struct('arr', array(named_struct('v', 1), named_struct('v', 
2))),
+             map('k', array(named_struct('v', 1), named_struct('v', 2)))),
+            (2,
+             CAST([] AS ARRAY<STRUCT<v: INT>>),
+             named_struct('arr', CAST([] AS ARRAY<STRUCT<v: INT>>)),
+             map('k', CAST([] AS ARRAY<STRUCT<v: INT>>))),
+            (3,
+             CAST(NULL AS ARRAY<STRUCT<v: INT>>),
+             CAST(NULL AS STRUCT<arr: ARRAY<STRUCT<v: INT>>>),
+             CAST(NULL AS MAP<STRING, ARRAY<STRUCT<v: INT>>>)),
+            (4,
+             array(CAST(NULL AS STRUCT<v: INT>), named_struct('v', 3)),
+             named_struct('arr', array(CAST(NULL AS STRUCT<v: INT>), 
named_struct('v', 3))),
+             map('k', array(CAST(NULL AS STRUCT<v: INT>), named_struct('v', 
3))))
+    """
+
+    // ARRAY_MAP keeps the container shape of its inputs: the result offsets 
come from the first
+    // input array and the result null map is the union of the inputs' null 
maps. A metadata
+    // requirement (OFFSET from cardinality(), NULL from IS NULL) on the 
ARRAY_MAP result therefore
+    // has to be collected on the input array container. Collecting it through 
the lambda element
+    // instead yields paths such as a.*.OFFSET, which the BE rejects when the 
element is a struct.
+    order_qt_root_array_map_cardinality """
+        SELECT id, cardinality(a) AS direct, cardinality(array_map(x -> x, a)) 
AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    order_qt_struct_field_array_map_cardinality """
+        SELECT id,
+               cardinality(element_at(s, 'arr')) AS direct,
+               cardinality(array_map(x -> x, element_at(s, 'arr'))) AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    order_qt_map_value_array_map_cardinality """
+        SELECT id,
+               cardinality(element_at(m, 'k')) AS direct,
+               cardinality(array_map(x -> x, element_at(m, 'k'))) AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    order_qt_multi_array_array_map_cardinality """
+        SELECT id, cardinality(array_map((x, y) -> x.v + y.v, a, a)) AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    order_qt_array_map_is_null """
+        SELECT id, a IS NULL AS direct, array_map(x -> x, a) IS NULL AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    // The lambda body still reads element data: only the element field it 
references is needed.
+    order_qt_lambda_reads_element_field """
+        SELECT id, cardinality(array_map(x -> x.v + 1, a)) AS mapped
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    // A metadata request that addresses an element of the ARRAY_MAP result 
instead of its own
+    // container (cardinality of a selected result element) is produced by the 
lambda. It must not
+    // be routed to the input array: that emitted `a.*.OFFSET` on the struct 
element and failed
+    // with E-3110. Only the lambda payload is read and the query keeps 
working.
+    order_qt_array_map_result_element_cardinality """
+        SELECT id, cardinality(element_at(array_map(x -> array(x.v), a), 1)) 
AS c
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    order_qt_array_map_result_element_is_null """
+        SELECT id, element_at(array_map(x -> array(x.v), a), 1) IS NULL AS n
+        FROM array_map_container_metadata_pruning_tbl ORDER BY id
+    """
+
+    explain {
+        sql """
+            SELECT cardinality(element_at(array_map(x -> array(x.v), a), 1))
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "a.*.v"
+        notContains "a.*.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT element_at(array_map(x -> array(x.v), a), 1) IS NULL
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "a.*.v"
+        notContains "a.*.NULL"
+    }
+
+    // map_values / map_keys produce an array whose container is the map 
itself: its length is the
+    // map's entry count. Cardinality over ARRAY_MAP of such an input must 
collect the map column's
+    // OFFSET, not `m.VALUES.OFFSET` / `m.KEYS.OFFSET`: the KEYS / VALUES 
sub-column has no offsets
+    // of its own when the value is a struct, and the BE rejects that metadata 
path.
+    sql """ DROP TABLE IF EXISTS array_map_map_producer_pruning_tbl """
+    sql """
+        CREATE TABLE array_map_map_producer_pruning_tbl (
+            id INT,
+            m  MAP<STRING, STRUCT<v: INT>>
+        ) ENGINE = OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_allocation" = "tag.location.default: 1")
+    """
+    sql """
+        INSERT INTO array_map_map_producer_pruning_tbl VALUES
+            (1, map('a', named_struct('v', 1), 'b', named_struct('v', 3))),
+            (2, map('c', named_struct('v', 10)))
+    """
+
+    order_qt_map_values_array_map """
+        SELECT id,
+               map_values(m) AS vals,
+               array_map(x -> x.v + 1, map_values(m)) AS mapped,
+               cardinality(array_map(x -> x.v + 1, map_values(m))) AS len
+        FROM array_map_map_producer_pruning_tbl ORDER BY id
+    """
+
+    order_qt_map_keys_array_map """
+        SELECT id, cardinality(array_map(x -> 1, map_keys(m))) AS len
+        FROM array_map_map_producer_pruning_tbl ORDER BY id
+    """
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> x.v + 1, map_values(m)))
+            FROM array_map_map_producer_pruning_tbl
+        """
+        contains "m.OFFSET"
+        notContains "m.VALUES.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> 1, map_keys(m)))
+            FROM array_map_map_producer_pruning_tbl
+        """
+        contains "m.OFFSET"
+        notContains "m.KEYS.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> x, a))
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "a.OFFSET"
+        contains "a.*"
+        notContains "a.*.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> x, element_at(s, 'arr')))
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "s.arr.OFFSET"
+        notContains "s.arr.*.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> x, element_at(m, 'k')))
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "m.VALUES.OFFSET"
+        notContains "m.VALUES.*.OFFSET"
+    }
+
+    explain {
+        sql """
+            SELECT array_map(x -> x, a) IS NULL
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "a.NULL"
+        notContains "a.*.NULL"
+    }
+
+    explain {
+        sql """
+            SELECT cardinality(array_map(x -> x.v + 1, a))
+            FROM array_map_container_metadata_pruning_tbl
+        """
+        contains "a.*.v"
+        contains "a.OFFSET"
+        notContains "a.*.OFFSET"
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to