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

morrySnow 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 810c253cff3 [fix](lazy topn) Preserve required slots in TopN lazy 
materialization (#65530)
810c253cff3 is described below

commit 810c253cff38165a01a6fac05a3b7db822a2c4ce
Author: morrySnow <[email protected]>
AuthorDate: Wed Jul 29 10:43:34 2026 +0800

    [fix](lazy topn) Preserve required slots in TopN lazy materialization 
(#65530)
    
    ### What problem does this PR solve?
    
    Related PR: #51329
    
    Problem Summary: TopN lazy materialization could produce invalid plans
    in two slot-pruning cases: an output alias was selected as lazy while
    its base slot remained an eager sorting output, or an eager sorting slot
    used by a Filter was removed together with lazy predicate slots. Keep
    aliases eager when their base is eager, and remove only selected lazy
    slots from Filter outputs.
    
    ### Release note
    
    Fix invalid plans caused by pruning slots still required by TopN lazy
    materialization.
---
 .../post/materialize/LazyMaterializeTopN.java      | 26 ++++++++---
 .../post/materialize/LazySlotPruning.java          | 12 ++++-
 .../post/materialize/LazyMaterializeTopNTest.java  | 54 ++++++++++++++++++++++
 .../post/materialize/LazySlotPruningTest.java      | 45 ++++++++++++++++++
 .../query_p0/topn_lazy/topn_lazy_on_data_model.out |  4 ++
 .../topNLazyMaterializationUsingIndex.out          | 14 ++++++
 .../topn_lazy/topn_lazy_on_data_model.groovy       | 10 +++-
 .../topNLazyMaterializationUsingIndex.groovy       | 39 ++++++++++++++++
 8 files changed, 194 insertions(+), 10 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopN.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopN.java
index 1b5f262bc88..9acec57125a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopN.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopN.java
@@ -42,6 +42,7 @@ import 
org.apache.doris.nereids.trees.plans.physical.PhysicalTVFRelation;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalTopN;
 import org.apache.doris.qe.SessionVariable;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
@@ -133,13 +134,10 @@ public class LazyMaterializeTopN extends 
PlanPostProcessor {
                 materializedSlots.add(slot);
             }
         }
-        List<Slot> requiredOutputSlots = new ArrayList<>();
-        for (Map.Entry<Slot, MaterializeSource> entry : 
materializeMap.entrySet()) {
-            if (requiredMaterializedSlots.contains(entry.getKey())
-                    || 
requiredMaterializedSlots.contains(entry.getValue().baseSlot)) {
-                requiredOutputSlots.add(entry.getKey());
-            }
-        }
+        // A lazy alias can share its base slot with another output that must 
be materialized for TopN.
+        // Keep the alias materialized too, otherwise LazySlotPruning removes 
the base slot needed by that output.
+        List<Slot> requiredOutputSlots = collectRequiredOutputSlots(
+                materializeMap, requiredMaterializedSlots, new 
HashSet<>(materializedSlots));
         for (Slot slot : requiredOutputSlots) {
             if (materializeMap.remove(slot) != null) {
                 materializedSlots.add(slot);
@@ -235,6 +233,20 @@ public class LazyMaterializeTopN extends PlanPostProcessor 
{
         return result;
     }
 
+    @VisibleForTesting
+    static List<Slot> collectRequiredOutputSlots(Map<Slot, MaterializeSource> 
materializeMap,
+            Set<Slot> requiredMaterializedSlots, Set<Slot> materializedSlots) {
+        List<Slot> requiredOutputSlots = new ArrayList<>();
+        for (Map.Entry<Slot, MaterializeSource> entry : 
materializeMap.entrySet()) {
+            if (requiredMaterializedSlots.contains(entry.getKey())
+                    || 
requiredMaterializedSlots.contains(entry.getValue().baseSlot)
+                    || materializedSlots.contains(entry.getValue().baseSlot)) {
+                requiredOutputSlots.add(entry.getKey());
+            }
+        }
+        return requiredOutputSlots;
+    }
+
     private void collectProjectExprInputSlots(Plan plan, Set<Slot> 
requiredMaterializedSlots) {
         if (plan instanceof PhysicalProject) {
             PhysicalProject<?> project = (PhysicalProject<?>) plan;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruning.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruning.java
index f2bf5831206..3273a7d82d3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruning.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruning.java
@@ -43,6 +43,7 @@ import 
org.apache.doris.nereids.trees.plans.physical.PhysicalTVFRelation;
 import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
 import org.apache.doris.qe.SessionVariable;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
@@ -146,8 +147,8 @@ public class LazySlotPruning extends 
DefaultPlanRewriter<LazySlotPruning.Context
                         filter.child().accept(this, contextForScan));
                 filter = (PhysicalFilter<? extends Plan>) filter
                         
.copyStatsAndGroupIdFrom(filter).resetLogicalProperties();
-                List<Slot> filterOutput = 
Lists.newArrayList(filter.getOutput());
-                filterOutput.removeAll(filter.getInputSlots());
+                // Predicate slots that are not lazy can still be required by 
TopN order keys.
+                List<Slot> filterOutput = 
computeFilterOutput(filter.getOutput(), context.lazySlots);
                 return new PhysicalProject<>(
                         filterOutput.stream().map(s -> (SlotReference) 
s).collect(Collectors.toList()),
                         Optional.empty(), null,
@@ -157,6 +158,13 @@ public class LazySlotPruning extends 
DefaultPlanRewriter<LazySlotPruning.Context
         return visit(filter, context);
     }
 
+    @VisibleForTesting
+    static List<Slot> computeFilterOutput(List<Slot> output, List<Slot> 
lazySlots) {
+        List<Slot> filterOutput = Lists.newArrayList(output);
+        filterOutput.removeAll(lazySlots);
+        return filterOutput;
+    }
+
     @Override
     public Plan visitPhysicalOlapScan(PhysicalOlapScan scan, Context context) {
         if (scan.getOutput().containsAll(context.lazySlots)) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopNTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopNTest.java
new file mode 100644
index 00000000000..d8763f27082
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazyMaterializeTopNTest.java
@@ -0,0 +1,54 @@
+// 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.processor.post.materialize;
+
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.algebra.Relation;
+import org.apache.doris.nereids.types.IntegerType;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.List;
+import java.util.Map;
+
+public class LazyMaterializeTopNTest {
+
+    @Test
+    public void testAliasIsRequiredWhenItsBaseSlotIsMaterialized() {
+        SlotReference baseSlot = new SlotReference("base", 
IntegerType.INSTANCE);
+        Slot aliasSlot = new Alias(baseSlot, "alias").toSlot();
+        SlotReference independentBaseSlot = new SlotReference("independent", 
IntegerType.INSTANCE);
+        Slot independentAliasSlot = new Alias(independentBaseSlot, 
"independent_alias").toSlot();
+        Relation relation = Mockito.mock(Relation.class);
+        Map<Slot, MaterializeSource> materializeMap = ImmutableMap.of(
+                aliasSlot, new MaterializeSource(relation, baseSlot),
+                independentAliasSlot, new MaterializeSource(relation, 
independentBaseSlot));
+
+        List<Slot> requiredOutputSlots = 
LazyMaterializeTopN.collectRequiredOutputSlots(
+                materializeMap, ImmutableSet.of(), ImmutableSet.of(baseSlot));
+
+        Assertions.assertEquals(ImmutableList.of(aliasSlot), 
requiredOutputSlots);
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruningTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruningTest.java
new file mode 100644
index 00000000000..84312664242
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/LazySlotPruningTest.java
@@ -0,0 +1,45 @@
+// 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.processor.post.materialize;
+
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.types.IntegerType;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class LazySlotPruningTest {
+
+    @Test
+    public void testFilterKeepsMaterializedPredicateSlot() {
+        SlotReference orderKey = new SlotReference("order_key", 
IntegerType.INSTANCE);
+        SlotReference lazyPredicateSlot = new SlotReference("lazy_predicate", 
IntegerType.INSTANCE);
+        SlotReference lazyOutputSlot = new SlotReference("lazy_output", 
IntegerType.INSTANCE);
+        SlotReference rowId = new SlotReference("row_id", 
IntegerType.INSTANCE);
+
+        List<Slot> filterOutput = LazySlotPruning.computeFilterOutput(
+                ImmutableList.of(orderKey, lazyPredicateSlot, rowId),
+                ImmutableList.of(lazyPredicateSlot, lazyOutputSlot));
+
+        Assertions.assertEquals(ImmutableList.of(orderKey, rowId), 
filterOutput);
+    }
+}
diff --git 
a/regression-test/data/query_p0/topn_lazy/topn_lazy_on_data_model.out 
b/regression-test/data/query_p0/topn_lazy/topn_lazy_on_data_model.out
index 694a8249cdf..23c468d9bdd 100644
--- a/regression-test/data/query_p0/topn_lazy/topn_lazy_on_data_model.out
+++ b/regression-test/data/query_p0/topn_lazy/topn_lazy_on_data_model.out
@@ -42,6 +42,10 @@ PhysicalResultSink
 --------------filter((mow.__DORIS_DELETE_SIGN__ = 0))
 ----------------PhysicalLazyMaterializeOlapScan[mow lazySlots:(mow.age)]
 
+-- !mow_alias_with_materialized_base_slot --
+a      \N
+b      \N
+
 -- !shape_aggkey_not_lazy --
 PhysicalResultSink
 --PhysicalProject
diff --git 
a/regression-test/data/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.out
 
b/regression-test/data/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.out
index 10720c0b9f0..b42ff9564fb 100644
--- 
a/regression-test/data/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.out
+++ 
b/regression-test/data/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.out
@@ -41,3 +41,17 @@ PhysicalResultSink
 ------------filter((t1.user_id > 0))
 --------------PhysicalLazyMaterializeOlapScan[t1 
lazySlots:(t1.username,t1.age,t1.addr)]
 
+-- !filter_order_key_not_pruned --
+1      10      \N      a
+
+-- !filter_order_key_not_pruned_shape --
+PhysicalResultSink
+--PhysicalProject[topn_lazy_filter_order_key.k1, 
topn_lazy_filter_order_key.k2, topn_lazy_filter_order_key.pad, 
topn_lazy_filter_order_key.v]
+----PhysicalLazyMaterialize[materializedSlots:(topn_lazy_filter_order_key.k1,topn_lazy_filter_order_key.k2)
 lazySlots:(topn_lazy_filter_order_key.pad,topn_lazy_filter_order_key.v)]
+------PhysicalTopN[MERGE_SORT]
+--------PhysicalDistribute[DistributionSpecGather]
+----------PhysicalTopN[LOCAL_SORT]
+------------PhysicalProject[regression_test_query_p0_topn_lazy_usingIndex.__DORIS_GLOBAL_ROWID_COL__topn_lazy_filter_order_key,
 topn_lazy_filter_order_key.k1, topn_lazy_filter_order_key.k2]
+--------------filter(OR[topn_lazy_filter_order_key.v IS 
NULL,(topn_lazy_filter_order_key.k2 < 40)])
+----------------PhysicalLazyMaterializeOlapScan[topn_lazy_filter_order_key 
lazySlots:(topn_lazy_filter_order_key.pad)]
+
diff --git 
a/regression-test/suites/query_p0/topn_lazy/topn_lazy_on_data_model.groovy 
b/regression-test/suites/query_p0/topn_lazy/topn_lazy_on_data_model.groovy
index ce73bcf01da..ee121cc25a2 100644
--- a/regression-test/suites/query_p0/topn_lazy/topn_lazy_on_data_model.groovy
+++ b/regression-test/suites/query_p0/topn_lazy/topn_lazy_on_data_model.groovy
@@ -61,6 +61,14 @@ suite("topn_lazy_on_data_model") {
     qt_shape_mow_key_lazy "explain shape plan select user_id  from mow order 
by username limit 1"
     qt_shape_mow_value_lazy "explain shape plan select age  from mow order by 
username limit 1"
 
+    order_qt_mow_alias_with_materialized_base_slot """
+        SELECT username AS username_alias, NULL
+        FROM mow
+        QUALIFY ROW_NUMBER() OVER (PARTITION BY username, user_id) = 1
+        ORDER BY username
+        LIMIT 5
+    """
+
     // agg key user_id is lazy materialized
     sql """
         drop table if exists agg; 
@@ -85,4 +93,4 @@ suite("topn_lazy_on_data_model") {
 
     
 
-}
\ No newline at end of file
+}
diff --git 
a/regression-test/suites/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.groovy
 
b/regression-test/suites/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.groovy
index 9f51a025981..c8fe475f155 100644
--- 
a/regression-test/suites/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.groovy
+++ 
b/regression-test/suites/query_p0/topn_lazy/usingIndex/topNLazyMaterializationUsingIndex.groovy
@@ -50,7 +50,30 @@ suite("topNLazyMaterializationUsingIndex") {
 
         insert into t2 values ( 1, 'a', 10, 'cd'),(1,'b', 20, 'cq');
 
+        drop table if exists topn_lazy_filter_order_key;
+        CREATE TABLE topn_lazy_filter_order_key
+        (
+        `k1` INT NOT NULL,
+        `k2` INT NOT NULL,
+        `v` INT NULL,
+        `pad` STRING NULL,
+        INDEX idx_v(v) USING INVERTED
+        )
+        DUPLICATE KEY(k1, k2)
+        DISTRIBUTED BY HASH(k1) BUCKETS 1
+        PROPERTIES (
+        "replication_allocation" = "tag.location.default: 1");
+
+        insert into topn_lazy_filter_order_key values
+            (1, 10, NULL, 'a'),
+            (2, 20, 1, 'b'),
+            (3, 30, 2, 'c'),
+            (4, 40, NULL, 'd'),
+            (5, 50, 3, 'e');
+
         set topn_lazy_materialization_using_index = true;
+        set topn_lazy_materialization_threshold = 1;
+        set enable_segment_limit_pushdown = false;
         SET detail_shape_nodes='PhysicalProject';
         """
         qt_plan """
@@ -76,4 +99,20 @@ suite("topNLazyMaterializationUsingIndex") {
             user_id > 0 order by user_id limit 1;
             """
 
+        order_qt_filter_order_key_not_pruned """
+        select k1, k2, v, pad
+        from topn_lazy_filter_order_key
+        where (v is null or k2 < 40)
+        order by k1, k2
+        limit 1;
+        """
+
+        qt_filter_order_key_not_pruned_shape """
+        explain shape plan 
+        select k1, k2, v, pad
+        from topn_lazy_filter_order_key
+        where (v is null or k2 < 40)
+        order by k1, k2
+        limit 1;
+        """
 }


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

Reply via email to