-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add a new rule for removing "false" filters #16206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /* | ||
| * Licensed 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 io.trino.sql.planner.iterative.rule; | ||
|
|
||
| import com.google.common.collect.ImmutableSet; | ||
| import io.trino.Session; | ||
| import io.trino.metadata.Metadata; | ||
| import io.trino.sql.PlannerContext; | ||
| import io.trino.sql.planner.DomainTranslator; | ||
| import io.trino.sql.planner.TypeProvider; | ||
| import io.trino.sql.planner.iterative.Rule; | ||
| import io.trino.sql.tree.Expression; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import static io.trino.sql.ExpressionUtils.combineConjuncts; | ||
| import static io.trino.sql.ExpressionUtils.extractConjuncts; | ||
| import static io.trino.sql.planner.DeterminismEvaluator.isDeterministic; | ||
| import static io.trino.sql.tree.BooleanLiteral.FALSE_LITERAL; | ||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| /** | ||
| * Uses DomainTranslator#getExtractionResult to infer that the expression is "false" in some cases (TupleDomain.none()). | ||
| */ | ||
| public class SimplifyFalseConditions | ||
| extends ExpressionRewriteRuleSet | ||
| { | ||
| public SimplifyFalseConditions(PlannerContext plannerContext) | ||
| { | ||
| super(createRewrite(plannerContext)); | ||
| } | ||
|
|
||
| @Override | ||
| public Set<Rule<?>> rules() | ||
| { | ||
| // Can only apply on expressions that are used for filtering, otherwise TupleDomain.none() might represent a NULL value | ||
| return ImmutableSet.of( | ||
| filterExpressionRewrite(), | ||
| joinExpressionRewrite()); | ||
| } | ||
|
|
||
| private static ExpressionRewriter createRewrite(PlannerContext plannerContext) | ||
| { | ||
| requireNonNull(plannerContext, "plannerContext is null"); | ||
|
|
||
| return (expression, context) -> rewrite(context.getSession(), plannerContext, context.getSymbolAllocator().getTypes(), expression); | ||
| } | ||
|
|
||
| public static Expression rewrite(Session session, PlannerContext plannerContext, TypeProvider types, Expression expression) | ||
| { | ||
| Metadata metadata = plannerContext.getMetadata(); | ||
| List<Expression> deterministicPredicates = new ArrayList<>(); | ||
| for (Expression conjunct : extractConjuncts(expression)) { | ||
| try { | ||
| if (isDeterministic(conjunct, metadata)) { | ||
| deterministicPredicates.add(conjunct); | ||
| } | ||
| } | ||
| catch (IllegalArgumentException ignored) { | ||
| // Might fail in case of an unresolved function | ||
| } | ||
| } | ||
|
|
||
| DomainTranslator.ExtractionResult decomposedPredicate = DomainTranslator.getExtractionResult( | ||
| plannerContext, | ||
| session, | ||
| combineConjuncts(metadata, deterministicPredicates), | ||
| types); | ||
|
|
||
| if (decomposedPredicate.getTupleDomain().isNone()) { | ||
| return FALSE_LITERAL; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if the expression was already a false_literal? do we need to return
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a reason for that, do you have something in mind? |
||
| } | ||
|
|
||
| return expression; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* | ||
| * Licensed 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 io.trino.sql.planner.iterative.rule; | ||
|
|
||
| import io.trino.sql.planner.assertions.BasePlanTest; | ||
| import org.testng.annotations.Test; | ||
|
|
||
| import static io.trino.sql.planner.assertions.PlanMatchPattern.output; | ||
| import static io.trino.sql.planner.assertions.PlanMatchPattern.values; | ||
| import static java.lang.String.format; | ||
|
|
||
| public class TestSimplifyFalseConditions | ||
| extends BasePlanTest | ||
| { | ||
| @Test | ||
| public void testPredicateIsFalse() | ||
| { | ||
| testRewrite("FALSE"); | ||
| testRewrite("NOT (TRUE)"); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCastNullToBoolean() | ||
| { | ||
| testRewriteWithAndWithoutNegation("CAST(null AS boolean)"); | ||
| } | ||
|
|
||
| @Test | ||
| public void testLogicalExpression() | ||
| { | ||
| testRewrite("TRUE AND CAST(null AS boolean)"); | ||
| testRewrite("col = BIGINT '44' AND CAST(null AS boolean)"); | ||
| testRewrite("col = BIGINT '44' AND FALSE"); | ||
| } | ||
|
|
||
| @Test | ||
| public void testComparison() | ||
| { | ||
| testRewriteWithAndWithoutNegation("col > CAST(null AS BIGINT)"); | ||
| testRewriteWithAndWithoutNegation("col >= CAST(null AS BIGINT)"); | ||
| testRewriteWithAndWithoutNegation("col < CAST(null AS BIGINT)"); | ||
| testRewriteWithAndWithoutNegation("col <= CAST(null AS BIGINT)"); | ||
| testRewriteWithAndWithoutNegation("col = CAST(null AS BIGINT)"); | ||
| testRewriteWithAndWithoutNegation("col != CAST(null AS BIGINT)"); | ||
| } | ||
|
|
||
| @Test | ||
| public void testIn() | ||
| { | ||
| testRewriteWithAndWithoutNegation("col IN (CAST(null AS BIGINT))"); | ||
| } | ||
|
|
||
| @Test | ||
| public void testBetween() | ||
| { | ||
| testRewrite("col BETWEEN (CAST(null AS BIGINT)) AND BIGINT '44'"); | ||
| testRewrite("col BETWEEN BIGINT '44' AND (CAST(null AS BIGINT))"); | ||
| } | ||
|
|
||
| private void testRewriteWithAndWithoutNegation(String inputPredicate) | ||
| { | ||
| testRewrite(inputPredicate); | ||
| testRewrite(format("NOT (%s)", inputPredicate)); | ||
| } | ||
|
|
||
| private void testRewrite(String inputPredicate) | ||
| { | ||
| assertPlan(String.format("SELECT * FROM (VALUES BIGINT '1') t(col) WHERE %s", inputPredicate), | ||
| output( | ||
| values("a"))); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's already a rule for this: RemoveTrivialFilters. The new logic should be implemented within that rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think it became a separate rule following #15558 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was mainly because of @martint's comment from 4 years ago:
trino/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPredicateIntoTableScan.java
Lines 241 to 243 in 2ddb5d8
But I don't object to merging these two, they seem to go together anyway + that will turn
RemoveTrivialFiltersinto anExpressionRewriteRuleSetso maybe it will also benefit the join case (I didn't check).I'm not sure the term "trivial" will still be accurate though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok to have a rule that simplifies expressions (we already have one) and one that removes filters where the expression is known to filter everything or nothing (we already have one, too). But we should not add yet another rule that mixes both concerns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took another look,
RemoveTrivialFilterscan't become anExpressionRewriteRuleSetbecauseExpressionRewriteRuleSetdoesn't replace a node of one type with a node of another type.The new rule's logic can't be moved into
SimplifyExpressionsbecause not all node types are supported (see #16206 (comment)).I can make
RemoveTrivialFiltersreturn 2 rules, but I failed to prove the join case (couldn't find an actual query that has a final plan that contains aJoinNodewith a filter that can be simplified toFALSE\TRUE).@findepi \ @martint WDYT should be the next step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that's because PPD pushes such filter under the join and then it gets simplified/eliminated there
I read it as implying
RemoveTrivialFiltersshouldn't callDomainTranslatorfor help.We tried to introduce a generic rule
ExpressionRewriteRuleSet-based to simplify boolean conditions that can be replaced with constant false. I know what problems this run into (distinguishing context: eg between projected).Did it result in more test coverage? like cases mentioned in #16206 (comment) ?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see #16206 (comment). Note that adding this logic into
RemoveTrivialFilterswill address this concern you raised #16206 (comment).I didn't add new tests for those cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please do?
These are interesting test cases to have, regardless of the implementation choices we end up doing in this PR