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

yiguolei pushed a commit to branch branch-2.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-2.1 by this push:
     new 3738d0fddc7 branch-2.1: [fix](nereids) fix push down non-foldable 
filter through project #47989 (#48084)
3738d0fddc7 is described below

commit 3738d0fddc7d1a85a918948c696a9a040bfd438a
Author: yujun <yu...@selectdb.com>
AuthorDate: Fri Feb 21 10:09:03 2025 +0800

    branch-2.1: [fix](nereids) fix push down non-foldable filter through 
project #47989 (#48084)
    
    cherry pick from #47989
---
 .../post/PushDownFilterThroughProject.java         |  13 +++++-
 .../doris/nereids/processor/post/Validator.java    |   7 ---
 .../rewrite/PushDownFilterThroughProject.java      |  46 ++++++++++++--------
 .../expressions/functions/ExpressionTrait.java     |   9 +++-
 .../PushDownFilterThroughProjectTest.java          |  41 ++++++++++++++++++
 .../rewrite/PushDowFilterThroughProjectTest.java   |  48 ++++++++++++++++++++-
 .../filter_push_down/push_filter_through.out       | Bin 10141 -> 10077 bytes
 7 files changed, 136 insertions(+), 28 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
index 864e817dc1f..4807d66b320 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
@@ -18,12 +18,17 @@
 package org.apache.doris.nereids.processor.post;
 
 import org.apache.doris.nereids.CascadesContext;
+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.physical.AbstractPhysicalPlan;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalFilter;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalProject;
 import org.apache.doris.nereids.util.ExpressionUtils;
 
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * merge consecutive projects
  */
@@ -37,9 +42,13 @@ public class PushDownFilterThroughProject extends 
PlanPostProcessor {
         }
 
         PhysicalProject<? extends Plan> project = (PhysicalProject<? extends 
Plan>) child;
+        Map<Slot, Expression> childAlias = project.getAliasToProducer();
+        if 
(filter.getInputSlots().stream().map(childAlias::get).filter(Objects::nonNull)
+                .anyMatch(Expression::containsNonfoldable)) {
+            return filter;
+        }
         PhysicalFilter<? extends Plan> newFilter = 
filter.withConjunctsAndChild(
-                ExpressionUtils.replace(filter.getConjuncts(), 
project.getAliasToProducer()),
-                project.child());
+                ExpressionUtils.replace(filter.getConjuncts(), childAlias), 
project.child());
         return ((AbstractPhysicalPlan) 
project.withChildren(newFilter.accept(this, context)))
                 .copyStatsAndGroupIdFrom(project);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java
