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

com.hazelcast.org.apache.calcite.plan.RelOptUtil Maven / Gradle / Ivy

There is a newer version: 5.4.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.plan;

import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableRules;
import com.hazelcast.org.apache.calcite.avatica.AvaticaConnection;
import com.hazelcast.org.apache.calcite.config.CalciteSystemProperty;
import com.hazelcast.org.apache.calcite.interpreter.Bindables;
import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.linq4j.function.Experimental;
import com.hazelcast.org.apache.calcite.rel.RelCollations;
import com.hazelcast.org.apache.calcite.rel.RelHomogeneousShuttle;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.RelShuttle;
import com.hazelcast.org.apache.calcite.rel.RelVisitor;
import com.hazelcast.org.apache.calcite.rel.RelWriter;
import com.hazelcast.org.apache.calcite.rel.core.Aggregate;
import com.hazelcast.org.apache.calcite.rel.core.AggregateCall;
import com.hazelcast.org.apache.calcite.rel.core.Calc;
import com.hazelcast.org.apache.calcite.rel.core.CorrelationId;
import com.hazelcast.org.apache.calcite.rel.core.Filter;
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.Project;
import com.hazelcast.org.apache.calcite.rel.core.RelFactories;
import com.hazelcast.org.apache.calcite.rel.core.Sort;
import com.hazelcast.org.apache.calcite.rel.core.TableScan;
import com.hazelcast.org.apache.calcite.rel.externalize.RelDotWriter;
import com.hazelcast.org.apache.calcite.rel.externalize.RelJsonWriter;
import com.hazelcast.org.apache.calcite.rel.externalize.RelWriterImpl;
import com.hazelcast.org.apache.calcite.rel.externalize.RelXmlWriter;
import com.hazelcast.org.apache.calcite.rel.hint.HintStrategyTable;
import com.hazelcast.org.apache.calcite.rel.hint.Hintable;
import com.hazelcast.org.apache.calcite.rel.hint.RelHint;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalAggregate;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalCalc;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalFilter;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalJoin;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalProject;
import com.hazelcast.org.apache.calcite.rel.metadata.RelMetadataQuery;
import com.hazelcast.org.apache.calcite.rel.rules.CoreRules;
import com.hazelcast.org.apache.calcite.rel.rules.MultiJoin;
import com.hazelcast.org.apache.calcite.rel.stream.StreamRules;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import com.hazelcast.org.apache.calcite.rex.LogicVisitor;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexCorrelVariable;
import com.hazelcast.org.apache.calcite.rex.RexExecutor;
import com.hazelcast.org.apache.calcite.rex.RexExecutorImpl;
import com.hazelcast.org.apache.calcite.rex.RexFieldAccess;
import com.hazelcast.org.apache.calcite.rex.RexInputRef;
import com.hazelcast.org.apache.calcite.rex.RexLiteral;
import com.hazelcast.org.apache.calcite.rex.RexLocalRef;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexOver;
import com.hazelcast.org.apache.calcite.rex.RexProgram;
import com.hazelcast.org.apache.calcite.rex.RexShuttle;
import com.hazelcast.org.apache.calcite.rex.RexSqlStandardConvertletTable;
import com.hazelcast.org.apache.calcite.rex.RexSubQuery;
import com.hazelcast.org.apache.calcite.rex.RexToSqlNodeConverter;
import com.hazelcast.org.apache.calcite.rex.RexToSqlNodeConverterImpl;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.rex.RexVisitorImpl;
import com.hazelcast.org.apache.calcite.runtime.CalciteContextException;
import com.hazelcast.org.apache.calcite.schema.ModifiableView;
import com.hazelcast.org.apache.calcite.sql.SqlExplainFormat;
import com.hazelcast.org.apache.calcite.sql.SqlExplainLevel;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.sql.type.MultisetSqlType;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.tools.RelBuilder;
import com.hazelcast.org.apache.calcite.tools.RelBuilderFactory;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.Litmus;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Permutation;
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.ImmutableSet;
import com.hazelcast.com.google.common.collect.Iterables;
import com.hazelcast.com.google.common.collect.LinkedHashMultimap;
import com.hazelcast.com.google.common.collect.Lists;
import com.hazelcast.com.google.common.collect.Multimap;

import com.hazelcast.org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import com.hazelcast.org.checkerframework.checker.initialization.qual.UnknownInitialization;
import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import com.hazelcast.org.checkerframework.checker.nullness.qual.PolyNull;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.AbstractList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.hazelcast.org.apache.calcite.rel.type.RelDataTypeImpl.NON_NULLABLE_SUFFIX;

import static java.util.Objects.requireNonNull;

/**
 * RelOptUtil defines static utility methods for use in optimizing
 * {@link RelNode}s.
 */
public abstract class RelOptUtil {
  //~ Static fields/initializers ---------------------------------------------

  public static final double EPSILON = 1.0e-5;

  @SuppressWarnings("Guava")
  @Deprecated // to be removed before 2.0
  public static final com.hazelcast.com.google.common.base.Predicate
      FILTER_PREDICATE = f -> !f.containsOver();

  @SuppressWarnings("Guava")
  @Deprecated // to be removed before 2.0
  public static final com.hazelcast.com.google.common.base.Predicate
      PROJECT_PREDICATE =
      RelOptUtil::notContainsWindowedAgg;

  @SuppressWarnings("Guava")
  @Deprecated // to be removed before 2.0
  public static final com.hazelcast.com.google.common.base.Predicate CALC_PREDICATE =
      RelOptUtil::notContainsWindowedAgg;

  //~ Methods ----------------------------------------------------------------

  /**
   * Whether this node is a limit without sort specification.
   */
  public static boolean isPureLimit(RelNode rel) {
    return isLimit(rel) && !isOrder(rel);
  }

  /**
   * Whether this node is a sort without limit specification.
   */
  public static boolean isPureOrder(RelNode rel) {
    return !isLimit(rel) && isOrder(rel);
  }

  /**
   * Whether this node contains a limit specification.
   */
  public static boolean isLimit(RelNode rel) {
    return (rel instanceof Sort) && ((Sort) rel).fetch != null;
  }

  /**
   * Whether this node contains a sort specification.
   */
  public static boolean isOrder(RelNode rel) {
    return (rel instanceof Sort) && !((Sort) rel).getCollation().getFieldCollations().isEmpty();
  }

  /**
   * Returns a set of tables used by this expression or its children.
   */
  public static Set findTables(RelNode rel) {
    return new LinkedHashSet<>(findAllTables(rel));
  }

  /**
   * Returns a list of all tables used by this expression or its children.
   */
  public static List findAllTables(RelNode rel) {
    final Multimap, RelNode> nodes =
        rel.getCluster().getMetadataQuery().getNodeTypes(rel);
    final List usedTables = new ArrayList<>();
    if (nodes == null) {
      return usedTables;
    }
    for (Map.Entry, Collection> e : nodes.asMap().entrySet()) {
      if (TableScan.class.isAssignableFrom(e.getKey())) {
        for (RelNode node : e.getValue()) {
          TableScan scan = (TableScan) node;
          usedTables.add(scan.getTable());
        }
      }
    }
    return usedTables;
  }

  /**
   * Returns a list of all table qualified names used by this expression
   * or its children.
   */
  public static List findAllTableQualifiedNames(RelNode rel) {
    return findAllTables(rel).stream()
        .map(table -> table.getQualifiedName().toString())
        .collect(Collectors.toList());
  }

  /**
   * Returns a list of variables set by a relational expression or its
   * descendants.
   */
  public static Set getVariablesSet(RelNode rel) {
    VariableSetVisitor visitor = new VariableSetVisitor();
    go(visitor, rel);
    return visitor.variables;
  }

  @Deprecated // to be removed before 2.0
  @SuppressWarnings("MixedMutabilityReturnType")
  public static List getVariablesSetAndUsed(RelNode rel0,
      RelNode rel1) {
    Set set = getVariablesSet(rel0);
    if (set.size() == 0) {
      return ImmutableList.of();
    }
    Set used = getVariablesUsed(rel1);
    if (used.size() == 0) {
      return ImmutableList.of();
    }
    final List result = new ArrayList<>();
    for (CorrelationId s : set) {
      if (used.contains(s) && !result.contains(s)) {
        result.add(s);
      }
    }
    return result;
  }

  /**
   * Returns a set of variables used by a relational expression or its
   * descendants.
   *
   * 

The set may contain "duplicates" (variables with different ids that, * when resolved, will reference the same source relational expression). * *

The item type is the same as * {@link com.hazelcast.org.apache.calcite.rex.RexCorrelVariable#id}. */ public static Set getVariablesUsed(RelNode rel) { CorrelationCollector visitor = new CorrelationCollector(); rel.accept(visitor); return visitor.vuv.variables; } /** Finds which columns of a correlation variable are used within a * relational expression. */ public static ImmutableBitSet correlationColumns(CorrelationId id, RelNode rel) { final CorrelationCollector collector = new CorrelationCollector(); rel.accept(collector); final ImmutableBitSet.Builder builder = ImmutableBitSet.builder(); for (int field : collector.vuv.variableFields.get(id)) { if (field >= 0) { builder.set(field); } } return builder.build(); } /** Returns true, and calls {@link Litmus#succeed()} if a given relational * expression does not contain a given correlation. */ public static boolean notContainsCorrelation(RelNode r, CorrelationId correlationId, Litmus litmus) { final Set set = getVariablesUsed(r); if (!set.contains(correlationId)) { return litmus.succeed(); } else { return litmus.fail("contains {}", correlationId); } } /** * Sets a {@link RelVisitor} going on a given relational expression, and * returns the result. */ public static void go( RelVisitor visitor, RelNode p) { try { visitor.go(p); } catch (Exception e) { throw new RuntimeException("while visiting tree", e); } } /** * Returns a list of the types of the fields in a given struct type. The * list is immutable. * * @param type Struct type * @return List of field types * @see com.hazelcast.org.apache.calcite.rel.type.RelDataType#getFieldNames() */ public static List getFieldTypeList(final RelDataType type) { return Util.transform(type.getFieldList(), RelDataTypeField::getType); } public static boolean areRowTypesEqual( RelDataType rowType1, RelDataType rowType2, boolean compareNames) { if (rowType1 == rowType2) { return true; } if (compareNames) { // if types are not identity-equal, then either the names or // the types must be different return false; } if (rowType2.getFieldCount() != rowType1.getFieldCount()) { return false; } final List f1 = rowType1.getFieldList(); final List f2 = rowType2.getFieldList(); for (Pair pair : Pair.zip(f1, f2)) { final RelDataType type1 = pair.left.getType(); final RelDataType type2 = pair.right.getType(); // If one of the types is ANY comparison should succeed if (type1.getSqlTypeName() == SqlTypeName.ANY || type2.getSqlTypeName() == SqlTypeName.ANY) { continue; } if (!type1.equals(type2)) { return false; } } return true; } /** * Verifies that a row type being added to an equivalence class matches the * existing type, raising an assertion if this is not the case. * * @param originalRel canonical rel for equivalence class * @param newRel rel being added to equivalence class * @param equivalenceClass object representing equivalence class */ public static void verifyTypeEquivalence( RelNode originalRel, RelNode newRel, Object equivalenceClass) { RelDataType expectedRowType = originalRel.getRowType(); RelDataType actualRowType = newRel.getRowType(); // Row types must be the same, except for field names. if (areRowTypesEqual(expectedRowType, actualRowType, false)) { return; } String s = "Cannot add expression of different type to set:\n" + "set type is " + expectedRowType.getFullTypeString() + "\nexpression type is " + actualRowType.getFullTypeString() + "\nset is " + equivalenceClass.toString() + "\nexpression is " + RelOptUtil.toString(newRel); throw new AssertionError(s); } /** * Copy the {@link com.hazelcast.org.apache.calcite.rel.hint.RelHint}s from {@code originalRel} * to {@code newRel} if both of them are {@link Hintable}. * *

The two relational expressions are assumed as semantically equivalent, * that means the hints should be attached to the relational expression * that expects to have them. * *

Try to propagate the hints to the first relational expression that matches, * this is needed because many planner rules would generate a sub-tree whose * root rel type is different with the original matched rel. * *

For the worst case, there is no relational expression that can apply these hints, * and the whole sub-tree would be visited. We add a protection here: * if the visiting depth is over than 3, just returns, because there are rare cases * the new created sub-tree has layers bigger than that. * *

This is a best effort, we do not know exactly how the nodes are transformed * in all kinds of planner rules, so for some complex relational expressions, * the hints would very probably lost. * *

This function is experimental and would change without any notes. * * @param originalRel Original relational expression * @param equiv New equivalent relational expression * @return A copy of {@code newRel} with attached qualified hints from {@code originalRel}, * or {@code newRel} directly if one of them are not {@link Hintable} */ @Experimental public static RelNode propagateRelHints(RelNode originalRel, RelNode equiv) { if (!(originalRel instanceof Hintable) || ((Hintable) originalRel).getHints().size() == 0) { return equiv; } final RelShuttle shuttle = new SubTreeHintPropagateShuttle( originalRel.getCluster().getHintStrategies(), ((Hintable) originalRel).getHints()); return equiv.accept(shuttle); } /** * Propagates the relational expression hints from root node to leaf node. * * @param rel The relational expression * @param reset Flag saying if to reset the existing hints before the propagation * @return New relational expression with hints propagated */ public static RelNode propagateRelHints(RelNode rel, boolean reset) { if (reset) { rel = rel.accept(new ResetHintsShuttle()); } final RelShuttle shuttle = new RelHintPropagateShuttle(rel.getCluster().getHintStrategies()); return rel.accept(shuttle); } /** * Copy the {@link com.hazelcast.org.apache.calcite.rel.hint.RelHint}s from {@code originalRel} * to {@code newRel} if both of them are {@link Hintable}. * *

The hints would be attached directly(e.g. without any filtering). * * @param originalRel Original relational expression * @param newRel New relational expression * @return A copy of {@code newRel} with attached hints from {@code originalRel}, * or {@code newRel} directly if one of them are not {@link Hintable} */ public static RelNode copyRelHints(RelNode originalRel, RelNode newRel) { return copyRelHints(originalRel, newRel, false); } /** * Copy the {@link com.hazelcast.org.apache.calcite.rel.hint.RelHint}s from {@code originalRel} * to {@code newRel} if both of them are {@link Hintable}. * *

The hints would be filtered by the specified hint strategies * if {@code filterHints} is true. * * @param originalRel Original relational expression * @param newRel New relational expression * @param filterHints Flag saying if to filter out unqualified hints for {@code newRel} * @return A copy of {@code newRel} with attached hints from {@code originalRel}, * or {@code newRel} directly if one of them are not {@link Hintable} */ public static RelNode copyRelHints(RelNode originalRel, RelNode newRel, boolean filterHints) { if (originalRel == newRel && !filterHints) { return originalRel; } if (originalRel instanceof Hintable && newRel instanceof Hintable && ((Hintable) originalRel).getHints().size() > 0) { final List hints = ((Hintable) originalRel).getHints(); if (filterHints) { HintStrategyTable hintStrategies = originalRel.getCluster().getHintStrategies(); return ((Hintable) newRel).attachHints(hintStrategies.apply(hints, newRel)); } else { // Keep all the hints if filterHints is false for 2 reasons: // 1. Keep sync with the hints propagation logic, // see RelHintPropagateShuttle for details. // 2. We may re-propagate these hints when decorrelating a query. return ((Hintable) newRel).attachHints(hints); } } return newRel; } /** * Returns a permutation describing where output fields come from. In * the returned map, value of {@code map.getTargetOpt(i)} is {@code n} if * field {@code i} projects input field {@code n} or applies a cast on * {@code n}, -1 if it is another expression. */ public static Mappings.TargetMapping permutationIgnoreCast( List nodes, RelDataType inputRowType) { final Mappings.TargetMapping mapping = Mappings.create( MappingType.PARTIAL_FUNCTION, nodes.size(), inputRowType.getFieldCount()); for (Ord node : Ord.zip(nodes)) { if (node.e instanceof RexInputRef) { mapping.set( node.i, ((RexInputRef) node.e).getIndex()); } else if (node.e.isA(SqlKind.CAST)) { final RexNode operand = ((RexCall) node.e).getOperands().get(0); if (operand instanceof RexInputRef) { mapping.set(node.i, ((RexInputRef) operand).getIndex()); } } } return mapping; } /** * Returns a permutation describing where output fields come from. In * the returned map, value of {@code map.getTargetOpt(i)} is {@code n} if * field {@code i} projects input field {@code n}, -1 if it is an * expression. */ public static Mappings.TargetMapping permutation( List nodes, RelDataType inputRowType) { final Mappings.TargetMapping mapping = Mappings.create( MappingType.PARTIAL_FUNCTION, nodes.size(), inputRowType.getFieldCount()); for (Ord node : Ord.zip(nodes)) { if (node.e instanceof RexInputRef) { mapping.set( node.i, ((RexInputRef) node.e).getIndex()); } } return mapping; } /** * Returns a permutation describing where the Project's fields come from * after the Project is pushed down. */ public static Mappings.TargetMapping permutationPushDownProject( List nodes, RelDataType inputRowType, int sourceOffset, int targetOffset) { final Mappings.TargetMapping mapping = Mappings.create(MappingType.PARTIAL_FUNCTION, inputRowType.getFieldCount() + sourceOffset, nodes.size() + targetOffset); for (Ord node : Ord.zip(nodes)) { if (node.e instanceof RexInputRef) { mapping.set( ((RexInputRef) node.e).getIndex() + sourceOffset, node.i + targetOffset); } } return mapping; } @Deprecated // to be removed before 2.0 public static RelNode createExistsPlan( RelOptCluster cluster, RelNode seekRel, @Nullable List conditions, @Nullable RexLiteral extraExpr, @Nullable String extraName) { assert extraExpr == null || extraName != null; RelNode ret = seekRel; if ((conditions != null) && (conditions.size() > 0)) { RexNode conditionExp = RexUtil.composeConjunction( cluster.getRexBuilder(), conditions, true); if (conditionExp != null) { final RelFactories.FilterFactory factory = RelFactories.DEFAULT_FILTER_FACTORY; ret = factory.createFilter(ret, conditionExp, ImmutableSet.of()); } } if (extraExpr != null) { RexBuilder rexBuilder = cluster.getRexBuilder(); assert extraExpr == rexBuilder.makeLiteral(true); // this should only be called for the exists case // first stick an Agg on top of the sub-query // agg does not like no agg functions so just pretend it is // doing a min(TRUE) final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(cluster, null); ret = relBuilder.push(ret) .project(extraExpr) .aggregate(relBuilder.groupKey(), relBuilder.min(relBuilder.field(0)).as(extraName)) .build(); } return ret; } @Deprecated // to be removed before 2.0 public static Exists createExistsPlan( RelNode seekRel, SubQueryType subQueryType, Logic logic, boolean notIn) { final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(seekRel.getCluster(), null); return createExistsPlan(seekRel, subQueryType, logic, notIn, relBuilder); } /** * Creates a plan suitable for use in EXISTS or IN * statements. * * @see com.hazelcast.org.apache.calcite.sql2rel.SqlToRelConverter * SqlToRelConverter#convertExists * * @param seekRel A query rel, for example the resulting rel from 'select * * from emp' or 'values (1,2,3)' or '('Foo', 34)'. * @param subQueryType Sub-query type * @param logic Whether to use 2- or 3-valued boolean logic * @param notIn Whether the operator is NOT IN * @param relBuilder Builder for relational expressions * * @return A pair of a relational expression which outer joins a boolean * condition column, and a numeric offset. The offset is 2 if column 0 is * the number of rows and column 1 is the number of rows with not-null keys; * 0 otherwise. */ public static Exists createExistsPlan( RelNode seekRel, SubQueryType subQueryType, Logic logic, boolean notIn, RelBuilder relBuilder) { switch (subQueryType) { case SCALAR: return new Exists(seekRel, false, true); default: break; } switch (logic) { case TRUE_FALSE_UNKNOWN: case UNKNOWN_AS_TRUE: if (notIn && !containsNullableFields(seekRel)) { logic = Logic.TRUE_FALSE; } break; default: break; } RelNode ret = seekRel; final RelOptCluster cluster = seekRel.getCluster(); final RexBuilder rexBuilder = cluster.getRexBuilder(); final int keyCount = ret.getRowType().getFieldCount(); final boolean outerJoin = notIn || logic == RelOptUtil.Logic.TRUE_FALSE_UNKNOWN; if (!outerJoin) { final LogicalAggregate aggregate = LogicalAggregate.create(ret, ImmutableList.of(), ImmutableBitSet.range(keyCount), null, ImmutableList.of()); return new Exists(aggregate, false, false); } // for IN/NOT IN, it needs to output the fields final List exprs = new ArrayList<>(); if (subQueryType == SubQueryType.IN) { for (int i = 0; i < keyCount; i++) { exprs.add(rexBuilder.makeInputRef(ret, i)); } } final int projectedKeyCount = exprs.size(); exprs.add(rexBuilder.makeLiteral(true)); ret = relBuilder.push(ret) .project(exprs) .aggregate( relBuilder.groupKey(ImmutableBitSet.range(projectedKeyCount)), relBuilder.min(relBuilder.field(projectedKeyCount))) .build(); switch (logic) { case TRUE_FALSE_UNKNOWN: case UNKNOWN_AS_TRUE: return new Exists(ret, true, true); default: return new Exists(ret, false, true); } } @Deprecated // to be removed before 2.0 public static RelNode createRenameRel( RelDataType outputType, RelNode rel) { RelDataType inputType = rel.getRowType(); List inputFields = inputType.getFieldList(); int n = inputFields.size(); List outputFields = outputType.getFieldList(); assert outputFields.size() == n : "rename: field count mismatch: in=" + inputType + ", out" + outputType; final List> renames = new ArrayList<>(); for (Pair pair : Pair.zip(inputFields, outputFields)) { final RelDataTypeField inputField = pair.left; final RelDataTypeField outputField = pair.right; assert inputField.getType().equals(outputField.getType()); final RexBuilder rexBuilder = rel.getCluster().getRexBuilder(); renames.add( Pair.of( rexBuilder.makeInputRef(inputField.getType(), inputField.getIndex()), outputField.getName())); } final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(rel.getCluster(), null); return relBuilder.push(rel) .project(Pair.left(renames), Pair.right(renames), true) .build(); } @Deprecated // to be removed before 2.0 public static RelNode createFilter(RelNode child, RexNode condition) { final RelFactories.FilterFactory factory = RelFactories.DEFAULT_FILTER_FACTORY; return factory.createFilter(child, condition, ImmutableSet.of()); } @Deprecated // to be removed before 2.0 public static RelNode createFilter(RelNode child, RexNode condition, RelFactories.FilterFactory filterFactory) { return filterFactory.createFilter(child, condition, ImmutableSet.of()); } /** Creates a filter, using the default filter factory, * or returns the original relational expression if the * condition is trivial. */ public static RelNode createFilter(RelNode child, Iterable conditions) { return createFilter(child, conditions, RelFactories.DEFAULT_FILTER_FACTORY); } /** Creates a filter using the default factory, * or returns the original relational expression if the * condition is trivial. */ public static RelNode createFilter(RelNode child, Iterable conditions, RelFactories.FilterFactory filterFactory) { final RelOptCluster cluster = child.getCluster(); final RexNode condition = RexUtil.composeConjunction(cluster.getRexBuilder(), conditions, true); if (condition == null) { return child; } else { return filterFactory.createFilter(child, condition, ImmutableSet.of()); } } @Deprecated // to be removed before 2.0 public static RelNode createNullFilter( RelNode rel, Integer[] fieldOrdinals) { RexNode condition = null; final RexBuilder rexBuilder = rel.getCluster().getRexBuilder(); RelDataType rowType = rel.getRowType(); int n; if (fieldOrdinals != null) { n = fieldOrdinals.length; } else { n = rowType.getFieldCount(); } List fields = rowType.getFieldList(); for (int i = 0; i < n; ++i) { int iField; if (fieldOrdinals != null) { iField = fieldOrdinals[i]; } else { iField = i; } RelDataType type = fields.get(iField).getType(); if (!type.isNullable()) { continue; } RexNode newCondition = rexBuilder.makeCall( SqlStdOperatorTable.IS_NOT_NULL, rexBuilder.makeInputRef(type, iField)); if (condition == null) { condition = newCondition; } else { condition = rexBuilder.makeCall( SqlStdOperatorTable.AND, condition, newCondition); } } if (condition == null) { // no filtering required return rel; } final RelFactories.FilterFactory factory = RelFactories.DEFAULT_FILTER_FACTORY; return factory.createFilter(rel, condition, ImmutableSet.of()); } /** * Creates a projection which casts a rel's output to a desired row type. * *

No need to create new projection if {@code rel} is already a project, * instead, create a projection with the input of {@code rel} and the new * cast expressions. * *

The desired row type and the row type to be converted must have the * same number of fields. * * @param rel producer of rows to be converted * @param castRowType row type after cast * @param rename if true, use field names from castRowType; if false, * preserve field names from rel * @return conversion rel */ public static RelNode createCastRel( final RelNode rel, RelDataType castRowType, boolean rename) { return createCastRel( rel, castRowType, rename, RelFactories.DEFAULT_PROJECT_FACTORY); } /** * Creates a projection which casts a rel's output to a desired row type. * *

No need to create new projection if {@code rel} is already a project, * instead, create a projection with the input of {@code rel} and the new * cast expressions. * *

The desired row type and the row type to be converted must have the * same number of fields. * * @param rel producer of rows to be converted * @param castRowType row type after cast * @param rename if true, use field names from castRowType; if false, * preserve field names from rel * @param projectFactory Project Factory * @return conversion rel */ public static RelNode createCastRel( final RelNode rel, RelDataType castRowType, boolean rename, RelFactories.ProjectFactory projectFactory) { assert projectFactory != null; RelDataType rowType = rel.getRowType(); if (areRowTypesEqual(rowType, castRowType, rename)) { // nothing to do return rel; } if (rowType.getFieldCount() != castRowType.getFieldCount()) { throw new IllegalArgumentException("Field counts are not equal: " + "rowType [" + rowType + "] castRowType [" + castRowType + "]"); } final RexBuilder rexBuilder = rel.getCluster().getRexBuilder(); List castExps; RelNode input; List hints = ImmutableList.of(); if (rel instanceof Project) { // No need to create another project node if the rel // is already a project. final Project project = (Project) rel; castExps = RexUtil.generateCastExpressions( rexBuilder, castRowType, ((Project) rel).getProjects()); input = rel.getInput(0); hints = project.getHints(); } else { castExps = RexUtil.generateCastExpressions( rexBuilder, castRowType, rowType); input = rel; } if (rename) { // Use names and types from castRowType. return projectFactory.createProject(input, hints, castExps, castRowType.getFieldNames()); } else { // Use names from rowType, types from castRowType. return projectFactory.createProject(input, hints, castExps, rowType.getFieldNames()); } } /** Gets all fields in an aggregate. */ public static Set getAllFields(Aggregate aggregate) { return getAllFields2(aggregate.getGroupSet(), aggregate.getAggCallList()); } /** Gets all fields in an aggregate. */ public static Set getAllFields2(ImmutableBitSet groupSet, List aggCallList) { final Set allFields = new TreeSet<>(); allFields.addAll(groupSet.asList()); for (AggregateCall aggregateCall : aggCallList) { allFields.addAll(aggregateCall.getArgList()); if (aggregateCall.filterArg >= 0) { allFields.add(aggregateCall.filterArg); } if (aggregateCall.distinctKeys != null) { allFields.addAll(aggregateCall.distinctKeys.asList()); } allFields.addAll(RelCollations.ordinals(aggregateCall.collation)); } return allFields; } /** * Creates a LogicalAggregate that removes all duplicates from the result of * an underlying relational expression. * * @param rel underlying rel * @return rel implementing SingleValueAgg */ public static RelNode createSingleValueAggRel( RelOptCluster cluster, RelNode rel) { final int aggCallCnt = rel.getRowType().getFieldCount(); final List aggCalls = new ArrayList<>(); for (int i = 0; i < aggCallCnt; i++) { aggCalls.add( AggregateCall.create(SqlStdOperatorTable.SINGLE_VALUE, false, false, false, ImmutableList.of(i), -1, null, RelCollations.EMPTY, 0, rel, null, null)); } return LogicalAggregate.create(rel, ImmutableList.of(), ImmutableBitSet.of(), null, aggCalls); } // CHECKSTYLE: IGNORE 1 /** @deprecated Use {@link RelBuilder#distinct()}. */ @Deprecated // to be removed before 2.0 public static RelNode createDistinctRel(RelNode rel) { return LogicalAggregate.create(rel, ImmutableList.of(), ImmutableBitSet.range(rel.getRowType().getFieldCount()), null, ImmutableList.of()); } @Deprecated // to be removed before 2.0 public static boolean analyzeSimpleEquiJoin( LogicalJoin join, int[] joinFieldOrdinals) { RexNode joinExp = join.getCondition(); if (joinExp.getKind() != SqlKind.EQUALS) { return false; } RexCall binaryExpression = (RexCall) joinExp; RexNode leftComparand = binaryExpression.operands.get(0); RexNode rightComparand = binaryExpression.operands.get(1); if (!(leftComparand instanceof RexInputRef)) { return false; } if (!(rightComparand instanceof RexInputRef)) { return false; } final int leftFieldCount = join.getLeft().getRowType().getFieldCount(); RexInputRef leftFieldAccess = (RexInputRef) leftComparand; if (!(leftFieldAccess.getIndex() < leftFieldCount)) { // left field must access left side of join return false; } RexInputRef rightFieldAccess = (RexInputRef) rightComparand; if (!(rightFieldAccess.getIndex() >= leftFieldCount)) { // right field must access right side of join return false; } joinFieldOrdinals[0] = leftFieldAccess.getIndex(); joinFieldOrdinals[1] = rightFieldAccess.getIndex() - leftFieldCount; return true; } /** * Splits out the equi-join components of a join condition, and returns * what's left. For example, given the condition * *

L.A = R.X AND L.B = L.C AND (L.D = 5 OR L.E = * R.Y)
* *

returns * *

    *
  • leftKeys = {A} *
  • rightKeys = {X} *
  • rest = L.B = L.C AND (L.D = 5 OR L.E = R.Y)
  • *
* * @param left left input to join * @param right right input to join * @param condition join condition * @param leftKeys The ordinals of the fields from the left input which are * equi-join keys * @param rightKeys The ordinals of the fields from the right input which * are equi-join keys * @param filterNulls List of boolean values for each join key position * indicating whether the operator filters out nulls or not. * Value is true if the operator is EQUALS and false if the * operator is IS NOT DISTINCT FROM (or an expanded version). * If filterNulls is null, only join conditions * with EQUALS operators are considered equi-join components. * Rest (including IS NOT DISTINCT FROM) are returned in * remaining join condition. * * @return remaining join filters that are not equijoins; may return a * {@link RexLiteral} true, but never null */ public static RexNode splitJoinCondition( RelNode left, RelNode right, RexNode condition, List leftKeys, List rightKeys, @Nullable List filterNulls) { final List nonEquiList = new ArrayList<>(); splitJoinCondition(left, right, condition, leftKeys, rightKeys, filterNulls, nonEquiList); return RexUtil.composeConjunction( left.getCluster().getRexBuilder(), nonEquiList); } /** As * {@link #splitJoinCondition(RelNode, RelNode, RexNode, List, List, List)}, * but writes non-equi conditions to a conjunctive list. */ public static void splitJoinCondition( RelNode left, RelNode right, RexNode condition, List leftKeys, List rightKeys, @Nullable List filterNulls, List nonEquiList) { splitJoinCondition( left.getCluster().getRexBuilder(), left.getRowType().getFieldCount(), condition, leftKeys, rightKeys, filterNulls, nonEquiList); } @Deprecated // to be removed before 2.0 public static boolean isEqui( RelNode left, RelNode right, RexNode condition) { final List leftKeys = new ArrayList<>(); final List rightKeys = new ArrayList<>(); final List filterNulls = new ArrayList<>(); final List nonEquiList = new ArrayList<>(); splitJoinCondition( left.getCluster().getRexBuilder(), left.getRowType().getFieldCount(), condition, leftKeys, rightKeys, filterNulls, nonEquiList); return nonEquiList.size() == 0; } /** * Splits out the equi-join (and optionally, a single non-equi) components * of a join condition, and returns what's left. Projection might be * required by the caller to provide join keys that are not direct field * references. * * @param sysFieldList list of system fields * @param leftRel left join input * @param rightRel right join input * @param condition join condition * @param leftJoinKeys The join keys from the left input which are equi-join * keys * @param rightJoinKeys The join keys from the right input which are * equi-join keys * @param filterNulls The join key positions for which null values will not * match. null values only match for the "is not distinct * from" condition. * @param rangeOp if null, only locate equi-joins; otherwise, locate a * single non-equi join predicate and return its operator * in this list; join keys associated with the non-equi * join predicate are at the end of the key lists * returned * @return What's left, never null */ public static RexNode splitJoinCondition( List sysFieldList, RelNode leftRel, RelNode rightRel, RexNode condition, List leftJoinKeys, List rightJoinKeys, @Nullable List filterNulls, @Nullable List rangeOp) { return splitJoinCondition( sysFieldList, ImmutableList.of(leftRel, rightRel), condition, ImmutableList.of(leftJoinKeys, rightJoinKeys), filterNulls, rangeOp); } /** * Splits out the equi-join (and optionally, a single non-equi) components * of a join condition, and returns what's left. Projection might be * required by the caller to provide join keys that are not direct field * references. * * @param sysFieldList list of system fields * @param inputs join inputs * @param condition join condition * @param joinKeys The join keys from the inputs which are equi-join * keys * @param filterNulls The join key positions for which null values will not * match. null values only match for the "is not distinct * from" condition. * @param rangeOp if null, only locate equi-joins; otherwise, locate a * single non-equi join predicate and return its operator * in this list; join keys associated with the non-equi * join predicate are at the end of the key lists * returned * @return What's left, never null */ public static RexNode splitJoinCondition( List sysFieldList, List inputs, RexNode condition, List> joinKeys, @Nullable List filterNulls, @Nullable List rangeOp) { final List nonEquiList = new ArrayList<>(); splitJoinCondition( sysFieldList, inputs, condition, joinKeys, filterNulls, rangeOp, nonEquiList); // Convert the remainders into a list that are AND'ed together. return RexUtil.composeConjunction( inputs.get(0).getCluster().getRexBuilder(), nonEquiList); } @Deprecated // to be removed before 2.0 public static @Nullable RexNode splitCorrelatedFilterCondition( LogicalFilter filter, List joinKeys, List correlatedJoinKeys) { final List nonEquiList = new ArrayList<>(); splitCorrelatedFilterCondition( filter, filter.getCondition(), joinKeys, correlatedJoinKeys, nonEquiList); // Convert the remainders into a list that are AND'ed together. return RexUtil.composeConjunction( filter.getCluster().getRexBuilder(), nonEquiList, true); } public static @Nullable RexNode splitCorrelatedFilterCondition( LogicalFilter filter, List joinKeys, List correlatedJoinKeys, boolean extractCorrelatedFieldAccess) { return splitCorrelatedFilterCondition( (Filter) filter, joinKeys, correlatedJoinKeys, extractCorrelatedFieldAccess); } public static @Nullable RexNode splitCorrelatedFilterCondition( Filter filter, List joinKeys, List correlatedJoinKeys, boolean extractCorrelatedFieldAccess) { final List nonEquiList = new ArrayList<>(); splitCorrelatedFilterCondition( filter, filter.getCondition(), joinKeys, correlatedJoinKeys, nonEquiList, extractCorrelatedFieldAccess); // Convert the remainders into a list that are AND'ed together. return RexUtil.composeConjunction( filter.getCluster().getRexBuilder(), nonEquiList, true); } private static void splitJoinCondition( List sysFieldList, List inputs, RexNode condition, List> joinKeys, @Nullable List filterNulls, @Nullable List rangeOp, List nonEquiList) { final int sysFieldCount = sysFieldList.size(); final RelOptCluster cluster = inputs.get(0).getCluster(); final RexBuilder rexBuilder = cluster.getRexBuilder(); final RelDataTypeFactory typeFactory = cluster.getTypeFactory(); final ImmutableBitSet[] inputsRange = new ImmutableBitSet[inputs.size()]; int totalFieldCount = 0; for (int i = 0; i < inputs.size(); i++) { final int firstField = totalFieldCount + sysFieldCount; totalFieldCount = firstField + inputs.get(i).getRowType().getFieldCount(); inputsRange[i] = ImmutableBitSet.range(firstField, totalFieldCount); } // adjustment array int[] adjustments = new int[totalFieldCount]; for (int i = 0; i < inputs.size(); i++) { final int adjustment = inputsRange[i].nextSetBit(0); for (int j = adjustment; j < inputsRange[i].length(); j++) { adjustments[j] = -adjustment; } } if (condition.getKind() == SqlKind.AND) { for (RexNode operand : ((RexCall) condition).getOperands()) { splitJoinCondition( sysFieldList, inputs, operand, joinKeys, filterNulls, rangeOp, nonEquiList); } return; } if (condition instanceof RexCall) { RexNode leftKey = null; RexNode rightKey = null; int leftInput = 0; int rightInput = 0; List leftFields = null; List rightFields = null; boolean reverse = false; final RexCall call = collapseExpandedIsNotDistinctFromExpr((RexCall) condition, rexBuilder); SqlKind kind = call.getKind(); // Only consider range operators if we haven't already seen one if ((kind == SqlKind.EQUALS) || (filterNulls != null && kind == SqlKind.IS_NOT_DISTINCT_FROM) || (rangeOp != null && rangeOp.isEmpty() && (kind == SqlKind.GREATER_THAN || kind == SqlKind.GREATER_THAN_OR_EQUAL || kind == SqlKind.LESS_THAN || kind == SqlKind.LESS_THAN_OR_EQUAL))) { final List operands = call.getOperands(); RexNode op0 = operands.get(0); RexNode op1 = operands.get(1); final ImmutableBitSet projRefs0 = InputFinder.bits(op0); final ImmutableBitSet projRefs1 = InputFinder.bits(op1); boolean foundBothInputs = false; for (int i = 0; i < inputs.size() && !foundBothInputs; i++) { if (projRefs0.intersects(inputsRange[i]) && projRefs0.union(inputsRange[i]).equals(inputsRange[i])) { if (leftKey == null) { leftKey = op0; leftInput = i; leftFields = inputs.get(leftInput).getRowType().getFieldList(); } else { rightKey = op0; rightInput = i; rightFields = inputs.get(rightInput).getRowType().getFieldList(); reverse = true; foundBothInputs = true; } } else if (projRefs1.intersects(inputsRange[i]) && projRefs1.union(inputsRange[i]).equals(inputsRange[i])) { if (leftKey == null) { leftKey = op1; leftInput = i; leftFields = inputs.get(leftInput).getRowType().getFieldList(); } else { rightKey = op1; rightInput = i; rightFields = inputs.get(rightInput).getRowType().getFieldList(); foundBothInputs = true; } } } if ((leftKey != null) && (rightKey != null)) { // replace right Key input ref rightKey = rightKey.accept( new RelOptUtil.RexInputConverter( rexBuilder, rightFields, rightFields, adjustments)); // left key only needs to be adjusted if there are system // fields, but do it for uniformity leftKey = leftKey.accept( new RelOptUtil.RexInputConverter( rexBuilder, leftFields, leftFields, adjustments)); RelDataType leftKeyType = leftKey.getType(); RelDataType rightKeyType = rightKey.getType(); if (leftKeyType != rightKeyType) { // perform casting RelDataType targetKeyType = typeFactory.leastRestrictive( ImmutableList.of(leftKeyType, rightKeyType)); if (targetKeyType == null) { throw new AssertionError("Cannot find common type for join keys " + leftKey + " (type " + leftKeyType + ") and " + rightKey + " (type " + rightKeyType + ")"); } if (leftKeyType != targetKeyType) { leftKey = rexBuilder.makeCast(targetKeyType, leftKey); } if (rightKeyType != targetKeyType) { rightKey = rexBuilder.makeCast(targetKeyType, rightKey); } } } } if ((leftKey != null) && (rightKey != null)) { // found suitable join keys // add them to key list, ensuring that if there is a // non-equi join predicate, it appears at the end of the // key list; also mark the null filtering property addJoinKey( joinKeys.get(leftInput), leftKey, (rangeOp != null) && !rangeOp.isEmpty()); addJoinKey( joinKeys.get(rightInput), rightKey, (rangeOp != null) && !rangeOp.isEmpty()); if (filterNulls != null && kind == SqlKind.EQUALS) { // nulls are considered not matching for equality comparison // add the position of the most recently inserted key filterNulls.add(joinKeys.get(leftInput).size() - 1); } if (rangeOp != null && kind != SqlKind.EQUALS && kind != SqlKind.IS_DISTINCT_FROM) { SqlOperator op = call.getOperator(); if (reverse) { op = requireNonNull(op.reverse()); } rangeOp.add(op); } return; } // else fall through and add this condition as nonEqui condition } // The operator is not of RexCall type // So we fail. Fall through. // Add this condition to the list of non-equi-join conditions. nonEquiList.add(condition); } /** Builds an equi-join condition from a set of left and right keys. */ public static RexNode createEquiJoinCondition( final RelNode left, final List leftKeys, final RelNode right, final List rightKeys, final RexBuilder rexBuilder) { final List leftTypes = RelOptUtil.getFieldTypeList(left.getRowType()); final List rightTypes = RelOptUtil.getFieldTypeList(right.getRowType()); return RexUtil.composeConjunction(rexBuilder, new AbstractList() { @Override public RexNode get(int index) { final int leftKey = leftKeys.get(index); final int rightKey = rightKeys.get(index); return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, rexBuilder.makeInputRef(leftTypes.get(leftKey), leftKey), rexBuilder.makeInputRef(rightTypes.get(rightKey), leftTypes.size() + rightKey)); } @Override public int size() { return leftKeys.size(); } }); } /** * Returns {@link SqlOperator} for given {@link SqlKind} or returns {@code operator} * when {@link SqlKind} is not known. * @param kind input kind * @param operator default operator value * @return SqlOperator for the given kind * @see RexUtil#op(SqlKind) */ public static SqlOperator op(SqlKind kind, SqlOperator operator) { switch (kind) { case EQUALS: return SqlStdOperatorTable.EQUALS; case NOT_EQUALS: return SqlStdOperatorTable.NOT_EQUALS; case GREATER_THAN: return SqlStdOperatorTable.GREATER_THAN; case GREATER_THAN_OR_EQUAL: return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL; case LESS_THAN: return SqlStdOperatorTable.LESS_THAN; case LESS_THAN_OR_EQUAL: return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; case IS_DISTINCT_FROM: return SqlStdOperatorTable.IS_DISTINCT_FROM; case IS_NOT_DISTINCT_FROM: return SqlStdOperatorTable.IS_NOT_DISTINCT_FROM; default: return operator; } } private static void addJoinKey( List joinKeyList, RexNode key, boolean preserveLastElementInList) { if (!joinKeyList.isEmpty() && preserveLastElementInList) { joinKeyList.add(joinKeyList.size() - 1, key); } else { joinKeyList.add(key); } } private static void splitCorrelatedFilterCondition( LogicalFilter filter, RexNode condition, List joinKeys, List correlatedJoinKeys, List nonEquiList) { if (condition instanceof RexCall) { RexCall call = (RexCall) condition; if (call.getOperator().getKind() == SqlKind.AND) { for (RexNode operand : call.getOperands()) { splitCorrelatedFilterCondition( filter, operand, joinKeys, correlatedJoinKeys, nonEquiList); } return; } if (call.getOperator().getKind() == SqlKind.EQUALS) { final List operands = call.getOperands(); RexNode op0 = operands.get(0); RexNode op1 = operands.get(1); if (!RexUtil.containsInputRef(op0) && op1 instanceof RexInputRef) { correlatedJoinKeys.add(op0); joinKeys.add((RexInputRef) op1); return; } else if ( op0 instanceof RexInputRef && !RexUtil.containsInputRef(op1)) { joinKeys.add((RexInputRef) op0); correlatedJoinKeys.add(op1); return; } } } // The operator is not of RexCall type // So we fail. Fall through. // Add this condition to the list of non-equi-join conditions. nonEquiList.add(condition); } @SuppressWarnings("unused") private static void splitCorrelatedFilterCondition( LogicalFilter filter, RexNode condition, List joinKeys, List correlatedJoinKeys, List nonEquiList, boolean extractCorrelatedFieldAccess) { splitCorrelatedFilterCondition( (Filter) filter, condition, joinKeys, correlatedJoinKeys, nonEquiList, extractCorrelatedFieldAccess); } private static void splitCorrelatedFilterCondition( Filter filter, RexNode condition, List joinKeys, List correlatedJoinKeys, List nonEquiList, boolean extractCorrelatedFieldAccess) { if (condition instanceof RexCall) { RexCall call = (RexCall) condition; if (call.getOperator().getKind() == SqlKind.AND) { for (RexNode operand : call.getOperands()) { splitCorrelatedFilterCondition( filter, operand, joinKeys, correlatedJoinKeys, nonEquiList, extractCorrelatedFieldAccess); } return; } if (call.getOperator().getKind() == SqlKind.EQUALS) { final List operands = call.getOperands(); RexNode op0 = operands.get(0); RexNode op1 = operands.get(1); if (extractCorrelatedFieldAccess) { if (!RexUtil.containsFieldAccess(op0) && op1 instanceof RexFieldAccess) { joinKeys.add(op0); correlatedJoinKeys.add(op1); return; } else if ( op0 instanceof RexFieldAccess && !RexUtil.containsFieldAccess(op1)) { correlatedJoinKeys.add(op0); joinKeys.add(op1); return; } } else { if (!RexUtil.containsInputRef(op0) && op1 instanceof RexInputRef) { correlatedJoinKeys.add(op0); joinKeys.add(op1); return; } else if ( op0 instanceof RexInputRef && !RexUtil.containsInputRef(op1)) { joinKeys.add(op0); correlatedJoinKeys.add(op1); return; } } } } // The operator is not of RexCall type // So we fail. Fall through. // Add this condition to the list of non-equi-join conditions. nonEquiList.add(condition); } private static void splitJoinCondition( final RexBuilder rexBuilder, final int leftFieldCount, RexNode condition, List leftKeys, List rightKeys, @Nullable List filterNulls, List nonEquiList) { if (condition instanceof RexCall) { RexCall call = (RexCall) condition; SqlKind kind = call.getKind(); if (kind == SqlKind.AND) { for (RexNode operand : call.getOperands()) { splitJoinCondition( rexBuilder, leftFieldCount, operand, leftKeys, rightKeys, filterNulls, nonEquiList); } return; } if (filterNulls != null) { call = collapseExpandedIsNotDistinctFromExpr(call, rexBuilder); kind = call.getKind(); } // "=" and "IS NOT DISTINCT FROM" are the same except for how they // treat nulls. if (kind == SqlKind.EQUALS || (filterNulls != null && kind == SqlKind.IS_NOT_DISTINCT_FROM)) { final List operands = call.getOperands(); if ((operands.get(0) instanceof RexInputRef) && (operands.get(1) instanceof RexInputRef)) { RexInputRef op0 = (RexInputRef) operands.get(0); RexInputRef op1 = (RexInputRef) operands.get(1); RexInputRef leftField; RexInputRef rightField; if ((op0.getIndex() < leftFieldCount) && (op1.getIndex() >= leftFieldCount)) { // Arguments were of form 'op0 = op1' leftField = op0; rightField = op1; } else if ( (op1.getIndex() < leftFieldCount) && (op0.getIndex() >= leftFieldCount)) { // Arguments were of form 'op1 = op0' leftField = op1; rightField = op0; } else { nonEquiList.add(condition); return; } leftKeys.add(leftField.getIndex()); rightKeys.add(rightField.getIndex() - leftFieldCount); if (filterNulls != null) { filterNulls.add(kind == SqlKind.EQUALS); } return; } // Arguments were not field references, one from each side, so // we fail. Fall through. } } // Add this condition to the list of non-equi-join conditions. if (!condition.isAlwaysTrue()) { nonEquiList.add(condition); } } /** * Collapses an expanded version of {@code IS NOT DISTINCT FROM} expression. * *

Helper method for * {@link #splitJoinCondition(RexBuilder, int, RexNode, List, List, List, List)} * and * {@link #splitJoinCondition(List, List, RexNode, List, List, List, List)}. * *

If the given expr call is an expanded version of * {@code IS NOT DISTINCT FROM} function call, collapses it and return a * {@code IS NOT DISTINCT FROM} function call. * *

For example: {@code t1.key IS NOT DISTINCT FROM t2.key} * can rewritten in expanded form as * {@code t1.key = t2.key OR (t1.key IS NULL AND t2.key IS NULL)}. * * @param call Function expression to try collapsing * @param rexBuilder {@link RexBuilder} instance to create new {@link RexCall} instances. * @return If the given function is an expanded IS NOT DISTINCT FROM function call, * return a IS NOT DISTINCT FROM function call. Otherwise return the input * function call as it is. */ public static RexCall collapseExpandedIsNotDistinctFromExpr(final RexCall call, final RexBuilder rexBuilder) { switch (call.getKind()) { case OR: return doCollapseExpandedIsNotDistinctFromOrExpr(call, rexBuilder); case CASE: return doCollapseExpandedIsNotDistinctFromCaseExpr(call, rexBuilder); default: return call; } } private static RexCall doCollapseExpandedIsNotDistinctFromOrExpr(final RexCall call, final RexBuilder rexBuilder) { if (call.getKind() != SqlKind.OR || call.getOperands().size() != 2) { return call; } final RexNode op0 = call.getOperands().get(0); final RexNode op1 = call.getOperands().get(1); if (!(op0 instanceof RexCall) || !(op1 instanceof RexCall)) { return call; } RexCall opEqCall = (RexCall) op0; RexCall opNullEqCall = (RexCall) op1; // Swapping the operands if necessary if (opEqCall.getKind() == SqlKind.AND && (opNullEqCall.getKind() == SqlKind.EQUALS || opNullEqCall.getKind() == SqlKind.IS_TRUE)) { RexCall temp = opEqCall; opEqCall = opNullEqCall; opNullEqCall = temp; } // Check if EQUALS is actually wrapped in IS TRUE expression if (opEqCall.getKind() == SqlKind.IS_TRUE) { RexNode tmp = opEqCall.getOperands().get(0); if (!(tmp instanceof RexCall)) { return call; } opEqCall = (RexCall) tmp; } if (opNullEqCall.getKind() != SqlKind.AND || opNullEqCall.getOperands().size() != 2 || opEqCall.getKind() != SqlKind.EQUALS) { return call; } final RexNode op10 = opNullEqCall.getOperands().get(0); final RexNode op11 = opNullEqCall.getOperands().get(1); if (op10.getKind() != SqlKind.IS_NULL || op11.getKind() != SqlKind.IS_NULL) { return call; } return doCollapseExpandedIsNotDistinctFrom(rexBuilder, call, (RexCall) op10, (RexCall) op11, opEqCall); } private static RexCall doCollapseExpandedIsNotDistinctFromCaseExpr(final RexCall call, final RexBuilder rexBuilder) { if (call.getKind() != SqlKind.CASE || call.getOperands().size() != 5) { return call; } final RexNode op0 = call.getOperands().get(0); final RexNode op1 = call.getOperands().get(1); final RexNode op2 = call.getOperands().get(2); final RexNode op3 = call.getOperands().get(3); final RexNode op4 = call.getOperands().get(4); if (!(op0 instanceof RexCall) || !(op1 instanceof RexCall) || !(op2 instanceof RexCall) || !(op3 instanceof RexCall) || !(op4 instanceof RexCall)) { return call; } RexCall ifCall = (RexCall) op0; RexCall thenCall = (RexCall) op1; RexCall elseIfCall = (RexCall) op2; RexCall elseIfThenCall = (RexCall) op3; RexCall elseCall = (RexCall) op4; if (ifCall.getKind() != SqlKind.IS_NULL || thenCall.getKind() != SqlKind.IS_NULL || elseIfCall.getKind() != SqlKind.IS_NULL || elseIfThenCall.getKind() != SqlKind.IS_NULL || elseCall.getKind() != SqlKind.EQUALS) { return call; } if (!ifCall.equals(elseIfThenCall) || !thenCall.equals(elseIfCall)) { return call; } return doCollapseExpandedIsNotDistinctFrom(rexBuilder, call, ifCall, elseIfCall, elseCall); } private static RexCall doCollapseExpandedIsNotDistinctFrom(final RexBuilder rexBuilder, final RexCall call, RexCall ifNull0Call, RexCall ifNull1Call, RexCall equalsCall) { final RexNode isNullInput0 = ifNull0Call.getOperands().get(0); final RexNode isNullInput1 = ifNull1Call.getOperands().get(0); final RexNode equalsInput0 = RexUtil .removeNullabilityCast(rexBuilder.getTypeFactory(), equalsCall.getOperands().get(0)); final RexNode equalsInput1 = RexUtil .removeNullabilityCast(rexBuilder.getTypeFactory(), equalsCall.getOperands().get(1)); if ((isNullInput0.equals(equalsInput0) && isNullInput1.equals(equalsInput1)) || (isNullInput1.equals(equalsInput0) && isNullInput0.equals(equalsInput1))) { return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM, ImmutableList.of(isNullInput0, isNullInput1)); } return call; } @Deprecated // to be removed before 2.0 public static void projectJoinInputs( RelNode[] inputRels, List leftJoinKeys, List rightJoinKeys, int systemColCount, List leftKeys, List rightKeys, List outputProj) { RelNode leftRel = inputRels[0]; RelNode rightRel = inputRels[1]; final RelOptCluster cluster = leftRel.getCluster(); final RexBuilder rexBuilder = cluster.getRexBuilder(); int origLeftInputSize = leftRel.getRowType().getFieldCount(); int origRightInputSize = rightRel.getRowType().getFieldCount(); final List newLeftFields = new ArrayList<>(); final List<@Nullable String> newLeftFieldNames = new ArrayList<>(); final List newRightFields = new ArrayList<>(); final List<@Nullable String> newRightFieldNames = new ArrayList<>(); int leftKeyCount = leftJoinKeys.size(); int rightKeyCount = rightJoinKeys.size(); int i; for (i = 0; i < systemColCount; i++) { outputProj.add(i); } for (i = 0; i < origLeftInputSize; i++) { final RelDataTypeField field = leftRel.getRowType().getFieldList().get(i); newLeftFields.add(rexBuilder.makeInputRef(field.getType(), i)); newLeftFieldNames.add(field.getName()); outputProj.add(systemColCount + i); } int newLeftKeyCount = 0; for (i = 0; i < leftKeyCount; i++) { RexNode leftKey = leftJoinKeys.get(i); if (leftKey instanceof RexInputRef) { // already added to the projected left fields // only need to remember the index in the join key list leftKeys.add(((RexInputRef) leftKey).getIndex()); } else { newLeftFields.add(leftKey); newLeftFieldNames.add(null); leftKeys.add(origLeftInputSize + newLeftKeyCount); newLeftKeyCount++; } } int leftFieldCount = origLeftInputSize + newLeftKeyCount; for (i = 0; i < origRightInputSize; i++) { final RelDataTypeField field = rightRel.getRowType().getFieldList().get(i); newRightFields.add(rexBuilder.makeInputRef(field.getType(), i)); newRightFieldNames.add(field.getName()); outputProj.add(systemColCount + leftFieldCount + i); } int newRightKeyCount = 0; for (i = 0; i < rightKeyCount; i++) { RexNode rightKey = rightJoinKeys.get(i); if (rightKey instanceof RexInputRef) { // already added to the projected left fields // only need to remember the index in the join key list rightKeys.add(((RexInputRef) rightKey).getIndex()); } else { newRightFields.add(rightKey); newRightFieldNames.add(null); rightKeys.add(origRightInputSize + newRightKeyCount); newRightKeyCount++; } } final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(cluster, null); // added project if need to produce new keys than the original input // fields if (newLeftKeyCount > 0) { leftRel = relBuilder.push(leftRel) .project(newLeftFields, newLeftFieldNames, true) .build(); } if (newRightKeyCount > 0) { rightRel = relBuilder.push(rightRel) .project(newRightFields, newRightFieldNames) .build(); } inputRels[0] = leftRel; inputRels[1] = rightRel; } @Deprecated // to be removed before 2.0 public static RelNode createProjectJoinRel( List outputProj, RelNode joinRel) { int newProjectOutputSize = outputProj.size(); List joinOutputFields = joinRel.getRowType().getFieldList(); // If no projection was passed in, or the number of desired projection // columns is the same as the number of columns returned from the // join, then no need to create a projection if ((newProjectOutputSize > 0) && (newProjectOutputSize < joinOutputFields.size())) { final List> newProjects = new ArrayList<>(); final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(joinRel.getCluster(), null); final RexBuilder rexBuilder = relBuilder.getRexBuilder(); for (int fieldIndex : outputProj) { final RelDataTypeField field = joinOutputFields.get(fieldIndex); newProjects.add( Pair.of( rexBuilder.makeInputRef(field.getType(), fieldIndex), field.getName())); } // Create a project rel on the output of the join. return relBuilder.push(joinRel) .project(Pair.left(newProjects), Pair.right(newProjects), true) .build(); } return joinRel; } @Deprecated // to be removed before 2.0 public static void registerAbstractRels(RelOptPlanner planner) { registerAbstractRules(planner); } @Experimental public static void registerAbstractRules(RelOptPlanner planner) { RelOptRules.ABSTRACT_RULES.forEach(planner::addRule); } @Experimental public static void registerAbstractRelationalRules(RelOptPlanner planner) { RelOptRules.ABSTRACT_RELATIONAL_RULES.forEach(planner::addRule); if (CalciteSystemProperty.COMMUTE.value()) { planner.addRule(CoreRules.JOIN_ASSOCIATE); } // todo: rule which makes Project({OrdinalRef}) disappear } private static void registerEnumerableRules(RelOptPlanner planner) { EnumerableRules.ENUMERABLE_RULES.forEach(planner::addRule); } private static void registerBaseRules(RelOptPlanner planner) { RelOptRules.BASE_RULES.forEach(planner::addRule); } @SuppressWarnings("unused") private static void registerReductionRules(RelOptPlanner planner) { RelOptRules.CONSTANT_REDUCTION_RULES.forEach(planner::addRule); } private static void registerMaterializationRules(RelOptPlanner planner) { RelOptRules.MATERIALIZATION_RULES.forEach(planner::addRule); } @SuppressWarnings("unused") private static void registerCalcRules(RelOptPlanner planner) { RelOptRules.CALC_RULES.forEach(planner::addRule); } @Experimental public static void registerDefaultRules(RelOptPlanner planner, boolean enableMaterializations, boolean enableBindable) { if (CalciteSystemProperty.ENABLE_COLLATION_TRAIT.value()) { registerAbstractRelationalRules(planner); } registerAbstractRules(planner); registerBaseRules(planner); if (enableMaterializations) { registerMaterializationRules(planner); } if (enableBindable) { for (RelOptRule rule : Bindables.RULES) { planner.addRule(rule); } } // Registers this rule for default ENUMERABLE convention // because: // 1. ScannableTable can bind data directly; // 2. Only BindableTable supports project push down now. // EnumerableInterpreterRule.INSTANCE would then transform // the BindableTableScan to // EnumerableInterpreter + BindableTableScan. // Note: the cost of EnumerableInterpreter + BindableTableScan // is always bigger that EnumerableTableScan because of the additional // EnumerableInterpreter node, but if there are pushing projects or filter, // we prefer BindableTableScan instead, // see BindableTableScan#computeSelfCost. planner.addRule(Bindables.BINDABLE_TABLE_SCAN_RULE); planner.addRule(CoreRules.PROJECT_TABLE_SCAN); planner.addRule(CoreRules.PROJECT_INTERPRETER_TABLE_SCAN); if (CalciteSystemProperty.ENABLE_ENUMERABLE.value()) { registerEnumerableRules(planner); planner.addRule(EnumerableRules.TO_INTERPRETER); } if (enableBindable && CalciteSystemProperty.ENABLE_ENUMERABLE.value()) { planner.addRule(EnumerableRules.TO_BINDABLE); } if (CalciteSystemProperty.ENABLE_STREAM.value()) { for (RelOptRule rule : StreamRules.RULES) { planner.addRule(rule); } } planner.addRule(CoreRules.FILTER_REDUCE_EXPRESSIONS); } /** * Dumps a plan as a string. * * @param header Header to print before the plan. Ignored if the format * is XML * @param rel Relational expression to explain * @param format Output format * @param detailLevel Detail level * @return Plan */ public static String dumpPlan( String header, RelNode rel, SqlExplainFormat format, SqlExplainLevel detailLevel) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); if (!header.equals("")) { pw.println(header); } RelWriter planWriter; switch (format) { case XML: planWriter = new RelXmlWriter(pw, detailLevel); break; case JSON: planWriter = new RelJsonWriter(); rel.explain(planWriter); return ((RelJsonWriter) planWriter).asString(); case DOT: planWriter = new RelDotWriter(pw, detailLevel, false); break; default: planWriter = new RelWriterImpl(pw, detailLevel, false); } rel.explain(planWriter); pw.flush(); return sw.toString(); } @Deprecated // to be removed before 2.0 public static String dumpPlan( String header, RelNode rel, boolean asXml, SqlExplainLevel detailLevel) { return dumpPlan(header, rel, asXml ? SqlExplainFormat.XML : SqlExplainFormat.TEXT, detailLevel); } /** * Creates the row type descriptor for the result of a DML operation, which * is a single column named ROWCOUNT of type BIGINT for INSERT; * a single column named PLAN for EXPLAIN. * * @param kind Kind of node * @param typeFactory factory to use for creating type descriptor * @return created type */ public static RelDataType createDmlRowType( SqlKind kind, RelDataTypeFactory typeFactory) { switch (kind) { case INSERT: case DELETE: case UPDATE: return typeFactory.createStructType( ImmutableList.of( Pair.of(AvaticaConnection.ROWCOUNT_COLUMN_NAME, typeFactory.createSqlType(SqlTypeName.BIGINT)))); case EXPLAIN: return typeFactory.createStructType( ImmutableList.of( Pair.of(AvaticaConnection.PLAN_COLUMN_NAME, typeFactory.createSqlType( SqlTypeName.VARCHAR, RelDataType.PRECISION_NOT_SPECIFIED)))); default: throw Util.unexpected(kind); } } /** * Returns whether two types are equal using 'equals'. * * @param desc1 Description of first type * @param type1 First type * @param desc2 Description of second type * @param type2 Second type * @param litmus What to do if an error is detected (types are not equal) * @return Whether the types are equal */ public static boolean eq( final String desc1, RelDataType type1, final String desc2, RelDataType type2, Litmus litmus) { // if any one of the types is ANY return true if (type1.getSqlTypeName() == SqlTypeName.ANY || type2.getSqlTypeName() == SqlTypeName.ANY) { return litmus.succeed(); } if (!type1.equals(type2)) { return litmus.fail("type mismatch:\n{}:\n{}\n{}:\n{}", desc1, type1.getFullTypeString(), desc2, type2.getFullTypeString()); } return litmus.succeed(); } /** * Returns whether two types are equal using * {@link #areRowTypesEqual(RelDataType, RelDataType, boolean)}. Both types * must not be null. * * @param desc1 Description of role of first type * @param type1 First type * @param desc2 Description of role of second type * @param type2 Second type * @param litmus Whether to assert if they are not equal * @return Whether the types are equal */ public static boolean equal( final String desc1, RelDataType type1, final String desc2, RelDataType type2, Litmus litmus) { if (!areRowTypesEqual(type1, type2, false)) { return litmus.fail(getFullTypeDifferenceString(desc1, type1, desc2, type2)); } return litmus.succeed(); } /** * Returns the detailed difference of two types. * * @param sourceDesc description of role of source type * @param sourceType source type * @param targetDesc description of role of target type * @param targetType target type * @return the detailed difference of two types */ public static String getFullTypeDifferenceString( final String sourceDesc, RelDataType sourceType, final String targetDesc, RelDataType targetType) { if (sourceType == targetType) { return ""; } final int sourceFieldCount = sourceType.getFieldCount(); final int targetFieldCount = targetType.getFieldCount(); if (sourceFieldCount != targetFieldCount) { return "Type mismatch: the field sizes are not equal.\n" + sourceDesc + ": " + sourceType.getFullTypeString() + "\n" + targetDesc + ": " + targetType.getFullTypeString(); } final StringBuilder stringBuilder = new StringBuilder(); final List f1 = sourceType.getFieldList(); final List f2 = targetType.getFieldList(); for (Pair pair : Pair.zip(f1, f2)) { final RelDataType t1 = pair.left.getType(); final RelDataType t2 = pair.right.getType(); // If one of the types is ANY comparison should succeed if (sourceType.getSqlTypeName() == SqlTypeName.ANY || targetType.getSqlTypeName() == SqlTypeName.ANY) { continue; } if (!t1.equals(t2)) { stringBuilder.append(pair.left.getName()); stringBuilder.append(": "); stringBuilder.append(t1.getFullTypeString()); stringBuilder.append(" -> "); stringBuilder.append(t2.getFullTypeString()); stringBuilder.append("\n"); } } final String difference = stringBuilder.toString(); if (!difference.isEmpty()) { return "Type mismatch:\n" + sourceDesc + ": " + sourceType.getFullTypeString() + "\n" + targetDesc + ": " + targetType.getFullTypeString() + "\n" + "Difference:\n" + difference; } else { return ""; } } /** Returns whether two relational expressions have the same row-type. */ public static boolean equalType(String desc0, RelNode rel0, String desc1, RelNode rel1, Litmus litmus) { // TODO: change 'equal' to 'eq', which is stronger. return equal(desc0, rel0.getRowType(), desc1, rel1.getRowType(), litmus); } /** * Returns a translation of the IS DISTINCT FROM (or IS * NOT DISTINCT FROM) sql operator. * * @param neg if false, returns a translation of IS NOT DISTINCT FROM */ public static RexNode isDistinctFrom( RexBuilder rexBuilder, RexNode x, RexNode y, boolean neg) { RexNode ret = null; if (x.getType().isStruct()) { assert y.getType().isStruct(); List xFields = x.getType().getFieldList(); List yFields = y.getType().getFieldList(); assert xFields.size() == yFields.size(); for (Pair pair : Pair.zip(xFields, yFields)) { RelDataTypeField xField = pair.left; RelDataTypeField yField = pair.right; RexNode newX = rexBuilder.makeFieldAccess( x, xField.getIndex()); RexNode newY = rexBuilder.makeFieldAccess( y, yField.getIndex()); RexNode newCall = isDistinctFromInternal(rexBuilder, newX, newY, neg); if (ret == null) { ret = newCall; } else { ret = rexBuilder.makeCall( SqlStdOperatorTable.AND, ret, newCall); } } } else { ret = isDistinctFromInternal(rexBuilder, x, y, neg); } // The result of IS DISTINCT FROM is NOT NULL because it can // only return TRUE or FALSE. assert ret != null; assert !ret.getType().isNullable(); return ret; } private static RexNode isDistinctFromInternal( RexBuilder rexBuilder, RexNode x, RexNode y, boolean neg) { if (neg) { // x is not distinct from y // x=y IS TRUE or ((x is null) and (y is null)), return rexBuilder.makeCall(SqlStdOperatorTable.OR, rexBuilder.makeCall(SqlStdOperatorTable.AND, rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, x), rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, y)), rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, x, y))); } else { // x is distinct from y // x=y IS NOT TRUE and ((x is not null) or (y is not null)), return rexBuilder.makeCall(SqlStdOperatorTable.AND, rexBuilder.makeCall(SqlStdOperatorTable.OR, rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, x), rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, y)), rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE, rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, x, y))); } } /** * Converts a relational expression to a string, showing just basic * attributes. */ public static String toString(final RelNode rel) { return toString(rel, SqlExplainLevel.EXPPLAN_ATTRIBUTES); } /** * Converts a relational expression to a string; * returns null if and only if {@code rel} is null. */ public static @PolyNull String toString( final @PolyNull RelNode rel, SqlExplainLevel detailLevel) { if (rel == null) { return null; } final StringWriter sw = new StringWriter(); final RelWriter planWriter = new RelWriterImpl( new PrintWriter(sw), detailLevel, false); rel.explain(planWriter); return sw.toString(); } @Deprecated // to be removed before 2.0 public static RelNode renameIfNecessary( RelNode rel, RelDataType desiredRowType) { final RelDataType rowType = rel.getRowType(); if (rowType == desiredRowType) { // Nothing to do. return rel; } assert !rowType.equals(desiredRowType); if (!areRowTypesEqual(rowType, desiredRowType, false)) { // The row types are different ignoring names. Nothing we can do. return rel; } rel = createRename( rel, desiredRowType.getFieldNames()); return rel; } public static String dumpType(RelDataType type) { final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); final TypeDumper typeDumper = new TypeDumper(pw); if (type.isStruct()) { typeDumper.acceptFields(type.getFieldList()); } else { typeDumper.accept(type); } pw.flush(); return sw.toString(); } /** * Returns the set of columns with unique names, with prior columns taking * precedence over columns that appear later in the list. */ public static List deduplicateColumns( List baseColumns, List extendedColumns) { final Set dedupedFieldNames = new HashSet<>(); final ImmutableList.Builder dedupedFields = ImmutableList.builder(); for (RelDataTypeField f : Iterables.concat(baseColumns, extendedColumns)) { if (dedupedFieldNames.add(f.getName())) { dedupedFields.add(f); } } return dedupedFields.build(); } /** * Decomposes a predicate into a list of expressions that are AND'ed * together. * * @param rexPredicate predicate to be analyzed * @param rexList list of decomposed RexNodes */ public static void decomposeConjunction( @Nullable RexNode rexPredicate, List rexList) { if (rexPredicate == null || rexPredicate.isAlwaysTrue()) { return; } if (rexPredicate.isA(SqlKind.AND)) { for (RexNode operand : ((RexCall) rexPredicate).getOperands()) { decomposeConjunction(operand, rexList); } } else { rexList.add(rexPredicate); } } /** * Decomposes a predicate into a list of expressions that are AND'ed * together, and a list of expressions that are preceded by NOT. * *

For example, {@code a AND NOT b AND NOT (c and d) AND TRUE AND NOT * FALSE} returns {@code rexList = [a], notList = [b, c AND d]}.

* *

TRUE and NOT FALSE expressions are ignored. FALSE and NOT TRUE * expressions are placed on {@code rexList} and {@code notList} as other * expressions.

* *

For example, {@code a AND TRUE AND NOT TRUE} returns * {@code rexList = [a], notList = [TRUE]}.

* * @param rexPredicate predicate to be analyzed * @param rexList list of decomposed RexNodes (except those with NOT) * @param notList list of decomposed RexNodes that were prefixed NOT */ public static void decomposeConjunction( @Nullable RexNode rexPredicate, List rexList, List notList) { if (rexPredicate == null || rexPredicate.isAlwaysTrue()) { return; } switch (rexPredicate.getKind()) { case AND: for (RexNode operand : ((RexCall) rexPredicate).getOperands()) { decomposeConjunction(operand, rexList, notList); } break; case NOT: final RexNode e = ((RexCall) rexPredicate).getOperands().get(0); if (e.isAlwaysFalse()) { return; } switch (e.getKind()) { case OR: final List ors = new ArrayList<>(); decomposeDisjunction(e, ors); for (RexNode or : ors) { switch (or.getKind()) { case NOT: rexList.add(((RexCall) or).operands.get(0)); break; default: notList.add(or); } } break; default: notList.add(e); } break; case LITERAL: if (!RexLiteral.isNullLiteral(rexPredicate) && RexLiteral.booleanValue(rexPredicate)) { return; // ignore TRUE } // fall through default: rexList.add(rexPredicate); break; } } /** * Decomposes a predicate into a list of expressions that are OR'ed * together. * * @param rexPredicate predicate to be analyzed * @param rexList list of decomposed RexNodes */ public static void decomposeDisjunction( @Nullable RexNode rexPredicate, List rexList) { if (rexPredicate == null || rexPredicate.isAlwaysFalse()) { return; } if (rexPredicate.isA(SqlKind.OR)) { for (RexNode operand : ((RexCall) rexPredicate).getOperands()) { decomposeDisjunction(operand, rexList); } } else { rexList.add(rexPredicate); } } /** * Returns a condition decomposed by AND. * *

For example, {@code conjunctions(TRUE)} returns the empty list; * {@code conjunctions(FALSE)} returns list {@code {FALSE}}.

*/ public static List conjunctions(@Nullable RexNode rexPredicate) { final List list = new ArrayList<>(); decomposeConjunction(rexPredicate, list); return list; } /** * Returns a condition decomposed by OR. * *

For example, {@code disjunctions(FALSE)} returns the empty list.

*/ public static List disjunctions(RexNode rexPredicate) { final List list = new ArrayList<>(); decomposeDisjunction(rexPredicate, list); return list; } /** * Ands two sets of join filters together, either of which can be null. * * @param rexBuilder rexBuilder to create AND expression * @param left filter on the left that the right will be AND'd to * @param right filter on the right * @return AND'd filter * * @see com.hazelcast.org.apache.calcite.rex.RexUtil#composeConjunction */ public static RexNode andJoinFilters( RexBuilder rexBuilder, @Nullable RexNode left, @Nullable RexNode right) { // don't bother AND'ing in expressions that always evaluate to // true if ((left != null) && !left.isAlwaysTrue()) { if ((right != null) && !right.isAlwaysTrue()) { left = rexBuilder.makeCall( SqlStdOperatorTable.AND, left, right); } } else { left = right; } // Joins must have some filter if (left == null) { left = rexBuilder.makeLiteral(true); } return left; } /** Decomposes the WHERE clause of a view into predicates that constraint * a column to a particular value. * *

This method is key to the validation of a modifiable view. Columns that * are constrained to a single value can be omitted from the * SELECT clause of a modifiable view. * * @param projectMap Mapping from column ordinal to the expression that * populate that column, to be populated by this method * @param filters List of remaining filters, to be populated by this method * @param constraint Constraint to be analyzed */ public static void inferViewPredicates(Map projectMap, List filters, RexNode constraint) { for (RexNode node : conjunctions(constraint)) { switch (node.getKind()) { case EQUALS: final List operands = ((RexCall) node).getOperands(); RexNode o0 = operands.get(0); RexNode o1 = operands.get(1); if (o0 instanceof RexLiteral) { o0 = operands.get(1); o1 = operands.get(0); } if (o0.getKind() == SqlKind.CAST) { o0 = ((RexCall) o0).getOperands().get(0); } if (o0 instanceof RexInputRef && o1 instanceof RexLiteral) { final int index = ((RexInputRef) o0).getIndex(); if (projectMap.get(index) == null) { projectMap.put(index, o1); continue; } } break; default: break; } filters.add(node); } } /** * Returns a mapping of the column ordinal in the underlying table to a column * constraint of the modifiable view. * * @param modifiableViewTable The modifiable view which has a constraint * @param targetRowType The target type */ public static Map getColumnConstraints( ModifiableView modifiableViewTable, RelDataType targetRowType, RelDataTypeFactory typeFactory) { final RexBuilder rexBuilder = new RexBuilder(typeFactory); final RexNode constraint = modifiableViewTable.getConstraint(rexBuilder, targetRowType); final Map projectMap = new HashMap<>(); final List filters = new ArrayList<>(); RelOptUtil.inferViewPredicates(projectMap, filters, constraint); assert filters.isEmpty(); return projectMap; } /** * Ensures that a source value does not violate the constraint of the target * column. * * @param sourceValue The insert value being validated * @param targetConstraint The constraint applied to sourceValue for validation * @param errorSupplier The function to apply when validation fails */ public static void validateValueAgainstConstraint(SqlNode sourceValue, RexNode targetConstraint, Supplier errorSupplier) { if (!(sourceValue instanceof SqlLiteral)) { // We cannot guarantee that the value satisfies the constraint. throw errorSupplier.get(); } final SqlLiteral insertValue = (SqlLiteral) sourceValue; final RexLiteral columnConstraint = (RexLiteral) targetConstraint; final RexSqlStandardConvertletTable convertletTable = new RexSqlStandardConvertletTable(); final RexToSqlNodeConverter sqlNodeToRexConverter = new RexToSqlNodeConverterImpl(convertletTable); final SqlLiteral constraintValue = (SqlLiteral) sqlNodeToRexConverter.convertLiteral(columnConstraint); if (!insertValue.equals(constraintValue)) { // The value does not satisfy the constraint. throw errorSupplier.get(); } } /** * Adjusts key values in a list by some fixed amount. * * @param keys list of key values * @param adjustment the amount to adjust the key values by * @return modified list */ public static List adjustKeys(List keys, int adjustment) { if (adjustment == 0) { return keys; } final List newKeys = new ArrayList<>(); for (int key : keys) { newKeys.add(key + adjustment); } return newKeys; } /** * Simplifies outer joins if filter above would reject nulls. * * @param joinRel Join * @param aboveFilters Filters from above * @param joinType Join type, can not be inner join */ public static JoinRelType simplifyJoin(RelNode joinRel, ImmutableList aboveFilters, JoinRelType joinType) { // No need to simplify if the join only outputs left side. if (!joinType.projectsRight()) { return joinType; } final int nTotalFields = joinRel.getRowType().getFieldCount(); final int nSysFields = 0; final int nFieldsLeft = joinRel.getInputs().get(0).getRowType().getFieldCount(); final int nFieldsRight = joinRel.getInputs().get(1).getRowType().getFieldCount(); assert nTotalFields == nSysFields + nFieldsLeft + nFieldsRight; // set the reference bitmaps for the left and right children ImmutableBitSet leftBitmap = ImmutableBitSet.range(nSysFields, nSysFields + nFieldsLeft); ImmutableBitSet rightBitmap = ImmutableBitSet.range(nSysFields + nFieldsLeft, nTotalFields); for (RexNode filter : aboveFilters) { if (joinType.generatesNullsOnLeft() && Strong.isNotTrue(filter, leftBitmap)) { joinType = joinType.cancelNullsOnLeft(); } if (joinType.generatesNullsOnRight() && Strong.isNotTrue(filter, rightBitmap)) { joinType = joinType.cancelNullsOnRight(); } if (!joinType.isOuterJoin()) { break; } } return joinType; } /** * Classifies filters according to where they should be processed. They * either stay where they are, are pushed to the join (if they originated * from above the join), or are pushed to one of the children. Filters that * are pushed are added to list passed in as input parameters. * * @param joinRel join node * @param filters filters to be classified * @param pushInto whether filters can be pushed into the join * @param pushLeft true if filters can be pushed to the left * @param pushRight true if filters can be pushed to the right * @param joinFilters list of filters to push to the join * @param leftFilters list of filters to push to the left child * @param rightFilters list of filters to push to the right child * @return whether at least one filter was pushed */ public static boolean classifyFilters( RelNode joinRel, List filters, boolean pushInto, boolean pushLeft, boolean pushRight, List joinFilters, List leftFilters, List rightFilters) { RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder(); List joinFields = joinRel.getRowType().getFieldList(); final int nSysFields = 0; // joinRel.getSystemFieldList().size(); final List leftFields = joinRel.getInputs().get(0).getRowType().getFieldList(); final int nFieldsLeft = leftFields.size(); final List rightFields = joinRel.getInputs().get(1).getRowType().getFieldList(); final int nFieldsRight = rightFields.size(); final int nTotalFields = nFieldsLeft + nFieldsRight; // set the reference bitmaps for the left and right children ImmutableBitSet leftBitmap = ImmutableBitSet.range(nSysFields, nSysFields + nFieldsLeft); ImmutableBitSet rightBitmap = ImmutableBitSet.range(nSysFields + nFieldsLeft, nTotalFields); final List filtersToRemove = new ArrayList<>(); for (RexNode filter : filters) { final InputFinder inputFinder = InputFinder.analyze(filter); final ImmutableBitSet inputBits = inputFinder.build(); // REVIEW - are there any expressions that need special handling // and therefore cannot be pushed? if (pushLeft && leftBitmap.contains(inputBits)) { // ignore filters that always evaluate to true if (!filter.isAlwaysTrue()) { // adjust the field references in the filter to reflect // that fields in the left now shift over by the number // of system fields final RexNode shiftedFilter = shiftFilter( nSysFields, nSysFields + nFieldsLeft, -nSysFields, rexBuilder, joinFields, nTotalFields, leftFields, filter); leftFilters.add(shiftedFilter); } filtersToRemove.add(filter); } else if (pushRight && rightBitmap.contains(inputBits)) { if (!filter.isAlwaysTrue()) { // adjust the field references in the filter to reflect // that fields in the right now shift over to the left final RexNode shiftedFilter = shiftFilter( nSysFields + nFieldsLeft, nTotalFields, -(nSysFields + nFieldsLeft), rexBuilder, joinFields, nTotalFields, rightFields, filter); rightFilters.add(shiftedFilter); } filtersToRemove.add(filter); } else { // If the filter can't be pushed to either child, we may push them into the join if (pushInto) { if (!joinFilters.contains(filter)) { joinFilters.add(filter); } filtersToRemove.add(filter); } } } // Remove filters after the loop, to prevent concurrent modification. if (!filtersToRemove.isEmpty()) { filters.removeAll(filtersToRemove); } // Did anything change? return !filtersToRemove.isEmpty(); } /** * Classifies filters according to where they should be processed. They * either stay where they are, are pushed to the join (if they originated * from above the join), or are pushed to one of the children. Filters that * are pushed are added to list passed in as input parameters. * * @param joinRel join node * @param filters filters to be classified * @param joinType join type * @param pushInto whether filters can be pushed into the ON clause * @param pushLeft true if filters can be pushed to the left * @param pushRight true if filters can be pushed to the right * @param joinFilters list of filters to push to the join * @param leftFilters list of filters to push to the left child * @param rightFilters list of filters to push to the right child * @return whether at least one filter was pushed * * @deprecated Use * {@link RelOptUtil#classifyFilters(RelNode, List, boolean, boolean, boolean, List, List, List)} */ @Deprecated // to be removed before 2.0 public static boolean classifyFilters( RelNode joinRel, List filters, JoinRelType joinType, boolean pushInto, boolean pushLeft, boolean pushRight, List joinFilters, List leftFilters, List rightFilters) { RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder(); List joinFields = joinRel.getRowType().getFieldList(); final int nTotalFields = joinFields.size(); final int nSysFields = 0; // joinRel.getSystemFieldList().size(); final List leftFields = joinRel.getInputs().get(0).getRowType().getFieldList(); final int nFieldsLeft = leftFields.size(); final List rightFields = joinRel.getInputs().get(1).getRowType().getFieldList(); final int nFieldsRight = rightFields.size(); // SemiJoin, CorrelateSemiJoin, CorrelateAntiJoin: right fields are not returned assert nTotalFields == (!joinType.projectsRight() ? nSysFields + nFieldsLeft : nSysFields + nFieldsLeft + nFieldsRight); // set the reference bitmaps for the left and right children ImmutableBitSet leftBitmap = ImmutableBitSet.range(nSysFields, nSysFields + nFieldsLeft); ImmutableBitSet rightBitmap = ImmutableBitSet.range(nSysFields + nFieldsLeft, nTotalFields); final List filtersToRemove = new ArrayList<>(); for (RexNode filter : filters) { final InputFinder inputFinder = InputFinder.analyze(filter); final ImmutableBitSet inputBits = inputFinder.build(); // REVIEW - are there any expressions that need special handling // and therefore cannot be pushed? // filters can be pushed to the left child if the left child // does not generate NULLs and the only columns referenced in // the filter originate from the left child if (pushLeft && leftBitmap.contains(inputBits)) { // ignore filters that always evaluate to true if (!filter.isAlwaysTrue()) { // adjust the field references in the filter to reflect // that fields in the left now shift over by the number // of system fields final RexNode shiftedFilter = shiftFilter( nSysFields, nSysFields + nFieldsLeft, -nSysFields, rexBuilder, joinFields, nTotalFields, leftFields, filter); leftFilters.add(shiftedFilter); } filtersToRemove.add(filter); // filters can be pushed to the right child if the right child // does not generate NULLs and the only columns referenced in // the filter originate from the right child } else if (pushRight && rightBitmap.contains(inputBits)) { if (!filter.isAlwaysTrue()) { // adjust the field references in the filter to reflect // that fields in the right now shift over to the left; // since we never push filters to a NULL generating // child, the types of the source should match the dest // so we don't need to explicitly pass the destination // fields to RexInputConverter final RexNode shiftedFilter = shiftFilter( nSysFields + nFieldsLeft, nTotalFields, -(nSysFields + nFieldsLeft), rexBuilder, joinFields, nTotalFields, rightFields, filter); rightFilters.add(shiftedFilter); } filtersToRemove.add(filter); } else { // If the filter can't be pushed to either child and the join // is an inner join, push them to the join if they originated // from above the join if (!joinType.isOuterJoin() && pushInto) { if (!joinFilters.contains(filter)) { joinFilters.add(filter); } filtersToRemove.add(filter); } } } // Remove filters after the loop, to prevent concurrent modification. if (!filtersToRemove.isEmpty()) { filters.removeAll(filtersToRemove); } // Did anything change? return !filtersToRemove.isEmpty(); } private static RexNode shiftFilter( int start, int end, int offset, RexBuilder rexBuilder, List joinFields, int nTotalFields, List rightFields, RexNode filter) { int[] adjustments = new int[nTotalFields]; for (int i = start; i < end; i++) { adjustments[i] = offset; } return filter.accept( new RexInputConverter( rexBuilder, joinFields, rightFields, adjustments)); } /** * Splits a filter into two lists, depending on whether or not the filter * only references its child input. * * @param childBitmap Fields in the child * @param predicate filters that will be split * @param pushable returns the list of filters that can be pushed to the * child input * @param notPushable returns the list of filters that cannot be pushed to * the child input */ public static void splitFilters( ImmutableBitSet childBitmap, @Nullable RexNode predicate, List pushable, List notPushable) { // for each filter, if the filter only references the child inputs, // then it can be pushed for (RexNode filter : conjunctions(predicate)) { ImmutableBitSet filterRefs = InputFinder.bits(filter); if (childBitmap.contains(filterRefs)) { pushable.add(filter); } else { notPushable.add(filter); } } } @Deprecated // to be removed before 2.0 public static boolean checkProjAndChildInputs( Project project, boolean checkNames) { int n = project.getProjects().size(); RelDataType inputType = project.getInput().getRowType(); if (inputType.getFieldList().size() != n) { return false; } List projFields = project.getRowType().getFieldList(); List inputFields = inputType.getFieldList(); boolean namesDifferent = false; for (int i = 0; i < n; ++i) { RexNode exp = project.getProjects().get(i); if (!(exp instanceof RexInputRef)) { return false; } RexInputRef fieldAccess = (RexInputRef) exp; if (i != fieldAccess.getIndex()) { // can't support reorder yet return false; } if (checkNames) { String inputFieldName = inputFields.get(i).getName(); String projFieldName = projFields.get(i).getName(); if (!projFieldName.equals(inputFieldName)) { namesDifferent = true; } } } // inputs are the same; return value depends on the checkNames // parameter return !checkNames || namesDifferent; } /** * Creates projection expressions reflecting the swapping of a join's input. * * @param newJoin the RelNode corresponding to the join with its inputs * swapped * @param origJoin original LogicalJoin * @param origOrder if true, create the projection expressions to reflect * the original (pre-swapped) join projection; otherwise, * create the projection to reflect the order of the swapped * projection * @return array of expression representing the swapped join inputs */ public static List createSwappedJoinExprs( RelNode newJoin, Join origJoin, boolean origOrder) { final List newJoinFields = newJoin.getRowType().getFieldList(); final RexBuilder rexBuilder = newJoin.getCluster().getRexBuilder(); final List exps = new ArrayList<>(); final int nFields = origOrder ? origJoin.getRight().getRowType().getFieldCount() : origJoin.getLeft().getRowType().getFieldCount(); for (int i = 0; i < newJoinFields.size(); i++) { final int source = (i + nFields) % newJoinFields.size(); RelDataTypeField field = origOrder ? newJoinFields.get(source) : newJoinFields.get(i); exps.add(rexBuilder.makeInputRef(field.getType(), source)); } return exps; } @Deprecated // to be removed before 2.0 public static RexNode pushFilterPastProject(RexNode filter, final Project projRel) { return pushPastProject(filter, projRel); } /** * Converts an expression that is based on the output fields of a * {@link Project} to an equivalent expression on the Project's * input fields. * * @param node The expression to be converted * @param project Project underneath the expression * @return converted expression */ public static RexNode pushPastProject(RexNode node, Project project) { return node.accept(pushShuttle(project)); } /** * Converts a list of expressions that are based on the output fields of a * {@link Project} to equivalent expressions on the Project's * input fields. * * @param nodes The expressions to be converted * @param project Project underneath the expression * @return converted expressions */ public static List pushPastProject(List nodes, Project project) { return pushShuttle(project).visitList(nodes); } /** As {@link #pushPastProject}, but returns null if the resulting expressions * are significantly more complex. * * @param bloat Maximum allowable increase in complexity */ public static @Nullable List pushPastProjectUnlessBloat( List nodes, Project project, int bloat) { if (bloat < 0) { // If bloat is negative never merge. return null; } if (RexOver.containsOver(nodes, null) && project.containsOver()) { // Is it valid relational algebra to apply windowed function to a windowed // function? Possibly. But it's invalid SQL, so don't go there. return null; } final List list = pushPastProject(nodes, project); final int bottomCount = RexUtil.nodeCount(project.getProjects()); final int topCount = RexUtil.nodeCount(nodes); final int mergedCount = RexUtil.nodeCount(list); if (mergedCount > bottomCount + topCount + bloat) { // The merged expression is more complex than the input expressions. // Do not merge. return null; } return list; } private static RexShuttle pushShuttle(final Project project) { return new RexShuttle() { @Override public RexNode visitInputRef(RexInputRef ref) { return project.getProjects().get(ref.getIndex()); } }; } /** * Converts an expression that is based on the output fields of a * {@link Calc} to an equivalent expression on the Calc's input fields. * * @param node The expression to be converted * @param calc Calc underneath the expression * @return converted expression */ public static RexNode pushPastCalc(RexNode node, Calc calc) { return node.accept(pushShuttle(calc)); } private static RexShuttle pushShuttle(final Calc calc) { final List projects = Util.transform(calc.getProgram().getProjectList(), calc.getProgram()::expandLocalRef); return new RexShuttle() { @Override public RexNode visitInputRef(RexInputRef ref) { return projects.get(ref.getIndex()); } }; } /** * Creates a new {@link com.hazelcast.org.apache.calcite.rel.rules.MultiJoin} to reflect * projection references from a * {@link Project} that is on top of the * {@link com.hazelcast.org.apache.calcite.rel.rules.MultiJoin}. * * @param multiJoin the original MultiJoin * @param project the Project on top of the MultiJoin * @return the new MultiJoin */ public static MultiJoin projectMultiJoin( MultiJoin multiJoin, Project project) { // Locate all input references in the projection expressions as well // the post-join filter. Since the filter effectively sits in // between the LogicalProject and the MultiJoin, the projection needs // to include those filter references. ImmutableBitSet inputRefs = InputFinder.bits(project.getProjects(), multiJoin.getPostJoinFilter()); // create new copies of the bitmaps List multiJoinInputs = multiJoin.getInputs(); List newProjFields = new ArrayList<>(); for (RelNode multiJoinInput : multiJoinInputs) { newProjFields.add( new BitSet(multiJoinInput.getRowType().getFieldCount())); } // set the bits found in the expressions int currInput = -1; int startField = 0; int nFields = 0; for (int bit : inputRefs) { while (bit >= (startField + nFields)) { startField += nFields; currInput++; assert currInput < multiJoinInputs.size(); nFields = multiJoinInputs.get(currInput).getRowType().getFieldCount(); } newProjFields.get(currInput).set(bit - startField); } // create a new MultiJoin containing the new field bitmaps // for each input return new MultiJoin( multiJoin.getCluster(), multiJoin.getInputs(), multiJoin.getJoinFilter(), multiJoin.getRowType(), multiJoin.isFullOuterJoin(), multiJoin.getOuterJoinConditions(), multiJoin.getJoinTypes(), Util.transform(newProjFields, ImmutableBitSet::fromBitSet), multiJoin.getJoinFieldRefCountsMap(), multiJoin.getPostJoinFilter()); } public static T addTrait( T rel, RelTrait trait) { //noinspection unchecked return (T) rel.copy( rel.getTraitSet().replace(trait), rel.getInputs()); } /** * Returns a shallow copy of a relational expression with a particular * input replaced. */ public static RelNode replaceInput( RelNode parent, int ordinal, RelNode newInput) { final List inputs = new ArrayList<>(parent.getInputs()); if (inputs.get(ordinal) == newInput) { return parent; } inputs.set(ordinal, newInput); return parent.copy(parent.getTraitSet(), inputs); } /** * Creates a {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalProject} that * projects particular fields of its input, according to a mapping. */ public static RelNode createProject( RelNode child, Mappings.TargetMapping mapping) { return createProject(child, Mappings.asListNonNull(mapping.inverse())); } public static RelNode createProject(RelNode child, Mappings.TargetMapping mapping, RelFactories.ProjectFactory projectFactory) { return createProject(projectFactory, child, Mappings.asListNonNull(mapping.inverse())); } /** Returns whether relational expression {@code target} occurs within a * relational expression {@code ancestor}. */ public static boolean contains(RelNode ancestor, final RelNode target) { if (ancestor == target) { // Short-cut common case. return true; } try { new RelVisitor() { @Override public void visit(RelNode node, int ordinal, @Nullable RelNode parent) { if (node == target) { throw Util.FoundOne.NULL; } super.visit(node, ordinal, parent); } // CHECKSTYLE: IGNORE 1 }.go(ancestor); return false; } catch (Util.FoundOne e) { return true; } } /** Within a relational expression {@code query}, replaces occurrences of * {@code find} with {@code replace}. */ public static RelNode replace(RelNode query, RelNode find, RelNode replace) { if (find == replace) { // Short-cut common case. return query; } assert equalType("find", find, "replace", replace, Litmus.THROW); if (query == find) { // Short-cut another common case. return replace; } return replaceRecurse(query, find, replace); } /** Helper for {@link #replace}. */ private static RelNode replaceRecurse( RelNode query, RelNode find, RelNode replace) { if (query == find) { return replace; } final List inputs = query.getInputs(); if (!inputs.isEmpty()) { final List newInputs = new ArrayList<>(); for (RelNode input : inputs) { newInputs.add(replaceRecurse(input, find, replace)); } if (!newInputs.equals(inputs)) { return query.copy(query.getTraitSet(), newInputs); } } return query; } @Deprecated // to be removed before 2.0 public static RelOptTable.ToRelContext getContext(RelOptCluster cluster) { return ViewExpanders.simpleContext(cluster); } /** Returns the number of {@link com.hazelcast.org.apache.calcite.rel.core.Join} nodes in a * tree. */ public static int countJoins(RelNode rootRel) { /** Visitor that counts join nodes. */ class JoinCounter extends RelVisitor { int joinCount; @Override public void visit(RelNode node, int ordinal, @com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable RelNode parent) { if (node instanceof Join) { ++joinCount; } super.visit(node, ordinal, parent); } int run(RelNode node) { go(node); return joinCount; } } return new JoinCounter().run(rootRel); } /** Permutes a record type according to a mapping. */ public static RelDataType permute(RelDataTypeFactory typeFactory, RelDataType rowType, Mapping mapping) { return typeFactory.createStructType( Mappings.apply3(mapping, rowType.getFieldList())); } @Deprecated // to be removed before 2.0 public static RelNode createProject( RelNode child, List exprList, List fieldNameList) { final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(child.getCluster(), null); return relBuilder.push(child) .project(exprList, fieldNameList, true) .build(); } @Deprecated // to be removed before 2.0 public static RelNode createProject( RelNode child, List> projectList, boolean optimize) { final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(child.getCluster(), null); return relBuilder.push(child) .projectNamed(Pair.left(projectList), Pair.right(projectList), !optimize) .build(); } /** * Creates a relational expression that projects the given fields of the * input. * *

Optimizes if the fields are the identity projection.

* * @param child Input relational expression * @param posList Source of each projected field * @return Relational expression that projects given fields */ public static RelNode createProject(final RelNode child, final List posList) { return createProject( RelFactories.DEFAULT_PROJECT_FACTORY, child, posList); } @Deprecated // to be removed before 2.0 public static RelNode createProject( RelNode child, List exprs, List fieldNames, boolean optimize) { final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(child.getCluster(), null); return relBuilder.push(child) .projectNamed(exprs, fieldNames, !optimize) .build(); } // CHECKSTYLE: IGNORE 1 /** @deprecated Use * {@link RelBuilder#projectNamed(Iterable, Iterable, boolean)} */ @Deprecated // to be removed before 2.0 public static RelNode createProject( RelNode child, List exprs, List fieldNames, boolean optimize, RelBuilder relBuilder) { return relBuilder.push(child) .projectNamed(exprs, fieldNames, !optimize) .build(); } @Deprecated // to be removed before 2.0 public static RelNode createRename( RelNode rel, List fieldNames) { final List fields = rel.getRowType().getFieldList(); assert fieldNames.size() == fields.size(); final List refs = new AbstractList() { @Override public int size() { return fields.size(); } @Override public RexNode get(int index) { return RexInputRef.of(index, fields); } }; final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(rel.getCluster(), null); return relBuilder.push(rel) .projectNamed(refs, fieldNames, false) .build(); } /** * Creates a relational expression which permutes the output fields of a * relational expression according to a permutation. * *

Optimizations:

* *
    *
  • If the relational expression is a * {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalCalc} or * {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalProject} that is already * acting as a permutation, combines the new permutation with the old;
  • * *
  • If the permutation is the identity, returns the original relational * expression.
  • *
* *

If a permutation is combined with its inverse, these optimizations * would combine to remove them both. * * @param rel Relational expression * @param permutation Permutation to apply to fields * @param fieldNames Field names; if null, or if a particular entry is null, * the name of the permuted field is used * @return relational expression which permutes its input fields */ public static RelNode permute( RelNode rel, Permutation permutation, @Nullable List fieldNames) { if (permutation.isIdentity()) { return rel; } if (rel instanceof LogicalCalc) { LogicalCalc calc = (LogicalCalc) rel; Permutation permutation1 = calc.getProgram().getPermutation(); if (permutation1 != null) { Permutation permutation2 = permutation.product(permutation1); return permute(rel, permutation2, null); } } if (rel instanceof LogicalProject) { Permutation permutation1 = ((LogicalProject) rel).getPermutation(); if (permutation1 != null) { Permutation permutation2 = permutation.product(permutation1); return permute(rel, permutation2, null); } } final List outputTypeList = new ArrayList<>(); final List outputNameList = new ArrayList<>(); final List exprList = new ArrayList<>(); final List projectRefList = new ArrayList<>(); final List fields = rel.getRowType().getFieldList(); final RelOptCluster cluster = rel.getCluster(); for (int i = 0; i < permutation.getTargetCount(); i++) { int target = permutation.getTarget(i); final RelDataTypeField targetField = fields.get(target); outputTypeList.add(targetField.getType()); outputNameList.add( ((fieldNames == null) || (fieldNames.size() <= i) || (fieldNames.get(i) == null)) ? targetField.getName() : fieldNames.get(i)); exprList.add( cluster.getRexBuilder().makeInputRef(fields.get(i).getType(), i)); final int source = permutation.getSource(i); projectRefList.add( new RexLocalRef( source, fields.get(source).getType())); } final RelDataTypeFactory typeFactory = cluster.getTypeFactory(); final RexProgram program = new RexProgram( rel.getRowType(), exprList, projectRefList, null, typeFactory.createStructType(outputTypeList, outputNameList)); return LogicalCalc.create(rel, program); } /** * Creates a relational expression that projects the given fields of the * input. * *

Optimizes if the fields are the identity projection. * * @param factory ProjectFactory * @param child Input relational expression * @param posList Source of each projected field * @return Relational expression that projects given fields */ public static RelNode createProject(final RelFactories.ProjectFactory factory, final RelNode child, final List posList) { RelDataType rowType = child.getRowType(); final List fieldNames = rowType.getFieldNames(); final RelBuilder relBuilder = RelBuilder.proto(factory).create(child.getCluster(), null); final List exprs = new AbstractList() { @Override public int size() { return posList.size(); } @Override public RexNode get(int index) { final int pos = posList.get(index); return relBuilder.getRexBuilder().makeInputRef(child, pos); } }; final List names = Util.select(fieldNames, posList); return relBuilder .push(child) .projectNamed(exprs, names, false) .build(); } @Deprecated // to be removed before 2.0 public static RelNode projectMapping( RelNode rel, Mapping mapping, @Nullable List fieldNames, RelFactories.ProjectFactory projectFactory) { assert mapping.getMappingType().isSingleSource(); assert mapping.getMappingType().isMandatorySource(); if (mapping.isIdentity()) { return rel; } final List outputNameList = new ArrayList<>(); final List exprList = new ArrayList<>(); final List fields = rel.getRowType().getFieldList(); final RexBuilder rexBuilder = rel.getCluster().getRexBuilder(); for (int i = 0; i < mapping.getTargetCount(); i++) { final int source = mapping.getSource(i); final RelDataTypeField sourceField = fields.get(source); outputNameList.add( ((fieldNames == null) || (fieldNames.size() <= i) || (fieldNames.get(i) == null)) ? sourceField.getName() : fieldNames.get(i)); exprList.add(rexBuilder.makeInputRef(rel, source)); } return projectFactory.createProject(rel, ImmutableList.of(), exprList, outputNameList); } /** Predicate for if a {@link Calc} does not contain windowed aggregates. */ public static boolean notContainsWindowedAgg(Calc calc) { return !calc.containsOver(); } /** Predicate for if a {@link Filter} does not windowed aggregates. */ public static boolean notContainsWindowedAgg(Filter filter) { return !filter.containsOver(); } /** Predicate for if a {@link Project} does not contain windowed aggregates. */ public static boolean notContainsWindowedAgg(Project project) { return !project.containsOver(); } /** Policies for handling two- and three-valued boolean logic. */ public enum Logic { /** Three-valued boolean logic. */ TRUE_FALSE_UNKNOWN, /** Nulls are not possible. */ TRUE_FALSE, /** Two-valued logic where UNKNOWN is treated as FALSE. * *

"x IS TRUE" produces the same result, and "WHERE x", "JOIN ... ON x" * and "HAVING x" have the same effect. */ UNKNOWN_AS_FALSE, /** Two-valued logic where UNKNOWN is treated as TRUE. * *

"x IS FALSE" produces the same result, as does "WHERE NOT x", etc. * *

In particular, this is the mode used by "WHERE k NOT IN q". If * "k IN q" produces TRUE or UNKNOWN, "NOT k IN q" produces FALSE or * UNKNOWN and the row is eliminated; if "k IN q" it returns FALSE, the * row is retained by the WHERE clause. */ UNKNOWN_AS_TRUE, /** A semi-join will have been applied, so that only rows for which the * value is TRUE will have been returned. */ TRUE, /** An anti-semi-join will have been applied, so that only rows for which * the value is FALSE will have been returned. * *

Currently only used within {@link LogicVisitor}, to ensure that * 'NOT (NOT EXISTS (q))' behaves the same as 'EXISTS (q)') */ FALSE; public Logic negate() { switch (this) { case UNKNOWN_AS_FALSE: case TRUE: return UNKNOWN_AS_TRUE; case UNKNOWN_AS_TRUE: return UNKNOWN_AS_FALSE; default: return this; } } /** Variant of {@link #negate()} to be used within {@link LogicVisitor}, * where FALSE values may exist. */ public Logic negate2() { switch (this) { case FALSE: return TRUE; case TRUE: return FALSE; case UNKNOWN_AS_FALSE: return UNKNOWN_AS_TRUE; case UNKNOWN_AS_TRUE: return UNKNOWN_AS_FALSE; default: return this; } } } /** * Pushes down expressions in "equal" join condition. * *

For example, given * "emp JOIN dept ON emp.deptno + 1 = dept.deptno", adds a project above * "emp" that computes the expression * "emp.deptno + 1". The resulting join condition is a simple combination * of AND, equals, and input fields, plus the remaining non-equal conditions. * * @param originalJoin Join whose condition is to be pushed down * @param relBuilder Factory to create project operator */ public static RelNode pushDownJoinConditions(Join originalJoin, RelBuilder relBuilder) { RexNode joinCond = originalJoin.getCondition(); final JoinRelType joinType = originalJoin.getJoinType(); final List extraLeftExprs = new ArrayList<>(); final List extraRightExprs = new ArrayList<>(); final int leftCount = originalJoin.getLeft().getRowType().getFieldCount(); final int rightCount = originalJoin.getRight().getRowType().getFieldCount(); // You cannot push a 'get' because field names might change. // // Pushing sub-queries is OK in principle (if they don't reference both // sides of the join via correlating variables) but we'd rather not do it // yet. if (!containsGet(joinCond) && RexUtil.SubQueryFinder.find(joinCond) == null) { joinCond = pushDownEqualJoinConditions(joinCond, leftCount, rightCount, extraLeftExprs, extraRightExprs, relBuilder.getRexBuilder()); } relBuilder.push(originalJoin.getLeft()); if (!extraLeftExprs.isEmpty()) { final List fields = relBuilder.peek().getRowType().getFieldList(); final List> pairs = new AbstractList>() { @Override public int size() { return leftCount + extraLeftExprs.size(); } @Override public Pair get(int index) { if (index < leftCount) { RelDataTypeField field = fields.get(index); return Pair.of( new RexInputRef(index, field.getType()), field.getName()); } else { return Pair.of(extraLeftExprs.get(index - leftCount), null); } } }; relBuilder.project(Pair.left(pairs), Pair.right(pairs)); } relBuilder.push(originalJoin.getRight()); if (!extraRightExprs.isEmpty()) { final List fields = relBuilder.peek().getRowType().getFieldList(); final int newLeftCount = leftCount + extraLeftExprs.size(); final List> pairs = new AbstractList>() { @Override public int size() { return rightCount + extraRightExprs.size(); } @Override public Pair get(int index) { if (index < rightCount) { RelDataTypeField field = fields.get(index); return Pair.of( new RexInputRef(index, field.getType()), field.getName()); } else { return Pair.of( RexUtil.shift( extraRightExprs.get(index - rightCount), -newLeftCount), null); } } }; relBuilder.project(Pair.left(pairs), Pair.right(pairs)); } final RelNode right = relBuilder.build(); final RelNode left = relBuilder.build(); relBuilder.push( originalJoin.copy(originalJoin.getTraitSet(), joinCond, left, right, joinType, originalJoin.isSemiJoinDone())); if (!extraLeftExprs.isEmpty() || !extraRightExprs.isEmpty()) { final int totalFields = joinType.projectsRight() ? leftCount + extraLeftExprs.size() + rightCount + extraRightExprs.size() : leftCount + extraLeftExprs.size(); final int[] mappingRanges = joinType.projectsRight() ? new int[] { 0, 0, leftCount, leftCount, leftCount + extraLeftExprs.size(), rightCount } : new int[] { 0, 0, leftCount }; Mappings.TargetMapping mapping = Mappings.createShiftMapping( totalFields, mappingRanges); relBuilder.project(relBuilder.fields(mapping.inverse())); } return relBuilder.build(); } @Deprecated // to be removed before 2.0 public static RelNode pushDownJoinConditions(Join originalJoin) { return pushDownJoinConditions(originalJoin, RelFactories.LOGICAL_BUILDER); } @Deprecated // to be removed before 2.0 public static RelNode pushDownJoinConditions(Join originalJoin, RelFactories.ProjectFactory projectFactory) { return pushDownJoinConditions( originalJoin, RelBuilder.proto(projectFactory)); } private static RelNode pushDownJoinConditions(Join originalJoin, RelBuilderFactory relBuilderFactory) { return pushDownJoinConditions(originalJoin, relBuilderFactory.create(originalJoin.getCluster(), null)); } private static boolean containsGet(RexNode node) { try { node.accept( new RexVisitorImpl(true) { @Override public Void visitCall(RexCall call) { if (call.getOperator() == RexBuilder.GET_OPERATOR) { throw Util.FoundOne.NULL; } return super.visitCall(call); } }); return false; } catch (Util.FoundOne e) { return true; } } /** * Pushes down parts of a join condition. * *

For example, given * "emp JOIN dept ON emp.deptno + 1 = dept.deptno", adds a project above * "emp" that computes the expression * "emp.deptno + 1". The resulting join condition is a simple combination * of AND, equals, and input fields. */ private static RexNode pushDownEqualJoinConditions( RexNode condition, int leftCount, int rightCount, List extraLeftExprs, List extraRightExprs, RexBuilder builder) { // Normalize the condition first RexNode node = (condition instanceof RexCall) ? collapseExpandedIsNotDistinctFromExpr((RexCall) condition, builder) : condition; switch (node.getKind()) { case EQUALS: case IS_NOT_DISTINCT_FROM: final RexCall call0 = (RexCall) node; final RexNode leftRex = call0.getOperands().get(0); final RexNode rightRex = call0.getOperands().get(1); final ImmutableBitSet leftBits = RelOptUtil.InputFinder.bits(leftRex); final ImmutableBitSet rightBits = RelOptUtil.InputFinder.bits(rightRex); final int pivot = leftCount + extraLeftExprs.size(); Side lside = Side.of(leftBits, pivot); Side rside = Side.of(rightBits, pivot); if (!lside.opposite(rside)) { return call0; } // fall through case AND: final RexCall call = (RexCall) node; final List list = new ArrayList<>(); List operands = Lists.newArrayList(call.getOperands()); for (int i = 0; i < operands.size(); i++) { RexNode operand = operands.get(i); if (operand instanceof RexCall) { operand = collapseExpandedIsNotDistinctFromExpr( (RexCall) operand, builder); } if (node.getKind() == SqlKind.AND && operand.getKind() != SqlKind.EQUALS && operand.getKind() != SqlKind.IS_NOT_DISTINCT_FROM) { // one of the join condition is neither EQ nor INDF list.add(operand); } else { final int left2 = leftCount + extraLeftExprs.size(); final RexNode e = pushDownEqualJoinConditions( operand, leftCount, rightCount, extraLeftExprs, extraRightExprs, builder); if (!e.equals(operand)) { final List remainingOperands = Util.skip(operands, i + 1); final int left3 = leftCount + extraLeftExprs.size(); fix(remainingOperands, left2, left3); fix(list, left2, left3); } list.add(e); } } if (!list.equals(call.getOperands())) { return call.clone(call.getType(), list); } return call; case OR: case INPUT_REF: case LITERAL: case NOT: return node; default: final ImmutableBitSet bits = RelOptUtil.InputFinder.bits(node); final int mid = leftCount + extraLeftExprs.size(); switch (Side.of(bits, mid)) { case LEFT: fix(extraRightExprs, mid, mid + 1); extraLeftExprs.add(node); return new RexInputRef(mid, node.getType()); case RIGHT: final int index2 = mid + rightCount + extraRightExprs.size(); extraRightExprs.add(node); return new RexInputRef(index2, node.getType()); case BOTH: case EMPTY: default: return node; } } } private static void fix(List operands, int before, int after) { if (before == after) { return; } for (int i = 0; i < operands.size(); i++) { RexNode node = operands.get(i); operands.set(i, RexUtil.shift(node, before, after - before)); } } /** * Determines whether any of the fields in a given relational expression may * contain null values, taking into account constraints on the field types and * also deduced predicates. * *

The method is cautious: It may sometimes return {@code true} when the * actual answer is {@code false}. In particular, it does this when there * is no executor, or the executor is not a sub-class of * {@link RexExecutorImpl}. */ private static boolean containsNullableFields(RelNode r) { final RexBuilder rexBuilder = r.getCluster().getRexBuilder(); final RelDataType rowType = r.getRowType(); final List list = new ArrayList<>(); final RelMetadataQuery mq = r.getCluster().getMetadataQuery(); for (RelDataTypeField field : rowType.getFieldList()) { if (field.getType().isNullable()) { list.add( rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, rexBuilder.makeInputRef(field.getType(), field.getIndex()))); } } if (list.isEmpty()) { // All columns are declared NOT NULL. return false; } final RelOptPredicateList predicates = mq.getPulledUpPredicates(r); if (RelOptPredicateList.isEmpty(predicates)) { // We have no predicates, so cannot deduce that any of the fields // declared NULL are really NOT NULL. return true; } final RexExecutor executor = r.getCluster().getPlanner().getExecutor(); if (!(executor instanceof RexExecutorImpl)) { // Cannot proceed without an executor. return true; } final RexImplicationChecker checker = new RexImplicationChecker(rexBuilder, executor, rowType); final RexNode first = RexUtil.composeConjunction(rexBuilder, predicates.pulledUpPredicates); final RexNode second = RexUtil.composeConjunction(rexBuilder, list); // Suppose we have EMP(empno INT NOT NULL, mgr INT), // and predicates [empno > 0, mgr > 0]. // We make first: "empno > 0 AND mgr > 0" // and second: "mgr IS NOT NULL" // and ask whether first implies second. // It does, so we have no nullable columns. return !checker.implies(first, second); } //~ Inner Classes ---------------------------------------------------------- /** * A {@code RelShuttle} which propagates all the hints of relational expression to * their children nodes. * *

Given a plan: * *

   *            Filter (Hint1)
   *                |
   *               Join
   *              /    \
   *            Scan  Project (Hint2)
   *                     |
   *                    Scan2
   * 
* *

Every hint has a {@code inheritPath} (integers list) which records its propagate path, * number `0` represents the hint is propagated from the first(left) child, * number `1` represents the hint is propagated from the second(right) child, * so the plan would have hints path as follows * (assumes each hint can be propagated to all child nodes): * *

    *
  • Filter would have hints {Hint1[]}
  • *
  • Join would have hints {Hint1[0]}
  • *
  • Scan would have hints {Hint1[0, 0]}
  • *
  • Project would have hints {Hint1[0,1], Hint2[]}
  • *
  • Scan2 would have hints {[Hint1[0, 1, 0], Hint2[0]}
  • *
*/ private static class RelHintPropagateShuttle extends RelHomogeneousShuttle { /** * Stack recording the hints and its current inheritPath. */ private final Deque, Deque>> inheritPaths = new ArrayDeque<>(); /** * The hint strategies to decide if a hint should be attached to * a relational expression. */ private final HintStrategyTable hintStrategies; RelHintPropagateShuttle(HintStrategyTable hintStrategies) { this.hintStrategies = hintStrategies; } /** * Visits a particular child of a parent. */ @Override protected RelNode visitChild(RelNode parent, int i, RelNode child) { inheritPaths.forEach(inheritPath -> inheritPath.right.push(i)); try { RelNode child2 = child.accept(this); if (child2 != child) { final List newInputs = new ArrayList<>(parent.getInputs()); newInputs.set(i, child2); return parent.copy(parent.getTraitSet(), newInputs); } return parent; } finally { inheritPaths.forEach(inheritPath -> inheritPath.right.pop()); } } @Override public RelNode visit(RelNode other) { if (other instanceof Hintable) { return visitHintable(other); } else { return visitChildren(other); } } /** * Handle the {@link Hintable}s. * *

There are two cases to handle hints: * *

    *
  • For TableScan: table scan is always a leaf node, * attach the hints of the propagation path directly;
  • *
  • For other {@link Hintable}s: if the node has hints itself, that means, * these hints are query hints that need to propagate to its children, * so we do these things: *
      *
    1. push the hints with empty inheritPath to the stack
    2. *
    3. visit the children nodes and propagate the hints
    4. *
    5. pop the hints pushed in step1
    6. *
    7. attach the hints of the propagation path
    8. *
    * if the node does not have hints, attach the hints of the propagation path directly. *
  • *
* * @param node {@link Hintable} to handle * @return New copy of the {@code hintable} with propagated hints attached */ private RelNode visitHintable(RelNode node) { final List topHints = ((Hintable) node).getHints(); final boolean hasHints = topHints != null && topHints.size() > 0; final boolean hasQueryHints = hasHints && !(node instanceof TableScan); if (hasQueryHints) { inheritPaths.push(Pair.of(topHints, new ArrayDeque<>())); } final RelNode node1 = visitChildren(node); if (hasQueryHints) { inheritPaths.pop(); } return attachHints(node1); } private RelNode attachHints(RelNode original) { assert original instanceof Hintable; if (inheritPaths.size() > 0) { final List hints = inheritPaths.stream() .sorted(Comparator.comparingInt(o -> o.right.size())) .map(path -> copyWithInheritPath(path.left, path.right)) .reduce(new ArrayList<>(), (acc, hints1) -> { acc.addAll(hints1); return acc; }); final List filteredHints = hintStrategies.apply(hints, original); if (filteredHints.size() > 0) { return ((Hintable) original).attachHints(filteredHints); } } return original; } private static List copyWithInheritPath(List hints, Deque inheritPath) { // Copy the Dequeue in reverse order. final List path = new ArrayList<>(); final Iterator iterator = inheritPath.descendingIterator(); while (iterator.hasNext()) { path.add(iterator.next()); } return hints.stream() .map(hint -> hint.copy(path)) .collect(Collectors.toList()); } } /** * A {@code RelShuttle} which propagates the given hints to the sub-tree from the root node. * It stops the search of current path if the node already has hints or the whole propagation * if there is already a matched node. * *

Given a plan: * *

   *            Filter
   *                |
   *               Join
   *              /    \
   *            Scan  Project (Hint2)
   *                     |
   *                    Scan2
   * 
* *

The [Filter, Join, Scan] are the candidates(in sequence) to propagate, * the whole propagation ends if we append the given hints to a node successfully. */ private static class SubTreeHintPropagateShuttle extends RelHomogeneousShuttle { /** Stack recording the appended inheritPath. */ private final List appendPath = new ArrayList<>(); /** * The hint strategies to decide if a hint should be attached to * a relational expression. */ private final HintStrategyTable hintStrategies; /** Hints to propagate. */ private final List hints; SubTreeHintPropagateShuttle(HintStrategyTable hintStrategies, List hints) { this.hintStrategies = hintStrategies; this.hints = hints; } /** * Visits a particular child of a parent. */ @Override protected RelNode visitChild(RelNode parent, int i, RelNode child) { appendPath.add(i); try { RelNode child2 = child.accept(this); if (child2 != child) { final List newInputs = new ArrayList<>(parent.getInputs()); newInputs.set(i, child2); return parent.copy(parent.getTraitSet(), newInputs); } return parent; } finally { // Remove the last element. appendPath.remove(appendPath.size() - 1); } } @Override public RelNode visit(RelNode other) { if (this.appendPath.size() > 3) { // Returns early if the visiting depth is bigger than 3 return other; } if (other instanceof Hintable) { return visitHintable(other); } else { return visitChildren(other); } } /** * Handle the {@link Hintable}s. * *

Try to propagate the given hints to the node, the propagation finishes if: * *

    *
  • This hintable already has hints, that means, the rel is definitely * not created by a planner rule(or copied by the planner rule)
  • *
  • This hintable appended the hints successfully
  • *
* * @param node {@link Hintable} to handle * @return New copy of the {@code hintable} with propagated hints attached */ private RelNode visitHintable(RelNode node) { final List topHints = ((Hintable) node).getHints(); final boolean hasHints = topHints != null && topHints.size() > 0; if (hasHints) { // This node is definitely not created by the planner, returns early. return node; } final RelNode node1 = attachHints(node); if (node1 != node) { return node1; } return visitChildren(node); } private RelNode attachHints(RelNode original) { assert original instanceof Hintable; final List hints = this.hints.stream() .map(hint -> copyWithAppendPath(hint, appendPath)) .collect(Collectors.toList()); final List filteredHints = hintStrategies.apply(hints, original); if (filteredHints.size() > 0) { return ((Hintable) original).attachHints(filteredHints); } return original; } private static RelHint copyWithAppendPath(RelHint hint, List appendPaths) { if (appendPaths.size() == 0) { return hint; } else { List newPath = new ArrayList<>(hint.inheritPath); newPath.addAll(appendPaths); return hint.copy(newPath); } } } /** * A {@code RelShuttle} which resets all the hints of a relational expression to * what they are originally like. * *

This would trigger a reverse transformation of what * {@link RelHintPropagateShuttle} does. * *

Transformation rules: * *

    *
  • Project: remove the hints that have non-empty inherit path * (which means the hint was not originally declared from it); *
  • Aggregate: remove the hints that have non-empty inherit path; *
  • Join: remove all the hints; *
  • TableScan: remove the hints that have non-empty inherit path. *
*/ private static class ResetHintsShuttle extends RelHomogeneousShuttle { @Override public RelNode visit(RelNode node) { node = visitChildren(node); if (node instanceof Hintable) { node = resetHints((Hintable) node); } return node; } private static RelNode resetHints(Hintable hintable) { if (hintable.getHints().size() > 0) { final List resetHints = hintable.getHints().stream() .filter(hint -> hint.inheritPath.size() == 0) .collect(Collectors.toList()); return hintable.withHints(resetHints); } else { return (RelNode) hintable; } } } /** Visitor that finds all variables used but not stopped in an expression. */ private static class VariableSetVisitor extends RelVisitor { final Set variables = new HashSet<>(); // implement RelVisitor @Override public void visit( RelNode p, int ordinal, @com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable RelNode parent) { super.visit(p, ordinal, parent); p.collectVariablesUsed(variables); // Important! Remove stopped variables AFTER we visit children // (which what super.visit() does) variables.removeAll(p.getVariablesSet()); } } /** Visitor that finds all variables used in an expression. */ public static class VariableUsedVisitor extends RexShuttle { public final Set variables = new LinkedHashSet<>(); public final Multimap variableFields = LinkedHashMultimap.create(); @NotOnlyInitialized private final @Nullable RelShuttle relShuttle; public VariableUsedVisitor(@UnknownInitialization @Nullable RelShuttle relShuttle) { this.relShuttle = relShuttle; } @Override public RexNode visitCorrelVariable(RexCorrelVariable p) { variables.add(p.id); variableFields.put(p.id, -1); return p; } @Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) { if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) { final RexCorrelVariable v = (RexCorrelVariable) fieldAccess.getReferenceExpr(); variableFields.put(v.id, fieldAccess.getField().getIndex()); } return super.visitFieldAccess(fieldAccess); } @Override public RexNode visitSubQuery(RexSubQuery subQuery) { if (relShuttle != null) { subQuery.rel.accept(relShuttle); // look inside sub-queries } return super.visitSubQuery(subQuery); } } /** Shuttle that finds the set of inputs that are used. */ public static class InputReferencedVisitor extends RexShuttle { public final NavigableSet inputPosReferenced = new TreeSet<>(); @Override public RexNode visitInputRef(RexInputRef inputRef) { inputPosReferenced.add(inputRef.getIndex()); return inputRef; } } /** Converts types to descriptive strings. */ public static class TypeDumper { private String indent; private final PrintWriter pw; TypeDumper(PrintWriter pw) { this.pw = pw; this.indent = ""; } void accept(RelDataType type) { if (type.isStruct()) { final List fields = type.getFieldList(); // RECORD ( // I INTEGER NOT NULL, // J VARCHAR(240)) pw.println("RECORD ("); String prevIndent = indent; String extraIndent = " "; this.indent = indent + extraIndent; acceptFields(fields); this.indent = prevIndent; pw.print(")"); if (!type.isNullable()) { pw.print(NON_NULLABLE_SUFFIX); } } else if (type instanceof MultisetSqlType) { // E.g. "INTEGER NOT NULL MULTISET NOT NULL" RelDataType componentType = requireNonNull( type.getComponentType(), () -> "type.getComponentType() for " + type); accept(componentType); pw.print(" MULTISET"); if (!type.isNullable()) { pw.print(NON_NULLABLE_SUFFIX); } } else { // E.g. "INTEGER" E.g. "VARCHAR(240) CHARACTER SET "ISO-8859-1" // COLLATE "ISO-8859-1$en_US$primary" NOT NULL" pw.print(type.getFullTypeString()); } } private void acceptFields(final List fields) { for (int i = 0; i < fields.size(); i++) { RelDataTypeField field = fields.get(i); if (i > 0) { pw.println(","); } pw.print(indent); pw.print(field.getName()); pw.print(" "); accept(field.getType()); } } } /** * Visitor which builds a bitmap of the inputs used by an expression. */ public static class InputFinder extends RexVisitorImpl { private final ImmutableBitSet.Builder bitBuilder; private final @Nullable Set extraFields; private InputFinder(@Nullable Set extraFields, ImmutableBitSet.Builder bitBuilder) { super(true); this.bitBuilder = bitBuilder; this.extraFields = extraFields; } public InputFinder() { this(null); } public InputFinder(@Nullable Set extraFields) { this(extraFields, ImmutableBitSet.builder()); } public InputFinder(@Nullable Set extraFields, ImmutableBitSet initialBits) { this(extraFields, initialBits.rebuild()); } /** Returns an input finder that has analyzed a given expression. */ public static InputFinder analyze(RexNode node) { final InputFinder inputFinder = new InputFinder(); node.accept(inputFinder); return inputFinder; } /** * Returns a bit set describing the inputs used by an expression. */ public static ImmutableBitSet bits(RexNode node) { return analyze(node).build(); } /** * Returns a bit set describing the inputs used by a collection of * project expressions and an optional condition. */ public static ImmutableBitSet bits(List exprs, @Nullable RexNode expr) { final InputFinder inputFinder = new InputFinder(); RexUtil.apply(inputFinder, exprs, expr); return inputFinder.build(); } /** Returns the bit set. * *

After calling this method, you cannot do any more visits or call this * method again. */ public ImmutableBitSet build() { return bitBuilder.build(); } @Override public Void visitInputRef(RexInputRef inputRef) { bitBuilder.set(inputRef.getIndex()); return null; } @Override public Void visitCall(RexCall call) { if (call.getOperator() == RexBuilder.GET_OPERATOR) { RexLiteral literal = (RexLiteral) call.getOperands().get(1); if (extraFields != null) { requireNonNull(literal, () -> "first operand in " + call); String value2 = (String) literal.getValue2(); requireNonNull(value2, () -> "value of the first operand in " + call); extraFields.add( new RelDataTypeFieldImpl( value2, -1, call.getType())); } } return super.visitCall(call); } } /** * Walks an expression tree, converting the index of RexInputRefs based on * some adjustment factor. */ public static class RexInputConverter extends RexShuttle { protected final RexBuilder rexBuilder; private final @Nullable List srcFields; protected final @Nullable List destFields; private final @Nullable List leftDestFields; private final @Nullable List rightDestFields; private final int nLeftDestFields; private final int[] adjustments; /** * Creates a RexInputConverter. * * @param rexBuilder builder for creating new RexInputRefs * @param srcFields fields where the RexInputRefs originated * from; if null, a new RexInputRef is always * created, referencing the input from destFields * corresponding to its current index value * @param destFields fields that the new RexInputRefs will be * referencing; if null, use the type information * from the source field when creating the new * RexInputRef * @param leftDestFields in the case where the destination is a join, * these are the fields from the left join input * @param rightDestFields in the case where the destination is a join, * these are the fields from the right join input * @param adjustments the amount to adjust each field by */ private RexInputConverter( RexBuilder rexBuilder, @Nullable List srcFields, @Nullable List destFields, @Nullable List leftDestFields, @Nullable List rightDestFields, int[] adjustments) { this.rexBuilder = rexBuilder; this.srcFields = srcFields; this.destFields = destFields; this.adjustments = adjustments; this.leftDestFields = leftDestFields; this.rightDestFields = rightDestFields; if (leftDestFields == null) { nLeftDestFields = 0; } else { assert destFields == null; nLeftDestFields = leftDestFields.size(); } } public RexInputConverter( RexBuilder rexBuilder, @Nullable List srcFields, @Nullable List leftDestFields, @Nullable List rightDestFields, int[] adjustments) { this( rexBuilder, srcFields, null, leftDestFields, rightDestFields, adjustments); } public RexInputConverter( RexBuilder rexBuilder, @Nullable List srcFields, @Nullable List destFields, int[] adjustments) { this(rexBuilder, srcFields, destFields, null, null, adjustments); } public RexInputConverter( RexBuilder rexBuilder, @Nullable List srcFields, int[] adjustments) { this(rexBuilder, srcFields, null, null, null, adjustments); } @Override public RexNode visitInputRef(RexInputRef var) { int srcIndex = var.getIndex(); int destIndex = srcIndex + adjustments[srcIndex]; RelDataType type; if (destFields != null) { type = destFields.get(destIndex).getType(); } else if (leftDestFields != null) { if (destIndex < nLeftDestFields) { type = leftDestFields.get(destIndex).getType(); } else { type = requireNonNull(rightDestFields, "rightDestFields") .get(destIndex - nLeftDestFields).getType(); } } else { type = requireNonNull(srcFields, "srcFields").get(srcIndex).getType(); } if ((adjustments[srcIndex] != 0) || (srcFields == null) || (type != srcFields.get(srcIndex).getType())) { return rexBuilder.makeInputRef(type, destIndex); } else { return var; } } } /** What kind of sub-query. */ public enum SubQueryType { EXISTS, IN, SCALAR } /** * Categorizes whether a bit set contains bits left and right of a * line. */ enum Side { LEFT, RIGHT, BOTH, EMPTY; static Side of(ImmutableBitSet bitSet, int middle) { final int firstBit = bitSet.nextSetBit(0); if (firstBit < 0) { return EMPTY; } if (firstBit >= middle) { return RIGHT; } if (bitSet.nextSetBit(middle) < 0) { return LEFT; } return BOTH; } public boolean opposite(Side side) { return (this == LEFT && side == RIGHT) || (this == RIGHT && side == LEFT); } } /** Shuttle that finds correlation variables inside a given relational * expression, including those that are inside * {@link RexSubQuery sub-queries}. */ private static class CorrelationCollector extends RelHomogeneousShuttle { @SuppressWarnings("assignment.type.incompatible") private final VariableUsedVisitor vuv = new VariableUsedVisitor(this); @Override public RelNode visit(RelNode other) { other.collectVariablesUsed(vuv.variables); other.accept(vuv); RelNode result = super.visit(other); // Important! Remove stopped variables AFTER we visit // children. (which what super.visit() does) vuv.variables.removeAll(other.getVariablesSet()); return result; } } /** Result of calling * {@link com.hazelcast.org.apache.calcite.plan.RelOptUtil#createExistsPlan}. */ public static class Exists { public final RelNode r; public final boolean indicator; public final boolean outerJoin; private Exists(RelNode r, boolean indicator, boolean outerJoin) { this.r = r; this.indicator = indicator; this.outerJoin = outerJoin; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy