This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.1 by this push:
new 4cc6076814b branch-3.1: [opt](nereids) logical filter replace null to
false #49457 (#51985)
4cc6076814b is described below
commit 4cc6076814b237f712b13feee4b821fa38702bd1
Author: yujun <[email protected]>
AuthorDate: Mon Jun 23 19:38:54 2025 +0800
branch-3.1: [opt](nereids) logical filter replace null to false #49457
(#51985)
Cherry-pick from #49457
---
.../rules/expression/ExpressionOptimization.java | 14 ++-
.../rules/expression/rules/FoldConstantRule.java | 9 +-
.../nereids/rules/rewrite/EliminateFilter.java | 34 ++++++-
.../expression/rules/ExpressionRewriteSqlTest.java | 65 +++++++++++++
.../nereids/rules/rewrite/EliminateFilterTest.java | 105 +++++++++++++++++++++
5 files changed, 220 insertions(+), 7 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/ExpressionOptimization.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/ExpressionOptimization.java
index a5b94d216e9..b91f9a3d0ec 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/ExpressionOptimization.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/ExpressionOptimization.java
@@ -42,13 +42,19 @@ import java.util.List;
public class ExpressionOptimization extends ExpressionRewrite {
public static final List<ExpressionRewriteRule> OPTIMIZE_REWRITE_RULES =
ImmutableList.of(
bottomUp(
- ExtractCommonFactorRule.INSTANCE,
- DistinctPredicatesRule.INSTANCE,
- SimplifyComparisonPredicate.INSTANCE,
SimplifyInPredicate.INSTANCE,
- SimplifyDecimalV3Comparison.INSTANCE,
+
+ // comparison predicates
+ SimplifyComparisonPredicate.INSTANCE,
+
+ // compound predicates
SimplifyRange.INSTANCE,
SimplifyConflictCompound.INSTANCE,
+ DistinctPredicatesRule.INSTANCE,
+ ExtractCommonFactorRule.INSTANCE,
+
+ SimplifyDecimalV3Comparison.INSTANCE,
+
DateFunctionRewrite.INSTANCE,
ArrayContainToArrayOverlap.INSTANCE,
CaseWhenToIf.INSTANCE,
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRule.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRule.java
index cd10b6a7e37..a4874d7ad31 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRule.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRule.java
@@ -23,6 +23,7 @@ import
org.apache.doris.nereids.rules.expression.ExpressionPatternRuleFactory;
import org.apache.doris.nereids.rules.expression.ExpressionRewrite;
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.qe.SessionVariable;
import com.google.common.collect.ImmutableList;
@@ -51,9 +52,13 @@ public class FoldConstantRule implements
ExpressionPatternRuleFactory {
/** evaluate by visitor */
public static Expression evaluate(Expression expr,
ExpressionRewriteContext ctx) {
- if (ctx.cascadesContext != null
+ SessionVariable sessionVariable = ctx.cascadesContext != null
&& ctx.cascadesContext.getConnectContext() != null
- &&
ctx.cascadesContext.getConnectContext().getSessionVariable().isEnableFoldConstantByBe())
{
+ ?
ctx.cascadesContext.getConnectContext().getSessionVariable() : null;
+ if (sessionVariable != null &&
sessionVariable.isDebugSkipFoldConstant()) {
+ return expr;
+ }
+ if (sessionVariable != null &&
sessionVariable.isEnableFoldConstantByBe()) {
return FULL_FOLD_REWRITER.rewrite(expr, ctx);
} else {
return FoldConstantRuleOnFE.VISITOR_INSTANCE.rewrite(expr, ctx);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateFilter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateFilter.java
index 1413faf3bb0..6b4668fffad 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateFilter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateFilter.java
@@ -21,6 +21,7 @@ import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
import org.apache.doris.nereids.rules.expression.rules.FoldConstantRule;
+import org.apache.doris.nereids.trees.expressions.CompoundPredicate;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
@@ -31,6 +32,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
import org.apache.doris.nereids.util.ExpressionUtils;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -49,7 +51,10 @@ public class EliminateFilter implements RewriteRuleFactory {
.thenApply(ctx -> {
LogicalFilter<Plan> filter = ctx.root;
ImmutableSet.Builder<Expression> newConjuncts =
ImmutableSet.builder();
+ ExpressionRewriteContext context =
+ new ExpressionRewriteContext(ctx.cascadesContext);
for (Expression expression : filter.getConjuncts()) {
+ expression =
FoldConstantRule.evaluate(eliminateNullLiteral(expression), context);
if (expression == BooleanLiteral.FALSE ||
expression.isNullLiteral()) {
return new
LogicalEmptyRelation(ctx.statementContext.getNextRelationId(),
filter.getOutput());
@@ -75,7 +80,7 @@ public class EliminateFilter implements RewriteRuleFactory {
new ExpressionRewriteContext(ctx.cascadesContext);
for (Expression expression : filter.getConjuncts()) {
Expression newExpr =
ExpressionUtils.replace(expression, replaceMap);
- Expression foldExpression =
FoldConstantRule.evaluate(newExpr, context);
+ Expression foldExpression =
FoldConstantRule.evaluate(eliminateNullLiteral(newExpr), context);
if (foldExpression == BooleanLiteral.FALSE ||
expression.isNullLiteral()) {
return new LogicalEmptyRelation(
@@ -94,4 +99,31 @@ public class EliminateFilter implements RewriteRuleFactory {
})
.toRule(RuleType.ELIMINATE_FILTER_ON_ONE_RELATION));
}
+
+ @VisibleForTesting
+ public Expression eliminateNullLiteral(Expression expression) {
+ if (!expression.anyMatch(e -> ((Expression) e).isNullLiteral())) {
+ return expression;
+ }
+
+ return replaceNullToFalse(expression);
+ }
+
+ // only replace null which its ancestors are all and/or
+ // NOTICE: NOT's type is boolean too, if replace null to false in NOT,
will got NOT(NULL) = NOT(FALSE) = TRUE,
+ // but it is wrong, NOT(NULL) = NULL. For a filter, only the AND / OR,
can keep NULL as FALSE.
+ private Expression replaceNullToFalse(Expression expression) {
+ if (expression.isNullLiteral()) {
+ return BooleanLiteral.FALSE;
+ }
+
+ if (expression instanceof CompoundPredicate) {
+ ImmutableList.Builder<Expression> builder =
ImmutableList.builderWithExpectedSize(
+ expression.children().size());
+ expression.children().forEach(e ->
builder.add(replaceNullToFalse(e)));
+ return expression.withChildren(builder.build());
+ }
+
+ return expression;
+ }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/ExpressionRewriteSqlTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/ExpressionRewriteSqlTest.java
new file mode 100644
index 00000000000..fcd0505fbe6
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/ExpressionRewriteSqlTest.java
@@ -0,0 +1,65 @@
+// 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.expression.rules;
+
+import org.apache.doris.nereids.sqltest.SqlTestBase;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import org.junit.jupiter.api.Test;
+
+class ExpressionRewriteSqlTest extends SqlTestBase {
+ @Test
+ public void testSimplifyRangeAndExtractCommonFactor() {
+ String sql = "select * from T1 where id > 1 and score > 1 or id > 1
and score > 10";
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(
+ logicalFilter().when(f ->
f.getPredicate().toSql().equals(
+ "AND[(id > 1),(score > 1)]"
+ )));
+
+ sql = "select * from T1 where id > 1 and score > 1 or id > 1 and id <
0";
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(
+ logicalFilter().when(f ->
f.getPredicate().toSql().equals(
+ "AND[(id > 1),(score > 1)]"
+ )));
+
+ sql = "select * from T1 where id > 1 and id < 0 or score > 1 and score
< 0";
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(logicalEmptyRelation());
+
+ sql = "select * from T1 where id > 1 and id < 0 and score > 1 and
score < 0";
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(logicalEmptyRelation());
+
+ sql = "select * from T1 where not(id > 1 and id < 0 or score > 1 and
score < 0)";
+ PlanChecker.from(connectContext)
+ .analyze(sql)
+ .rewrite()
+ .matches(logicalFilter().when(
+ f -> f.getPredicate().toSql().equals("AND[( not id IS
NULL),( not score IS NULL)]")));
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateFilterTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateFilterTest.java
index d3d1316eaac..692f6532541 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateFilterTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateFilterTest.java
@@ -19,21 +19,31 @@ package org.apache.doris.nereids.rules.rewrite;
import org.apache.doris.nereids.rules.expression.ExpressionNormalization;
import org.apache.doris.nereids.trees.expressions.And;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.GreaterThan;
+import org.apache.doris.nereids.trees.expressions.Not;
import org.apache.doris.nereids.trees.expressions.Or;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.types.IntegerType;
+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;
import org.apache.doris.nereids.util.PlanChecker;
import org.apache.doris.nereids.util.PlanConstructor;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Tests for {@link EliminateFilter}.
*/
@@ -62,6 +72,80 @@ class EliminateFilterTest implements
MemoPatternMatchSupported {
.matches(logicalEmptyRelation());
}
+ @Test
+ void testEliminateFilterReduceNull() {
+ List<Expression> exprList = Arrays.asList(
+ new EqualTo(scan1.getOutput().get(0), Literal.of(1)),
+ new GreaterThan(scan1.getOutput().get(1), Literal.of(1)),
+ NullLiteral.INSTANCE,
+ NullLiteral.INSTANCE);
+ Expression expr = new
Or(ExpressionUtils.falseOrNull(scan1.getOutput().get(1)),
+ new GreaterThan(scan1.getOutput().get(0), Literal.of(1)));
+ LogicalPlan filter = new LogicalPlanBuilder(scan1)
+ .filter(expr)
+ .build();
+
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(
+ logicalFilter().when(f ->
f.getPredicate().toSql().equals("(id > 1)"))
+ );
+
+ expr = new Or(ExpressionUtils.falseOrNull(scan1.getOutput().get(0)),
+ ExpressionUtils.falseOrNull(scan1.getOutput().get(1)));
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(expr)
+ .build();
+
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(new And(exprList))
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(new And(NullLiteral.INSTANCE, new Or(exprList)))
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(new Or(NullLiteral.INSTANCE, NullLiteral.INSTANCE))
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(NullLiteral.INSTANCE)
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(new Not(NullLiteral.INSTANCE))
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalEmptyRelation());
+
+ filter = new LogicalPlanBuilder(scan1)
+ .filter(new Not(new And(exprList)))
+ .build();
+ PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+ .applyTopDown(new EliminateFilter())
+ .matches(logicalFilter().when(
+ f -> f.getPredicate().toSql().equals("( not AND[(id =
1),(name > 1),NULL,NULL])"))
+ );
+ }
+
@Test
void testEliminateFilterTrue() {
LogicalPlan filterTrue = new LogicalPlanBuilder(scan1)
@@ -102,4 +186,25 @@ class EliminateFilterTest implements
MemoPatternMatchSupported {
logicalFilter(logicalOlapScan()).when(f ->
f.getPredicate() instanceof GreaterThan)
);
}
+
+ @Test
+ void testEliminateNullLiteral() {
+ Expression a = new SlotReference("a", IntegerType.INSTANCE);
+ Expression b = new SlotReference("b", IntegerType.INSTANCE);
+ Expression one = Literal.of(1);
+ Expression two = Literal.of(2);
+ Expression expression = new And(Arrays.asList(
+ new And(new GreaterThan(a, one), new
NullLiteral(IntegerType.INSTANCE)),
+ new Or(Arrays.asList(new GreaterThan(b, two), new
NullLiteral(IntegerType.INSTANCE),
+ new EqualTo(a, new NullLiteral(IntegerType.INSTANCE)))),
+ new Not(new And(new GreaterThan(a, one), new
NullLiteral(IntegerType.INSTANCE)))
+ ));
+ Expression expectExpression = new And(Arrays.asList(
+ new And(new GreaterThan(a, one), BooleanLiteral.FALSE),
+ new Or(Arrays.asList(new GreaterThan(b, two),
BooleanLiteral.FALSE,
+ new EqualTo(a, new
NullLiteral(IntegerType.INSTANCE)))),
+ new Not(new And(new GreaterThan(a, one), new
NullLiteral(IntegerType.INSTANCE)))
+ ));
+ Assertions.assertEquals(expectExpression, new
EliminateFilter().eliminateNullLiteral(expression));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]