index 8ff3a43bf79..00102b78dfe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java
@@ -59,13 +59,6 @@ public class Validator extends PlanPostProcessor {
         Preconditions.checkArgument(!filter.getConjuncts().isEmpty()
                 && filter.getPredicate() != BooleanLiteral.TRUE, "Filter 
predicate can't be empty or true");
 
-        Plan child = filter.child();
-        // Forbidden filter-project, we must make filter-project -> 
project-filter.
-        if (child instanceof PhysicalProject) {
-            throw new AnalysisException(
-                    "Nereids generate a filter-project plan, but backend not 
support:\n" + filter.treeString());
-        }
-
         return visit(filter, context);
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
index 5842beaf3d6..bb1cf3245cc 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
@@ -33,6 +33,8 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -67,6 +69,7 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
     private static Plan 
pushDownFilterThroughProject(LogicalFilter<LogicalProject<Plan>> filter) {
         LogicalProject<? extends Plan> project = filter.child();
         Set<Slot> childOutputs = project.getOutputSet();
+        Map<Slot, Expression> childAlias = project.getAliasToProducer();
         // we need run this rule before subquery unnesting
         // therefore the conjuncts may contain slots from outer query
         // we should only push down conjuncts without any outer query's slot,
@@ -74,16 +77,15 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
         // splitConjuncts.first -> conjuncts having outer query slots which 
should NOT be pushed down
         // splitConjuncts.second -> conjuncts without any outer query slots 
which should be pushed down
         Pair<Set<Expression>, Set<Expression>> splitConjuncts =
-                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs);
-        if (splitConjuncts.second.isEmpty()) {
-            // all conjuncts contain outer query's slots, no conjunct can be 
pushed down
-            // just return unchanged plan
+                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias);
+        Set<Expression> remainPredicates = splitConjuncts.first;
+        Set<Expression> pushDownPredicates = splitConjuncts.second;
+        if (pushDownPredicates.isEmpty()) {
             return null;
         }
-        project = (LogicalProject<? extends Plan>) project.withChildren(new 
LogicalFilter<>(
-                ExpressionUtils.replace(splitConjuncts.second, 
project.getAliasToProducer()),
-                project.child()));
-        return PlanUtils.filterOrSelf(splitConjuncts.first, project);
+        project = (LogicalProject<? extends Plan>) project.withChildren(
+                new LogicalFilter<>(pushDownPredicates, project.child()));
+        return PlanUtils.filterOrSelf(remainPredicates, project);
     }
 
     private static Plan pushDownFilterThroughLimitProject(
@@ -91,32 +93,42 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
         LogicalLimit<LogicalProject<Plan>> limit = filter.child();
         LogicalProject<Plan> project = limit.child();
         Set<Slot> childOutputs = project.getOutputSet();
+        Map<Slot, Expression> childAlias = project.getAliasToProducer();
         // split the conjuncts by child's output
         Pair<Set<Expression>, Set<Expression>> splitConjuncts =
-                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs);
-        if (splitConjuncts.second.isEmpty()) {
+                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias);
+        Set<Expression> remainPredicates = splitConjuncts.first;
+        Set<Expression> pushDownPredicates = splitConjuncts.second;
+        if (pushDownPredicates.isEmpty()) {
             return null;
         }
         project = project.withProjectsAndChild(project.getProjects(),
-                new LogicalFilter<>(
-                        ExpressionUtils.replace(splitConjuncts.second,
-                                project.getAliasToProducer()),
-                        limit.withChildren(project.child())));
-        return PlanUtils.filterOrSelf(splitConjuncts.first, project);
+                new LogicalFilter<>(pushDownPredicates, 
limit.withChildren(project.child())));
+        return PlanUtils.filterOrSelf(remainPredicates, project);
     }
 
     private static Pair<Set<Expression>, Set<Expression>> 
