Skip to content

Commit

Permalink
Remove redundant table scan predicate during predicate pushdown
Browse files Browse the repository at this point in the history
  • Loading branch information
sopel39 committed Oct 2, 2019
1 parent eecd1b7 commit 556afcc
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
import io.prestosql.sql.planner.iterative.rule.RemoveRedundantIdentityProjections;
import io.prestosql.sql.planner.iterative.rule.RemoveRedundantLimit;
import io.prestosql.sql.planner.iterative.rule.RemoveRedundantSort;
import io.prestosql.sql.planner.iterative.rule.RemoveRedundantTableScanPredicate;
import io.prestosql.sql.planner.iterative.rule.RemoveRedundantTopN;
import io.prestosql.sql.planner.iterative.rule.RemoveTrivialFilters;
import io.prestosql.sql.planner.iterative.rule.RemoveUnreferencedScalarApplyNodes;
Expand Down Expand Up @@ -542,6 +543,11 @@ public PlanOptimizers(
builder.add(predicatePushDown); // Run predicate push down one more time in case we can leverage new information from layouts' effective predicate
builder.add(new RemoveUnsupportedDynamicFilters());
builder.add(simplifyOptimizer); // Should be always run after PredicatePushDown
builder.add(new IterativeOptimizer(
ruleStats,
statsCalculator,
costCalculator,
ImmutableSet.of(new RemoveRedundantTableScanPredicate(metadata))));
builder.add(projectionPushDown);
builder.add(inlineProjections);
builder.add(new UnaliasSymbolReferences()); // Run unalias after merging projections to simplify projections more efficiently
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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.prestosql.sql.planner.iterative.rule;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.prestosql.Session;
import io.prestosql.matching.Capture;
import io.prestosql.matching.Captures;
import io.prestosql.matching.Pattern;
import io.prestosql.metadata.Metadata;
import io.prestosql.spi.connector.ColumnHandle;
import io.prestosql.spi.predicate.Domain;
import io.prestosql.spi.predicate.TupleDomain;
import io.prestosql.sql.planner.DomainTranslator;
import io.prestosql.sql.planner.LiteralEncoder;
import io.prestosql.sql.planner.PlanNodeIdAllocator;
import io.prestosql.sql.planner.Symbol;
import io.prestosql.sql.planner.TypeProvider;
import io.prestosql.sql.planner.iterative.Rule;
import io.prestosql.sql.planner.plan.FilterNode;
import io.prestosql.sql.planner.plan.PlanNode;
import io.prestosql.sql.planner.plan.TableScanNode;
import io.prestosql.sql.planner.plan.ValuesNode;
import io.prestosql.sql.tree.Expression;

import java.util.Map;
import java.util.Objects;

import static com.google.common.base.Preconditions.checkState;
import static io.prestosql.matching.Capture.newCapture;
import static io.prestosql.sql.ExpressionUtils.filterDeterministicConjuncts;
import static io.prestosql.sql.ExpressionUtils.filterNonDeterministicConjuncts;
import static io.prestosql.sql.planner.iterative.rule.PushPredicateIntoTableScan.createResultingPredicate;
import static io.prestosql.sql.planner.plan.Patterns.filter;
import static io.prestosql.sql.planner.plan.Patterns.source;
import static io.prestosql.sql.planner.plan.Patterns.tableScan;
import static io.prestosql.sql.tree.BooleanLiteral.TRUE_LITERAL;
import static java.util.Objects.requireNonNull;

public class RemoveRedundantTableScanPredicate
implements Rule<FilterNode>
{
private static final Capture<TableScanNode> TABLE_SCAN = newCapture();

private static final Pattern<FilterNode> PATTERN =
filter().with(source().matching(
tableScan().capturedAs(TABLE_SCAN)));

private final Metadata metadata;
private final DomainTranslator domainTranslator;

public RemoveRedundantTableScanPredicate(Metadata metadata)
{
this.metadata = requireNonNull(metadata, "metadata is null");
this.domainTranslator = new DomainTranslator(new LiteralEncoder(metadata));
}

@Override
public Pattern<FilterNode> getPattern()
{
return PATTERN;
}

@Override
public Result apply(FilterNode filterNode, Captures captures, Context context)
{
TableScanNode tableScan = captures.get(TABLE_SCAN);

PlanNode rewritten = removeRedundantTableScanPredicate(
tableScan,
filterNode.getPredicate(),
context.getSession(),
context.getSymbolAllocator().getTypes(),
context.getIdAllocator());

if (rewritten instanceof FilterNode
&& Objects.equals(((FilterNode) rewritten).getPredicate(), filterNode.getPredicate())) {
return Result.empty();
}

return Result.ofPlanNode(rewritten);
}

private PlanNode removeRedundantTableScanPredicate(
TableScanNode node,
Expression predicate,
Session session,
TypeProvider types,
PlanNodeIdAllocator idAllocator)
{
Expression deterministicPredicate = filterDeterministicConjuncts(predicate);
Expression nonDeterministicPredicate = filterNonDeterministicConjuncts(predicate);

DomainTranslator.ExtractionResult decomposedPredicate = DomainTranslator.fromPredicate(
metadata,
session,
deterministicPredicate,
types);

TupleDomain<ColumnHandle> predicateDomain = decomposedPredicate.getTupleDomain()
.transform(node.getAssignments()::get);

TupleDomain<ColumnHandle> unenforcedDomain;
if (predicateDomain.getDomains().isPresent()) {
Map<ColumnHandle, Domain> predicateColumnDomains = predicateDomain.getDomains().get();

// table scans with none domain should be converted to ValuesNode
checkState(node.getEnforcedConstraint().getDomains().isPresent());
Map<ColumnHandle, Domain> enforcedColumnDomains = node.getEnforcedConstraint().getDomains().get();

ImmutableMap.Builder<ColumnHandle, Domain> unenforcedColumnDomains = ImmutableMap.builder();
for (Map.Entry<ColumnHandle, Domain> entry : predicateColumnDomains.entrySet()) {
ColumnHandle columnHandle = entry.getKey();
Domain predicateColumnDomain = entry.getValue();
Domain enforcedColumnDomain = enforcedColumnDomains.getOrDefault(columnHandle, Domain.all(predicateColumnDomain.getType()));
predicateColumnDomain = predicateColumnDomain.intersect(enforcedColumnDomain);
if (!predicateColumnDomain.contains(enforcedColumnDomain)) {
unenforcedColumnDomains.put(columnHandle, predicateColumnDomain);
}
}

unenforcedDomain = TupleDomain.withColumnDomains(unenforcedColumnDomains.build());
}
else {
// TODO: DomainTranslator.fromPredicate can infer that the expression is "false" in some cases (TupleDomain.none()).
// This should move to another rule that simplifies the filter using that logic and then rely on RemoveTrivialFilters
// to turn the subtree into a Values node
return new ValuesNode(node.getId(), node.getOutputSymbols(), ImmutableList.of());
}

Map<ColumnHandle, Symbol> assignments = ImmutableBiMap.copyOf(node.getAssignments()).inverse();
Expression resultingPredicate = createResultingPredicate(
domainTranslator.toPredicate(unenforcedDomain.transform(assignments::get)),
nonDeterministicPredicate,
decomposedPredicate.getRemainingExpression());

if (!TRUE_LITERAL.equals(resultingPredicate)) {
return new FilterNode(idAllocator.getNextId(), node, resultingPredicate);
}

return node;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.prestosql.Session;
import io.prestosql.sql.planner.assertions.BasePlanTest;
import io.prestosql.sql.planner.assertions.PlanMatchPattern;
import io.prestosql.sql.planner.optimizations.PlanOptimizer;
import io.prestosql.sql.planner.plan.ExchangeNode;
import io.prestosql.sql.planner.plan.JoinNode;
import io.prestosql.sql.planner.plan.ProjectNode;
import io.prestosql.sql.planner.plan.WindowNode;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -476,4 +479,28 @@ public void testNonDeterministicPredicateNotPushedDown()
"orders",
ImmutableMap.of("CUST_KEY", "custkey"))))))));
}

@Test
public void testRemovesRedundantTableScanPredicate()
{
assertPlan(
"SELECT t1.orderstatus " +
"FROM (SELECT orderstatus FROM orders WHERE rand() = orderkey AND orderkey = 123) t1, (VALUES 'F', 'K') t2(col) " +
"WHERE t1.orderstatus = t2.col AND (t2.col = 'F' OR t2.col = 'K') AND t1.orderstatus LIKE '%'",
Session.builder(getQueryRunner().getDefaultSession())
.setSystemProperty(ENABLE_DYNAMIC_FILTERING, "false")
.build(),
anyTree(
node(
JoinNode.class,
node(ProjectNode.class,
filter("(ORDERKEY = BIGINT '123') AND rand() = CAST(ORDERKEY AS double) AND ORDERSTATUS LIKE '%'",
tableScan(
"orders",
ImmutableMap.of(
"ORDERSTATUS", "orderstatus",
"ORDERKEY", "orderkey")))),
anyTree(
values("COL")))));
}
}
Loading

0 comments on commit 556afcc

Please sign in to comment.