private PlanNode planTableScan(TableScanNode node, Expression predicate) { DomainTranslator.ExtractionResult decomposedPredicate = DomainTranslator.fromPredicate(metadata, session, predicate, symbolAllocator.getTypes()); TupleDomain<ColumnHandle> simplifiedConstraint = decomposedPredicate .getTupleDomain() .transform(node.getAssignments()::get) .intersect(node.getCurrentConstraint()); List<TableLayoutResult> layouts = metadata.getLayouts( session, node.getTable(), new Constraint<>(simplifiedConstraint, bindings -> true), Optional.of(ImmutableSet.copyOf(node.getAssignments().values()))); if (layouts.isEmpty()) { return new ValuesNode(idAllocator.getNextId(), node.getOutputSymbols(), ImmutableList.of()); } TableLayoutResult layout = layouts.get(0); TableScanNode result = new TableScanNode( node.getId(), node.getTable(), node.getOutputSymbols(), node.getAssignments(), Optional.of(layout.getLayout().getHandle()), simplifiedConstraint.intersect(layout.getLayout().getPredicate()), Optional.ofNullable(node.getOriginalConstraint()).orElse(predicate)); Map<ColumnHandle, Symbol> assignments = ImmutableBiMap.copyOf(node.getAssignments()).inverse(); Expression resultingPredicate = combineConjuncts( decomposedPredicate.getRemainingExpression(), DomainTranslator.toPredicate( layout.getUnenforcedConstraint().transform(assignments::get))); if (!BooleanLiteral.TRUE_LITERAL.equals(resultingPredicate)) { return new FilterNode(idAllocator.getNextId(), result, resultingPredicate); } return result; }
@Override public PlanNode rewriteAggregation( AggregationNode node, Expression inheritedPredicate, PlanRewriter<Expression> planRewriter) { EqualityInference equalityInference = createEqualityInference(inheritedPredicate); List<Expression> pushdownConjuncts = new ArrayList<>(); List<Expression> postAggregationConjuncts = new ArrayList<>(); // Strip out non-deterministic conjuncts postAggregationConjuncts.addAll( ImmutableList.copyOf(filter(extractConjuncts(inheritedPredicate), not(deterministic())))); inheritedPredicate = stripNonDeterministicConjuncts(inheritedPredicate); // Sort non-equality predicates by those that can be pushed down and those that cannot for (Expression conjunct : EqualityInference.nonInferrableConjuncts(inheritedPredicate)) { Expression rewrittenConjunct = equalityInference.rewriteExpression(conjunct, in(node.getGroupBy())); if (rewrittenConjunct != null) { pushdownConjuncts.add(rewrittenConjunct); } else { postAggregationConjuncts.add(conjunct); } } // Add the equality predicates back in EqualityInference.EqualityPartition equalityPartition = equalityInference.generateEqualitiesPartitionedBy(in(node.getGroupBy())); pushdownConjuncts.addAll(equalityPartition.getScopeEqualities()); postAggregationConjuncts.addAll(equalityPartition.getScopeComplementEqualities()); postAggregationConjuncts.addAll(equalityPartition.getScopeStraddlingEqualities()); PlanNode rewrittenSource = planRewriter.rewrite(node.getSource(), combineConjuncts(pushdownConjuncts)); PlanNode output = node; if (rewrittenSource != node.getSource()) { output = new AggregationNode( node.getId(), rewrittenSource, node.getGroupBy(), node.getAggregations(), node.getFunctions(), node.getMasks(), node.getStep(), node.getSampleWeight(), node.getConfidence()); } if (!postAggregationConjuncts.isEmpty()) { output = new FilterNode( idAllocator.getNextId(), output, combineConjuncts(postAggregationConjuncts)); } return output; }
@Override public PlanNode rewriteNode( PlanNode node, Expression inheritedPredicate, PlanRewriter<Expression> planRewriter) { PlanNode rewrittenNode = planRewriter.defaultRewrite(node, BooleanLiteral.TRUE_LITERAL); if (!inheritedPredicate.equals(BooleanLiteral.TRUE_LITERAL)) { // Drop in a FilterNode b/c we cannot push our predicate down any further rewrittenNode = new FilterNode(idAllocator.getNextId(), rewrittenNode, inheritedPredicate); } return rewrittenNode; }
@Override public PlanNode visitTableCommit(TableCommitNode node, RewriteContext<Void> context) { Optional<DeleteNode> delete = findNode(node.getSource(), DeleteNode.class); if (!delete.isPresent()) { return context.defaultRewrite(node); } Optional<TableScanNode> tableScan = findNode(delete.get().getSource(), TableScanNode.class); if (!tableScan.isPresent()) { return context.defaultRewrite(node); } TableScanNode tableScanNode = tableScan.get(); if (!metadata.supportsMetadataDelete( session, tableScanNode.getTable(), tableScanNode.getLayout().get())) { return context.defaultRewrite(node); } return new MetadataDeleteNode( idAllocator.getNextId(), delete.get().getTarget(), Iterables.getOnlyElement(node.getOutputSymbols()), tableScanNode.getLayout().get()); }
private PlanNode planTableScan(TableScanNode node, Expression predicate, Context context) { DomainTranslator.ExtractionResult decomposedPredicate = DomainTranslator.fromPredicate(metadata, session, predicate, symbolAllocator.getTypes()); TupleDomain<ColumnHandle> simplifiedConstraint = decomposedPredicate .getTupleDomain() .transform(node.getAssignments()::get) .intersect(node.getCurrentConstraint()); checkState(node.getOutputSymbols().containsAll(context.getLookupSymbols())); Set<ColumnHandle> lookupColumns = context .getLookupSymbols() .stream() .map(node.getAssignments()::get) .collect(toImmutableSet()); Set<ColumnHandle> outputColumns = node.getOutputSymbols() .stream() .map(node.getAssignments()::get) .collect(toImmutableSet()); Optional<ResolvedIndex> optionalResolvedIndex = metadata.resolveIndex( session, node.getTable(), lookupColumns, outputColumns, simplifiedConstraint); if (!optionalResolvedIndex.isPresent()) { // No index available, so give up by returning something return node; } ResolvedIndex resolvedIndex = optionalResolvedIndex.get(); Map<ColumnHandle, Symbol> inverseAssignments = ImmutableBiMap.copyOf(node.getAssignments()).inverse(); PlanNode source = new IndexSourceNode( idAllocator.getNextId(), resolvedIndex.getIndexHandle(), node.getTable(), node.getLayout(), context.getLookupSymbols(), node.getOutputSymbols(), node.getAssignments(), simplifiedConstraint); Expression resultingPredicate = combineConjuncts( DomainTranslator.toPredicate( resolvedIndex.getUnresolvedTupleDomain().transform(inverseAssignments::get)), decomposedPredicate.getRemainingExpression()); if (!resultingPredicate.equals(TRUE_LITERAL)) { // todo it is likely we end up with redundant filters here because the predicate push down // has already been run... the fix is to run predicate push down again source = new FilterNode(idAllocator.getNextId(), source, resultingPredicate); } context.markSuccess(); return source; }
@Override public PlanNode visitJoin(JoinNode node, RewriteContext<Void> context) { PlanNode leftRewritten = context.rewrite(node.getLeft()); PlanNode rightRewritten = context.rewrite(node.getRight()); if (!node.getCriteria().isEmpty()) { // Index join only possible with JOIN criteria List<Symbol> leftJoinSymbols = Lists.transform(node.getCriteria(), JoinNode.EquiJoinClause::getLeft); List<Symbol> rightJoinSymbols = Lists.transform(node.getCriteria(), JoinNode.EquiJoinClause::getRight); Optional<PlanNode> leftIndexCandidate = IndexSourceRewriter.rewriteWithIndex( leftRewritten, ImmutableSet.copyOf(leftJoinSymbols), symbolAllocator, idAllocator, metadata, session); if (leftIndexCandidate.isPresent()) { // Sanity check that we can trace the path for the index lookup key Map<Symbol, Symbol> trace = IndexKeyTracer.trace(leftIndexCandidate.get(), ImmutableSet.copyOf(leftJoinSymbols)); checkState(!trace.isEmpty() && leftJoinSymbols.containsAll(trace.keySet())); } Optional<PlanNode> rightIndexCandidate = IndexSourceRewriter.rewriteWithIndex( rightRewritten, ImmutableSet.copyOf(rightJoinSymbols), symbolAllocator, idAllocator, metadata, session); if (rightIndexCandidate.isPresent()) { // Sanity check that we can trace the path for the index lookup key Map<Symbol, Symbol> trace = IndexKeyTracer.trace( rightIndexCandidate.get(), ImmutableSet.copyOf(rightJoinSymbols)); checkState(!trace.isEmpty() && rightJoinSymbols.containsAll(trace.keySet())); } switch (node.getType()) { case INNER: // Prefer the right candidate over the left candidate IndexJoinNode indexJoinNode = null; if (rightIndexCandidate.isPresent()) { indexJoinNode = new IndexJoinNode( idAllocator.getNextId(), IndexJoinNode.Type.INNER, leftRewritten, rightIndexCandidate.get(), createEquiJoinClause(leftJoinSymbols, rightJoinSymbols), Optional.empty(), Optional.empty()); } else if (leftIndexCandidate.isPresent()) { indexJoinNode = new IndexJoinNode( idAllocator.getNextId(), IndexJoinNode.Type.INNER, rightRewritten, leftIndexCandidate.get(), createEquiJoinClause(rightJoinSymbols, leftJoinSymbols), Optional.empty(), Optional.empty()); } if (indexJoinNode != null) { if (node.getFilter().isPresent()) { return new FilterNode( idAllocator.getNextId(), indexJoinNode, node.getFilter().get()); } return indexJoinNode; } break; case LEFT: // We cannot use indices for outer joins until index join supports in-line filtering if (!node.getFilter().isPresent() && rightIndexCandidate.isPresent()) { return new IndexJoinNode( idAllocator.getNextId(), IndexJoinNode.Type.SOURCE_OUTER, leftRewritten, rightIndexCandidate.get(), createEquiJoinClause(leftJoinSymbols, rightJoinSymbols), Optional.empty(), Optional.empty()); } break; case RIGHT: // We cannot use indices for outer joins until index join supports in-line filtering if (!node.getFilter().isPresent() && leftIndexCandidate.isPresent()) { return new IndexJoinNode( idAllocator.getNextId(), IndexJoinNode.Type.SOURCE_OUTER, rightRewritten, leftIndexCandidate.get(), createEquiJoinClause(rightJoinSymbols, leftJoinSymbols), Optional.empty(), Optional.empty()); } break; case FULL: break; default: throw new IllegalArgumentException("Unknown type: " + node.getType()); } } if (leftRewritten != node.getLeft() || rightRewritten != node.getRight()) { return new JoinNode( node.getId(), node.getType(), leftRewritten, rightRewritten, node.getCriteria(), node.getFilter(), node.getLeftHashSymbol(), node.getRightHashSymbol()); } return node; }
@Override public PlanNode rewriteTableScan( TableScanNode node, Expression inheritedPredicate, PlanRewriter<Expression> planRewriter) { DomainTranslator.ExtractionResult extractionResult = DomainTranslator.fromPredicate( inheritedPredicate, symbolAllocator.getTypes(), node.getAssignments()); Expression extractionRemainingExpression = extractionResult.getRemainingExpression(); TupleDomain tupleDomain = extractionResult.getTupleDomain(); if (node.getGeneratedPartitions().isPresent()) { // Add back in the TupleDomain that was used to generate the previous set of Partitions if // present // And just for kicks, throw in the domain summary too (as that can only help prune down the // ranges) // The domains should never widen between each pass. tupleDomain = tupleDomain .intersect(node.getGeneratedPartitions().get().getTupleDomainInput()) .intersect(node.getPartitionsDomainSummary()); } PartitionResult matchingPartitions = splitManager.getPartitions(node.getTable(), Optional.of(tupleDomain)); List<Partition> partitions = matchingPartitions.getPartitions(); TupleDomain undeterminedTupleDomain = matchingPartitions.getUndeterminedTupleDomain(); Expression unevaluatedDomainPredicate = DomainTranslator.toPredicate( undeterminedTupleDomain, ImmutableBiMap.copyOf(node.getAssignments()).inverse()); // Construct the post scan predicate. Add the unevaluated TupleDomain back first since those // are generally cheaper to evaluate than anything we can't extract Expression postScanPredicate = combineConjuncts(unevaluatedDomainPredicate, extractionRemainingExpression); // Do some early partition pruning partitions = ImmutableList.copyOf( filter( partitions, not(shouldPrunePartition(postScanPredicate, node.getAssignments())))); GeneratedPartitions generatedPartitions = new GeneratedPartitions(tupleDomain, partitions); PlanNode output = node; if (!node.getGeneratedPartitions().equals(Optional.of(generatedPartitions))) { // Only overwrite the originalConstraint if it was previously null Expression originalConstraint = node.getOriginalConstraint() == null ? inheritedPredicate : node.getOriginalConstraint(); output = new TableScanNode( node.getId(), node.getTable(), node.getOutputSymbols(), node.getAssignments(), originalConstraint, Optional.of(generatedPartitions)); } if (!postScanPredicate.equals(BooleanLiteral.TRUE_LITERAL)) { output = new FilterNode(idAllocator.getNextId(), output, postScanPredicate); } return output; }
@Override public PlanNode rewriteSemiJoin( SemiJoinNode node, Expression inheritedPredicate, PlanRewriter<Expression> planRewriter) { Expression sourceEffectivePredicate = EffectivePredicateExtractor.extract(node.getSource()); List<Expression> sourceConjuncts = new ArrayList<>(); List<Expression> filteringSourceConjuncts = new ArrayList<>(); List<Expression> postJoinConjuncts = new ArrayList<>(); // TODO: see if there are predicates that can be inferred from the semi join output // Push inherited and source predicates to filtering source via a contrived join predicate // (but needs to avoid touching NULL values in the filtering source) Expression joinPredicate = equalsExpression(node.getSourceJoinSymbol(), node.getFilteringSourceJoinSymbol()); EqualityInference joinInference = createEqualityInference(inheritedPredicate, sourceEffectivePredicate, joinPredicate); for (Expression conjunct : Iterables.concat( EqualityInference.nonInferrableConjuncts(inheritedPredicate), EqualityInference.nonInferrableConjuncts(sourceEffectivePredicate))) { Expression rewrittenConjunct = joinInference.rewriteExpression(conjunct, equalTo(node.getFilteringSourceJoinSymbol())); if (rewrittenConjunct != null && DeterminismEvaluator.isDeterministic(rewrittenConjunct)) { // Alter conjunct to include an OR filteringSourceJoinSymbol IS NULL disjunct Expression rewrittenConjunctOrNull = expressionOrNullSymbols(equalTo(node.getFilteringSourceJoinSymbol())) .apply(rewrittenConjunct); filteringSourceConjuncts.add(rewrittenConjunctOrNull); } } EqualityInference.EqualityPartition joinInferenceEqualityPartition = joinInference.generateEqualitiesPartitionedBy( equalTo(node.getFilteringSourceJoinSymbol())); filteringSourceConjuncts.addAll( ImmutableList.copyOf( transform( joinInferenceEqualityPartition.getScopeEqualities(), expressionOrNullSymbols(equalTo(node.getFilteringSourceJoinSymbol()))))); // Push inheritedPredicates down to the source if they don't involve the semi join output EqualityInference inheritedInference = createEqualityInference(inheritedPredicate); for (Expression conjunct : EqualityInference.nonInferrableConjuncts(inheritedPredicate)) { Expression rewrittenConjunct = inheritedInference.rewriteExpression(conjunct, in(node.getSource().getOutputSymbols())); // Since each source row is reflected exactly once in the output, ok to push // non-deterministic predicates down if (rewrittenConjunct != null) { sourceConjuncts.add(rewrittenConjunct); } else { postJoinConjuncts.add(conjunct); } } // Add the inherited equality predicates back in EqualityInference.EqualityPartition equalityPartition = inheritedInference.generateEqualitiesPartitionedBy( in(node.getSource().getOutputSymbols())); sourceConjuncts.addAll(equalityPartition.getScopeEqualities()); postJoinConjuncts.addAll(equalityPartition.getScopeComplementEqualities()); postJoinConjuncts.addAll(equalityPartition.getScopeStraddlingEqualities()); PlanNode rewrittenSource = planRewriter.rewrite(node.getSource(), combineConjuncts(sourceConjuncts)); PlanNode rewrittenFilteringSource = planRewriter.rewrite( node.getFilteringSource(), combineConjuncts(filteringSourceConjuncts)); PlanNode output = node; if (rewrittenSource != node.getSource() || rewrittenFilteringSource != node.getFilteringSource()) { output = new SemiJoinNode( node.getId(), rewrittenSource, rewrittenFilteringSource, node.getSourceJoinSymbol(), node.getFilteringSourceJoinSymbol(), node.getSemiJoinOutput()); } if (!postJoinConjuncts.isEmpty()) { output = new FilterNode(idAllocator.getNextId(), output, combineConjuncts(postJoinConjuncts)); } return output; }
@Override public PlanNode rewriteJoin( JoinNode node, Expression inheritedPredicate, PlanRewriter<Expression> planRewriter) { boolean isCrossJoin = (node.getType() == JoinNode.Type.CROSS); // See if we can rewrite outer joins in terms of a plain inner join node = tryNormalizeToInnerJoin(node, inheritedPredicate); Expression leftEffectivePredicate = EffectivePredicateExtractor.extract(node.getLeft()); Expression rightEffectivePredicate = EffectivePredicateExtractor.extract(node.getRight()); Expression joinPredicate = extractJoinPredicate(node); Expression leftPredicate; Expression rightPredicate; Expression postJoinPredicate; Expression newJoinPredicate; switch (node.getType()) { case INNER: InnerJoinPushDownResult innerJoinPushDownResult = processInnerJoin( inheritedPredicate, leftEffectivePredicate, rightEffectivePredicate, joinPredicate, node.getLeft().getOutputSymbols()); leftPredicate = innerJoinPushDownResult.getLeftPredicate(); rightPredicate = innerJoinPushDownResult.getRightPredicate(); postJoinPredicate = innerJoinPushDownResult.getPostJoinPredicate(); newJoinPredicate = innerJoinPushDownResult.getJoinPredicate(); break; case LEFT: OuterJoinPushDownResult leftOuterJoinPushDownResult = processOuterJoin( inheritedPredicate, leftEffectivePredicate, rightEffectivePredicate, joinPredicate, node.getLeft().getOutputSymbols()); leftPredicate = leftOuterJoinPushDownResult.getOuterJoinPredicate(); rightPredicate = leftOuterJoinPushDownResult.getInnerJoinPredicate(); postJoinPredicate = leftOuterJoinPushDownResult.getPostJoinPredicate(); newJoinPredicate = joinPredicate; // Use the same as the original break; case RIGHT: OuterJoinPushDownResult rightOuterJoinPushDownResult = processOuterJoin( inheritedPredicate, rightEffectivePredicate, leftEffectivePredicate, joinPredicate, node.getRight().getOutputSymbols()); leftPredicate = rightOuterJoinPushDownResult.getInnerJoinPredicate(); rightPredicate = rightOuterJoinPushDownResult.getOuterJoinPredicate(); postJoinPredicate = rightOuterJoinPushDownResult.getPostJoinPredicate(); newJoinPredicate = joinPredicate; // Use the same as the original break; default: throw new UnsupportedOperationException("Unsupported join type: " + node.getType()); } PlanNode leftSource = planRewriter.rewrite(node.getLeft(), leftPredicate); PlanNode rightSource = planRewriter.rewrite(node.getRight(), rightPredicate); PlanNode output = node; if (leftSource != node.getLeft() || rightSource != node.getRight() || !newJoinPredicate.equals(joinPredicate)) { List<JoinNode.EquiJoinClause> criteria = node.getCriteria(); // Rewrite criteria and add projections if there is a new join predicate if (!newJoinPredicate.equals(joinPredicate) || isCrossJoin) { // Create identity projections for all existing symbols ImmutableMap.Builder<Symbol, Expression> leftProjections = ImmutableMap.builder(); leftProjections.putAll( IterableTransformer.<Symbol>on(node.getLeft().getOutputSymbols()) .toMap(symbolToQualifiedNameReference()) .map()); ImmutableMap.Builder<Symbol, Expression> rightProjections = ImmutableMap.builder(); rightProjections.putAll( IterableTransformer.<Symbol>on(node.getRight().getOutputSymbols()) .toMap(symbolToQualifiedNameReference()) .map()); // HACK! we don't support cross joins right now, so put in a simple fake join predicate // instead if all of the join clauses got simplified out // TODO: remove this code when cross join support is added Iterable<Expression> simplifiedJoinConjuncts = transform(extractConjuncts(newJoinPredicate), simplifyExpressions()); simplifiedJoinConjuncts = filter( simplifiedJoinConjuncts, not(Predicates.<Expression>equalTo(BooleanLiteral.TRUE_LITERAL))); if (Iterables.isEmpty(simplifiedJoinConjuncts)) { simplifiedJoinConjuncts = ImmutableList.<Expression>of( new ComparisonExpression( ComparisonExpression.Type.EQUAL, new LongLiteral("0"), new LongLiteral("0"))); } // Create new projections for the new join clauses ImmutableList.Builder<JoinNode.EquiJoinClause> builder = ImmutableList.builder(); for (Expression conjunct : simplifiedJoinConjuncts) { checkState( joinEqualityExpression(node.getLeft().getOutputSymbols()).apply(conjunct), "Expected join predicate to be a valid join equality"); ComparisonExpression equality = (ComparisonExpression) conjunct; boolean alignedComparison = Iterables.all( DependencyExtractor.extractUnique(equality.getLeft()), in(node.getLeft().getOutputSymbols())); Expression leftExpression = (alignedComparison) ? equality.getLeft() : equality.getRight(); Expression rightExpression = (alignedComparison) ? equality.getRight() : equality.getLeft(); Symbol leftSymbol = symbolAllocator.newSymbol(leftExpression, extractType(leftExpression)); leftProjections.put(leftSymbol, leftExpression); Symbol rightSymbol = symbolAllocator.newSymbol(rightExpression, extractType(rightExpression)); rightProjections.put(rightSymbol, rightExpression); builder.add(new JoinNode.EquiJoinClause(leftSymbol, rightSymbol)); } leftSource = new ProjectNode(idAllocator.getNextId(), leftSource, leftProjections.build()); rightSource = new ProjectNode(idAllocator.getNextId(), rightSource, rightProjections.build()); criteria = builder.build(); } output = new JoinNode(node.getId(), node.getType(), leftSource, rightSource, criteria); } if (!postJoinPredicate.equals(BooleanLiteral.TRUE_LITERAL)) { output = new FilterNode(idAllocator.getNextId(), output, postJoinPredicate); } return output; }