com.hazelcast.org.apache.calcite.plan.RelOptUtil Maven / Gradle / Ivy
/*
* 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.EnumerableBindable;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableInterpreterRule;
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.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.JoinAssociateRule;
import com.hazelcast.org.apache.calcite.rel.rules.MultiJoin;
import com.hazelcast.org.apache.calcite.rel.rules.ProjectTableScanRule;
import com.hazelcast.org.apache.calcite.rel.rules.ReduceExpressionsRule;
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.rel.type.RelDataTypeSystem;
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 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.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* 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 =
RelOptUtil::notContainsWindowedAgg;
@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) {
if ((rel instanceof Sort) && ((Sort) rel).fetch != null) {
return true;
}
return false;
}
/**
* Whether this node contains a sort specification.
*/
public static boolean isOrder(RelNode rel) {
if ((rel instanceof Sort) && !((Sort) rel).getCollation().getFieldCollations().isEmpty()) {
return true;
}
return false;
}
/**
* 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<>();
for (Entry, Collection> e : nodes.asMap().entrySet()) {
if (TableScan.class.isAssignableFrom(e.getKey())) {
for (RelNode node : e.getValue()) {
usedTables.add(node.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 Lists.transform(findAllTables(rel),
table -> table.getQualifiedName().toString());
}
/**
* 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
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 Lists.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 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,
List conditions,
RexLiteral extraExpr,
String extraName) {
assert extraExpr == null || extraName != null;
RelNode ret = seekRel;
if ((conditions != null) && (conditions.size() > 0)) {
RexNode conditionExp =
RexUtil.composeConjunction(
cluster.getRexBuilder(), conditions, true);
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);
}
switch (logic) {
case TRUE_FALSE_UNKNOWN:
case UNKNOWN_AS_TRUE:
if (notIn && !containsNullableFields(seekRel)) {
logic = Logic.TRUE_FALSE;
}
}
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 extends RexNode> 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 extends RexNode> 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.
*
* @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.
*
* @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;
}
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);
}
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, RelCollations.EMPTY, 0, rel, null,
null));
}
return LogicalAggregate.create(rel, ImmutableList.of(), ImmutableBitSet.of(),
null, aggCalls);
}
/** @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 @Nonnull RexNode splitJoinCondition(
RelNode left,
RelNode right,
RexNode condition,
List leftKeys,
List rightKeys,
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,
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,
List filterNulls,
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 @Nonnull RexNode splitJoinCondition(
List sysFieldList,
List inputs,
RexNode condition,
List> joinKeys,
List filterNulls,
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 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 RexNode splitCorrelatedFilterCondition(
LogicalFilter filter,
List joinKeys,
List correlatedJoinKeys,
boolean extractCorrelatedFieldAccess) {
return splitCorrelatedFilterCondition(
(Filter) filter,
joinKeys,
correlatedJoinKeys,
extractCorrelatedFieldAccess);
}
public static 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,
List filterNulls,
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 instanceof RexCall) {
RexCall call = (RexCall) condition;
if (call.getKind() == SqlKind.AND) {
for (RexNode operand : call.getOperands()) {
splitJoinCondition(
sysFieldList,
inputs,
operand,
joinKeys,
filterNulls,
rangeOp,
nonEquiList);
}
return;
}
RexNode leftKey = null;
RexNode rightKey = null;
int leftInput = 0;
int rightInput = 0;
List leftFields = null;
List rightFields = null;
boolean reverse = false;
call = collapseExpandedIsNotDistinctFromExpr(call, 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 ((rangeOp == null)
&& ((leftKey == null) || (rightKey == null))) {
// no equality join keys found yet:
// try transforming the condition to
// equality "join" conditions, e.g.
// f(LHS) > 0 ===> ( f(LHS) > 0 ) = TRUE,
// and make the RHS produce TRUE, but only if we're strictly
// looking for equi-joins
final ImmutableBitSet projRefs = InputFinder.bits(condition);
leftKey = null;
rightKey = null;
boolean foundInput = false;
for (int i = 0; i < inputs.size() && !foundInput; i++) {
if (inputsRange[i].contains(projRefs)) {
leftInput = i;
leftFields = inputs.get(leftInput).getRowType().getFieldList();
leftKey = condition.accept(
new RelOptUtil.RexInputConverter(
rexBuilder,
leftFields,
leftFields,
adjustments));
rightKey = rexBuilder.makeLiteral(true);
// effectively performing an equality comparison
kind = SqlKind.EQUALS;
foundInput = true;
}
}
}
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) {
if (reverse) {
kind = kind.reverse();
}
rangeOp.add(op(kind, call.getOperator()));
}
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 @Nonnull 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();
}
});
}
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);
}
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,
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();
final RelDataTypeSystem typeSystem =
cluster.getTypeFactory().getTypeSystem();
int origLeftInputSize = leftRel.getRowType().getFieldCount();
int origRightInputSize = rightRel.getRowType().getFieldCount();
final List newLeftFields = new ArrayList<>();
final List newLeftFieldNames = new ArrayList<>();
final List newRightFields = new ArrayList<>();
final List 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(JoinAssociateRule.INSTANCE);
}
// 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);
}
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);
}
private static void registerCalcRules(RelOptPlanner planner) {
RelOptRules.CALC_RULES.forEach(planner::addRule);
}
@Experimental
public static void registerDefaultRules(RelOptPlanner planner,
boolean enableMaterialziations, boolean enableBindable) {
if (CalciteSystemProperty.ENABLE_COLLATION_TRAIT.value()) {
registerAbstractRelationalRules(planner);
}
registerAbstractRules(planner);
registerBaseRules(planner);
if (enableMaterialziations) {
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(ProjectTableScanRule.INSTANCE);
planner.addRule(ProjectTableScanRule.INTERPRETER);
if (CalciteSystemProperty.ENABLE_ENUMERABLE.value()) {
registerEnumerableRules(planner);
planner.addRule(EnumerableInterpreterRule.INSTANCE);
}
if (enableBindable && CalciteSystemProperty.ENABLE_ENUMERABLE.value()) {
planner.addRule(
EnumerableBindable.EnumerableToBindableConverterRule.INSTANCE);
}
if (CalciteSystemProperty.ENABLE_STREAM.value()) {
for (RelOptRule rule : StreamRules.RULES) {
planner.addRule(rule);
}
}
planner.addRule(ReduceExpressionsRule.FILTER_INSTANCE);
}
public static StringBuilder appendRelDescription(
StringBuilder sb, RelNode rel) {
sb.append("rel#").append(rel.getId())
.append(':').append(rel.getDigest());
return sb;
}
/**
* 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();
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("Type mismatch:\n{}:\n{}\n{}:\n{}",
desc1, type1.getFullTypeString(),
desc2, type2.getFullTypeString());
}
return litmus.succeed();
}
/** 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.
*/
public static String toString(
final 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(
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(
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(
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(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,
RexNode left,
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;
}
}
}
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 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
*/
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.inputBitSet.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,
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 extends RexNode> nodes,
Project project) {
final List list = new ArrayList<>();
pushShuttle(project).visitList(nodes, list);
return list;
}
/** 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 extends RexNode> nodes, Project project, int bloat) {
if (bloat < 0) {
// If bloat is negative never merge.
return null;
}
if (RexOver.containsOver(nodes, null)
&& RexOver.containsOver(project.getProjects(), null)) {
// 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());
}
};
}
/**
* 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(),
Lists.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.asList(mapping.inverse()));
}
public static RelNode createProject(RelNode child, Mappings.TargetMapping mapping,
RelFactories.ProjectFactory projectFactory) {
return createProject(projectFactory, child, Mappings.asList(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() {
public void visit(RelNode node, int ordinal, 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, 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 extends RexNode> 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 extends RexNode> exprs,
List fieldNames,
boolean optimize) {
final RelBuilder relBuilder =
RelFactories.LOGICAL_BUILDER.create(child.getCluster(), null);
return relBuilder.push(child)
.projectNamed(exprs, fieldNames, !optimize)
.build();
}
/** @deprecated Use
* {@link RelBuilder#projectNamed(Iterable, Iterable, boolean)} */
@Deprecated // to be removed before 2.0
public static RelNode createProject(
RelNode child,
List extends RexNode> 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() {
public int size() {
return fields.size();
}
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,
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() {
public int size() {
return posList.size();
}
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,
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.getProgram().containsAggs();
}
/** Predicate for if a {@link Filter} does not windowed aggregates. */
public static boolean notContainsWindowedAgg(Filter filter) {
return !RexOver.containsOver(filter.getCondition());
}
/** Predicate for if a {@link Project} does not contain windowed aggregates. */
public static boolean notContainsWindowedAgg(Project project) {
return !RexOver.containsOver(project.getProjects(), null);
}
/** 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>() {
public int size() {
return leftCount + extraLeftExprs.size();
}
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>() {
public int size() {
return rightCount + extraRightExprs.size();
}
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 (predicates.pulledUpPredicates.isEmpty()) {
// 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, (RexExecutorImpl) 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.
*/
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());
}
}
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:
*
* - push the hints with empty inheritPath to the stack
* - visit the children nodes and propagate the hints
* - pop the hints pushed in step1
* - attach the hints of the propagation path
*
* 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.
*/
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);
}
}
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 {
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
public void visit(
RelNode p,
int ordinal,
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();
private final RelShuttle relShuttle;
public VariableUsedVisitor(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 SortedSet inputPosReferenced = new TreeSet<>();
public RexNode visitInputRef(RexInputRef inputRef) {
inputPosReferenced.add(inputRef.getIndex());
return inputRef;
}
}
/** Converts types to descriptive strings. */
public static class TypeDumper {
private final String extraIndent = " ";
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;
this.indent = indent + extraIndent;
acceptFields(fields);
this.indent = prevIndent;
pw.print(")");
if (!type.isNullable()) {
pw.print(" NOT NULL");
}
} else if (type instanceof MultisetSqlType) {
// E.g. "INTEGER NOT NULL MULTISET NOT NULL"
accept(type.getComponentType());
pw.print(" MULTISET");
if (!type.isNullable()) {
pw.print(" NOT NULL");
}
} 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