All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.rel.metadata.RelMdPredicates Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * 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 com.hazelcast.org.apache.calcite.rel.metadata;

import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.plan.RelOptCluster;
import com.hazelcast.org.apache.calcite.plan.RelOptPredicateList;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.plan.RexImplicationChecker;
import com.hazelcast.org.apache.calcite.plan.Strong;
import com.hazelcast.org.apache.calcite.plan.volcano.RelSubset;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.core.Aggregate;
import com.hazelcast.org.apache.calcite.rel.core.Exchange;
import com.hazelcast.org.apache.calcite.rel.core.Filter;
import com.hazelcast.org.apache.calcite.rel.core.Intersect;
import com.hazelcast.org.apache.calcite.rel.core.Join;
import com.hazelcast.org.apache.calcite.rel.core.JoinRelType;
import com.hazelcast.org.apache.calcite.rel.core.Minus;
import com.hazelcast.org.apache.calcite.rel.core.Project;
import com.hazelcast.org.apache.calcite.rel.core.Sort;
import com.hazelcast.org.apache.calcite.rel.core.TableModify;
import com.hazelcast.org.apache.calcite.rel.core.TableScan;
import com.hazelcast.org.apache.calcite.rel.core.Union;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexExecutor;
import com.hazelcast.org.apache.calcite.rex.RexInputRef;
import com.hazelcast.org.apache.calcite.rex.RexLiteral;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexPermuteInputsShuttle;
import com.hazelcast.org.apache.calcite.rex.RexSimplify;
import com.hazelcast.org.apache.calcite.rex.RexUnknownAs;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.rex.RexVisitorImpl;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.util.BitSets;
import com.hazelcast.org.apache.calcite.util.Bug;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.org.apache.calcite.util.mapping.Mapping;
import com.hazelcast.org.apache.calcite.util.mapping.MappingType;
import com.hazelcast.org.apache.calcite.util.mapping.Mappings;

import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.Iterables;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;

