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

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


The following commit(s) were added to refs/heads/nested_column_prune by this 
push:
     new 7596968d87b support prune nested column in fe
7596968d87b is described below

commit 7596968d87b7a544d9e3235340b58021819c3b02
Author: 924060929 <[email protected]>
AuthorDate: Tue Oct 14 17:21:24 2025 +0800

    support prune nested column in fe
---
 .../org/apache/doris/datasource/FileScanNode.java  |   2 +
 .../org/apache/doris/nereids/StatementContext.java |  11 -
 .../glue/translator/PhysicalPlanTranslator.java    |  19 -
 .../glue/translator/PlanTranslatorContext.java     |   5 +
 .../doris/nereids/jobs/executor/Rewriter.java      |   4 +-
 .../doris/nereids/rules/analysis/BindRelation.java |   9 +-
 .../rewrite/AccessPathExpressionCollector.java     | 444 +++++++++++++++++
 .../rules/rewrite/AccessPathPlanCollector.java     | 161 ++++++
 ...lumnCollector.java => NestedColumnPruning.java} | 277 ++---------
 .../nereids/rules/rewrite/SlotTypeReplacer.java    | 541 +++++++++++++++++++++
 .../rules/rewrite/VariantSubPathPruning.java       |   3 +-
 .../nereids/trees/expressions/SlotReference.java   |  30 +-
 .../expressions/functions/scalar/ArrayFirst.java   |   6 +
 .../expressions/functions/scalar/ArrayLast.java    |   6 +
 .../expressions/visitor/ScalarFunctionVisitor.java |  10 +
 .../trees/plans/logical/LogicalFileScan.java       |  38 +-
 .../trees/plans/logical/LogicalHudiScan.java       |  22 +-
 .../trees/plans/logical/LogicalOlapScan.java       |  16 +
 .../apache/doris/nereids/types/VariantType.java    |   2 +-
 .../org/apache/doris/planner/OlapScanNode.java     |   6 +
 .../java/org/apache/doris/planner/ScanNode.java    |  50 ++
 ...estedColumn.java => PruneNestedColumnTest.java} | 142 +++++-
 gensrc/thrift/Descriptors.thrift                   |  92 ++--
 23 files changed, 1550 insertions(+), 346 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/FileScanNode.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/FileScanNode.java
index 8cfe183ebf0..8694ebcbc6c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/FileScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/FileScanNode.java
@@ -171,6 +171,8 @@ public abstract class FileScanNode extends ExternalScanNode 
{
         }
         output.append(String.format("numNodes=%s", numNodes)).append("\n");
 
+        printNestedColumns(output, prefix);
+
         // pushdown agg
         output.append(prefix).append(String.format("pushdown agg=%s", 
pushDownAggNoGroupingOp));
         if (pushDownAggNoGroupingOp.equals(TPushAggOp.COUNT)) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
index e8d4b28ea83..2539206925c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
@@ -17,7 +17,6 @@
 
 package org.apache.doris.nereids;
 
-import org.apache.doris.analysis.AccessPathInfo;
 import org.apache.doris.analysis.StatementBase;
 import org.apache.doris.analysis.TableScanParams;
 import org.apache.doris.analysis.TableSnapshot;
@@ -278,8 +277,6 @@ public class StatementContext implements Closeable {
 
     private boolean hasNestedColumns;
 
-    private Map<Integer, AccessPathInfo> slotIdToAcessPathInfo = new 
HashMap<>();
-
     public StatementContext() {
         this(ConnectContext.get(), null, 0);
     }
@@ -1007,12 +1004,4 @@ public class StatementContext implements Closeable {
     public void setHasNestedColumns(boolean hasNestedColumns) {
         this.hasNestedColumns = hasNestedColumns;
     }
-
-    public void setSlotIdToAccessPathInfo(int slotId, AccessPathInfo 
accessPathInfo) {
-        this.slotIdToAcessPathInfo.put(slotId, accessPathInfo);
-    }
-
-    public Optional<AccessPathInfo> getAccessPathInfo(Slot slot) {
-        return 
Optional.ofNullable(this.slotIdToAcessPathInfo.get(slot.getExprId().asInt()));
-    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
index 2e8b26b41a5..de8448f4880 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java
@@ -17,7 +17,6 @@
 
 package org.apache.doris.nereids.glue.translator;
 
-import org.apache.doris.analysis.AccessPathInfo;
 import org.apache.doris.analysis.AggregateInfo;
 import org.apache.doris.analysis.AnalyticWindow;
 import org.apache.doris.analysis.BinaryPredicate;
@@ -75,7 +74,6 @@ import 
org.apache.doris.fs.TransactionScopeCachingDirectoryListerFactory;
 import org.apache.doris.info.BaseTableRefInfo;
 import org.apache.doris.info.TableNameInfo;
 import org.apache.doris.info.TableRefInfo;
-import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.processor.post.runtimefilterv2.RuntimeFilterV2;
 import org.apache.doris.nereids.properties.DistributionSpec;
@@ -174,7 +172,6 @@ import org.apache.doris.nereids.types.ArrayType;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.JsonType;
 import org.apache.doris.nereids.types.MapType;
-import org.apache.doris.nereids.types.NestedColumnPrunable;
 import org.apache.doris.nereids.types.StructType;
 import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.JoinUtils;
@@ -249,7 +246,6 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -830,22 +826,7 @@ public class PhysicalPlanTranslator extends 
DefaultPlanVisitor<PlanFragment, Pla
         OlapTable olapTable = olapScan.getTable();
         // generate real output tuple
         TupleDescriptor tupleDescriptor = generateTupleDesc(slots, olapTable, 
context);
-        StatementContext statementContext = context.getStatementContext();
-
         List<SlotDescriptor> slotDescriptors = tupleDescriptor.getSlots();
-        for (int i = 0; i < slots.size(); i++) {
-            Slot slot = slots.get(i);
-            if (slot.getDataType() instanceof NestedColumnPrunable) {
-                Optional<AccessPathInfo> accessPathInfo = 
statementContext.getAccessPathInfo(slot);
-                if (accessPathInfo.isPresent()) {
-                    SlotDescriptor slotDescriptor = slotDescriptors.get(i);
-                    AccessPathInfo accessPath = accessPathInfo.get();
-                    
slotDescriptor.setType(accessPath.getPrunedType().toCatalogDataType());
-                    
slotDescriptor.setAllAccessPaths(accessPath.getAllAccessPaths());
-                    
slotDescriptor.setPredicateAccessPaths(accessPath.getPredicateAccessPaths());
-                }
-            }
-        }
 
         // put virtual column expr into slot desc
         Map<ExprId, Expression> slotToVirtualColumnMap = 
olapScan.getSlotToVirtualColumnMap();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PlanTranslatorContext.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PlanTranslatorContext.java
index 1dd79a033cd..0f303b88b55 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PlanTranslatorContext.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PlanTranslatorContext.java
@@ -336,6 +336,11 @@ public class PlanTranslatorContext {
         }
         this.addExprIdSlotRefPair(slotReference.getExprId(), slotRef);
         slotDescriptor.setIsNullable(slotReference.nullable());
+
+        if (slotReference.getAllAccessPaths().isPresent()) {
+            
slotDescriptor.setAllAccessPaths(slotReference.getAllAccessPaths().get());
+            
slotDescriptor.setPredicateAccessPaths(slotReference.getPredicateAccessPaths().get());
+        }
         return slotDescriptor;
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
index 820b6ec0453..eac528bc152 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
@@ -109,7 +109,7 @@ import 
org.apache.doris.nereids.rules.rewrite.MergeProjectable;
 import org.apache.doris.nereids.rules.rewrite.MergeSetOperations;
 import org.apache.doris.nereids.rules.rewrite.MergeSetOperationsExcept;
 import org.apache.doris.nereids.rules.rewrite.MergeTopNs;
-import org.apache.doris.nereids.rules.rewrite.NestedColumnCollector;
+import org.apache.doris.nereids.rules.rewrite.NestedColumnPruning;
 import org.apache.doris.nereids.rules.rewrite.NormalizeSort;
 import org.apache.doris.nereids.rules.rewrite.OperativeColumnDerive;
 import org.apache.doris.nereids.rules.rewrite.OrExpansion;
@@ -909,7 +909,7 @@ public class Rewriter extends AbstractBatchJobExecutor {
                 }
                 rewriteJobs.add(
                         topic("nested column prune",
-                            custom(RuleType.NESTED_COLUMN_PRUNING, 
NestedColumnCollector::new)
+                            custom(RuleType.NESTED_COLUMN_PRUNING, 
NestedColumnPruning::new)
                         )
                 );
                 rewriteJobs.addAll(jobs(
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
index 84652b9eed0..36b7f0064ef 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
@@ -424,7 +424,8 @@ public class BindRelation extends OneAnalysisRuleFactory {
                     if (hmsTable.getDlaType() == DLAType.HUDI) {
                         LogicalHudiScan hudiScan = new 
LogicalHudiScan(unboundRelation.getRelationId(), hmsTable,
                                 qualifierWithoutTableName, ImmutableList.of(), 
Optional.empty(),
-                                unboundRelation.getTableSample(), 
unboundRelation.getTableSnapshot());
+                                unboundRelation.getTableSample(), 
unboundRelation.getTableSnapshot(),
+                                Optional.empty());
                         hudiScan = hudiScan.withScanParams(
                                 hmsTable, 
Optional.ofNullable(unboundRelation.getScanParams()));
                         return hudiScan;
@@ -434,7 +435,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
                                 ImmutableList.of(),
                                 unboundRelation.getTableSample(),
                                 unboundRelation.getTableSnapshot(),
-                                
Optional.ofNullable(unboundRelation.getScanParams()));
+                                
Optional.ofNullable(unboundRelation.getScanParams()), Optional.empty());
                     }
                 case ICEBERG_EXTERNAL_TABLE:
                     IcebergExternalTable icebergExternalTable = 
(IcebergExternalTable) table;
@@ -464,7 +465,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
                         qualifierWithoutTableName, ImmutableList.of(),
                         unboundRelation.getTableSample(),
                         unboundRelation.getTableSnapshot(),
-                        Optional.ofNullable(unboundRelation.getScanParams()));
+                        Optional.ofNullable(unboundRelation.getScanParams()), 
Optional.empty());
                 case PAIMON_EXTERNAL_TABLE:
                 case MAX_COMPUTE_EXTERNAL_TABLE:
                 case TRINO_CONNECTOR_EXTERNAL_TABLE:
@@ -473,7 +474,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
                             qualifierWithoutTableName, ImmutableList.of(),
                             unboundRelation.getTableSample(),
                             unboundRelation.getTableSnapshot(),
-                            
Optional.ofNullable(unboundRelation.getScanParams()));
+                            
Optional.ofNullable(unboundRelation.getScanParams()), Optional.empty());
                 case SCHEMA:
                     LogicalSchemaScan schemaScan = new 
LogicalSchemaScan(unboundRelation.getRelationId(), table,
                             qualifierWithoutTableName);
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
new file mode 100644
index 00000000000..7caa3e59c45
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java
@@ -0,0 +1,444 @@
+// 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.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.nereids.StatementContext;
+import 
org.apache.doris.nereids.rules.rewrite.AccessPathExpressionCollector.CollectorContext;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
+import 
org.apache.doris.nereids.trees.expressions.ArrayItemReference.ArrayItemSlot;
+import org.apache.doris.nereids.trees.expressions.Cast;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayCount;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayExists;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFilter;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFirst;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFirstIndex;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayLast;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayLastIndex;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMatchAll;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMatchAny;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayReverseSplit;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArraySortBy;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArraySplit;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsKey;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsValue;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.MapKeys;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.MapValues;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionVisitor;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.NestedColumnPrunable;
+import org.apache.doris.nereids.util.Utils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Stack;
+
+/**
+ * collect the access path, for example: `select struct_element(s, 'data')` 
has access path: ['s', 'data']
+ */
+public class AccessPathExpressionCollector extends 
DefaultExpressionVisitor<Void, CollectorContext> {
+    private StatementContext statementContext;
+    private boolean bottomPredicate;
+    private Multimap<Integer, CollectAccessPathResult> slotToAccessPaths;
+    private Stack<Map<String, Expression>> nameToLambdaArguments = new 
Stack<>();
+
+    public AccessPathExpressionCollector(
+            StatementContext statementContext, Multimap<Integer, 
CollectAccessPathResult> slotToAccessPaths,
+            boolean bottomPredicate) {
+        this.statementContext = statementContext;
+        this.slotToAccessPaths = slotToAccessPaths;
+        this.bottomPredicate = bottomPredicate;
+    }
+
+    public void collect(Expression expression) {
+        expression.accept(this, new CollectorContext(statementContext, 
bottomPredicate));
+    }
+
+    private Void continueCollectAccessPath(Expression expr, CollectorContext 
context) {
+        return expr.accept(this, context);
+    }
+
+    @Override
+    public Void visit(Expression expr, CollectorContext context) {
+        for (Expression child : expr.children()) {
+            child.accept(this, new CollectorContext(context.statementContext, 
context.bottomFilter));
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitSlotReference(SlotReference slotReference, 
CollectorContext context) {
+        DataType dataType = slotReference.getDataType();
+        if (dataType instanceof NestedColumnPrunable) {
+            context.accessPathBuilder.addPrefix(slotReference.getName());
+            ImmutableList<String> path = 
Utils.fastToImmutableList(context.accessPathBuilder.accessPath);
+            int slotId = slotReference.getExprId().asInt();
+            slotToAccessPaths.put(slotId, new CollectAccessPathResult(path, 
context.bottomFilter));
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitArrayItemSlot(ArrayItemSlot arrayItemSlot, 
CollectorContext context) {
+        if (nameToLambdaArguments.isEmpty()) {
+            return null;
+        }
+        context.accessPathBuilder.addPrefix("*");
+        Expression argument = 
nameToLambdaArguments.peek().get(arrayItemSlot.getName());
+        if (argument == null) {
+            return null;
+        }
+        return continueCollectAccessPath(argument, context);
+    }
+
+    @Override
+    public Void visitAlias(Alias alias, CollectorContext context) {
+        return alias.child(0).accept(this, context);
+    }
+
+    @Override
+    public Void visitCast(Cast cast, CollectorContext context) {
+        return cast.child(0).accept(this, context);
+    }
+
+    // array element at
+    @Override
+    public Void visitElementAt(ElementAt elementAt, CollectorContext context) {
+        List<Expression> arguments = elementAt.getArguments();
+        Expression first = arguments.get(0);
+        if (first.getDataType().isArrayType() || 
first.getDataType().isMapType()) {
+            context.accessPathBuilder.addPrefix("*");
+            continueCollectAccessPath(first, context);
+
+            for (int i = 1; i < arguments.size(); i++) {
+                visit(arguments.get(i), context);
+            }
+            return null;
+        } else {
+            return visit(elementAt, context);
+        }
+    }
+
+    // struct element_at
+    @Override
+    public Void visitStructElement(StructElement structElement, 
CollectorContext context) {
+        List<Expression> arguments = structElement.getArguments();
+        Expression struct = arguments.get(0);
+        Expression fieldName = arguments.get(1);
+        DataType fieldType = fieldName.getDataType();
+
+        if (fieldName.isLiteral() && (fieldType.isIntegerLikeType() || 
fieldType.isStringLikeType())) {
+            context.accessPathBuilder.addPrefix(((Literal) 
fieldName).getStringValue());
+            return continueCollectAccessPath(struct, context);
+        }
+
+        for (Expression argument : arguments) {
+            visit(argument, context);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitMapKeys(MapKeys mapKeys, CollectorContext context) {
+        context.accessPathBuilder.addPrefix("KEYS");
+        return continueCollectAccessPath(mapKeys.getArgument(0), context);
+    }
+
+    @Override
+    public Void visitMapValues(MapValues mapValues, CollectorContext context) {
+        LinkedList<String> suffixPath = context.accessPathBuilder.accessPath;
+        if (!suffixPath.isEmpty() && suffixPath.get(0).equals("*")) {
+            CollectorContext removeStarContext
+                    = new CollectorContext(context.statementContext, 
context.bottomFilter);
+            
removeStarContext.accessPathBuilder.accessPath.addAll(suffixPath.subList(1, 
suffixPath.size()));
+            removeStarContext.accessPathBuilder.addPrefix("VALUES");
+            return continueCollectAccessPath(mapValues.getArgument(0), 
removeStarContext);
+        }
+        context.accessPathBuilder.addPrefix("VALUES");
+        return continueCollectAccessPath(mapValues.getArgument(0), context);
+    }
+
+    @Override
+    public Void visitMapContainsKey(MapContainsKey mapContainsKey, 
CollectorContext context) {
+        context.accessPathBuilder.addPrefix("KEYS");
+        return continueCollectAccessPath(mapContainsKey.getArgument(0), 
context);
+    }
+
+    @Override
+    public Void visitMapContainsValue(MapContainsValue mapContainsValue, 
CollectorContext context) {
+        context.accessPathBuilder.addPrefix("VALUES");
+        return continueCollectAccessPath(mapContainsValue.getArgument(0), 
context);
+    }
+
+    @Override
+    public Void visitArrayMap(ArrayMap arrayMap, CollectorContext context) {
+        // ARRAY_MAP(lambda, <arr> [ , <arr> ... ] )
+
+        Expression argument = arrayMap.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            return collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayMap, context);
+    }
+
+    @Override
+    public Void visitArrayCount(ArrayCount arrayCount, CollectorContext 
context) {
+        // ARRAY_COUNT(<lambda>, <arr>[, ... ])
+
+        Expression argument = arrayCount.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            return collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayCount, context);
+    }
+
+    @Override
+    public Void visitArrayExists(ArrayExists arrayExists, CollectorContext 
context) {
+        // ARRAY_EXISTS([ <lambda>, ] <arr1> [, <arr2> , ...] )
+
+        Expression argument = arrayExists.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            return collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayExists, context);
+    }
+
+    @Override
+    public Void visitArrayFilter(ArrayFilter arrayFilter, CollectorContext 
context) {
+        // ARRAY_FILTER(<lambda>, <arr>)
+
+        Expression argument = arrayFilter.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayFilter, context);
+    }
+
+    @Override
+    public Void visitArrayFirst(ArrayFirst arrayFirst, CollectorContext 
context) {
+        // ARRAY_FIRST(<lambda>, <arr>)
+
+        Expression argument = arrayFirst.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayFirst, context);
+    }
+
+    @Override
+    public Void visitArrayFirstIndex(ArrayFirstIndex arrayFirstIndex, 
CollectorContext context) {
+        // ARRAY_FIRST_INDEX(<lambda>, <arr> [, ...])
+
+        Expression argument = arrayFirstIndex.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayFirstIndex, context);
+    }
+
+    @Override
+    public Void visitArrayLast(ArrayLast arrayLast, CollectorContext context) {
+        // ARRAY_LAST(<lambda>, <arr>)
+
+        Expression argument = arrayLast.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayLast, context);
+    }
+
+    @Override
+    public Void visitArrayLastIndex(ArrayLastIndex arrayLastIndex, 
CollectorContext context) {
+        // ARRAY_LAST_INDEX(<lambda>, <arr> [, ...])
+
+        Expression argument = arrayLastIndex.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayLastIndex, context);
+    }
+
+    @Override
+    public Void visitArrayMatchAny(ArrayMatchAny arrayMatchAny, 
CollectorContext context) {
+        // array_match_any(lambda, <arr> [, <arr> ...])
+
+        Expression argument = arrayMatchAny.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayMatchAny, context);
+    }
+
+    @Override
+    public Void visitArrayMatchAll(ArrayMatchAll arrayMatchAll, 
CollectorContext context) {
+        // array_match_all(lambda, <arr> [, <arr> ...])
+
+        Expression argument = arrayMatchAll.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayMatchAll, context);
+    }
+
+    @Override
+    public Void visitArrayReverseSplit(ArrayReverseSplit arrayReverseSplit, 
CollectorContext context) {
+        // ARRAY_REVERSE_SPLIT(<lambda>, <arr> [, ...])
+
+        Expression argument = arrayReverseSplit.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arrayReverseSplit, context);
+    }
+
+    @Override
+    public Void visitArraySplit(ArraySplit arraySplit, CollectorContext 
context) {
+        // ARRAY_SPLIT(<lambda>, arr [, ...])
+
+        Expression argument = arraySplit.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arraySplit, context);
+    }
+
+    @Override
+    public Void visitArraySortBy(ArraySortBy arraySortBy, CollectorContext 
context) {
+        // ARRAY_SORTBY(<lambda>, <arr> [, ...])
+
+        Expression argument = arraySortBy.getArgument(0);
+        if ((argument instanceof Lambda)) {
+            collectArrayPathInLambda((Lambda) argument, context);
+        }
+        return visit(arraySortBy, context);
+    }
+
+    private Void collectArrayPathInLambda(Lambda lambda, CollectorContext 
context) {
+        List<Expression> arguments = lambda.getArguments();
+        Map<String, Expression> nameToArray = Maps.newLinkedHashMap();
+        for (Expression argument : arguments) {
+            if (argument instanceof ArrayItemReference) {
+                nameToArray.put(((ArrayItemReference) argument).getName(), 
argument.child(0));
+            }
+        }
+
+        List<String> path = context.accessPathBuilder.getPathList();
+        if (!path.isEmpty() && path.get(0).equals("*")) {
+            context.accessPathBuilder.removePrefix();
+        }
+
+        nameToLambdaArguments.push(nameToArray);
+        try {
+            continueCollectAccessPath(arguments.get(0), context);
+        } finally {
+            nameToLambdaArguments.pop();
+        }
+        return null;
+    }
+
+    /** CollectorContext */
+    public static class CollectorContext {
+        private StatementContext statementContext;
+        private AccessPathBuilder accessPathBuilder;
+        private boolean bottomFilter;
+
+        public CollectorContext(StatementContext statementContext, boolean 
bottomFilter) {
+            this.statementContext = statementContext;
+            this.accessPathBuilder = new AccessPathBuilder();
+            this.bottomFilter = bottomFilter;
+        }
+    }
+
+    private static class AccessPathBuilder {
+        private LinkedList<String> accessPath;
+
+        public AccessPathBuilder() {
+            accessPath = new LinkedList<>();
+        }
+
+        public AccessPathBuilder addPrefix(String prefix) {
+            accessPath.addFirst(prefix);
+            return this;
+        }
+
+        public AccessPathBuilder removePrefix() {
+            accessPath.removeFirst();
+            return this;
+        }
+
+        public List<String> getPathList() {
+            return accessPath;
+        }
+
+        @Override
+        public String toString() {
+            return String.join(".", accessPath);
+        }
+    }
+
+    /** AccessPathIsPredicate */
+    public static class CollectAccessPathResult {
+        private final List<String> path;
+        private final boolean isPredicate;
+
+        public CollectAccessPathResult(List<String> path, boolean isPredicate) 
{
+            this.path = path;
+            this.isPredicate = isPredicate;
+        }
+
+        public List<String> getPath() {
+            return path;
+        }
+
+        public boolean isPredicate() {
+            return isPredicate;
+        }
+
+        @Override
+        public String toString() {
+            return String.join(".", path) + ", " + isPredicate;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            CollectAccessPathResult that = (CollectAccessPathResult) o;
+            return isPredicate == that.isPredicate && Objects.equals(path, 
that.path);
+        }
+
+        @Override
+        public int hashCode() {
+            return path.hashCode();
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathPlanCollector.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathPlanCollector.java
new file mode 100644
index 00000000000..21406c2ea13
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathPlanCollector.java
@@ -0,0 +1,161 @@
+// 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.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.nereids.StatementContext;
+import 
org.apache.doris.nereids.rules.rewrite.AccessPathExpressionCollector.CollectAccessPathResult;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEAnchor;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFileScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanVisitor;
+import org.apache.doris.nereids.types.NestedColumnPrunable;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** AccessPathPlanCollector */
+public class AccessPathPlanCollector extends DefaultPlanVisitor<Void, 
StatementContext> {
+    private Multimap<Integer, CollectAccessPathResult> allSlotToAccessPaths = 
LinkedHashMultimap.create();
+    private Map<Slot, List<CollectAccessPathResult>> scanSlotToAccessPaths = 
new LinkedHashMap<>();
+
+    public Map<Slot, List<CollectAccessPathResult>> collect(Plan root, 
StatementContext context) {
+        root.accept(this, context);
+        return scanSlotToAccessPaths;
+    }
+
+    @Override
+    public Void visitLogicalFilter(LogicalFilter<? extends Plan> filter, 
StatementContext context) {
+        boolean bottomFilter = filter.child().arity() == 0;
+        collectByExpressions(filter, context, bottomFilter);
+        return filter.child().accept(this, context);
+    }
+
+    @Override
+    public Void visitLogicalCTEAnchor(
+            LogicalCTEAnchor<? extends Plan, ? extends Plan> cteAnchor, 
StatementContext context) {
+        // first, collect access paths in the outer slots, and propagate outer 
slot's access path to inner slots
+        cteAnchor.right().accept(this, context);
+
+        // second, push down access path in the inner slots
+        cteAnchor.left().accept(this, context);
+        return null;
+    }
+
+    @Override
+    public Void visitLogicalCTEConsumer(LogicalCTEConsumer cteConsumer, 
StatementContext context) {
+        // propagate outer slot's access path to inner slots
+        for (Entry<Slot, Slot> slots : 
cteConsumer.getConsumerToProducerOutputMap().entrySet()) {
+            Slot outerSlot = slots.getKey();
+
+            if (outerSlot.getDataType() instanceof NestedColumnPrunable) {
+                int outerSlotId = outerSlot.getExprId().asInt();
+                int innerSlotId = slots.getValue().getExprId().asInt();
+                allSlotToAccessPaths.putAll(innerSlotId, 
allSlotToAccessPaths.get(outerSlotId));
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitLogicalCTEProducer(LogicalCTEProducer<? extends Plan> 
cteProducer, StatementContext context) {
+        return cteProducer.child().accept(this, context);
+    }
+
+    @Override
+    public Void visitLogicalUnion(LogicalUnion union, StatementContext 
context) {
+        // now we will not prune complex type through union, because we can 
not prune the complex type's literal,
+        // for example, we can not prune the literal now: array(map(1, 
named_struct('a', 100, 'b', 100))),
+        // so we can not prune this sql:
+        // select struct_element(map_values(s[0]), 'a')
+        // from (
+        //     select s from tbl
+        //     union all
+        //     select array(map(1, named_struct('a', 100, 'b', 100)))
+        // ) tbl;
+        //
+        // so we will not propagate access paths through the union
+        for (Plan child : union.children()) {
+            child.accept(this, context);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitLogicalOlapScan(LogicalOlapScan olapScan, 
StatementContext context) {
+        for (Slot slot : olapScan.getOutput()) {
+            if (!(slot.getDataType() instanceof NestedColumnPrunable)) {
+                continue;
+            }
+            Collection<CollectAccessPathResult> accessPaths = 
allSlotToAccessPaths.get(slot.getExprId().asInt());
+            if (!accessPaths.isEmpty()) {
+                scanSlotToAccessPaths.put(slot, new ArrayList<>(accessPaths));
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitLogicalFileScan(LogicalFileScan fileScan, 
StatementContext context) {
+        for (Slot slot : fileScan.getOutput()) {
+            if (!(slot.getDataType() instanceof NestedColumnPrunable)) {
+                continue;
+            }
+            Collection<CollectAccessPathResult> accessPaths = 
allSlotToAccessPaths.get(slot.getExprId().asInt());
+            if (!accessPaths.isEmpty()) {
+                scanSlotToAccessPaths.put(slot, new ArrayList<>(accessPaths));
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(Plan plan, StatementContext context) {
+        collectByExpressions(plan, context);
+
+        for (Plan child : plan.children()) {
+            child.accept(this, context);
+        }
+        return null;
+    }
+
+    private void collectByExpressions(Plan plan, StatementContext context) {
+        collectByExpressions(plan, context, false);
+    }
+
+    private void collectByExpressions(Plan plan, StatementContext context, 
boolean bottomPredicate) {
+        AccessPathExpressionCollector exprCollector
+                = new AccessPathExpressionCollector(context, 
allSlotToAccessPaths, bottomPredicate);
+        for (Expression expression : plan.getExpressions()) {
+            exprCollector.collect(expression);
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnCollector.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnPruning.java
similarity index 57%
rename from 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnCollector.java
rename to 
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnPruning.java
index d95ace059b4..d6ac455149c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnCollector.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnPruning.java
@@ -22,36 +22,22 @@ import org.apache.doris.common.Pair;
 import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.jobs.JobContext;
-import org.apache.doris.nereids.trees.expressions.Alias;
-import org.apache.doris.nereids.trees.expressions.Cast;
-import org.apache.doris.nereids.trees.expressions.Expression;
+import 
org.apache.doris.nereids.rules.rewrite.AccessPathExpressionCollector.CollectAccessPathResult;
 import org.apache.doris.nereids.trees.expressions.Slot;
-import org.apache.doris.nereids.trees.expressions.SlotReference;
-import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap;
-import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
-import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsKey;
-import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsValue;
-import org.apache.doris.nereids.trees.expressions.functions.scalar.MapKeys;
-import org.apache.doris.nereids.trees.expressions.functions.scalar.MapValues;
-import 
org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
-import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionVisitor;
 import org.apache.doris.nereids.trees.plans.Plan;
-import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
 import org.apache.doris.nereids.trees.plans.visitor.CustomRewriter;
 import org.apache.doris.nereids.types.ArrayType;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.MapType;
-import org.apache.doris.nereids.types.NestedColumnPrunable;
 import org.apache.doris.nereids.types.NullType;
 import org.apache.doris.nereids.types.StructField;
 import org.apache.doris.nereids.types.StructType;
-import org.apache.doris.nereids.util.Utils;
 import org.apache.doris.thrift.TAccessPathType;
 import org.apache.doris.thrift.TColumnAccessPaths;
 import org.apache.doris.thrift.TColumnNameAccessPath;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.TreeMultimap;
 import org.apache.commons.lang3.StringUtils;
@@ -59,14 +45,26 @@ import org.apache.commons.lang3.StringUtils;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 
-/** NestedColumnCollector */
-public class NestedColumnCollector implements CustomRewriter {
+/**
+ * <li> 1. prune the data type of struct/map
+ *
+ * <p> for example, column s is a struct&lt;id: int, value: double&gt;,
+ *     and `select struct(s, 'id') from tbl` will return the data type: 
`struct&lt;id: int&gt;`
+ * </p>
+ * </li>
+ *
+ * <li> 2. collect the access paths
+ * <p> for example, select struct(s, 'id'), struct(s, 'data') from tbl` will 
collect the access path:
+ *     [s.id, s.data]
+ * </p>
+ * </li>
+ */
+public class NestedColumnPruning implements CustomRewriter {
     @Override
     public Plan rewriteRoot(Plan plan, JobContext jobContext) {
         StatementContext statementContext = 
jobContext.getCascadesContext().getStatementContext();
@@ -74,17 +72,25 @@ public class NestedColumnCollector implements 
CustomRewriter {
         //     return plan;
         // }
 
-        AccessPathCollector collector = new AccessPathCollector();
-        List<AccessPathIsPredicate> slotToAccessPaths = 
collector.collectInPlan(plan, statementContext);
+        AccessPathPlanCollector collector = new AccessPathPlanCollector();
+        Map<Slot, List<CollectAccessPathResult>> slotToAccessPaths = 
collector.collect(plan, statementContext);
         Map<Integer, AccessPathInfo> slotToResult = 
pruneDataType(slotToAccessPaths);
-        for (Entry<Integer, AccessPathInfo> kv : slotToResult.entrySet()) {
-            Integer slotId = kv.getKey();
-            statementContext.setSlotIdToAccessPathInfo(slotId, kv.getValue());
+
+        if (!slotToResult.isEmpty()) {
+            Map<Integer, AccessPathInfo> slotIdToPruneType = 
Maps.newLinkedHashMap();
+            for (Entry<Integer, AccessPathInfo> kv : slotToResult.entrySet()) {
+                Integer slotId = kv.getKey();
+                AccessPathInfo accessPathInfo = kv.getValue();
+                slotIdToPruneType.put(slotId, accessPathInfo);
+            }
+            SlotTypeReplacer typeReplacer = new 
SlotTypeReplacer(slotIdToPruneType);
+            return plan.accept(typeReplacer, null);
         }
         return plan;
     }
 
-    private static Map<Integer, AccessPathInfo> 
pruneDataType(List<AccessPathIsPredicate> slotToAccessPaths) {
+    private static Map<Integer, AccessPathInfo> pruneDataType(
+            Map<Slot, List<CollectAccessPathResult>> slotToAccessPaths) {
         Map<Integer, AccessPathInfo> result = new LinkedHashMap<>();
         Map<Slot, DataTypeAccessTree> slotIdToAllAccessTree = new 
LinkedHashMap<>();
         Map<Slot, DataTypeAccessTree> slotIdToPredicateAccessTree = new 
LinkedHashMap<>();
@@ -98,22 +104,24 @@ public class NestedColumnCollector implements 
CustomRewriter {
                 Comparator.naturalOrder(), pathComparator);
 
         // first: build access data type tree
-        for (AccessPathIsPredicate accessPathIsPredicate : slotToAccessPaths) {
-            Slot slot = accessPathIsPredicate.slot;
-            List<String> path = accessPathIsPredicate.path;
-
-            DataTypeAccessTree allAccessTree = 
slotIdToAllAccessTree.computeIfAbsent(
-                    slot, i -> DataTypeAccessTree.ofRoot(slot)
-            );
-            allAccessTree.setAccessByPath(path, 0);
-            allAccessPaths.put(slot.getExprId().asInt(), path);
-
-            if (accessPathIsPredicate.isPredicate()) {
-                DataTypeAccessTree predicateAccessTree = 
slotIdToPredicateAccessTree.computeIfAbsent(
+        for (Entry<Slot, List<CollectAccessPathResult>> kv : 
slotToAccessPaths.entrySet()) {
+            Slot slot = kv.getKey();
+            List<CollectAccessPathResult> collectAccessPathResults = 
kv.getValue();
+            for (CollectAccessPathResult collectAccessPathResult : 
collectAccessPathResults) {
+                List<String> path = collectAccessPathResult.getPath();
+                DataTypeAccessTree allAccessTree = 
slotIdToAllAccessTree.computeIfAbsent(
                         slot, i -> DataTypeAccessTree.ofRoot(slot)
                 );
-                predicateAccessTree.setAccessByPath(path, 0);
-                predicateAccessPaths.put(slot.getExprId().asInt(), path);
+                allAccessTree.setAccessByPath(path, 0);
+                allAccessPaths.put(slot.getExprId().asInt(), path);
+
+                if (collectAccessPathResult.isPredicate()) {
+                    DataTypeAccessTree predicateAccessTree = 
slotIdToPredicateAccessTree.computeIfAbsent(
+                            slot, i -> DataTypeAccessTree.ofRoot(slot)
+                    );
+                    predicateAccessTree.setAccessByPath(path, 0);
+                    predicateAccessPaths.put(slot.getExprId().asInt(), path);
+                }
             }
         }
 
@@ -173,168 +181,6 @@ public class NestedColumnCollector implements 
CustomRewriter {
         return result;
     }
 
-    private class AccessPathCollector extends DefaultExpressionVisitor<Void, 
CollectorContext> {
-        private List<AccessPathIsPredicate> slotToAccessPaths = new 
ArrayList<>();
-
-        public List<AccessPathIsPredicate> collectInPlan(
-                Plan plan, StatementContext statementContext) {
-            boolean bottomFilter = plan instanceof LogicalFilter && 
plan.child(0).arity() == 0;
-            for (Expression expression : plan.getExpressions()) {
-                expression.accept(this, new CollectorContext(statementContext, 
bottomFilter));
-            }
-            for (Plan child : plan.children()) {
-                collectInPlan(child, statementContext);
-            }
-            return slotToAccessPaths;
-        }
-
-        private Void continueCollectAccessPath(Expression expr, 
CollectorContext context) {
-            return expr.accept(this, context);
-        }
-
-        @Override
-        public Void visit(Expression expr, CollectorContext context) {
-            for (Expression child : expr.children()) {
-                child.accept(this, new 
CollectorContext(context.statementContext, context.bottomFilter));
-            }
-            return null;
-        }
-
-        @Override
-        public Void visitSlotReference(SlotReference slotReference, 
CollectorContext context) {
-            DataType dataType = slotReference.getDataType();
-            if (dataType instanceof NestedColumnPrunable) {
-                context.accessPathBuilder.addPrefix(slotReference.getName());
-                ImmutableList<String> path = 
Utils.fastToImmutableList(context.accessPathBuilder.accessPath);
-                slotToAccessPaths.add(new AccessPathIsPredicate(slotReference, 
path, context.bottomFilter));
-            }
-            return null;
-        }
-
-        @Override
-        public Void visitAlias(Alias alias, CollectorContext context) {
-            return alias.child(0).accept(this, context);
-        }
-
-        @Override
-        public Void visitCast(Cast cast, CollectorContext context) {
-            return cast.child(0).accept(this, context);
-        }
-
-        // array element at
-        @Override
-        public Void visitElementAt(ElementAt elementAt, CollectorContext 
context) {
-            List<Expression> arguments = elementAt.getArguments();
-            Expression first = arguments.get(0);
-            if (first.getDataType().isArrayType() || 
first.getDataType().isMapType()
-                    || first.getDataType().isVariantType()) {
-                context.accessPathBuilder.addPrefix("*");
-                continueCollectAccessPath(first, context);
-
-                for (int i = 1; i < arguments.size(); i++) {
-                    visit(arguments.get(i), context);
-                }
-                return null;
-            } else {
-                return visit(elementAt, context);
-            }
-        }
-
-        // struct element_at
-        @Override
-        public Void visitStructElement(StructElement structElement, 
CollectorContext context) {
-            List<Expression> arguments = structElement.getArguments();
-            Expression struct = arguments.get(0);
-            Expression fieldName = arguments.get(1);
-            DataType fieldType = fieldName.getDataType();
-
-            if (fieldName.isLiteral() && (fieldType.isIntegerLikeType() || 
fieldType.isStringLikeType())) {
-                context.accessPathBuilder.addPrefix(((Literal) 
fieldName).getStringValue());
-                return continueCollectAccessPath(struct, context);
-            }
-
-            for (Expression argument : arguments) {
-                visit(argument, context);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visitMapKeys(MapKeys mapKeys, CollectorContext context) {
-            context.accessPathBuilder.addPrefix("KEYS");
-            return continueCollectAccessPath(mapKeys.getArgument(0), context);
-        }
-
-        @Override
-        public Void visitMapValues(MapValues mapValues, CollectorContext 
context) {
-            LinkedList<String> suffixPath = 
context.accessPathBuilder.accessPath;
-            if (!suffixPath.isEmpty() && suffixPath.get(0).equals("*")) {
-                CollectorContext removeStarContext
-                        = new CollectorContext(context.statementContext, 
context.bottomFilter);
-                
removeStarContext.accessPathBuilder.accessPath.addAll(suffixPath.subList(1, 
suffixPath.size()));
-                removeStarContext.accessPathBuilder.addPrefix("VALUES");
-                return continueCollectAccessPath(mapValues.getArgument(0), 
removeStarContext);
-            }
-            context.accessPathBuilder.addPrefix("VALUES");
-            return continueCollectAccessPath(mapValues.getArgument(0), 
context);
-        }
-
-        @Override
-        public Void visitMapContainsKey(MapContainsKey mapContainsKey, 
CollectorContext context) {
-            context.accessPathBuilder.addPrefix("KEYS");
-            return continueCollectAccessPath(mapContainsKey.getArgument(0), 
context);
-        }
-
-        @Override
-        public Void visitMapContainsValue(MapContainsValue mapContainsValue, 
CollectorContext context) {
-            context.accessPathBuilder.addPrefix("VALUES");
-            return continueCollectAccessPath(mapContainsValue.getArgument(0), 
context);
-        }
-
-        @Override
-        public Void visitArrayMap(ArrayMap arrayMap, CollectorContext context) 
{
-            // Lambda lambda = (Lambda) arrayMap.getArgument(0);
-            // Expression array = arrayMap.getArgument(1);
-
-            // String arrayName = lambda.getLambdaArgumentName(0);
-            return super.visitArrayMap(arrayMap, context);
-        }
-    }
-
-    private static class CollectorContext {
-        private StatementContext statementContext;
-        private AccessPathBuilder accessPathBuilder;
-        private boolean bottomFilter;
-
-        public CollectorContext(StatementContext statementContext, boolean 
bottomFilter) {
-            this.statementContext = statementContext;
-            this.accessPathBuilder = new AccessPathBuilder();
-            this.bottomFilter = bottomFilter;
-        }
-    }
-
-    private static class AccessPathBuilder {
-        private LinkedList<String> accessPath;
-
-        public AccessPathBuilder() {
-            accessPath = new LinkedList<>();
-        }
-
-        public AccessPathBuilder addPrefix(String prefix) {
-            accessPath.addFirst(prefix);
-            return this;
-        }
-
-        public List<String> toStringList() {
-            return new ArrayList<>(accessPath);
-        }
-
-        @Override
-        public String toString() {
-            return String.join(".", accessPath);
-        }
-    }
-
     private static class DataTypeAccessTree {
         private DataType type;
         private boolean isRoot;
@@ -488,33 +334,4 @@ public class NestedColumnCollector implements 
CustomRewriter {
             }
         }
     }
-
-    private static class AccessPathIsPredicate {
-        private final Slot slot;
-        private final List<String> path;
-        private final boolean isPredicate;
-
-        public AccessPathIsPredicate(Slot slot, List<String> path, boolean 
isPredicate) {
-            this.slot = slot;
-            this.path = path;
-            this.isPredicate = isPredicate;
-        }
-
-        public Slot getSlot() {
-            return slot;
-        }
-
-        public List<String> getPath() {
-            return path;
-        }
-
-        public boolean isPredicate() {
-            return isPredicate;
-        }
-
-        @Override
-        public String toString() {
-            return slot.getName() + ": " + String.join(".", path) + ", " + 
isPredicate;
-        }
-    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
new file mode 100644
index 00000000000..ae61878f036
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
@@ -0,0 +1,541 @@
+// 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.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.analysis.AccessPathInfo;
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.OrderExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.Function;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer;
+import 
org.apache.doris.nereids.trees.plans.logical.LogicalDeferMaterializeOlapScan;
+import 
org.apache.doris.nereids.trees.plans.logical.LogicalDeferMaterializeTopN;
+import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalExcept;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFileScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalGenerate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalIntersect;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPartitionTopN;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalRepeat;
+import org.apache.doris.nereids.trees.plans.logical.LogicalResultSink;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSink;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
+import org.apache.doris.nereids.trees.plans.logical.LogicalTopN;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.trees.plans.logical.LogicalWindow;
+import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.util.MoreFieldsThread;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableMultimap.Builder;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/** SlotTypeReplacer */
+public class SlotTypeReplacer extends DefaultPlanRewriter<Void> {
+    private Map<Integer, AccessPathInfo> replacedDataTypes;
+
+    public SlotTypeReplacer(Map<Integer, AccessPathInfo> 
bottomReplacedDataTypes) {
+        this.replacedDataTypes = 
Maps.newLinkedHashMap(bottomReplacedDataTypes);
+    }
+
+    @Override
+    public Plan visitLogicalCTEProducer(LogicalCTEProducer<? extends Plan> 
cteProducer, Void context) {
+        return super.visitLogicalCTEProducer(cteProducer, context);
+    }
+
+    @Override
+    public Plan visitLogicalWindow(LogicalWindow<? extends Plan> window, Void 
context) {
+        window = visitChildren(this, window, context);
+        Pair<Boolean, ? extends List<? extends Expression>> replaced = 
replaceExpressions(
+                window.getExpressions(), false, false);
+        if (replaced.first) {
+            return window.withExpressionsAndChild((List) replaced.second, 
window.child());
+        }
+        return window;
+    }
+
+    @Override
+    public Plan visitLogicalCTEConsumer(LogicalCTEConsumer cteConsumer, Void 
context) {
+        Map<Slot, Slot> consumerToProducerOutputMap = 
cteConsumer.getConsumerToProducerOutputMap();
+        Multimap<Slot, Slot> producerToConsumerOutputMap = 
cteConsumer.getProducerToConsumerOutputMap();
+
+        Map<Slot, Slot> replacedConsumerToProducerOutputMap = new 
LinkedHashMap<>();
+        Builder<Slot, Slot> replacedProducerToConsumerOutputMap = 
ImmutableMultimap.builder();
+
+        boolean changed = false;
+        for (Entry<Slot, Slot> kv : consumerToProducerOutputMap.entrySet()) {
+            Slot consumerSlot = kv.getKey();
+            Slot producerSlot = kv.getValue();
+            AccessPathInfo accessPathInfo = 
replacedDataTypes.get(producerSlot.getExprId().asInt());
+            if (accessPathInfo != null) {
+                DataType prunedType = accessPathInfo.getPrunedType();
+                if (!prunedType.equals(producerSlot.getDataType())) {
+                    replacedDataTypes.put(consumerSlot.getExprId().asInt(), 
accessPathInfo);
+                    changed = true;
+                    producerSlot = 
producerSlot.withNullableAndDataType(producerSlot.nullable(), prunedType);
+                    consumerSlot = 
consumerSlot.withNullableAndDataType(consumerSlot.nullable(), prunedType);
+                }
+            }
+            replacedConsumerToProducerOutputMap.put(consumerSlot, 
producerSlot);
+        }
+
+        for (Entry<Slot, Collection<Slot>> kv : 
producerToConsumerOutputMap.asMap().entrySet()) {
+            Slot producerSlot = kv.getKey();
+            Collection<Slot> consumerSlots = kv.getValue();
+            AccessPathInfo accessPathInfo = 
replacedDataTypes.get(producerSlot.getExprId().asInt());
+            if (accessPathInfo != null && 
!accessPathInfo.getPrunedType().equals(producerSlot.getDataType())) {
+                DataType replacedDataType = accessPathInfo.getPrunedType();
+                changed = true;
+                producerSlot = 
producerSlot.withNullableAndDataType(producerSlot.nullable(), replacedDataType);
+                for (Slot consumerSlot : consumerSlots) {
+                    consumerSlot = 
consumerSlot.withNullableAndDataType(consumerSlot.nullable(), replacedDataType);
+                    replacedProducerToConsumerOutputMap.put(producerSlot, 
consumerSlot);
+                }
+            } else {
+                replacedProducerToConsumerOutputMap.putAll(producerSlot, 
consumerSlots);
+            }
+        }
+
+        if (changed) {
+            return new LogicalCTEConsumer(
+                    cteConsumer.getRelationId(), cteConsumer.getCteId(), 
cteConsumer.getName(),
+                    replacedConsumerToProducerOutputMap, 
replacedProducerToConsumerOutputMap.build()
+            );
+        }
+        return cteConsumer;
+    }
+
+    @Override
+    public Plan visitLogicalJoin(LogicalJoin<? extends Plan, ? extends Plan> 
join, Void context) {
+        join = visitChildren(this, join, context);
+        Pair<Boolean, List<Expression>> replacedHashJoinConjuncts
+                = replaceExpressions(join.getHashJoinConjuncts(), false, 
false);
+        Pair<Boolean, List<Expression>> replacedOtherJoinConjuncts
+                = replaceExpressions(join.getOtherJoinConjuncts(), false, 
false);
+
+        if (replacedHashJoinConjuncts.first || 
replacedOtherJoinConjuncts.first) {
+            return join.withJoinConjuncts(
+                    replacedHashJoinConjuncts.second,
+                    replacedOtherJoinConjuncts.second,
+                    join.getJoinReorderContext());
+        }
+        return join;
+    }
+
+    @Override
+    public Plan visitLogicalProject(LogicalProject<? extends Plan> project, 
Void context) {
+        project = visitChildren(this, project, context);
+
+        Pair<Boolean, List<NamedExpression>> projects = 
replaceExpressions(project.getProjects(), true, false);
+        if (projects.first) {
+            return project.withProjects(projects.second);
+        }
+        return project;
+    }
+
+    @Override
+    public Plan visitLogicalPartitionTopN(LogicalPartitionTopN<? extends Plan> 
partitionTopN, Void context) {
+        partitionTopN = visitChildren(this, partitionTopN, context);
+
+        Pair<Boolean, List<Expression>> replacedPartitionKeys = 
replaceExpressions(
+                partitionTopN.getPartitionKeys(), false, false);
+        Pair<Boolean, List<OrderExpression>> replacedOrderExpressions
+                = replaceOrderExpressions(partitionTopN.getOrderKeys());
+        if (replacedPartitionKeys.first || replacedOrderExpressions.first) {
+            return partitionTopN.withPartitionKeysAndOrderKeys(
+                    replacedPartitionKeys.second, 
replacedOrderExpressions.second);
+        }
+        return partitionTopN;
+    }
+
+    @Override
+    public Plan visitLogicalDeferMaterializeTopN(LogicalDeferMaterializeTopN<? 
extends Plan> topN, Void context) {
+        topN = visitChildren(this, topN, context);
+
+        LogicalTopN logicalTopN = (LogicalTopN) 
topN.getLogicalTopN().accept(this, context);
+        if (logicalTopN != topN.getLogicalTopN()) {
+            SlotReference replacedColumnIdSlot = replaceExpressions(
+                    ImmutableList.of(topN.getColumnIdSlot()), false, 
false).second.get(0);
+            return new LogicalDeferMaterializeTopN(
+                    logicalTopN, topN.getDeferMaterializeSlotIds(), 
replacedColumnIdSlot);
+        }
+
+        return topN;
+    }
+
+    @Override
+    public Plan visitLogicalExcept(LogicalExcept except, Void context) {
+        except = visitChildren(this, except, context);
+
+        Pair<Boolean, List<List<SlotReference>>> 
replacedRegularChildrenOutputs = replaceMultiExpressions(
+                except.getRegularChildrenOutputs());
+
+        Pair<Boolean, List<NamedExpression>> replacedOutputs
+                = replaceExpressions(except.getOutputs(), true, false);
+
+        if (replacedRegularChildrenOutputs.first || replacedOutputs.first) {
+            return new LogicalExcept(except.getQualifier(), 
except.getOutputs(),
+                    except.getRegularChildrenOutputs(), except.children());
+        }
+
+        return except;
+    }
+
+    @Override
+    public Plan visitLogicalIntersect(LogicalIntersect intersect, Void 
context) {
+        intersect = visitChildren(this, intersect, context);
+
+        Pair<Boolean, List<List<SlotReference>>> 
replacedRegularChildrenOutputs = replaceMultiExpressions(
+                intersect.getRegularChildrenOutputs());
+
+        Pair<Boolean, List<NamedExpression>> replacedOutputs
+                = replaceExpressions(intersect.getOutputs(), true, false);
+
+        if (replacedRegularChildrenOutputs.first || replacedOutputs.first) {
+            return new LogicalIntersect(intersect.getQualifier(), 
intersect.getOutputs(),
+                    intersect.getRegularChildrenOutputs(), 
intersect.children());
+        }
+        return intersect;
+    }
+
+    @Override
+    public Plan visitLogicalUnion(LogicalUnion union, Void context) {
+        union = visitChildren(this, union, context);
+
+        Pair<Boolean, List<List<SlotReference>>> 
replacedRegularChildrenOutputs = replaceMultiExpressions(
+                union.getRegularChildrenOutputs());
+
+        Pair<Boolean, List<NamedExpression>> replacedOutputs
+                = replaceExpressions(union.getOutputs(), true, false);
+
+        if (replacedRegularChildrenOutputs.first || replacedOutputs.first) {
+            return new LogicalUnion(
+                    union.getQualifier(),
+                    replacedOutputs.second,
+                    replacedRegularChildrenOutputs.second,
+                    union.getConstantExprsList(),
+                    union.hasPushedFilter(),
+                    union.children()
+            );
+        }
+
+        return union;
+    }
+
+    @Override
+    public Plan visitLogicalRepeat(LogicalRepeat<? extends Plan> repeat, Void 
context) {
+        repeat = visitChildren(this, repeat, context);
+
+        Pair<Boolean, List<List<Expression>>> replacedGroupingSets
+                = replaceMultiExpressions(repeat.getGroupingSets());
+        Pair<Boolean, List<NamedExpression>> replacedOutputs
+                = replaceExpressions(repeat.getOutputExpressions(), true, 
false);
+
+        if (replacedGroupingSets.first || replacedOutputs.first) {
+            return repeat.withGroupSetsAndOutput(replacedGroupingSets.second, 
replacedOutputs.second);
+        }
+        return repeat;
+    }
+
+    @Override
+    public Plan visitLogicalGenerate(LogicalGenerate<? extends Plan> generate, 
Void context) {
+        generate = visitChildren(this, generate, context);
+
+        Pair<Boolean, List<Function>> replacedGenerators
+                = replaceExpressions(generate.getGenerators(), false, false);
+        Pair<Boolean, List<Slot>> replacedGeneratorOutput
+                = replaceExpressions(generate.getGeneratorOutput(), false, 
false);
+        if (replacedGenerators.first || replacedGeneratorOutput.first) {
+            return new LogicalGenerate<>(replacedGenerators.second, 
replacedGeneratorOutput.second,
+                    generate.getExpandColumnAlias(), generate.child());
+        }
+        return generate;
+    }
+
+    @Override
+    public Plan visitLogicalAggregate(LogicalAggregate<? extends Plan> 
aggregate, Void context) {
+        aggregate = visitChildren(this, aggregate, context);
+
+        Pair<Boolean, List<Expression>> replacedGroupBy = replaceExpressions(
+                aggregate.getGroupByExpressions(), false, false);
+        Pair<Boolean, List<NamedExpression>> replacedOutput = 
replaceExpressions(
+                aggregate.getOutputExpressions(), true, false);
+
+        if (replacedGroupBy.first || replacedOutput.first) {
+            return aggregate.withGroupByAndOutput(replacedGroupBy.second, 
replacedOutput.second);
+        }
+        return aggregate;
+    }
+
+    @Override
+    public Plan visitLogicalSort(LogicalSort<? extends Plan> sort, Void 
context) {
+        sort = visitChildren(this, sort, context);
+
+        Pair<Boolean, List<OrderKey>> replaced = 
replaceOrderKeys(sort.getOrderKeys());
+        if (replaced.first) {
+            return sort.withOrderKeys(replaced.second);
+        }
+        return sort;
+    }
+
+    @Override
+    public Plan visitLogicalTopN(LogicalTopN<? extends Plan> topN, Void 
context) {
+        topN = visitChildren(this, topN, context);
+
+        Pair<Boolean, List<OrderKey>> replaced = 
replaceOrderKeys(topN.getOrderKeys());
+        if (replaced.first) {
+            return topN.withOrderKeys(replaced.second);
+        }
+        return topN;
+    }
+
+    @Override
+    public Plan visitLogicalDeferMaterializeOlapScan(
+            LogicalDeferMaterializeOlapScan deferMaterializeOlapScan, Void 
context) {
+
+        LogicalOlapScan logicalOlapScan
+                = (LogicalOlapScan) 
deferMaterializeOlapScan.getLogicalOlapScan().accept(this, context);
+
+        if (logicalOlapScan != deferMaterializeOlapScan.getLogicalOlapScan()) {
+            SlotReference replacedColumnIdSlot = replaceExpressions(
+                    
ImmutableList.of(deferMaterializeOlapScan.getColumnIdSlot()), false, 
false).second.get(0);
+            return new LogicalDeferMaterializeOlapScan(
+                    logicalOlapScan, 
deferMaterializeOlapScan.getDeferMaterializeSlotIds(), replacedColumnIdSlot
+            );
+        }
+        return deferMaterializeOlapScan;
+    }
+
+    @Override
+    public Plan visitLogicalFilter(LogicalFilter<? extends Plan> filter, Void 
context) {
+        filter = visitChildren(this, filter, context);
+
+        Pair<Boolean, Set<Expression>> replaced = 
replaceExpressions(filter.getConjuncts(), false, false);
+        if (replaced.first) {
+            return filter.withConjuncts(replaced.second);
+        }
+        return filter;
+    }
+
+    @Override
+    public Plan visitLogicalFileScan(LogicalFileScan fileScan, Void context) {
+        Pair<Boolean, List<Slot>> replaced = 
replaceExpressions(fileScan.getOutput(), false, true);
+        if (replaced.first) {
+            return fileScan.withCachedOutput(replaced.second);
+        }
+        return fileScan;
+    }
+
+    @Override
+    public Plan visitLogicalOlapScan(LogicalOlapScan olapScan, Void context) {
+        Pair<Boolean, List<Slot>> replaced = 
replaceExpressions(olapScan.getOutput(), false, true);
+        if (replaced.first) {
+            return olapScan.withPrunedTypeSlots(replaced.second);
+        }
+        return olapScan;
+    }
+
+    @Override
+    public Plan visitLogicalEmptyRelation(LogicalEmptyRelation emptyRelation, 
Void context) {
+        Pair<Boolean, List<NamedExpression>> replacedProjects
+                = replaceExpressions(emptyRelation.getProjects(), true, false);
+
+        if (replacedProjects.first) {
+            return emptyRelation.withProjects(replacedProjects.second);
+        }
+        return emptyRelation;
+    }
+
+    @Override
+    public Plan visitLogicalOneRowRelation(LogicalOneRowRelation 
oneRowRelation, Void context) {
+        Pair<Boolean, List<NamedExpression>> replacedProjects
+                = replaceExpressions(oneRowRelation.getProjects(), true, 
false);
+
+        if (replacedProjects.first) {
+            return oneRowRelation.withProjects(replacedProjects.second);
+        }
+        return oneRowRelation;
+    }
+
+    @Override
+    public Plan visitLogicalResultSink(LogicalResultSink<? extends Plan> 
logicalResultSink, Void context) {
+        logicalResultSink = visitChildren(this, logicalResultSink, context);
+
+        Pair<Boolean, List<NamedExpression>> replacedOutput = 
replaceExpressions(logicalResultSink.getOutputExprs(),
+                false, false);
+        if (replacedOutput.first) {
+            return logicalResultSink.withOutputExprs(replacedOutput.second);
+        }
+        return logicalResultSink;
+    }
+
+    @Override
+    public Plan visitLogicalSink(LogicalSink<? extends Plan> logicalSink, Void 
context) {
+        // do nothing
+        return logicalSink;
+    }
+
+    private Pair<Boolean, List<OrderExpression>> 
replaceOrderExpressions(List<OrderExpression> orderExpressions) {
+        ImmutableList.Builder<OrderExpression> newOrderKeys
+                = 
ImmutableList.builderWithExpectedSize(orderExpressions.size());
+        boolean changed = false;
+        for (OrderExpression orderExpression : orderExpressions) {
+            Expression newOrderKeyExpr = 
replaceSlot(orderExpression.getOrderKey().getExpr(), false);
+            if (newOrderKeyExpr != orderExpression.getOrderKey().getExpr()) {
+                newOrderKeys.add(new 
OrderExpression(orderExpression.getOrderKey().withExpression(newOrderKeyExpr)));
+                changed = true;
+            } else {
+                newOrderKeys.add(orderExpression);
+            }
+        }
+        return Pair.of(changed, newOrderKeys.build());
+    }
+
+    private Pair<Boolean, List<OrderKey>> replaceOrderKeys(List<OrderKey> 
orderKeys) {
+        ImmutableList.Builder<OrderKey> newOrderKeys = 
ImmutableList.builderWithExpectedSize(orderKeys.size());
+        boolean changed = false;
+        for (OrderKey orderKey : orderKeys) {
+            Expression newOrderKeyExpr = replaceSlot(orderKey.getExpr(), 
false);
+            if (newOrderKeyExpr != orderKey.getExpr()) {
+                newOrderKeys.add(orderKey.withExpression(newOrderKeyExpr));
+                changed = true;
+            } else {
+                newOrderKeys.add(orderKey);
+            }
+        }
+        return Pair.of(changed, newOrderKeys.build());
+    }
+
+    private <C extends Collection<E>, E extends Expression>
+            Pair<Boolean, List<C>> replaceMultiExpressions(List<C> 
expressionsList) {
+        ImmutableList.Builder<C> result = 
ImmutableList.builderWithExpectedSize(expressionsList.size());
+        boolean changed = false;
+        for (C expressions : expressionsList) {
+            Pair<Boolean, C> replaced = replaceExpressions(expressions, false, 
false);
+            changed |= replaced.first;
+            result.add(replaced.second);
+        }
+        return Pair.of(changed, result.build());
+    }
+
+    private <C extends Collection<E>, E extends Expression> Pair<Boolean, C> 
replaceExpressions(
+            C expressions, boolean propagateType, boolean fillAccessPaths) {
+        ImmutableCollection.Builder<E> newExprs;
+        if (expressions instanceof List) {
+            newExprs = ImmutableList.builder();
+        } else {
+            newExprs = ImmutableSet.builder();
+        }
+
+        boolean changed = false;
+        for (Expression oldExpr : expressions) {
+            Expression newExpr = replaceSlot(oldExpr, fillAccessPaths);
+            if (newExpr != oldExpr) {
+                newExprs.add((E) newExpr);
+                changed = true;
+
+                if (propagateType && oldExpr instanceof NamedExpression
+                        && 
!oldExpr.getDataType().equals(newExpr.getDataType())) {
+                    replacedDataTypes.put(
+                            ((NamedExpression) oldExpr).getExprId().asInt(),
+                            // not need access path in the upper slots
+                            new AccessPathInfo(newExpr.getDataType(), null, 
null)
+                    );
+                }
+            } else {
+                newExprs.add((E) oldExpr);
+            }
+        }
+        return Pair.of(changed, (C) newExprs.build());
+    }
+
+    private Expression replaceSlot(Expression expr, boolean fillAccessPath) {
+        return MoreFieldsThread.keepFunctionSignature(false, () -> {
+            return expr.rewriteUp(e -> {
+                if (e instanceof Lambda) {
+                    return rewriteLambda((Lambda) e, fillAccessPath);
+                } else if (e instanceof SlotReference) {
+                    AccessPathInfo accessPathInfo = 
replacedDataTypes.get(((SlotReference) e).getExprId().asInt());
+                    if (accessPathInfo != null) {
+                        SlotReference newSlot
+                                = (SlotReference) ((SlotReference) 
e).withNullableAndDataType(
+                                e.nullable(), accessPathInfo.getPrunedType());
+                        if (fillAccessPath) {
+                            newSlot = newSlot.withAccessPaths(
+                                    accessPathInfo.getAllAccessPaths(), 
accessPathInfo.getPredicateAccessPaths()
+                            );
+                        }
+                        return newSlot;
+                    }
+                }
+                return e;
+            });
+        });
+    }
+
+    private Expression rewriteLambda(Lambda e, boolean fillAccessPath) {
+        // we should rewrite ArrayItemReference first, then we can replace the 
ArrayItemSlot int the lambda
+        Expression[] newChildren = new Expression[e.arity()];
+        for (int i = 0; i < e.arity(); i++) {
+            Expression child = e.child(i);
+            if (child instanceof ArrayItemReference) {
+                Expression newRef = 
child.withChildren(replaceSlot(child.child(0), fillAccessPath));
+                replacedDataTypes.put(((ArrayItemReference) 
child).getExprId().asInt(),
+                        new AccessPathInfo(newRef.getDataType(), null, null));
+                newChildren[i] = newRef;
+            } else {
+                newChildren[i] = child;
+            }
+        }
+
+        for (int i = 0; i < newChildren.length; i++) {
+            Expression child = newChildren[i];
+            if (!(child instanceof ArrayItemReference)) {
+                newChildren[i] = replaceSlot(child, fillAccessPath);
+            }
+        }
+
+        return e.withChildren(newChildren);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/VariantSubPathPruning.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/VariantSubPathPruning.java
index 1ed8d151270..abb65a283ed 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/VariantSubPathPruning.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/VariantSubPathPruning.java
@@ -22,7 +22,6 @@ import org.apache.doris.common.util.DebugUtil;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.jobs.JobContext;
 import org.apache.doris.nereids.properties.OrderKey;
-import org.apache.doris.nereids.rules.rewrite.ColumnPruning.PruneContext;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -87,7 +86,7 @@ import java.util.Set;
  * generating the slots for the required sub path on scan, union, and cte 
consumer.
  * Then, it replaces the element_at with the corresponding slot.
  */
-public class VariantSubPathPruning extends DefaultPlanRewriter<PruneContext> 
implements CustomRewriter {
+public class VariantSubPathPruning implements CustomRewriter {
     public static final Logger LOG = 
LogManager.getLogger(VariantSubPathPruning.class);
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/SlotReference.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/SlotReference.java
index ce2d3dbb3e5..ad09b4f870a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/SlotReference.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/SlotReference.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.exceptions.UnboundException;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.util.Utils;
+import org.apache.doris.thrift.TColumnAccessPaths;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -56,6 +57,8 @@ public class SlotReference extends Slot {
     // that need return original table and name for view not its original 
table if u query a view
     private final TableIf oneLevelTable;
     private final Column oneLevelColumn;
+    private final Optional<TColumnAccessPaths> allAccessPaths;
+    private final Optional<TColumnAccessPaths> predicateAccessPaths;
 
     public SlotReference(String name, DataType dataType) {
         this(StatementScopeIdGenerator.newExprId(), name, dataType, true, 
ImmutableList.of(),
@@ -92,6 +95,14 @@ public class SlotReference extends Slot {
                 subPath, Optional.empty());
     }
 
+    public SlotReference(ExprId exprId, Supplier<String> name, DataType 
dataType, boolean nullable,
+            List<String> qualifier, @Nullable TableIf originalTable, @Nullable 
Column originalColumn,
+            @Nullable TableIf oneLevelTable, Column oneLevelColumn,
+            List<String> subPath, Optional<Pair<Integer, Integer>> indexInSql) 
{
+        this(exprId, name, dataType, nullable, qualifier, originalTable, 
originalColumn, oneLevelTable,
+                oneLevelColumn, subPath, indexInSql, Optional.empty(), 
Optional.empty());
+    }
+
     /**
      * Constructor for SlotReference.
      *
@@ -106,7 +117,8 @@ public class SlotReference extends Slot {
     public SlotReference(ExprId exprId, Supplier<String> name, DataType 
dataType, boolean nullable,
             List<String> qualifier, @Nullable TableIf originalTable, @Nullable 
Column originalColumn,
             @Nullable TableIf oneLevelTable, Column oneLevelColumn,
-            List<String> subPath, Optional<Pair<Integer, Integer>> indexInSql) 
{
+            List<String> subPath, Optional<Pair<Integer, Integer>> indexInSql,
+            Optional<TColumnAccessPaths> allAccessPaths, 
Optional<TColumnAccessPaths> predicateAccessPaths) {
         super(indexInSql);
         this.exprId = exprId;
         this.name = name;
@@ -119,6 +131,8 @@ public class SlotReference extends Slot {
         this.oneLevelTable = oneLevelTable;
         this.oneLevelColumn = oneLevelColumn;
         this.subPath = Objects.requireNonNull(subPath, "subPath can not be 
null");
+        this.allAccessPaths = allAccessPaths;
+        this.predicateAccessPaths = predicateAccessPaths;
     }
 
     public static SlotReference of(String name, DataType type) {
@@ -342,4 +356,18 @@ public class SlotReference extends Slot {
     public boolean hasAutoInc() {
         return originalColumn != null ? originalColumn.isAutoInc() : false;
     }
+
+    public SlotReference withAccessPaths(TColumnAccessPaths allAccessPaths, 
TColumnAccessPaths predicateAccessPaths) {
+        return new SlotReference(exprId, name, dataType, nullable, qualifier,
+                originalTable, originalColumn, oneLevelTable, oneLevelColumn,
+                subPath, indexInSqlString, Optional.of(allAccessPaths), 
Optional.of(predicateAccessPaths));
+    }
+
+    public Optional<TColumnAccessPaths> getAllAccessPaths() {
+        return allAccessPaths;
+    }
+
+    public Optional<TColumnAccessPaths> getPredicateAccessPaths() {
+        return predicateAccessPaths;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayFirst.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayFirst.java
index 8c0babc39ca..5410de371a7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayFirst.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayFirst.java
@@ -20,6 +20,7 @@ package 
org.apache.doris.nereids.trees.expressions.functions.scalar;
 import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 
 import java.util.List;
 
@@ -51,4 +52,9 @@ public class ArrayFirst extends ElementAt
     public List<FunctionSignature> getImplSignature() {
         return SIGNATURES;
     }
+
+    @Override
+    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+        return visitor.visitArrayFirst(this, context);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayLast.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayLast.java
index e1ed4f4d27b..b9f5650156f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayLast.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayLast.java
@@ -20,6 +20,7 @@ package 
org.apache.doris.nereids.trees.expressions.functions.scalar;
 import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 
 import java.util.List;
 
@@ -51,4 +52,9 @@ public class ArrayLast extends ElementAt
     public ElementAt withChildren(List<Expression> children) {
         return new ArrayLast(getFunctionParams(children));
     }
+
+    @Override
+    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+        return visitor.visitArrayLast(this, context);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
index 37b6b6233bd..f32e91edd23 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
@@ -54,10 +54,12 @@ import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayEnumerat
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayExcept;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayExists;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFilter;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFirst;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFirstIndex;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayFlatten;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayIntersect;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayJoin;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayLast;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayLastIndex;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMatchAll;
@@ -624,6 +626,10 @@ public interface ScalarFunctionVisitor<R, C> {
         return visitScalarFunction(arrayFilter, context);
     }
 
+    default R visitArrayFirst(ArrayFirst arrayFirst, C context) {
+        return visitElementAt(arrayFirst, context);
+    }
+
     default R visitArrayFirstIndex(ArrayFirstIndex arrayFirstIndex, C context) 
{
         return visitScalarFunction(arrayFirstIndex, context);
     }
@@ -636,6 +642,10 @@ public interface ScalarFunctionVisitor<R, C> {
         return visitScalarFunction(arrayJoin, context);
     }
 
+    default R visitArrayLast(ArrayLast arrayLast, C context) {
+        return visitElementAt(arrayLast, context);
+    }
+
     default R visitArrayLastIndex(ArrayLastIndex arrayLastIndex, C context) {
         return visitScalarFunction(arrayLastIndex, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
index 4167e68856d..5d0f41f3818 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
@@ -51,16 +51,17 @@ public class LogicalFileScan extends LogicalCatalogRelation 
{
     protected final Optional<TableSample> tableSample;
     protected final Optional<TableSnapshot> tableSnapshot;
     protected final Optional<TableScanParams> scanParams;
+    protected final Optional<List<Slot>> cachedOutputs;
 
     public LogicalFileScan(RelationId id, ExternalTable table, List<String> 
qualifier,
                            Collection<Slot> operativeSlots,
                            Optional<TableSample> tableSample, 
Optional<TableSnapshot> tableSnapshot,
-                           Optional<TableScanParams> scanParams) {
+                           Optional<TableScanParams> scanParams, 
Optional<List<Slot>> cachedOutputs) {
         this(id, table, qualifier,
                 
table.initSelectedPartitions(MvccUtil.getSnapshotFromContext(table)),
                 operativeSlots, ImmutableList.of(),
                 tableSample, tableSnapshot,
-                scanParams, Optional.empty(), Optional.empty());
+                scanParams, Optional.empty(), Optional.empty(), cachedOutputs);
     }
 
     /**
@@ -70,13 +71,15 @@ public class LogicalFileScan extends LogicalCatalogRelation 
{
             SelectedPartitions selectedPartitions, Collection<Slot> 
operativeSlots,
             List<NamedExpression> virtualColumns, Optional<TableSample> 
tableSample,
             Optional<TableSnapshot> tableSnapshot, Optional<TableScanParams> 
scanParams,
-            Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties) {
+            Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties,
+            Optional<List<Slot>> cachedSlots) {
         super(id, PlanType.LOGICAL_FILE_SCAN, table, qualifier, 
operativeSlots, virtualColumns,
                 groupExpression, logicalProperties);
         this.selectedPartitions = selectedPartitions;
         this.tableSample = tableSample;
         this.tableSnapshot = tableSnapshot;
         this.scanParams = scanParams;
+        this.cachedOutputs = cachedSlots;
     }
 
     public SelectedPartitions getSelectedPartitions() {
@@ -116,7 +119,7 @@ public class LogicalFileScan extends LogicalCatalogRelation 
{
     public LogicalFileScan withGroupExpression(Optional<GroupExpression> 
groupExpression) {
         return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
-                scanParams, groupExpression, 
Optional.of(getLogicalProperties()));
+                scanParams, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     @Override
@@ -124,20 +127,20 @@ public class LogicalFileScan extends 
LogicalCatalogRelation {
             Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
         return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
-                scanParams, groupExpression, logicalProperties);
+                scanParams, groupExpression, logicalProperties, cachedOutputs);
     }
 
     public LogicalFileScan withSelectedPartitions(SelectedPartitions 
selectedPartitions) {
         return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
-                scanParams, Optional.empty(), 
Optional.of(getLogicalProperties()));
+                scanParams, Optional.empty(), 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     @Override
     public LogicalFileScan withRelationId(RelationId relationId) {
         return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
-                scanParams, Optional.empty(), Optional.empty());
+                scanParams, Optional.empty(), Optional.empty(), cachedOutputs);
     }
 
     @Override
@@ -150,6 +153,19 @@ public class LogicalFileScan extends 
LogicalCatalogRelation {
         return super.equals(o) && Objects.equals(selectedPartitions, 
((LogicalFileScan) o).selectedPartitions);
     }
 
+    @Override
+    public List<Slot> computeOutput() {
+        if (cachedOutputs.isPresent()) {
+            return cachedOutputs.get();
+        }
+        return super.computeOutput();
+    }
+
+    @Override
+    public List<Slot> computeAsteriskOutput() {
+        return super.computeAsteriskOutput();
+    }
+
     /**
      * SelectedPartitions contains the selected partitions and the total 
partition number.
      * Mainly for hive table partition pruning.
@@ -207,7 +223,13 @@ public class LogicalFileScan extends 
LogicalCatalogRelation {
     public LogicalFileScan withOperativeSlots(Collection<Slot> operativeSlots) 
{
         return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
-                scanParams, groupExpression, 
Optional.of(getLogicalProperties()));
+                scanParams, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
+    }
+
+    public LogicalFileScan withCachedOutput(List<Slot> cachedOutputs) {
+        return new LogicalFileScan(relationId, (ExternalTable) table, 
qualifier,
+                selectedPartitions, operativeSlots, virtualColumns, 
tableSample, tableSnapshot,
+                scanParams, groupExpression, Optional.empty(), 
Optional.of(cachedOutputs));
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
index 9a123a04d2b..134f48309d7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHudiScan.java
@@ -77,9 +77,10 @@ public class LogicalHudiScan extends LogicalFileScan {
             Collection<Slot> operativeSlots,
             List<NamedExpression> virtualColumns,
             Optional<GroupExpression> groupExpression,
-            Optional<LogicalProperties> logicalProperties) {
+            Optional<LogicalProperties> logicalProperties,
+            Optional<List<Slot>> cachedOutputs) {
         super(id, table, qualifier, selectedPartitions, operativeSlots, 
virtualColumns,
-                tableSample, tableSnapshot, scanParams, groupExpression, 
logicalProperties);
+                tableSample, tableSnapshot, scanParams, groupExpression, 
logicalProperties, cachedOutputs);
         Objects.requireNonNull(scanParams, "scanParams should not null");
         Objects.requireNonNull(incrementalRelation, "incrementalRelation 
should not null");
         this.incrementalRelation = incrementalRelation;
@@ -87,10 +88,11 @@ public class LogicalHudiScan extends LogicalFileScan {
 
     public LogicalHudiScan(RelationId id, ExternalTable table, List<String> 
qualifier,
             Collection<Slot> operativeSlots, Optional<TableScanParams> 
scanParams,
-            Optional<TableSample> tableSample, Optional<TableSnapshot> 
tableSnapshot) {
+            Optional<TableSample> tableSample, Optional<TableSnapshot> 
tableSnapshot,
+            Optional<List<Slot>> cachedOutputs) {
         this(id, table, qualifier, ((HMSExternalTable) 
table).initHudiSelectedPartitions(tableSnapshot),
                 tableSample, tableSnapshot, scanParams, Optional.empty(), 
operativeSlots, ImmutableList.of(),
-                Optional.empty(), Optional.empty());
+                Optional.empty(), Optional.empty(), cachedOutputs);
     }
 
     public Optional<TableScanParams> getScanParams() {
@@ -140,7 +142,7 @@ public class LogicalHudiScan extends LogicalFileScan {
     public LogicalHudiScan withGroupExpression(Optional<GroupExpression> 
groupExpression) {
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
                 selectedPartitions, tableSample, tableSnapshot, scanParams, 
incrementalRelation,
-                operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()));
+                operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     @Override
@@ -148,20 +150,20 @@ public class LogicalHudiScan extends LogicalFileScan {
             Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
             selectedPartitions, tableSample, tableSnapshot, scanParams, 
incrementalRelation,
-            operativeSlots, virtualColumns, groupExpression, 
logicalProperties);
+            operativeSlots, virtualColumns, groupExpression, 
logicalProperties, cachedOutputs);
     }
 
     public LogicalHudiScan withSelectedPartitions(SelectedPartitions 
selectedPartitions) {
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
             selectedPartitions, tableSample, tableSnapshot, scanParams, 
incrementalRelation,
-            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()));
+            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     @Override
     public LogicalHudiScan withRelationId(RelationId relationId) {
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
             selectedPartitions, tableSample, tableSnapshot, scanParams, 
incrementalRelation,
-            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()));
+            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     @Override
@@ -173,7 +175,7 @@ public class LogicalHudiScan extends LogicalFileScan {
     public LogicalFileScan withOperativeSlots(Collection<Slot> operativeSlots) 
{
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
             selectedPartitions, tableSample, tableSnapshot, scanParams, 
incrementalRelation,
-            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()));
+            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 
     /**
@@ -226,6 +228,6 @@ public class LogicalHudiScan extends LogicalFileScan {
         }
         return new LogicalHudiScan(relationId, (ExternalTable) table, 
qualifier,
             selectedPartitions, tableSample, tableSnapshot, scanParams, 
newIncrementalRelation,
-            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()));
+            operativeSlots, virtualColumns, groupExpression, 
Optional.of(getLogicalProperties()), cachedOutputs);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapScan.java
index 8df1cd2f6a4..33d8571026e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOlapScan.java
@@ -840,4 +840,20 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
         }
         return replaceMap;
     }
+
+    /** withPrunedTypeSlots */
+    public LogicalOlapScan withPrunedTypeSlots(List<Slot> outputSlots) {
+        Map<Pair<Long, String>, Slot> replaceSlotMap = new HashMap<>();
+        for (Slot outputSlot : outputSlots) {
+            Pair<Long, String> key = Pair.of(selectedIndexId, 
outputSlot.getName());
+            replaceSlotMap.put(key, outputSlot);
+        }
+
+        return new LogicalOlapScan(relationId, (Table) table, qualifier,
+                Optional.empty(), Optional.empty(),
+                selectedPartitionIds, false, selectedTabletIds,
+                selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
+                hints, replaceSlotMap, tableSample, directMvScan, 
colToSubPathsMap, selectedTabletIds,
+                operativeSlots, virtualColumns, scoreOrderKeys, scoreLimit, 
annOrderKeys, annLimit);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantType.java
index 4cc3bfccf3e..7a5ff6d72d3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantType.java
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
  * Example: VARIANT <`a.b`:INT, `a.c`:DATETIMEV2>
  *
  */
-public class VariantType extends PrimitiveType implements NestedColumnPrunable 
{
+public class VariantType extends PrimitiveType {
 
     public static final VariantType INSTANCE = new VariantType(0);
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java 
b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
index ca3edc13c50..a7c7b804f48 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
@@ -62,6 +62,8 @@ import org.apache.doris.resource.computegroup.ComputeGroup;
 import org.apache.doris.statistics.StatisticalType;
 import org.apache.doris.system.Backend;
 import org.apache.doris.thrift.TColumn;
+import org.apache.doris.thrift.TColumnAccessPaths;
+import org.apache.doris.thrift.TColumnNameAccessPath;
 import org.apache.doris.thrift.TExplainLevel;
 import org.apache.doris.thrift.TExpr;
 import org.apache.doris.thrift.TNetworkAddress;
@@ -88,6 +90,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -1088,6 +1091,9 @@ public class OlapScanNode extends ScanNode {
             output.append(prefix).append("rewrittenProjectList: ").append(
                     getExplainString(rewrittenProjectList)).append("\n");
         }
+
+        printNestedColumns(output, prefix);
+
         return output.toString();
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/ScanNode.java 
b/fe/fe-core/src/main/java/org/apache/doris/planner/ScanNode.java
index 4a9a1945d9b..4b420a6e019 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/ScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/ScanNode.java
@@ -66,6 +66,7 @@ import com.google.common.collect.RangeSet;
 import com.google.common.collect.Sets;
 import com.google.common.collect.TreeRangeSet;
 import org.apache.commons.collections.map.CaseInsensitiveMap;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -557,6 +558,55 @@ public abstract class ScanNode extends PlanNode implements 
SplitGenerator {
                 .addValue(super.debugString()).toString();
     }
 
+    protected void printNestedColumns(StringBuilder output, String prefix) {
+        boolean printNestedColumnsHeader = true;
+        for (SlotDescriptor slot : getTupleDesc().getSlots()) {
+            String prunedType = null;
+            if (!slot.getType().equals(slot.getColumn().getType())) {
+                prunedType = slot.getType().toString();
+            }
+
+            String allAccessPathsString = null;
+            if (slot.getAllAccessPaths() != null
+                    && slot.getAllAccessPaths().name_access_paths != null
+                    && !slot.getAllAccessPaths().name_access_paths.isEmpty()) {
+                allAccessPathsString = 
slot.getAllAccessPaths().name_access_paths
+                        .stream()
+                        .map(a -> StringUtils.join(a.path, "."))
+                        .collect(Collectors.joining(", "));
+            }
+            String predicateAccessPathsString = null;
+            if (slot.getPredicateAccessPaths() != null
+                    && slot.getPredicateAccessPaths().name_access_paths != null
+                    && 
!slot.getPredicateAccessPaths().name_access_paths.isEmpty()) {
+                predicateAccessPathsString = 
slot.getPredicateAccessPaths().name_access_paths
+                        .stream()
+                        .map(a -> StringUtils.join(a.path, "."))
+                        .collect(Collectors.joining(", "));
+            }
+            if (prunedType == null && allAccessPathsString == null &&  
predicateAccessPathsString == null) {
+                continue;
+            }
+
+            if (printNestedColumnsHeader) {
+                output.append(prefix).append("NESTED COLUMNS:\n");
+                printNestedColumnsHeader = false;
+            }
+            output.append(prefix).append("  
").append(slot.getColumn().getName()).append(":\n");
+            output.append(prefix).append("    origin type: 
").append(slot.getColumn().getType()).append("\n");
+            if (prunedType != null) {
+                output.append(prefix).append("    pruned type: 
").append(prunedType).append("\n");
+            }
+            if (allAccessPathsString != null) {
+                output.append(prefix).append("    all access paths: 
[").append(allAccessPathsString).append("]\n");
+            }
+            if (predicateAccessPathsString != null) {
+                output.append(prefix).append("    predicate access paths: [")
+                        .append(predicateAccessPathsString).append("]\n");
+            }
+        }
+    }
+
     public List<TupleId> getOutputTupleIds() {
         if (outputTupleDesc != null) {
             return Lists.newArrayList(outputTupleDesc.getId());
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumn.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
similarity index 64%
rename from 
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumn.java
rename to 
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
index 7cccbf18496..feec310b4f7 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumn.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
@@ -19,8 +19,19 @@ package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.analysis.SlotDescriptor;
 import org.apache.doris.catalog.Type;
+import org.apache.doris.common.Pair;
 import org.apache.doris.nereids.NereidsPlanner;
 import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalUnion;
+import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.planner.OlapScanNode;
 import org.apache.doris.planner.PlanFragment;
 import org.apache.doris.thrift.TAccessPathType;
@@ -33,10 +44,15 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TreeSet;
+import java.util.function.Consumer;
 
-public class PruneNestedColumn extends TestWithFeService {
+public class PruneNestedColumnTest extends TestWithFeService {
     @BeforeAll
     public void createTable() throws Exception {
         createDatabase("test");
@@ -54,6 +70,23 @@ public class PruneNestedColumn extends TestWithFeService {
                 + "properties ('replication_num'='1')");
 
         
connectContext.getSessionVariable().setDisableNereidsRules(RuleType.PRUNE_EMPTY_PARTITION.name());
+        connectContext.getSessionVariable().enableNereidsTimeout = false;
+    }
+
+    @Test
+    public void testPruneArrayLambda() throws Exception {
+        // map_values(struct_element(s, 'data').*)[0].a
+        assertColumn("select struct_element(array_map(x -> map_values(x)[0], 
struct_element(s, 'data'))[0], 'a') from tbl",
+                "struct<data:array<map<int,struct<a:int>>>>",
+                ImmutableList.of(path("s", "data", "*", "VALUES", "a")),
+                ImmutableList.of()
+        );
+
+        assertColumn("select array_map((x, y) -> 
struct_element(map_values(x)[0], 'a') + struct_element(map_values(y)[0], 'b'), 
struct_element(s, 'data'), struct_element(s, 'data')) from tbl",
+                "struct<data:array<map<int,struct<a:int,b:double>>>>",
+                ImmutableList.of(path("s", "data", "*", "VALUES", "a"), 
path("s", "data", "*", "VALUES", "b")),
+                ImmutableList.of()
+        );
     }
 
     @Test
@@ -114,14 +147,6 @@ public class PruneNestedColumn extends TestWithFeService {
                 ImmutableList.of(path("s", "data", "*", "*", "b")),
                 ImmutableList.of()
         );
-        // assertColumn("select struct_element(struct_element(s, 
'data')[1][1], 'b') from tbl where struct_element(s, 'city')='beijing",
-        //         "struct<data:array<map<int,struct<b:double>>>>",
-        //         predicatePath("city"),
-        //         path("data", "*", "*", "b")
-        // );
-
-        // assertColumn("select array_map(x -> x[2], struct_element(s, 
'data')) from tbl", "struct<data:array<map<int,struct<a:int,b:double>>>>", 
path("data"));
-        // assertColumn("select array_map(x -> struct_element(x[2], 'b'), 
struct_element(s, 'data')) from tbl", 
"struct<data:array<map<int,struct<b:double>>>>", path("data", "*", "*", "b"));
     }
 
     @Test
@@ -207,10 +232,57 @@ public class PruneNestedColumn extends TestWithFeService {
         );
     }
 
+    @Test
+    public void testCte() throws Throwable {
+        assertColumn("with t as (select id, s from tbl) select 
struct_element(t1.s, 'city') from t t1 join t t2 on t1.id = t2.id",
+                "struct<city:text>",
+                ImmutableList.of(path("s", "city")),
+                ImmutableList.of()
+        );
+
+        assertColumn("with t as (select id, struct_element(s, 'city') as c 
from tbl) select t1.c from t t1 join t t2 on t1.id = t2.id",
+                "struct<city:text>",
+                ImmutableList.of(path("s", "city")),
+                ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void testUnion() throws Throwable {
+        assertColumn("select struct_element(s, 'city') from (select s from tbl 
union all select null)a",
+                
"struct<city:text,data:array<map<int,struct<a:int,b:double>>>>",
+                ImmutableList.of(path("s")),
+                ImmutableList.of()
+        );
+
+        assertColumn("select * from (select struct_element(s, 'city') from tbl 
union all select null)a",
+                "struct<city:text>",
+                ImmutableList.of(path("s", "city")),
+                ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void testCteAndUnion() throws Throwable {
+        assertColumn("with t as (select id, s from tbl) select 
struct_element(s, 'city') from (select * from t union all select 1, null) tmp",
+                
"struct<city:text,data:array<map<int,struct<a:int,b:double>>>>",
+                ImmutableList.of(path("s")),
+                ImmutableList.of()
+        );
+
+        assertColumn("with t as (select id, s from tbl) select * from (select 
struct_element(s, 'city') from t union all select null) tmp",
+                "struct<city:text>",
+                ImmutableList.of(path("s", "city")),
+                ImmutableList.of()
+        );
+    }
+
     private void assertColumn(String sql, String expectType,
             List<TColumnNameAccessPath> expectAllAccessPaths,
             List<TColumnNameAccessPath> expectPredicateAccessPaths) throws 
Exception {
-        List<SlotDescriptor> slotDescriptors = collectComplexSlots(sql);
+        Pair<PhysicalPlan, List<SlotDescriptor>> result = 
collectComplexSlots(sql);
+        PhysicalPlan physicalPlan = result.first;
+        List<SlotDescriptor> slotDescriptors = result.second;
         if (expectType == null) {
             Assertions.assertEquals(0, slotDescriptors.size());
             return;
@@ -230,11 +302,57 @@ public class PruneNestedColumn extends TestWithFeService {
         TreeSet<TColumnNameAccessPath> actualPredicateAccessPaths
                 = new 
TreeSet<>(slotDescriptors.get(0).getPredicateAccessPaths().name_access_paths);
         Assertions.assertEquals(expectPredicateAccessPathSet, 
actualPredicateAccessPaths);
+
+        Map<Integer, DataType> slotIdToDataTypes = new LinkedHashMap<>();
+        Consumer<Expression> assertHasSameType = e -> {
+            if (e instanceof NamedExpression) {
+                DataType dataType = slotIdToDataTypes.get(((NamedExpression) 
e).getExprId().asInt());
+                if (dataType != null) {
+                    Assertions.assertEquals(dataType, e.getDataType());
+                } else {
+                    slotIdToDataTypes.put(((NamedExpression) 
e).getExprId().asInt(), e.getDataType());
+                }
+            }
+        };
+
+        // assert same slot id has same type
+        physicalPlan.foreachUp(plan -> {
+            List<? extends Expression> expressions = ((PhysicalPlan) 
plan).getExpressions();
+            for (Expression expression : expressions) {
+                expression.foreach(e -> {
+                    assertHasSameType.accept((Expression) e);
+                    if (e instanceof Alias && e.child(0) instanceof Slot) {
+                        assertHasSameType.accept((Alias) e);
+                    } else if (e instanceof ArrayItemReference) {
+                        assertHasSameType.accept((ArrayItemReference) e);
+                    }
+                });
+            }
+
+            if (plan instanceof PhysicalCTEConsumer) {
+                for (Entry<Slot, Collection<Slot>> kv : ((PhysicalCTEConsumer) 
plan).getProducerToConsumerSlotMap()
+                        .asMap().entrySet()) {
+                    Slot producerSlot = kv.getKey();
+                    for (Slot consumerSlot : kv.getValue()) {
+                        Assertions.assertEquals(producerSlot.getDataType(), 
consumerSlot.getDataType());
+                    }
+                }
+            } else if (plan instanceof PhysicalUnion) {
+                List<Slot> output = ((PhysicalUnion) plan).getOutput();
+                for (List<SlotReference> regularChildrenOutput : 
((PhysicalUnion) plan).getRegularChildrenOutputs()) {
+                    Assertions.assertEquals(output.size(), 
regularChildrenOutput.size());
+                    for (int i = 0; i < output.size(); i++) {
+                        Assertions.assertEquals(output.get(i).getDataType(), 
regularChildrenOutput.get(i).getDataType());
+                    }
+                }
+            }
+        });
     }
 
-    private List<SlotDescriptor> collectComplexSlots(String sql) throws 
Exception {
+    private Pair<PhysicalPlan, List<SlotDescriptor>> 
collectComplexSlots(String sql) throws Exception {
         NereidsPlanner planner = (NereidsPlanner) 
getSqlStmtExecutor(sql).planner();
         List<SlotDescriptor> complexSlots = new ArrayList<>();
+        PhysicalPlan physicalPlan = planner.getPhysicalPlan();
         for (PlanFragment fragment : planner.getFragments()) {
             List<OlapScanNode> olapScanNodes = 
fragment.getPlanRoot().collectInCurrentFragment(OlapScanNode.class::isInstance);
             for (OlapScanNode olapScanNode : olapScanNodes) {
@@ -247,7 +365,7 @@ public class PruneNestedColumn extends TestWithFeService {
                 }
             }
         }
-        return complexSlots;
+        return Pair.of(physicalPlan, complexSlots);
     }
 
     private TColumnNameAccessPath path(String... path) {
diff --git a/gensrc/thrift/Descriptors.thrift b/gensrc/thrift/Descriptors.thrift
index 5276f9df4cb..4eee5d9de50 100644
--- a/gensrc/thrift/Descriptors.thrift
+++ b/gensrc/thrift/Descriptors.thrift
@@ -27,6 +27,52 @@ enum TPatternType {
   MATCH_NAME_GLOB = 2
 }
 
+enum TAccessPathType {
+  NAME = 1,
+  // ICEBERG = 2 // implement in the future
+}
+
+struct TColumnNameAccessPath {
+   // the specification of special path:
+   //   <empty>: access the whole complex column
+   //   *:
+   //     1. access every items when the type is array
+   //     2. access key and value when the type is map
+   //   KEYS: only access the keys of map
+   //   VALUES: only access the keys of map
+   //
+   // example:
+   //  s: struct<
+   //    data: array<
+   //      map<
+   //        int,
+   //        struct<
+   //          a: id
+   //          b: double
+   //        >
+   //      >
+   //    >
+   //  >
+   // if we want to access `map_keys(s.data[0])`, the path will be: ['s', 
'data', '*', 'KEYS'],
+   // if we want to access `map_values(s.data[0])[0].b`, the path will be: 
['s', 'data', '*', 'VALUES', 'b'],
+   // if we want to access `s.data[0]['k'].b`, the path will be ['s', 'data', 
'*', '*', 'b']
+   // if we want to access the whole struct of s, the path will be: ['s'],
+   1: required list<string> path
+}
+
+/*
+// implement in the future
+struct TIcebergColumnAccessPath {
+   1: required list<i64> path
+}
+*/
+
+struct TColumnAccessPaths {
+  1: required TAccessPathType type
+  2: optional list<TColumnNameAccessPath> name_access_paths
+  // 3: optional list<TIcebergColumnAccessPath> iceberg_column_access_paths // 
implement in the future
+}
+
 struct TColumn {
     1: required string column_name
     2: required Types.TColumnType column_type
@@ -81,52 +127,6 @@ struct TSlotDescriptor {
   20: optional TColumnAccessPaths predicate_access_paths
 }
 
-enum TAccessPathType {
-  NAME = 1,
-  // ICEBERG = 2 // implement in the future
-}
-
-struct TColumnNameAccessPath {
-   // the specification of special path:
-   //   <empty>: access the whole complex column
-   //   *:
-   //     1. access every items when the type is array
-   //     2. access key and value when the type is map
-   //   KEYS: only access the keys of map
-   //   VALUES: only access the keys of map
-   //
-   // example:
-   //  s: struct<
-   //    data: array<
-   //      map<
-   //        int,
-   //        struct<
-   //          a: id
-   //          b: double
-   //        >
-   //      >
-   //    >
-   //  >
-   // if we want to access `map_keys(s.data[0])`, the path will be: ['s', 
'data', '*', 'KEYS'],
-   // if we want to access `map_values(s.data[0])[0].b`, the path will be: 
['s', 'data', '*', 'VALUES', 'b'],
-   // if we want to access `s.data[0]['k'].b`, the path will be ['s', 'data', 
'*', '*', 'b']
-   // if we want to access the whole struct of s, the path will be: ['s'],
-   1: required list<string> path
-}
-
-/*
-// implement in the future
-struct TIcebergColumnAccessPath {
-   1: required list<i64> path
-}
-*/
-
-struct TColumnAccessPaths {
-  1: required TAccessPathType type
-  2: optional list<TColumnNameAccessPath> name_access_paths
-  // 3: optional list<TIcebergColumnAccessPath> iceberg_column_access_paths // 
implement in the future
-}
-
 struct TTupleDescriptor {
   1: required Types.TTupleId id
   2: required i32 byteSize // deprecated


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

Reply via email to