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

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


The following commit(s) were added to refs/heads/master by this push:
     new 98b715a1d32 [feat](nereids) derive operative slots (#48738)
98b715a1d32 is described below

commit 98b715a1d32358dbb492b7e1de2f24614eba9927
Author: minghong <zhoumingh...@selectdb.com>
AuthorDate: Fri Mar 14 10:10:40 2025 +0800

    [feat](nereids) derive operative slots (#48738)
    
    ### What problem does this PR solve?
    used for (in next pr)
    1. lazy materialization
    2. only fetch column stats for operative slots in StatsCalculator
    3. print less col stats in profile
---
 .../doris/nereids/jobs/executor/Rewriter.java      |   4 +-
 .../org/apache/doris/nereids/rules/RuleType.java   |   2 +
 .../doris/nereids/rules/analysis/BindRelation.java |   8 +-
 .../exploration/mv/MaterializedViewUtils.java      |   3 +-
 .../LogicalOlapScanToPhysicalOlapScan.java         |   3 +-
 .../rules/rewrite/OperativeColumnDerive.java       | 146 +++++++++++++++++++++
 .../trees/plans/algebra/CatalogRelation.java       |  15 +++
 .../plans/logical/LogicalCatalogRelation.java      |  15 ++-
 .../trees/plans/logical/LogicalOlapScan.java       |  80 ++++++++---
 .../trees/plans/physical/PhysicalOlapScan.java     |  38 ++++--
 .../translator/PhysicalPlanTranslatorTest.java     |   3 +-
 .../doris/nereids/jobs/RewriteTopDownJobTest.java  |   3 +-
 .../postprocess/MergeProjectPostProcessTest.java   |   2 +-
 .../PushDownFilterThroughProjectTest.java          |   4 +-
 .../rules/rewrite/OperativeColumnDeriveTest.java   |  90 +++++++++++++
 .../doris/nereids/trees/plans/PlanEqualsTest.java  |   9 +-
 .../nereids/trees/plans/PlanToStringTest.java      |   2 +-
 .../apache/doris/nereids/util/PlanConstructor.java |   2 +-
 .../nereids_p0/stats/partitionRowCount.groovy      |   2 +-
 .../operative_slots/operative_slots.groovy         |  79 +++++++++++
 20 files changed, 462 insertions(+), 48 deletions(-)

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 b080df1df4f..80c8760d0e5 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
@@ -101,6 +101,7 @@ 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.NormalizeSort;
+import org.apache.doris.nereids.rules.rewrite.OperativeColumnDerive;
 import org.apache.doris.nereids.rules.rewrite.OrExpansion;
 import 
org.apache.doris.nereids.rules.rewrite.ProjectOtherJoinConditionForNestedLoopJoin;
 import org.apache.doris.nereids.rules.rewrite.PruneEmptyPartition;
@@ -503,7 +504,8 @@ public class Rewriter extends AbstractBatchJobExecutor {
                                 new CheckAfterRewrite()
                         )
                 ),
-                topDown(new CollectCteConsumerOutput())
+                topDown(new CollectCteConsumerOutput()),
+                custom(RuleType.OPERATIVE_COLUMN_DERIVE, 
OperativeColumnDerive::new)
             )
     );
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index 254af1bf1f5..744268e9793 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -377,6 +377,8 @@ public enum RuleType {
     REWRITE_SENTINEL(RuleTypeClass.REWRITE),
     COLLECT_COLUMNS(RuleTypeClass.REWRITE),
 
+    OPERATIVE_COLUMN_DERIVE(RuleTypeClass.REWRITE),
+
     // topn opts
     DEFER_MATERIALIZE_TOP_N_RESULT(RuleTypeClass.REWRITE),
     // short circuit rule
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 0ae11ee4707..82dddc9575a 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
@@ -186,7 +186,9 @@ public class BindRelation extends OneAnalysisRuleFactory {
         if (!CollectionUtils.isEmpty(partIds) && 
!unboundRelation.getIndexName().isPresent()) {
             scan = new LogicalOlapScan(unboundRelation.getRelationId(),
                     (OlapTable) table, qualifier, partIds,
-                    tabletIds, unboundRelation.getHints(), 
unboundRelation.getTableSample());
+                    tabletIds, unboundRelation.getHints(),
+                    unboundRelation.getTableSample(),
+                    ImmutableList.of());
         } else {
             Optional<String> indexName = unboundRelation.getIndexName();
             // For direct mv scan.
@@ -204,11 +206,11 @@ public class BindRelation extends OneAnalysisRuleFactory {
                     (OlapTable) table, qualifier, tabletIds,
                     CollectionUtils.isEmpty(partIds) ? ((OlapTable) 
table).getPartitionIds() : partIds, indexId,
                     preAggStatus, CollectionUtils.isEmpty(partIds) ? 
ImmutableList.of() : partIds,
-                    unboundRelation.getHints(), 
unboundRelation.getTableSample());
+                    unboundRelation.getHints(), 
unboundRelation.getTableSample(), ImmutableList.of());
             } else {
                 scan = new LogicalOlapScan(unboundRelation.getRelationId(),
                     (OlapTable) table, qualifier, tabletIds, 
unboundRelation.getHints(),
-                    unboundRelation.getTableSample());
+                    unboundRelation.getTableSample(), ImmutableList.of());
             }
         }
         if (!tabletIds.isEmpty()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
index ddae11fb735..74cc21e0342 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
@@ -229,7 +229,8 @@ public class MaterializedViewUtils {
                 ImmutableList.of(),
                 // this must be empty, or it will be used to sample
                 ImmutableList.of(),
-                Optional.empty());
+                Optional.empty(),
+                ImmutableList.of());
         return BindRelation.checkAndAddDeleteSignFilter(olapScan, 
cascadesContext.getConnectContext(),
                 olapScan.getTable());
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalOlapScanToPhysicalOlapScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalOlapScanToPhysicalOlapScan.java
index 472d2e169db..4d006e557ac 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalOlapScanToPhysicalOlapScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalOlapScanToPhysicalOlapScan.java
@@ -61,7 +61,8 @@ public class LogicalOlapScanToPhysicalOlapScan extends 
OneImplementationRuleFact
                         
olapScan.getOutputByIndex(olapScan.getTable().getBaseIndexId()),
                         Optional.empty(),
                         olapScan.getLogicalProperties(),
-                        olapScan.getTableSample())
+                        olapScan.getTableSample(),
+                        olapScan.getOperativeSlots())
         ).toRule(RuleType.LOGICAL_OLAP_SCAN_TO_PHYSICAL_OLAP_SCAN_RULE);
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDerive.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDerive.java
new file mode 100644
index 00000000000..74a97fc2c64
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDerive.java
@@ -0,0 +1,146 @@
+// 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.jobs.JobContext;
+import 
org.apache.doris.nereids.rules.rewrite.OperativeColumnDerive.DeriveContext;
+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.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCatalogRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSink;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.trees.plans.visitor.CustomRewriter;
+import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * derive operative columns
+ */
+public class OperativeColumnDerive extends DefaultPlanRewriter<DeriveContext> 
implements CustomRewriter {
+
+    @Override
+    public Plan rewriteRoot(Plan plan, JobContext jobContext) {
+        return plan.accept(this, new DeriveContext());
+    }
+
+    @Override
+    public Plan visit(Plan plan, DeriveContext context) {
+        context.addOperativeSlots(plan.getInputSlots());
+        return visitChildren(this, plan, context);
+    }
+
+    @Override
+    public Plan visitLogicalSink(LogicalSink<? extends Plan> sink, 
DeriveContext context) {
+        return visitChildren(this, sink, context);
+    }
+
+    private Plan deriveUnion(LogicalUnion union, DeriveContext context) {
+        for (int i = 0; i < union.getOutput().size(); i++) {
+            Slot output = union.getOutput().get(i);
+            if (context.operativeSlots.contains(output)) {
+                for (List<SlotReference> childOutput : 
union.getRegularChildrenOutputs()) {
+                    context.operativeSlots.add(childOutput.get(i));
+                }
+            }
+        }
+        return visitChildren(this, union, context);
+    }
+
+    @Override
+    public Plan visitLogicalUnion(LogicalUnion union, DeriveContext context) {
+        Plan round1 = deriveUnion(union, context);
+        // check for back propagation
+        boolean needBackPropagation = false;
+        for (int i = 0; i < union.getOutput().size(); i++) {
+            Slot output = union.getOutput().get(i);
+            if (!context.operativeSlots.contains(output)) {
+                for (List<SlotReference> childOutput : 
union.getRegularChildrenOutputs()) {
+                    if (context.operativeSlots.contains(childOutput.get(i))) {
+                        // if any child output is operative, this output is 
operative
+                        context.operativeSlots.add(output);
+                        needBackPropagation = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (needBackPropagation) {
+            return deriveUnion(union, context);
+        } else {
+            return round1;
+        }
+    }
+
+    @Override
+    public Plan visitLogicalProject(LogicalProject<? extends Plan> project, 
DeriveContext context) {
+        for (NamedExpression ne : project.getProjects()) {
+            if (!(ne instanceof Slot)) {
+                if (ne.child(0) instanceof Slot) {
+                    if (context.operativeSlots.contains(ne.toSlot())) {
+                        context.operativeSlots.add((Slot) ne.child(0));
+                    }
+                } else {
+                    context.addOperativeSlots(ne.getInputSlots());
+                    context.addOperativeSlot(ne.toSlot());
+                }
+            }
+        }
+        Plan plan = visitChildren(this, project, context);
+        // back propagate
+        for (NamedExpression ne : project.getProjects()) {
+            if (!(ne instanceof Slot) && ne.child(0) instanceof Slot) {
+                if (context.operativeSlots.contains(((Slot) ne.child(0)))) {
+                    context.addOperativeSlot(ne.toSlot());
+                }
+            }
+        }
+        return plan;
+    }
+
+    @Override
+    public Plan visitLogicalCatalogRelation(LogicalCatalogRelation relation, 
DeriveContext context) {
+        Set<Slot> intersectSlots = new HashSet<>(relation.getOutput());
+        intersectSlots.retainAll(context.operativeSlots);
+        return (Plan) relation.withOperativeSlots(intersectSlots);
+    }
+
+    /**
+     * DeriveContext
+     */
+    public static class DeriveContext {
+        public Set<Slot> operativeSlots;
+
+        public DeriveContext() {
+            this.operativeSlots = new HashSet<>();
+        }
+
+        public void addOperativeSlot(Slot slot) {
+            operativeSlots.add(slot);
+        }
+
+        public void addOperativeSlots(Set<Slot> slots) {
+            operativeSlots.addAll(slots);
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/CatalogRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/CatalogRelation.java
index d713ba16a8a..04e96345906 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/CatalogRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/CatalogRelation.java
@@ -20,6 +20,12 @@ package org.apache.doris.nereids.trees.plans.algebra;
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.TableIf;
 import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Slot;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
 
 /** CatalogRelation */
 public interface CatalogRelation extends Relation {
@@ -27,4 +33,13 @@ public interface CatalogRelation extends Relation {
     TableIf getTable();
 
     DatabaseIf getDatabase() throws AnalysisException;
+
+    default CatalogRelation withOperativeSlots(Collection<Slot> 
operativeSlots) {
+        return this;
+    }
+
+    default List<Slot> getOperativeSlots() {
+        return ImmutableList.of();
+    }
+
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
index 5eb9d868cdb..d6e90804830 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCatalogRelation.java
@@ -43,6 +43,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -57,17 +58,24 @@ public abstract class LogicalCatalogRelation extends 
LogicalRelation implements
     // [catalogName, databaseName]
     protected final ImmutableList<String> qualifier;
 
+    protected final ImmutableList<Slot> operativeSlots;
+
     public LogicalCatalogRelation(RelationId relationId, PlanType type, 
TableIf table, List<String> qualifier) {
-        super(relationId, type);
-        this.table = Objects.requireNonNull(table, "table can not be null");
-        this.qualifier = 
ImmutableList.copyOf(Objects.requireNonNull(qualifier, "qualifier can not be 
null"));
+        this(relationId, type, table, qualifier, Optional.empty(), 
Optional.empty());
     }
 
     public LogicalCatalogRelation(RelationId relationId, PlanType type, 
TableIf table, List<String> qualifier,
             Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties) {
+        this(relationId, type, table, qualifier, groupExpression, 
logicalProperties, ImmutableList.of());
+    }
+
+    public LogicalCatalogRelation(RelationId relationId, PlanType type, 
TableIf table, List<String> qualifier,
+            Optional<GroupExpression> groupExpression, 
Optional<LogicalProperties> logicalProperties,
+            Collection<Slot> operativeSlots) {
         super(relationId, type, groupExpression, logicalProperties);
         this.table = Objects.requireNonNull(table, "table can not be null");
         this.qualifier = 
ImmutableList.copyOf(Objects.requireNonNull(qualifier, "qualifier can not be 
null"));
+        this.operativeSlots = ImmutableList.copyOf(operativeSlots);
     }
 
     @Override
@@ -196,4 +204,5 @@ public abstract class LogicalCatalogRelation extends 
LogicalRelation implements
     public void computeFd(DataTrait.Builder builder) {
         // don't generate any equal pair
     }
+
 }
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 3cffa3136e7..ce57f026ea5 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
@@ -35,6 +35,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.PreAggStatus;
 import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation;
 import org.apache.doris.nereids.trees.plans.algebra.OlapScan;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -55,6 +56,7 @@ import org.json.JSONObject;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -149,34 +151,35 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
                 ImmutableList.of(),
                 -1, false, PreAggStatus.unset(), ImmutableList.of(), 
ImmutableList.of(),
                 Maps.newHashMap(), Optional.empty(), false, ImmutableMap.of(),
-                ImmutableList.of());
+                ImmutableList.of(), ImmutableList.of());
     }
 
     public LogicalOlapScan(RelationId id, OlapTable table, List<String> 
qualifier, List<Long> tabletIds,
-            List<String> hints, Optional<TableSample> tableSample) {
+            List<String> hints, Optional<TableSample> tableSample, 
Collection<Slot> operativeSlots) {
         this(id, table, qualifier, Optional.empty(), Optional.empty(),
                 table.getPartitionIds(), false, tabletIds,
                 -1, false, PreAggStatus.unset(), ImmutableList.of(), hints, 
Maps.newHashMap(),
-                tableSample, false, ImmutableMap.of(), ImmutableList.of());
+                tableSample, false, ImmutableMap.of(), ImmutableList.of(), 
operativeSlots);
     }
 
     public LogicalOlapScan(RelationId id, OlapTable table, List<String> 
qualifier, List<Long> specifiedPartitions,
-            List<Long> tabletIds, List<String> hints, Optional<TableSample> 
tableSample) {
+            List<Long> tabletIds, List<String> hints, Optional<TableSample> 
tableSample, List<Slot> operativeSlots) {
         this(id, table, qualifier, Optional.empty(), Optional.empty(),
                 // must use specifiedPartitions here for prune partition by 
sql like 'select * from t partition p1'
                 specifiedPartitions, false, tabletIds,
                 -1, false, PreAggStatus.unset(), specifiedPartitions, hints, 
Maps.newHashMap(),
-                tableSample, false, ImmutableMap.of(), ImmutableList.of());
+                tableSample, false, ImmutableMap.of(), ImmutableList.of(), 
operativeSlots);
     }
 
     public LogicalOlapScan(RelationId id, OlapTable table, List<String> 
qualifier, List<Long> tabletIds,
                            List<Long> selectedPartitionIds, long 
selectedIndexId, PreAggStatus preAggStatus,
-                           List<Long> specifiedPartitions, List<String> hints, 
Optional<TableSample> tableSample) {
+                           List<Long> specifiedPartitions, List<String> hints, 
Optional<TableSample> tableSample,
+                           Collection<Slot> operativeSlots) {
         this(id, table, qualifier, Optional.empty(), Optional.empty(),
                 selectedPartitionIds, false, tabletIds,
                 selectedIndexId, true, preAggStatus,
                 specifiedPartitions, hints, Maps.newHashMap(), tableSample, 
true, ImmutableMap.of(),
-                ImmutableList.of());
+                ImmutableList.of(), operativeSlots);
     }
 
     /**
@@ -189,9 +192,10 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
             PreAggStatus preAggStatus, List<Long> specifiedPartitions,
             List<String> hints, Map<Pair<Long, String>, Slot> 
cacheSlotWithSlotName,
             Optional<TableSample> tableSample, boolean directMvScan,
-            Map<String, Set<List<String>>> colToSubPathsMap, List<Long> 
specifiedTabletIds) {
+            Map<String, Set<List<String>>> colToSubPathsMap, List<Long> 
specifiedTabletIds,
+            Collection<Slot> operativeSlots) {
         super(id, PlanType.LOGICAL_OLAP_SCAN, table, qualifier,
-                groupExpression, logicalProperties);
+                groupExpression, logicalProperties, operativeSlots);
         Preconditions.checkArgument(selectedPartitionIds != null,
                 "selectedPartitionIds can not be null");
         this.selectedTabletIds = Utils.fastToImmutableList(selectedTabletIds);
@@ -239,7 +243,8 @@ public class LogicalOlapScan extends LogicalCatalogRelation 
implements OlapScan
                 "qualified", qualifiedName(),
                 "indexName", 
getSelectedMaterializedIndexName().orElse("<index_not_selected>"),
                 "selectedIndexId", selectedIndexId,
-                "preAgg", preAggStatus
+                "preAgg", preAggStatus,
+                "operativeCol", operativeSlots
         );
     }
 
@@ -278,7 +283,8 @@ public class LogicalOlapScan extends LogicalCatalogRelation 
implements OlapScan
                 groupExpression, Optional.of(getLogicalProperties()),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
     @Override
@@ -287,15 +293,20 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
         return new LogicalOlapScan(relationId, (Table) table, qualifier, 
groupExpression, logicalProperties,
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
+    /**
+     * withSelectedPartitionIds
+     */
     public LogicalOlapScan withSelectedPartitionIds(List<Long> 
selectedPartitionIds) {
         return new LogicalOlapScan(relationId, (Table) table, qualifier,
                 Optional.empty(), Optional.of(getLogicalProperties()),
                 selectedPartitionIds, true, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
     public LogicalOlapScan withMaterializedIndexSelected(long indexId) {
@@ -303,39 +314,55 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
                 Optional.empty(), Optional.of(getLogicalProperties()),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 indexId, true, PreAggStatus.unset(), 
manuallySpecifiedPartitions, hints, cacheSlotWithSlotName,
-                tableSample, directMvScan, colToSubPathsMap, 
manuallySpecifiedTabletIds);
+                tableSample, directMvScan, colToSubPathsMap, 
manuallySpecifiedTabletIds, operativeSlots);
     }
 
+    /**
+     * withSelectedTabletIds
+     */
     public LogicalOlapScan withSelectedTabletIds(List<Long> selectedTabletIds) 
{
         return new LogicalOlapScan(relationId, (Table) table, qualifier,
                 Optional.empty(), Optional.of(getLogicalProperties()),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
+    /**
+     * withPreAggStatus
+     */
     public LogicalOlapScan withPreAggStatus(PreAggStatus preAggStatus) {
         return new LogicalOlapScan(relationId, (Table) table, qualifier,
                 Optional.empty(), Optional.of(getLogicalProperties()),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
+    /**
+     * constructor
+     */
     public LogicalOlapScan withColToSubPathsMap(Map<String, Set<List<String>>> 
colToSubPathsMap) {
         return new LogicalOlapScan(relationId, (Table) table, qualifier,
                 Optional.empty(), Optional.empty(),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
+    /**
+     * constructor
+     */
     public LogicalOlapScan withManuallySpecifiedTabletIds(List<Long> 
manuallySpecifiedTabletIds) {
         return new LogicalOlapScan(relationId, (Table) table, qualifier,
                 Optional.empty(), Optional.of(getLogicalProperties()),
                 selectedPartitionIds, partitionPruned, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds);
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap, manuallySpecifiedTabletIds,
+                operativeSlots);
     }
 
     @Override
@@ -345,7 +372,8 @@ public class LogicalOlapScan extends LogicalCatalogRelation 
implements OlapScan
                 Optional.empty(), Optional.empty(),
                 selectedPartitionIds, false, selectedTabletIds,
                 selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
-                hints, Maps.newHashMap(), tableSample, directMvScan, 
colToSubPathsMap, selectedTabletIds);
+                hints, Maps.newHashMap(), tableSample, directMvScan, 
colToSubPathsMap, selectedTabletIds,
+                operativeSlots);
     }
 
     @Override
@@ -632,6 +660,16 @@ public class LogicalOlapScan extends 
LogicalCatalogRelation implements OlapScan
         }
     }
 
+    @Override
+    public CatalogRelation withOperativeSlots(Collection<Slot> operativeSlots) 
{
+        return new LogicalOlapScan(relationId, (Table) table, qualifier,
+                groupExpression, Optional.of(getLogicalProperties()),
+                selectedPartitionIds, partitionPruned, selectedTabletIds,
+                selectedIndexId, indexSelected, preAggStatus, 
manuallySpecifiedPartitions,
+                hints, cacheSlotWithSlotName, tableSample, directMvScan, 
colToSubPathsMap,
+                manuallySpecifiedTabletIds, operativeSlots);
+    }
+
     Map<Slot, Slot> constructReplaceMap(MTMV mtmv) {
         Map<Slot, Slot> replaceMap = new HashMap<>();
         // Need remove invisible column, and then mapping them
@@ -661,4 +699,8 @@ public class LogicalOlapScan extends LogicalCatalogRelation 
implements OlapScan
         }
         return replaceMap;
     }
+
+    public List<Slot> getOperativeSlots() {
+        return operativeSlots;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalOlapScan.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalOlapScan.java
index 698a189aa26..74c5cb0be3b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalOlapScan.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalOlapScan.java
@@ -28,6 +28,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.PlanType;
 import org.apache.doris.nereids.trees.plans.PreAggStatus;
 import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.algebra.CatalogRelation;
 import org.apache.doris.nereids.trees.plans.algebra.OlapScan;
 import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
 import org.apache.doris.nereids.util.Utils;
@@ -36,6 +37,7 @@ import org.apache.doris.statistics.Statistics;
 import com.google.common.collect.ImmutableList;
 import org.json.JSONObject;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -52,6 +54,7 @@ public class PhysicalOlapScan extends PhysicalCatalogRelation 
implements OlapSca
     private final PreAggStatus preAggStatus;
     private final List<Slot> baseOutputs;
     private final Optional<TableSample> tableSample;
+    private final ImmutableList<Slot> operativeSlots;
 
     /**
      * Constructor for PhysicalOlapScan.
@@ -60,10 +63,12 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
             List<Long> selectedTabletIds, List<Long> selectedPartitionIds, 
DistributionSpec distributionSpec,
             PreAggStatus preAggStatus, List<Slot> baseOutputs,
             Optional<GroupExpression> groupExpression, LogicalProperties 
logicalProperties,
-            Optional<TableSample> tableSample) {
-        this(id, olapTable, qualifier, selectedIndexId, selectedTabletIds, 
selectedPartitionIds, distributionSpec,
-                preAggStatus, baseOutputs, groupExpression, logicalProperties, 
null, null,
-                tableSample);
+            Optional<TableSample> tableSample, List<Slot> operativeSlots) {
+        this(id, olapTable, qualifier,
+                selectedIndexId, selectedTabletIds, selectedPartitionIds, 
distributionSpec,
+                preAggStatus, baseOutputs,
+                groupExpression, logicalProperties, null,
+                null, tableSample, operativeSlots);
     }
 
     /**
@@ -73,7 +78,9 @@ public class PhysicalOlapScan extends PhysicalCatalogRelation 
implements OlapSca
             List<Long> selectedTabletIds, List<Long> selectedPartitionIds, 
DistributionSpec distributionSpec,
             PreAggStatus preAggStatus, List<Slot> baseOutputs,
             Optional<GroupExpression> groupExpression, LogicalProperties 
logicalProperties,
-            PhysicalProperties physicalProperties, Statistics statistics, 
Optional<TableSample> tableSample) {
+            PhysicalProperties physicalProperties, Statistics statistics,
+            Optional<TableSample> tableSample,
+            Collection<Slot> operativeSlots) {
         super(id, PlanType.PHYSICAL_OLAP_SCAN, olapTable, qualifier,
                 groupExpression, logicalProperties, physicalProperties, 
statistics);
         this.selectedIndexId = selectedIndexId;
@@ -83,6 +90,7 @@ public class PhysicalOlapScan extends PhysicalCatalogRelation 
implements OlapSca
         this.preAggStatus = preAggStatus;
         this.baseOutputs = ImmutableList.copyOf(baseOutputs);
         this.tableSample = tableSample;
+        this.operativeSlots = ImmutableList.copyOf(operativeSlots);
     }
 
     @Override
@@ -131,7 +139,11 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
         if (selectedPartitionIds.size() != partitionCount) {
             partitions = " partitions(" + selectedPartitionIds.size() + "/" + 
partitionCount + ")";
         }
-        return Utils.toSqlString("PhysicalOlapScan[" + table.getName() + index 
+ partitions + "]"
+        String operativeCol = "";
+        if (!operativeSlots.isEmpty()) {
+            operativeCol = " operativeSlots(" + operativeSlots + ")";
+        }
+        return Utils.toSqlString("PhysicalOlapScan[" + table.getName() + index 
+ partitions + operativeCol + "]"
                         + getGroupIdWithPrefix(),
                 "stats", statistics, "RFs", builder
         );
@@ -172,7 +184,7 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
     public PhysicalOlapScan withGroupExpression(Optional<GroupExpression> 
groupExpression) {
         return new PhysicalOlapScan(relationId, getTable(), qualifier, 
selectedIndexId, selectedTabletIds,
                 selectedPartitionIds, distributionSpec, preAggStatus, 
baseOutputs,
-                groupExpression, getLogicalProperties(), tableSample);
+                groupExpression, getLogicalProperties(), tableSample, 
operativeSlots);
     }
 
     @Override
@@ -180,7 +192,7 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
             Optional<LogicalProperties> logicalProperties, List<Plan> 
children) {
         return new PhysicalOlapScan(relationId, getTable(), qualifier, 
selectedIndexId, selectedTabletIds,
                 selectedPartitionIds, distributionSpec, preAggStatus, 
baseOutputs, groupExpression,
-                logicalProperties.get(), tableSample);
+                logicalProperties.get(), tableSample, operativeSlots);
     }
 
     @Override
@@ -188,7 +200,7 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
             PhysicalProperties physicalProperties, Statistics statistics) {
         return new PhysicalOlapScan(relationId, getTable(), qualifier, 
selectedIndexId, selectedTabletIds,
                 selectedPartitionIds, distributionSpec, preAggStatus, 
baseOutputs, groupExpression,
-                getLogicalProperties(), physicalProperties, statistics, 
tableSample);
+                getLogicalProperties(), physicalProperties, statistics, 
tableSample, operativeSlots);
     }
 
     @Override
@@ -208,4 +220,12 @@ public class PhysicalOlapScan extends 
PhysicalCatalogRelation implements OlapSca
     public Optional<TableSample> getTableSample() {
         return tableSample;
     }
+
+    @Override
+    public CatalogRelation withOperativeSlots(Collection<Slot> operativeSlots) 
{
+        return new PhysicalOlapScan(relationId, (OlapTable) table, qualifier, 
selectedIndexId, selectedTabletIds,
+                selectedPartitionIds, distributionSpec, preAggStatus, 
baseOutputs,
+                groupExpression, getLogicalProperties(), 
getPhysicalProperties(), statistics,
+                tableSample, operativeSlots);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
index d27223a9d3b..5fc63eea3bb 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslatorTest.java
@@ -67,7 +67,8 @@ public class PhysicalPlanTranslatorTest {
         LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> DataTrait.EMPTY_TRAIT);
         PhysicalOlapScan scan = new 
PhysicalOlapScan(StatementScopeIdGenerator.newRelationId(), t1, qualifier, 
t1.getBaseIndexId(),
                 Collections.emptyList(), Collections.emptyList(), null, 
PreAggStatus.on(),
-                ImmutableList.of(), Optional.empty(), t1Properties, 
Optional.empty());
+                ImmutableList.of(), Optional.empty(), t1Properties, 
Optional.empty(),
+                ImmutableList.of());
         Literal t1FilterRight = new IntegerLiteral(1);
         Expression t1FilterExpr = new GreaterThan(col1, t1FilterRight);
         PhysicalFilter<PhysicalOlapScan> filter =
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/RewriteTopDownJobTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/RewriteTopDownJobTest.java
index f4adc72b32a..5fb4e16988c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/RewriteTopDownJobTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/RewriteTopDownJobTest.java
@@ -105,7 +105,8 @@ public class RewriteTopDownJobTest {
     private static class LogicalBoundRelation extends LogicalCatalogRelation {
 
         public LogicalBoundRelation(TableIf table, List<String> qualifier) {
-            super(StatementScopeIdGenerator.newRelationId(), 
PlanType.LOGICAL_BOUND_RELATION, table, qualifier);
+            super(StatementScopeIdGenerator.newRelationId(), 
PlanType.LOGICAL_BOUND_RELATION, table, qualifier,
+                    Optional.empty(), Optional.empty());
         }
 
         public LogicalBoundRelation(TableIf table, List<String> qualifier, 
Optional<GroupExpression> groupExpression,
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
index 203c2ef36ca..c4648ed5a6b 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/MergeProjectPostProcessTest.java
@@ -78,7 +78,7 @@ public class MergeProjectPostProcessTest {
         LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> DataTrait.EMPTY_TRAIT);
         PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1, qualifier, 0L,
                 Collections.emptyList(), Collections.emptyList(), null, 
PreAggStatus.on(), ImmutableList.of(),
-                Optional.empty(), t1Properties, Optional.empty());
+                Optional.empty(), t1Properties, Optional.empty(), 
ImmutableList.of());
         Alias x = new Alias(a, "x");
         List<NamedExpression> projList3 = Lists.newArrayList(x, b, c);
         PhysicalProject proj3 = new PhysicalProject(projList3, placeHolder, 
scan);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
index aa3cef4c86b..d93b1111c19 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/postprocess/PushDownFilterThroughProjectTest.java
@@ -93,7 +93,7 @@ public class PushDownFilterThroughProjectTest {
         PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1,
                 qualifier, 0L, Collections.emptyList(), 
Collections.emptyList(), null,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
t1Properties,
-                Optional.empty());
+                Optional.empty(), ImmutableList.of());
         Alias x = new Alias(a, "x");
         List<NamedExpression> projList3 = Lists.newArrayList(x, b, c);
         PhysicalProject proj3 = new PhysicalProject(projList3, placeHolder, 
scan);
@@ -132,7 +132,7 @@ public class PushDownFilterThroughProjectTest {
         PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1,
                 qualifier, 0L, Collections.emptyList(), 
Collections.emptyList(), null,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
t1Properties,
-                Optional.empty());
+                Optional.empty(), ImmutableList.of());
         Alias x = new Alias(a, "x");
         List<NamedExpression> projList3 = Lists.newArrayList(x, b, c);
         PhysicalProject proj3 = new PhysicalProject(projList3, placeHolder, 
scan);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDeriveTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDeriveTest.java
new file mode 100644
index 00000000000..5c9a6213ffa
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/OperativeColumnDeriveTest.java
@@ -0,0 +1,90 @@
+// 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.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import org.junit.jupiter.api.Test;
+
+public class OperativeColumnDeriveTest extends TestWithFeService implements 
MemoPatternMatchSupported {
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test");
+
+        createTable("create table test.student (\n" + "id int not null,\n" + 
"name varchar(128),\n"
+                + "age int,sex int)\n" + "distributed by hash(id) buckets 10\n"
+                + "properties('replication_num' = '1');");
+
+        createTable("create table test.score (\n" + "sid int not null, \n" + 
"cid int not null, \n" + "grade double)\n"
+                + "distributed by hash(sid,cid) buckets 10\n" + 
"properties('replication_num' = '1');");
+
+        createTable("create table test.course (\n" + "cid int not null, \n" + 
"cname varchar(128), \n"
+                + "teacher varchar(128))\n" + "distributed by hash(cid) 
buckets 10\n"
+                + "properties('replication_num' = '1');");
+
+        connectContext.setDatabase("test");
+    }
+
+    @Test
+    public void testProject() {
+        PlanChecker.from(connectContext)
+                .analyze("select sid+1, grade as x from score")
+                .customRewrite(new OperativeColumnDerive())
+                .matches(logicalProject(
+                        logicalOlapScan().when(scan ->
+                                scan.getOperativeSlots().size() == 1
+                                        && 
scan.getOperativeSlots().get(0).getName().equals("sid"))
+                ));
+    }
+
+    @Test
+    public void testUnionAll() {
+        PlanChecker.from(connectContext)
+                .analyze("select a, b from (select sid as a, cid as b from 
score union all select id as a, age as b from student) T")
+                .customRewrite(new OperativeColumnDerive())
+                .matches(logicalUnion(
+                        logicalProject(
+                                logicalOlapScan().when(scan -> 
scan.getOperativeSlots().isEmpty())
+                        ),
+                        logicalProject(
+                                logicalOlapScan().when(scan -> 
scan.getOperativeSlots().isEmpty())
+                        )
+                ));
+    }
+
+    @Test
+    public void testUnionAllBackPropagate() {
+        PlanChecker.from(connectContext)
+                .analyze("select a, b from (select sid as a, cid as b from 
score where cid > 0 union all select id as a, age as b from student) T")
+                .customRewrite(new OperativeColumnDerive())
+                .matches(logicalUnion(
+                        logicalProject(
+                                logicalFilter(
+                                        logicalOlapScan().when(scan -> 
scan.getOperativeSlots().size() == 1
+                                                    && 
scan.getOperativeSlots().get(0).getName().equals("cid")
+                                        ))
+                        ),
+                        logicalProject(
+                                logicalOlapScan().when(scan -> 
scan.getOperativeSlots().size() == 1
+                                            && 
scan.getOperativeSlots().get(0).getName().equals("age"))
+                        )
+                ));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanEqualsTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanEqualsTest.java
index f76f2a83d5c..062bd3e71b9 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanEqualsTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanEqualsTest.java
@@ -267,18 +267,21 @@ class PlanEqualsTest {
         PhysicalOlapScan actual = new PhysicalOlapScan(id, olapTable, 
Lists.newArrayList("a"),
                 1L, selectedTabletId, olapTable.getPartitionIds(), 
distributionSpecHash,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
logicalProperties,
-                Optional.empty());
+                Optional.empty(),
+                ImmutableList.of());
 
         PhysicalOlapScan expected = new PhysicalOlapScan(id, olapTable, 
Lists.newArrayList("a"),
                 1L, selectedTabletId, olapTable.getPartitionIds(), 
distributionSpecHash,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
logicalProperties,
-                Optional.empty());
+                Optional.empty(),
+                ImmutableList.of());
         Assertions.assertEquals(expected, actual);
 
         PhysicalOlapScan unexpected = new PhysicalOlapScan(id, olapTable, 
Lists.newArrayList("b"),
                 12345L, selectedTabletId, olapTable.getPartitionIds(), 
distributionSpecHash,
                 PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
logicalProperties,
-                Optional.empty());
+                Optional.empty(),
+                ImmutableList.of());
         Assertions.assertNotEquals(unexpected, actual);
     }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanToStringTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanToStringTest.java
index 312afd22b92..9a16337346e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanToStringTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/PlanToStringTest.java
@@ -84,7 +84,7 @@ public class PlanToStringTest {
         Assertions.assertTrue(
                 plan.toString().matches("LogicalOlapScan \\( 
qualified=db\\.table, "
                         + "indexName=<index_not_selected>, "
-                        + "selectedIndexId=-1, preAgg=UNSET \\)"));
+                        + "selectedIndexId=-1, preAgg=UNSET, operativeCol=\\[] 
\\)"));
     }
 
     @Test
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanConstructor.java 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanConstructor.java
index 3866ce98bf3..73c07dd47d3 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanConstructor.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanConstructor.java
@@ -113,7 +113,7 @@ public class PlanConstructor {
             int hashColumn, List<Long> selectedPartitions) {
         return new LogicalOlapScan(RelationId.createGenerator().getNextId(),
                 newOlapTable(tableId, tableName, hashColumn), 
ImmutableList.of("db"),
-                selectedPartitions, ImmutableList.of(), Optional.empty());
+                selectedPartitions, ImmutableList.of(), Optional.empty(), 
ImmutableList.of());
     }
 
     public static RelationId getNextRelationId() {
diff --git a/regression-test/suites/nereids_p0/stats/partitionRowCount.groovy 
b/regression-test/suites/nereids_p0/stats/partitionRowCount.groovy
index 1250db62fe5..f18232b1dae 100644
--- a/regression-test/suites/nereids_p0/stats/partitionRowCount.groovy
+++ b/regression-test/suites/nereids_p0/stats/partitionRowCount.groovy
@@ -35,7 +35,7 @@ suite("partitionRowCount") {
         sql """physical plan
             select * from partitionRowCountTable where a < 250;
             """
-        contains("PhysicalOlapScan[partitionRowCountTable partitions(2/3)]@0 ( 
stats=4 )")
+        contains("PhysicalOlapScan[partitionRowCountTable partitions(2/3) 
operativeSlots([a#0])]@0 ( stats=4 )")
     }
 
 }
\ No newline at end of file
diff --git 
a/regression-test/suites/query_p0/operative_slots/operative_slots.groovy 
b/regression-test/suites/query_p0/operative_slots/operative_slots.groovy
new file mode 100644
index 00000000000..c8f4f89c6c4
--- /dev/null
+++ b/regression-test/suites/query_p0/operative_slots/operative_slots.groovy
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("operative_slots") {
+    sql """
+    drop table if exists vt;
+    CREATE TABLE IF NOT EXISTS vt (
+                `user_id` int NOT NULL COMMENT "用户id",
+                `name` STRING COMMENT "用户年龄",
+                `v` VARIANT NULL
+                )
+                DISTRIBUTED BY HASH(user_id) PROPERTIES("replication_num" = 
"1");
+
+    insert into vt values (6, 'doris6', '{"k1" : 100, "k2": 1}'), (7, 
'doris7', '{"k1" : 2, "k2": 2}');
+
+    drop table if exists t;
+    CREATE TABLE `t` (
+    `k` int NULL,
+    `v1` bigint NULL,
+    `v2` bigint NULL 
+    ) ENGINE=OLAP
+    UNIQUE KEY(`k`)
+    DISTRIBUTED BY HASH(`k`) BUCKETS 1
+    PROPERTIES (
+    "replication_allocation" = "tag.location.default: 1"
+    );
+
+    insert into t values (1, 2, 3);
+
+    set disable_join_reorder = true;
+    """
+
+
+    explain {
+        sql "physical plan select * from t join[broadcast] vt on t.k = 
vt.v['k1'];"
+        contains("operativeSlots([k#0, __DORIS_DELETE_SIGN__#3])")
+        contains("operativeSlots([v['k1']#17])")
+        // expect plan
+        //    PhysicalResultSink[311] ( outputExprs=[k#0, v1#1, user_id#4, 
name#5, v#6] )
+        //    +--PhysicalDistribute[306]@6 ( stats=1, 
distributionSpec=DistributionSpecGather )
+        //    +--PhysicalProject[301]@6 ( stats=1, projects=[k#0, v1#1, 
user_id#4, name#5, v#6] )
+        //    +--PhysicalHashJoin[296]@5 ( stats=1, type=INNER_JOIN, 
hashCondition=[(k#0 = expr_cast(element_at(v, 'k1') as INT)#7)], 
otherCondition=[], markCondition=[], hint=[broadcast] )
+        //            |--PhysicalProject[280]@2 ( stats=1, projects=[k#0, 
v1#1] )
+        //            |  +--PhysicalFilter[275]@1 ( stats=1, 
predicates=(__DORIS_DELETE_SIGN__#2 = 0) )
+        //    |     +--PhysicalOlapScan[t operativeSlots([k#0, 
__DORIS_DELETE_SIGN__#2])]@0 ( stats=1 )
+        //    +--PhysicalDistribute[291]@4 ( stats=1, 
distributionSpec=DistributionSpecReplicated )
+        //          +--PhysicalProject[286]@4 ( stats=1, projects=[user_id#4, 
name#5, v#6, cast(v['k1']#15 as INT) AS `expr_cast(element_at(v, 'k1') as 
INT)`#7] )
+        //              +--PhysicalOlapScan[vt operativeSlots([v['k1']#15])]@3 
( stats=1 )
+    }
+
+    explain {
+        sql "physical plan select * from t where v1=0;"
+        contains("operativeSlots([v1#1, __DORIS_DELETE_SIGN__#3]")
+    }
+
+    explain {
+        sql "physical plan select sum(k) from t group by v1;"
+        contains("operativeSlots([k#0, v1#1, __DORIS_DELETE_SIGN__#3])")
+    }
+
+    explain {
+        sql "physical plan select rank() over (partition by v2 order by v1) 
from t;"
+        contains("operativeSlots([v1#1, v2#2, __DORIS_DELETE_SIGN__#3])")
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to