splitConjunctsByChildOutput(
-            Set<Expression> conjuncts, Set<Slot> childOutputs) {
+            Set<Expression> conjuncts, Set<Slot> childOutputs, Map<Slot, 
Expression> childAlias) {
         Set<Expression> pushDownPredicates = Sets.newLinkedHashSet();
         Set<Expression> remainPredicates = Sets.newLinkedHashSet();
         for (Expression conjunct : conjuncts) {
             Set<Slot> conjunctSlots = conjunct.getInputSlots();
-            if (childOutputs.containsAll(conjunctSlots)) {
+            // If filter contains non-foldable expression, it can push down, 
for example:
+            // `filter(a + random(1, 10) > 1) -> project(a)` => `project(a) -> 
filter(a + random(1, 10) > 1)`.
+            // If filter slot is alias and its expression contains 
non-foldable expression, it can't push down, example:
+            // `filter(a > 1) -> project(b + random(1, 10) as a)`, if push 
down filter, it got
+            // `project(b + random(1, 10) as a) -> filter(b + random(1, 10) > 
1)`, it contains two distinct RANDOM.
+            if (childOutputs.containsAll(conjunctSlots)
+                    && 
conjunctSlots.stream().map(childAlias::get).filter(Objects::nonNull)
+                            .noneMatch(Expression::containsNonfoldable)) {
                 pushDownPredicates.add(conjunct);
             } else {
                 remainPredicates.add(conjunct);
             }
         }
+        if (!pushDownPredicates.isEmpty()) {
+            pushDownPredicates = ExpressionUtils.replace(pushDownPredicates, 
childAlias);
+        }
         return Pair.of(remainPredicates, pushDownPredicates);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java
index daaab359e84..6e33f2a5e11 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java
@@ -77,6 +77,13 @@ public interface ExpressionTrait extends 
TreeNode<Expression> {
         return true;
     }
 
+    /**
+     * Identify the expression is containing non-foldable expr or not
+     */
+    default boolean containsNonfoldable() {
+        return anyMatch(expr -> !((ExpressionTrait) expr).foldable());
+    }
+
     /**
      * Identify the expression itself is deterministic or not, default true
      */
@@ -85,7 +92,7 @@ public interface ExpressionTrait extends TreeNode<Expression> 
{
     }
 
     /**
-     * Identify the expression is containing deterministic expr or not
+     * Identify the expression is containing non-deterministic expr or not
      */
     default boolean containsNondeterministic() {
         return anyMatch(expr -> !((ExpressionTrait) expr).isDeterministic());
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 08ccc56f9ba..fd434504b2e 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
@@ -23,12 +23,15 @@ import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.processor.post.PushDownFilterThroughProject;
 import org.apache.doris.nereids.properties.FunctionalDependencies;
 import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.expressions.Add;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 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.expressions.functions.scalar.Random;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.plans.PreAggStatus;
 import org.apache.doris.nereids.trees.plans.RelationId;
@@ -111,4 +114,42 @@ public class PushDownFilterThroughProjectTest {
                 ((PhysicalFilter<?>) 
newPlan.child(0).child(0)).getExpressions();
         Assertions.assertEquals(newFilterConjuncts.get(0).child(0), a);
     }
+
+    @Test
+    public void testNotPushFilterWithNonfoldable(@Injectable LogicalProperties 
placeHolder,
+            @Injectable CascadesContext ctx) {
+        OlapTable t1 = PlanConstructor.newOlapTable(0, "t1", 0, 
KeysType.DUP_KEYS);
+        List<String> qualifier = new ArrayList<>();
+        qualifier.add("test");
+        List<Slot> t1Output = new ArrayList<>();
+        SlotReference a = new SlotReference("a", IntegerType.INSTANCE);
+        SlotReference b = new SlotReference("b", IntegerType.INSTANCE);
+        SlotReference c = new SlotReference("c", IntegerType.INSTANCE);
+        t1Output.add(a);
+        t1Output.add(b);
+        t1Output.add(c);
+        LogicalProperties t1Properties = new LogicalProperties(() -> t1Output, 
() -> FunctionalDependencies.EMPTY_FUNC_DEPS);
+        PhysicalOlapScan scan = new 
PhysicalOlapScan(RelationId.createGenerator().getNextId(), t1,
+                qualifier, 0L, Collections.emptyList(), 
Collections.emptyList(), null,
+                PreAggStatus.on(), ImmutableList.of(), Optional.empty(), 
t1Properties,
+                Optional.empty());
+        Alias x = new Alias(a, "x");
+        List<NamedExpression> projList3 = Lists.newArrayList(x, b, c);
+        PhysicalProject proj3 = new PhysicalProject(projList3, placeHolder, 
scan);
+        Alias y = new Alias(
+                new Add(x.toSlot(), new Random(new BigIntLiteral(1L), new 
BigIntLiteral(10L))),
+                "y");
+        Alias z = new Alias(b, "z");
+        List<NamedExpression> projList2 = Lists.newArrayList(y, z, c);
+        PhysicalProject proj2 = new PhysicalProject(projList2, placeHolder, 
proj3);
+        Set<Expression> conjuncts = Sets.newHashSet();
+        conjuncts.add(new EqualTo(y.toSlot(), Literal.of(0)));
+        PhysicalFilter filter = new PhysicalFilter(conjuncts, 
proj2.getLogicalProperties(), proj2);
+
+        PushDownFilterThroughProject processor = new 
PushDownFilterThroughProject();
+        PhysicalPlan newPlan = (PhysicalPlan) filter.accept(processor, ctx);
+        Assertions.assertTrue(newPlan instanceof PhysicalFilter);
+        Assertions.assertTrue(newPlan.child(0) instanceof PhysicalProject);
+        Assertions.assertTrue(newPlan.child(0).child(0) instanceof 
PhysicalProject);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDowFilterThroughProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDowFilterThroughProjectTest.java
index a737acbf016..080c0a33522 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDowFilterThroughProjectTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDowFilterThroughProjectTest.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.Add;
 import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Expression;
@@ -25,9 +26,12 @@ import org.apache.doris.nereids.trees.expressions.IsNull;
 import org.apache.doris.nereids.trees.expressions.OrderExpression;
 import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.expressions.WindowExpression;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Random;
 import org.apache.doris.nereids.trees.expressions.functions.window.Rank;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
 import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.LogicalPlanBuilder;
 import org.apache.doris.nereids.util.MemoPatternMatchSupported;
 import org.apache.doris.nereids.util.MemoTestUtils;
@@ -36,6 +40,7 @@ import org.apache.doris.nereids.util.PlanConstructor;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Test;
 
 class PushDowFilterThroughProjectTest implements MemoPatternMatchSupported {
@@ -70,7 +75,7 @@ class PushDowFilterThroughProjectTest implements 
MemoPatternMatchSupported {
         Alias alias = new Alias(new ExprId(1), window, "window");
         ConnectContext context = MemoTestUtils.createConnectContext();
 
-        // filter -> limit -> project(windows)
+        // filter -> project(windows)
         LogicalPlan plan = new LogicalPlanBuilder(scan)
                 .projectExprs(ImmutableList.of(alias))
                 .filter(new IsNull(alias.toSlot()))
@@ -92,6 +97,47 @@ class PushDowFilterThroughProjectTest implements 
MemoPatternMatchSupported {
                 .matches(logicalFilter(logicalLimit(logicalProject())));
     }
 
+    @Test
+    void notPushDownFilterThroughNonfoldable() {
+        ConnectContext context = MemoTestUtils.createConnectContext();
+        Alias foldableAlias = new Alias(new ExprId(1), 
scan.getOutput().get(0), "a");
+        Alias nonfoldableAlias = new Alias(new ExprId(2),
+                new Random(new BigIntLiteral(1L), new BigIntLiteral(10L)), 
"b");
+        Expression nonfoldableAdd = new Add(foldableAlias.toSlot(),
+                new Random(new BigIntLiteral(1L), new BigIntLiteral(2L)));
+        Expression condition = ExpressionUtils.and(Lists.newArrayList(new 
IsNull(foldableAlias.toSlot()),
+                new IsNull(nonfoldableAlias.toSlot()), new 
IsNull(nonfoldableAdd)));
+        LogicalPlan plan = new LogicalPlanBuilder(scan)
+                .projectExprs(ImmutableList.of(foldableAlias, 
nonfoldableAlias))
+                .filter(condition)
+                .build();
+
+        PlanChecker.from(context, plan)
+                .applyTopDown(new PushDownFilterThroughProject())
+                .matches(
+                        logicalFilter(
+                                logicalProject(
+                                        logicalFilter().when(f -> 
f.getPredicate().toSql().equals(
+                                                "(id IS NULL AND (id + 
random(1, 2)) IS NULL)"))))
+                                .when(f -> f.getPredicate().toSql().equals("b 
IS NULL")));
+
+        plan = new LogicalPlanBuilder(scan)
+                .projectExprs(ImmutableList.of(foldableAlias, 
nonfoldableAlias))
+                .limit(1)
+                .filter(condition)
+                .build();
+
+        PlanChecker.from(context, plan)
+                .applyTopDown(new PushDownFilterThroughProject())
+                .matches(
+                        logicalFilter(
+                                logicalProject(
+                                        logicalFilter(logicalLimit())
+                                                .when(f -> 
f.getPredicate().toSql().equals(
+                                                        "(id IS NULL AND (id + 
random(1, 2)) IS NULL)"))))
+                                .when(f -> f.getPredicate().toSql().equals("b 
IS NULL")));
+    }
+
     @Test
     void pushDownFilterThroughLimit() {
         ConnectContext context = MemoTestUtils.createConnectContext();
diff --git 
a/regression-test/data/nereids_rules_p0/filter_push_down/push_filter_through.out
 
b/regression-test/data/nereids_rules_p0/filter_push_down/push_filter_through.out
index 27ad895837c..50e35770b63 100644
Binary files 
a/regression-test/data/nereids_rules_p0/filter_push_down/push_filter_through.out
 and 
b/regression-test/data/nereids_rules_p0/filter_push_down/push_filter_through.out
 differ


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

Reply via email to