/**
 * Utility to infer Predicates that are applicable above a RelNode.
 *
 * 

This is currently used by * {@link com.hazelcast.org.apache.calcite.rel.rules.JoinPushTransitivePredicatesRule} to * infer Predicates that can be inferred from one side of a Join * to the other. * *

The PullUp Strategy is sound but not complete. Here are some of the * limitations: *

    * *
  1. For Aggregations we only PullUp predicates that only contain * Grouping Keys. This can be extended to infer predicates on Aggregation * expressions from expressions on the aggregated columns. For e.g. *
     * select a, max(b) from R1 where b > 7
     *   → max(b) > 7 or max(b) is null
     * 
    * *
  2. For Projections we only look at columns that are projected without * any function applied. So: *
     * select a from R1 where a > 7
     *   → "a > 7" is pulled up from the Projection.
     * select a + 1 from R1 where a + 1 > 7
     *   → "a + 1 gt; 7" is not pulled up
     * 
    * *
  3. There are several restrictions on Joins: *
      *
    • We only pullUp inferred predicates for now. Pulling up existing * predicates causes an explosion of duplicates. The existing predicates * are pushed back down as new predicates. Once we have rules to eliminate * duplicate Filter conditions, we should pullUp all predicates. * *
    • For Left Outer: we infer new predicates from the left and set them * as applicable on the Right side. No predicates are pulledUp. * *
    • Right Outer Joins are handled in an analogous manner. * *
    • For Full Outer Joins no predicates are pulledUp or inferred. *
    *
*/ public class RelMdPredicates implements MetadataHandler { public static final RelMetadataProvider SOURCE = ReflectiveRelMetadataProvider .reflectiveSource(new RelMdPredicates(), BuiltInMetadata.Predicates.Handler.class); private static final List EMPTY_LIST = ImmutableList.of(); @Override public MetadataDef getDef() { return BuiltInMetadata.Predicates.DEF; } /** Catch-all implementation for * {@link BuiltInMetadata.Predicates#getPredicates()}, * invoked using reflection. * * @see com.hazelcast.org.apache.calcite.rel.metadata.RelMetadataQuery#getPulledUpPredicates(RelNode) */ public RelOptPredicateList getPredicates(RelNode rel, RelMetadataQuery mq) { return RelOptPredicateList.EMPTY; } /** * Infers predicates for a table scan. */ public RelOptPredicateList getPredicates(TableScan table, RelMetadataQuery mq) { return RelOptPredicateList.EMPTY; } /** * Infers predicates for a project. * *
    *
  1. create a mapping from input to projection. Map only positions that * directly reference an input column. *
  2. Expressions that only contain above columns are retained in the * Project's pullExpressions list. *
  3. For e.g. expression 'a + e = 9' below will not be pulled up because 'e' * is not in the projection list. * *
       * inputPullUpExprs:      {a > 7, b + c < 10, a + e = 9}
       * projectionExprs:       {a, b, c, e / 2}
       * projectionPullupExprs: {a > 7, b + c < 10}
       * 
    * *
*/ public RelOptPredicateList getPredicates(Project project, RelMetadataQuery mq) { final RelNode input = project.getInput(); final RexBuilder rexBuilder = project.getCluster().getRexBuilder(); final RelOptPredicateList inputInfo = mq.getPulledUpPredicates(input); final List projectPullUpPredicates = new ArrayList<>(); ImmutableBitSet.Builder columnsMappedBuilder = ImmutableBitSet.builder(); Mapping m = Mappings.create(MappingType.PARTIAL_FUNCTION, input.getRowType().getFieldCount(), project.getRowType().getFieldCount()); for (Ord expr : Ord.zip(project.getProjects())) { if (expr.e instanceof RexInputRef) { int sIdx = ((RexInputRef) expr.e).getIndex(); m.set(sIdx, expr.i); columnsMappedBuilder.set(sIdx); // Project can also generate constants. We need to include them. } else if (RexLiteral.isNullLiteral(expr.e)) { projectPullUpPredicates.add( rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, rexBuilder.makeInputRef(project, expr.i))); } else if (RexUtil.isConstant(expr.e)) { final List args = ImmutableList.of(rexBuilder.makeInputRef(project, expr.i), expr.e); final SqlOperator op = args.get(0).getType().isNullable() || args.get(1).getType().isNullable() ? SqlStdOperatorTable.IS_NOT_DISTINCT_FROM : SqlStdOperatorTable.EQUALS; projectPullUpPredicates.add(rexBuilder.makeCall(op, args)); } } // Go over childPullUpPredicates. If a predicate only contains columns in // 'columnsMapped' construct a new predicate based on mapping. final ImmutableBitSet columnsMapped = columnsMappedBuilder.build(); for (RexNode r : inputInfo.pulledUpPredicates) { RexNode r2 = projectPredicate(rexBuilder, input, r, columnsMapped); if (!r2.isAlwaysTrue()) { r2 = r2.accept(new RexPermuteInputsShuttle(m, input)); projectPullUpPredicates.add(r2); } } return RelOptPredicateList.of(rexBuilder, projectPullUpPredicates); } /** Converts a predicate on a particular set of columns into a predicate on * a subset of those columns, weakening if necessary. * *

If not possible to simplify, returns {@code true}, which is the weakest * possible predicate. * *

Examples:

    *
  1. The predicate {@code $7 = $9} on columns [7] * becomes {@code $7 is not null} *
  2. The predicate {@code $7 = $9 + $11} on columns [7, 9] * becomes {@code $7 is not null or $9 is not null} *
  3. The predicate {@code $7 = $9 and $9 = 5} on columns [7] becomes * {@code $7 = 5} *
  4. The predicate * {@code $7 = $9 and ($9 = $1 or $9 = $2) and $1 > 3 and $2 > 10} * on columns [7] becomes {@code $7 > 3} *
* *

We currently only handle examples 1 and 2. * * @param rexBuilder Rex builder * @param input Input relational expression * @param r Predicate expression * @param columnsMapped Columns which the final predicate can reference * @return Predicate expression narrowed to reference only certain columns */ private static RexNode projectPredicate(final RexBuilder rexBuilder, RelNode input, RexNode r, ImmutableBitSet columnsMapped) { ImmutableBitSet rCols = RelOptUtil.InputFinder.bits(r); if (columnsMapped.contains(rCols)) { // All required columns are present. No need to weaken. return r; } if (columnsMapped.intersects(rCols)) { final List list = new ArrayList<>(); for (int c : columnsMapped.intersect(rCols)) { if (input.getRowType().getFieldList().get(c).getType().isNullable() && Strong.isNull(r, ImmutableBitSet.of(c))) { list.add( rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, rexBuilder.makeInputRef(input, c))); } } if (!list.isEmpty()) { return RexUtil.composeDisjunction(rexBuilder, list); } } // Cannot weaken to anything non-trivial return rexBuilder.makeLiteral(true); } /** * Add the Filter condition to the pulledPredicates list from the input. */ public RelOptPredicateList getPredicates(Filter filter, RelMetadataQuery mq) { final RelNode input = filter.getInput(); final RexBuilder rexBuilder = filter.getCluster().getRexBuilder(); final RelOptPredicateList inputInfo = mq.getPulledUpPredicates(input); // Simplify condition using RexSimplify. final RexNode condition = filter.getCondition(); final RexExecutor executor = Util.first(filter.getCluster().getPlanner().getExecutor(), RexUtil.EXECUTOR); final RexSimplify simplify = new RexSimplify(rexBuilder, RelOptPredicateList.EMPTY, executor); final RexNode simplifiedCondition = simplify.simplify(condition); return Util.first(inputInfo, RelOptPredicateList.EMPTY) .union(rexBuilder, RelOptPredicateList.of(rexBuilder, RexUtil.retainDeterministic( RelOptUtil.conjunctions(simplifiedCondition)))); } /** * Infers predicates for a {@link com.hazelcast.org.apache.calcite.rel.core.Join} (including * {@code SemiJoin}). */ public RelOptPredicateList getPredicates(Join join, RelMetadataQuery mq) { RelOptCluster cluster = join.getCluster(); RexBuilder rexBuilder = cluster.getRexBuilder(); final RexExecutor executor = Util.first(cluster.getPlanner().getExecutor(), RexUtil.EXECUTOR); final RelNode left = join.getInput(0); final RelNode right = join.getInput(1); final RelOptPredicateList leftInfo = mq.getPulledUpPredicates(left); final RelOptPredicateList rightInfo = mq.getPulledUpPredicates(right); JoinConditionBasedPredicateInference joinInference = new JoinConditionBasedPredicateInference(join, RexUtil.composeConjunction(rexBuilder, leftInfo.pulledUpPredicates), RexUtil.composeConjunction(rexBuilder, rightInfo.pulledUpPredicates), new RexSimplify(rexBuilder, RelOptPredicateList.EMPTY, executor)); return joinInference.inferPredicates(false); } /** * Infers predicates for an Aggregate. * *

Pulls up predicates that only contains references to columns in the * GroupSet. For e.g. * *

   * inputPullUpExprs : { a > 7, b + c < 10, a + e = 9}
   * groupSet         : { a, b}
   * pulledUpExprs    : { a > 7}
   * 
*/ public RelOptPredicateList getPredicates(Aggregate agg, RelMetadataQuery mq) { final RelNode input = agg.getInput(); final RexBuilder rexBuilder = agg.getCluster().getRexBuilder(); final RelOptPredicateList inputInfo = mq.getPulledUpPredicates(input); final List aggPullUpPredicates = new ArrayList<>(); ImmutableBitSet groupKeys = agg.getGroupSet(); if (groupKeys.isEmpty()) { // "GROUP BY ()" can convert an empty relation to a non-empty relation, so // it is not valid to pull up predicates. In particular, consider the // predicate "false": it is valid on all input rows (trivially - there are // no rows!) but not on the output (there is one row). return RelOptPredicateList.EMPTY; } Mapping m = Mappings.create(MappingType.PARTIAL_FUNCTION, input.getRowType().getFieldCount(), agg.getRowType().getFieldCount()); int i = 0; for (int j : groupKeys) { m.set(j, i++); } for (RexNode r : inputInfo.pulledUpPredicates) { ImmutableBitSet rCols = RelOptUtil.InputFinder.bits(r); if (groupKeys.contains(rCols)) { r = r.accept(new RexPermuteInputsShuttle(m, input)); aggPullUpPredicates.add(r); } } return RelOptPredicateList.of(rexBuilder, aggPullUpPredicates); } /** * Infers predicates for a Union. */ public RelOptPredicateList getPredicates(Union union, RelMetadataQuery mq) { final RexBuilder rexBuilder = union.getCluster().getRexBuilder(); Set finalPredicates = new HashSet<>(); final List finalResidualPredicates = new ArrayList<>(); for (Ord input : Ord.zip(union.getInputs())) { RelOptPredicateList info = mq.getPulledUpPredicates(input.e); if (info.pulledUpPredicates.isEmpty()) { return RelOptPredicateList.EMPTY; } final Set predicates = new HashSet<>(); final List residualPredicates = new ArrayList<>(); for (RexNode pred : info.pulledUpPredicates) { if (input.i == 0) { predicates.add(pred); continue; } if (finalPredicates.contains(pred)) { predicates.add(pred); } else { residualPredicates.add(pred); } } // Add new residual predicates finalResidualPredicates.add(RexUtil.composeConjunction(rexBuilder, residualPredicates)); // Add those that are not part of the final set to residual for (RexNode e : finalPredicates) { if (!predicates.contains(e)) { // This node was in previous union inputs, but it is not in this one for (int j = 0; j < input.i; j++) { finalResidualPredicates.set(j, RexUtil.composeConjunction(rexBuilder, Arrays.asList(finalResidualPredicates.get(j), e))); } } } // Final predicates finalPredicates = predicates; } final List predicates = new ArrayList<>(finalPredicates); final RelOptCluster cluster = union.getCluster(); final RexExecutor executor = Util.first(cluster.getPlanner().getExecutor(), RexUtil.EXECUTOR); RexNode disjunctivePredicate = new RexSimplify(rexBuilder, RelOptPredicateList.EMPTY, executor) .simplifyUnknownAs(rexBuilder.makeCall(SqlStdOperatorTable.OR, finalResidualPredicates), RexUnknownAs.FALSE); if (!disjunctivePredicate.isAlwaysTrue()) { predicates.add(disjunctivePredicate); } return RelOptPredicateList.of(rexBuilder, predicates); } /** * Infers predicates for a Intersect. */ public RelOptPredicateList getPredicates(Intersect intersect, RelMetadataQuery mq) { final RexBuilder rexBuilder = intersect.getCluster().getRexBuilder(); final RexExecutor executor = Util.first(intersect.getCluster().getPlanner().getExecutor(), RexUtil.EXECUTOR); final RexImplicationChecker rexImplicationChecker = new RexImplicationChecker(rexBuilder, executor, intersect.getRowType()); Set finalPredicates = new HashSet<>(); for (Ord input : Ord.zip(intersect.getInputs())) { RelOptPredicateList info = mq.getPulledUpPredicates(input.e); if (info == null || info.pulledUpPredicates.isEmpty()) { continue; } for (RexNode pred: info.pulledUpPredicates) { if (finalPredicates.stream().anyMatch( finalPred -> rexImplicationChecker.implies(finalPred, pred))) { // There's already a stricter predicate in finalPredicates, // thus no need to count this one. continue; } // Remove looser predicate and add this one into finalPredicates finalPredicates = finalPredicates.stream() .filter(finalPred -> !rexImplicationChecker.implies(pred, finalPred)) .collect(Collectors.toSet()); finalPredicates.add(pred); } } return RelOptPredicateList.of(rexBuilder, finalPredicates); } /** * Infers predicates for a Minus. */ public RelOptPredicateList getPredicates(Minus minus, RelMetadataQuery mq) { return mq.getPulledUpPredicates(minus.getInput(0)); } /** * Infers predicates for a Sort. */ public RelOptPredicateList getPredicates(Sort sort, RelMetadataQuery mq) { RelNode input = sort.getInput(); return mq.getPulledUpPredicates(input); } /** * Infers predicates for a TableModify. */ public RelOptPredicateList getPredicates(TableModify tableModify, RelMetadataQuery mq) { return mq.getPulledUpPredicates(tableModify.getInput()); } /** * Infers predicates for an Exchange. */ public RelOptPredicateList getPredicates(Exchange exchange, RelMetadataQuery mq) { RelNode input = exchange.getInput(); return mq.getPulledUpPredicates(input); } // CHECKSTYLE: IGNORE 1 /** * Returns the * {@link BuiltInMetadata.Predicates#getPredicates()} * statistic. * @see RelMetadataQuery#getPulledUpPredicates(RelNode) */ public RelOptPredicateList getPredicates(RelSubset r, RelMetadataQuery mq) { if (!Bug.CALCITE_1048_FIXED) { return RelOptPredicateList.EMPTY; } final RexBuilder rexBuilder = r.getCluster().getRexBuilder(); RelOptPredicateList list = null; for (RelNode r2 : r.getRels()) { RelOptPredicateList list2 = mq.getPulledUpPredicates(r2); if (list2 != null) { list = list == null ? list2 : list.union(rexBuilder, list2); } } return Util.first(list, RelOptPredicateList.EMPTY); } /** * Utility to infer predicates from one side of the join that apply on the * other side. * *

Contract is:

    * *
  • initialize with a {@link com.hazelcast.org.apache.calcite.rel.core.Join} and * optional predicates applicable on its left and right subtrees. * *
  • you can * then ask it for equivalentPredicate(s) given a predicate. * *
* *

So for: *

    *
  1. 'R1(x) join R2(y) on x = y' a call for * equivalentPredicates on 'x > 7' will return ' * [y > 7]' *
  2. 'R1(x) join R2(y) on x = y join R3(z) on y = z' a call for * equivalentPredicates on the second join 'x > 7' will return *
*/ static class JoinConditionBasedPredicateInference { final Join joinRel; final int nSysFields; final int nFieldsLeft; final int nFieldsRight; final ImmutableBitSet leftFieldsBitSet; final ImmutableBitSet rightFieldsBitSet; final ImmutableBitSet allFieldsBitSet; @SuppressWarnings("JdkObsolete") SortedMap equivalence; final Map exprFields; final Set allExprs; final Set equalityPredicates; final @Nullable RexNode leftChildPredicates; final @Nullable RexNode rightChildPredicates; final RexSimplify simplify; @SuppressWarnings("JdkObsolete") JoinConditionBasedPredicateInference(Join joinRel, @Nullable RexNode leftPredicates, @Nullable RexNode rightPredicates, RexSimplify simplify) { super(); this.joinRel = joinRel; this.simplify = simplify; nFieldsLeft = joinRel.getLeft().getRowType().getFieldList().size(); nFieldsRight = joinRel.getRight().getRowType().getFieldList().size(); nSysFields = joinRel.getSystemFieldList().size(); leftFieldsBitSet = ImmutableBitSet.range(nSysFields, nSysFields + nFieldsLeft); rightFieldsBitSet = ImmutableBitSet.range(nSysFields + nFieldsLeft, nSysFields + nFieldsLeft + nFieldsRight); allFieldsBitSet = ImmutableBitSet.range(0, nSysFields + nFieldsLeft + nFieldsRight); exprFields = new HashMap<>(); allExprs = new HashSet<>(); if (leftPredicates == null) { leftChildPredicates = null; } else { Mappings.TargetMapping leftMapping = Mappings.createShiftMapping( nSysFields + nFieldsLeft, nSysFields, 0, nFieldsLeft); leftChildPredicates = leftPredicates.accept( new RexPermuteInputsShuttle(leftMapping, joinRel.getInput(0))); allExprs.add(leftChildPredicates); for (RexNode r : RelOptUtil.conjunctions(leftChildPredicates)) { exprFields.put(r, RelOptUtil.InputFinder.bits(r)); allExprs.add(r); } } if (rightPredicates == null) { rightChildPredicates = null; } else { Mappings.TargetMapping rightMapping = Mappings.createShiftMapping( nSysFields + nFieldsLeft + nFieldsRight, nSysFields + nFieldsLeft, 0, nFieldsRight); rightChildPredicates = rightPredicates.accept( new RexPermuteInputsShuttle(rightMapping, joinRel.getInput(1))); allExprs.add(rightChildPredicates); for (RexNode r : RelOptUtil.conjunctions(rightChildPredicates)) { exprFields.put(r, RelOptUtil.InputFinder.bits(r)); allExprs.add(r); } } equivalence = new TreeMap<>(); equalityPredicates = new HashSet<>(); for (int i = 0; i < nSysFields + nFieldsLeft + nFieldsRight; i++) { equivalence.put(i, BitSets.of(i)); } // Only process equivalences found in the join conditions. Processing // Equivalences from the left or right side infer predicates that are // already present in the Tree below the join. List exprs = RelOptUtil.conjunctions(joinRel.getCondition()); final EquivalenceFinder eF = new EquivalenceFinder(); exprs.forEach(input -> input.accept(eF)); equivalence = BitSets.closure(equivalence); } /** * The PullUp Strategy is sound but not complete. *
    *
  1. We only pullUp inferred predicates for now. Pulling up existing * predicates causes an explosion of duplicates. The existing predicates are * pushed back down as new predicates. Once we have rules to eliminate * duplicate Filter conditions, we should pullUp all predicates. *
  2. For Left Outer: we infer new predicates from the left and set them as * applicable on the Right side. No predicates are pulledUp. *
  3. Right Outer Joins are handled in an analogous manner. *
  4. For Full Outer Joins no predicates are pulledUp or inferred. *
*/ public RelOptPredicateList inferPredicates( boolean includeEqualityInference) { final List inferredPredicates = new ArrayList<>(); final Set allExprs = new HashSet<>(this.allExprs); final JoinRelType joinType = joinRel.getJoinType(); switch (joinType) { case SEMI: case INNER: case LEFT: infer(leftChildPredicates, allExprs, inferredPredicates, includeEqualityInference, joinType == JoinRelType.LEFT ? rightFieldsBitSet : allFieldsBitSet); break; default: break; } switch (joinType) { case SEMI: case INNER: case RIGHT: infer(rightChildPredicates, allExprs, inferredPredicates, includeEqualityInference, joinType == JoinRelType.RIGHT ? leftFieldsBitSet : allFieldsBitSet); break; default: break; } Mappings.TargetMapping rightMapping = Mappings.createShiftMapping( nSysFields + nFieldsLeft + nFieldsRight, 0, nSysFields + nFieldsLeft, nFieldsRight); final RexPermuteInputsShuttle rightPermute = new RexPermuteInputsShuttle(rightMapping, joinRel); Mappings.TargetMapping leftMapping = Mappings.createShiftMapping( nSysFields + nFieldsLeft, 0, nSysFields, nFieldsLeft); final RexPermuteInputsShuttle leftPermute = new RexPermuteInputsShuttle(leftMapping, joinRel); final List leftInferredPredicates = new ArrayList<>(); final List rightInferredPredicates = new ArrayList<>(); for (RexNode iP : inferredPredicates) { ImmutableBitSet iPBitSet = RelOptUtil.InputFinder.bits(iP); if (leftFieldsBitSet.contains(iPBitSet)) { leftInferredPredicates.add(iP.accept(leftPermute)); } else if (rightFieldsBitSet.contains(iPBitSet)) { rightInferredPredicates.add(iP.accept(rightPermute)); } } final RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder(); switch (joinType) { case SEMI: Iterable pulledUpPredicates; pulledUpPredicates = Iterables.concat( RelOptUtil.conjunctions(leftChildPredicates), leftInferredPredicates); return RelOptPredicateList.of(rexBuilder, pulledUpPredicates, leftInferredPredicates, rightInferredPredicates); case INNER: pulledUpPredicates = Iterables.concat( RelOptUtil.conjunctions(leftChildPredicates), RelOptUtil.conjunctions(rightChildPredicates), RexUtil.retainDeterministic( RelOptUtil.conjunctions(joinRel.getCondition())), inferredPredicates); return RelOptPredicateList.of(rexBuilder, pulledUpPredicates, leftInferredPredicates, rightInferredPredicates); case LEFT: return RelOptPredicateList.of(rexBuilder, RelOptUtil.conjunctions(leftChildPredicates), leftInferredPredicates, rightInferredPredicates); case RIGHT: return RelOptPredicateList.of(rexBuilder, RelOptUtil.conjunctions(rightChildPredicates), inferredPredicates, EMPTY_LIST); default: assert inferredPredicates.size() == 0; return RelOptPredicateList.EMPTY; } } public @Nullable RexNode left() { return leftChildPredicates; } public @Nullable RexNode right() { return rightChildPredicates; } private void infer(@Nullable RexNode predicates, Set allExprs, List inferredPredicates, boolean includeEqualityInference, ImmutableBitSet inferringFields) { for (RexNode r : RelOptUtil.conjunctions(predicates)) { if (!includeEqualityInference && equalityPredicates.contains(r)) { continue; } for (Mapping m : mappings(r)) { RexNode tr = r.accept( new RexPermuteInputsShuttle(m, joinRel.getInput(0), joinRel.getInput(1))); // Filter predicates can be already simplified, so we should work with // simplified RexNode versions as well. It also allows prevent of having // some duplicates in in result pulledUpPredicates RexNode simplifiedTarget = simplify.simplifyFilterPredicates(RelOptUtil.conjunctions(tr)); if (simplifiedTarget == null) { simplifiedTarget = joinRel.getCluster().getRexBuilder().makeLiteral(false); } if (checkTarget(inferringFields, allExprs, tr) && checkTarget(inferringFields, allExprs, simplifiedTarget)) { inferredPredicates.add(simplifiedTarget); allExprs.add(simplifiedTarget); } } } } Iterable mappings(final RexNode predicate) { final ImmutableBitSet fields = requireNonNull(exprFields.get(predicate), () -> "exprFields.get(predicate) is null for " + predicate); if (fields.cardinality() == 0) { return Collections.emptyList(); } return () -> new ExprsItr(fields); } private static boolean checkTarget(ImmutableBitSet inferringFields, Set allExprs, RexNode tr) { return inferringFields.contains(RelOptUtil.InputFinder.bits(tr)) && !allExprs.contains(tr) && !isAlwaysTrue(tr); } @SuppressWarnings("JdkObsolete") private void markAsEquivalent(int p1, int p2) { BitSet b = requireNonNull(equivalence.get(p1), () -> "equivalence.get(p1) for " + p1); b.set(p2); b = requireNonNull(equivalence.get(p2), () -> "equivalence.get(p2) for " + p2); b.set(p1); } /** * Find expressions of the form 'col_x = col_y'. */ class EquivalenceFinder extends RexVisitorImpl { protected EquivalenceFinder() { super(true); } @Override public Void visitCall(RexCall call) { if (call.getOperator().getKind() == SqlKind.EQUALS) { int lPos = pos(call.getOperands().get(0)); int rPos = pos(call.getOperands().get(1)); if (lPos != -1 && rPos != -1) { markAsEquivalent(lPos, rPos); equalityPredicates.add(call); } } return null; } } /** * Given an expression returns all the possible substitutions. * *

For example, for an expression 'a + b + c' and the following * equivalences:

     * a : {a, b}
     * b : {a, b}
     * c : {c, e}
     * 
* *

The following Mappings will be returned: *

     * {a → a, b → a, c → c}
     * {a → a, b → a, c → e}
     * {a → a, b → b, c → c}
     * {a → a, b → b, c → e}
     * {a → b, b → a, c → c}
     * {a → b, b → a, c → e}
     * {a → b, b → b, c → c}
     * {a → b, b → b, c → e}
     * 
* *

which imply the following inferences: *

     * a + a + c
     * a + a + e
     * a + b + c
     * a + b + e
     * b + a + c
     * b + a + e
     * b + b + c
     * b + b + e
     * 
*/ class ExprsItr implements Iterator { final int[] columns; final BitSet[] columnSets; final int[] iterationIdx; @Nullable Mapping nextMapping; boolean firstCall; @SuppressWarnings("JdkObsolete") ExprsItr(ImmutableBitSet fields) { nextMapping = null; columns = new int[fields.cardinality()]; columnSets = new BitSet[fields.cardinality()]; iterationIdx = new int[fields.cardinality()]; for (int j = 0, i = fields.nextSetBit(0); i >= 0; i = fields .nextSetBit(i + 1), j++) { columns[j] = i; int fieldIndex = i; columnSets[j] = requireNonNull(equivalence.get(i), () -> "equivalence.get(i) is null for " + fieldIndex + ", " + equivalence); iterationIdx[j] = 0; } firstCall = true; } @Override public boolean hasNext() { if (firstCall) { initializeMapping(); firstCall = false; } else { computeNextMapping(iterationIdx.length - 1); } return nextMapping != null; } @Override public Mapping next() { if (nextMapping == null) { throw new NoSuchElementException(); } return nextMapping; } @Override public void remove() { throw new UnsupportedOperationException(); } private void computeNextMapping(int level) { int t = columnSets[level].nextSetBit(iterationIdx[level]); if (t < 0) { if (level == 0) { nextMapping = null; } else { int tmp = columnSets[level].nextSetBit(0); requireNonNull(nextMapping, "nextMapping").set(columns[level], tmp); iterationIdx[level] = tmp + 1; computeNextMapping(level - 1); } } else { requireNonNull(nextMapping, "nextMapping").set(columns[level], t); iterationIdx[level] = t + 1; } } private void initializeMapping() { nextMapping = Mappings.create(MappingType.PARTIAL_FUNCTION, nSysFields + nFieldsLeft + nFieldsRight, nSysFields + nFieldsLeft + nFieldsRight); for (int i = 0; i < columnSets.length; i++) { BitSet c = columnSets[i]; int t = c.nextSetBit(iterationIdx[i]); if (t < 0) { nextMapping = null; return; } nextMapping.set(columns[i], t); iterationIdx[i] = t + 1; } } } private static int pos(RexNode expr) { if (expr instanceof RexInputRef) { return ((RexInputRef) expr).getIndex(); } return -1; } private static boolean isAlwaysTrue(RexNode predicate) { if (predicate instanceof RexCall) { RexCall c = (RexCall) predicate; if (c.getOperator().getKind() == SqlKind.EQUALS) { int lPos = pos(c.getOperands().get(0)); int rPos = pos(c.getOperands().get(1)); return lPos != -1 && lPos == rPos; } } return predicate.isAlwaysTrue(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy