/*
* 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 org.eigenbase.relopt;
import java.io.*;
import java.util.*;
import org.eigenbase.rel.*;
import org.eigenbase.rel.rules.*;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.*;
import org.eigenbase.sql.fun.*;
import org.eigenbase.sql.type.*;
import org.eigenbase.sql.validate.SqlValidatorUtil;
import org.eigenbase.util.*;
import org.eigenbase.util.mapping.*;
import net.hydromatic.linq4j.Ord;
import net.hydromatic.optiq.util.BitSets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* <code>RelOptUtil</code> 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;
private static final Function<RelDataTypeField, RelDataType> GET_TYPE =
new Function<RelDataTypeField, RelDataType>() {
public RelDataType apply(RelDataTypeField field) {
return field.getType();
}
};
//~ Methods ----------------------------------------------------------------
/**
* Returns a list of variables set by a relational expression or its
* descendants.
*/
public static Set<String> getVariablesSet(RelNode rel) {
VariableSetVisitor visitor = new VariableSetVisitor();
go(visitor, rel);
return visitor.variables;
}
/**
* Returns a set of distinct variables set by <code>rel0</code> and used by
* <code>rel1</code>.
*/
public static List<String> getVariablesSetAndUsed(
RelNode rel0,
RelNode rel1) {
Set<String> set = getVariablesSet(rel0);
if (set.size() == 0) {
return ImmutableList.of();
}
Set<String> used = getVariablesUsed(rel1);
if (used.size() == 0) {
return ImmutableList.of();
}
List<String> result = new ArrayList<String>();
for (String 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. The item type is the same as
* {@link org.eigenbase.rex.RexVariable#getName}
*/
public static Set<String> getVariablesUsed(RelNode rel) {
final VariableUsedVisitor vuv = new VariableUsedVisitor();
final VisitorRelVisitor visitor =
new VisitorRelVisitor(vuv) {
// implement RelVisitor
public void visit(
RelNode p,
int ordinal,
RelNode parent) {
p.collectVariablesUsed(vuv.variables);
super.visit(p, ordinal, parent);
// Important! Remove stopped variables AFTER we visit
// children. (which what super.visit() does)
vuv.variables.removeAll(p.getVariablesStopped());
}
};
visitor.go(rel);
return vuv.variables;
}
/**
* 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 (Throwable e) {
throw Util.newInternal(e, "while visiting tree");
}
}
/**
* 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 org.eigenbase.reltype.RelDataType#getFieldNames()
*/
public static List<RelDataType> getFieldTypeList(final RelDataType type) {
return Lists.transform(type.getFieldList(), GET_TYPE);
}
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<RelDataTypeField> f1 = rowType1.getFieldList();
final List<RelDataTypeField> f2 = rowType2.getFieldList();
for (Pair<RelDataTypeField, RelDataTypeField> 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 " + newRel.toString();
throw Util.newInternal(s);
}
/**
* 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<RexNode> nodes,
RelDataType inputRowType) {
final Mappings.TargetMapping mapping =
Mappings.create(
MappingType.PARTIAL_FUNCTION,
nodes.size(),
inputRowType.getFieldCount());
for (Ord<RexNode> node : Ord.zip(nodes)) {
if (node.e instanceof RexInputRef) {
mapping.set(
node.i,
((RexInputRef) node.e).getIndex());
} else if (node.e.isA(SqlKind.CAST)) {
RexNode operand = ((RexCall) node.e).getOperands().get(0);
if (operand instanceof RexInputRef) {
mapping.set(
node.i,
((RexInputRef) operand).getIndex());
}
}
}
return mapping;
}
/**
* Creates a plan suitable for use in <code>EXISTS</code> or <code>IN</code>
* statements. See {@link
* org.eigenbase.sql2rel.SqlToRelConverter#convertExists} Note: this
* implementation of createExistsPlan is only called from
* net.sf.farrago.fennel.rel. The last two arguments do not apply to
* those invocations and can be removed from the method.
*
* @param cluster Cluster
* @param seekRel A query rel, for example the resulting rel from 'select *
* from emp' or 'values (1,2,3)' or '('Foo', 34)'.
* @param conditions May be null
* @param extraExpr Column expression to add. "TRUE" for EXISTS and IN
* @param extraName Name of expression to add.
* @return relational expression which outer joins a boolean condition
* column
*/
public static RelNode createExistsPlan(
RelOptCluster cluster,
RelNode seekRel,
List<RexNode> 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);
ret = createFilter(ret, conditionExp,
RelFactories.DEFAULT_FILTER_FACTORY);
}
if (extraExpr != null) {
RexBuilder rexBuilder = cluster.getRexBuilder();
RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
assert extraExpr == rexBuilder.makeLiteral(true);
// this should only be called for the exists case
// first stick an Agg on top of the subquery
// agg does not like no agg functions so just pretend it is
// doing a min(TRUE)
ret = createProject(ret, ImmutableList.of(extraExpr), null);
final List<RelDataType> argTypes =
ImmutableList.of(
typeFactory.createSqlType(SqlTypeName.BOOLEAN));
SqlAggFunction minFunction =
new SqlMinMaxAggFunction(
argTypes,
true,
SqlMinMaxAggFunction.MINMAX_COMPARABLE);
RelDataType returnType =
minFunction.inferReturnType(
new AggregateRelBase.AggCallBinding(
typeFactory, minFunction, argTypes, 0));
final AggregateCall aggCall =
new AggregateCall(
minFunction,
false,
ImmutableList.of(0),
returnType,
extraName);
ret =
new AggregateRel(
ret.getCluster(),
ret,
BitSets.of(),
ImmutableList.of(aggCall));
}
return ret;
}
/**
* Creates a plan suitable for use in <code>EXISTS</code> or <code>IN</code>
* statements.
*
* @see org.eigenbase.sql2rel.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 needsOuterJoin Whether query needs outer join
*
* @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 Pair<RelNode, Boolean> createExistsPlan(
RelNode seekRel,
SubqueryType subqueryType,
Logic logic,
boolean needsOuterJoin) {
switch (subqueryType) {
case SCALAR:
return Pair.of(seekRel, false);
default:
RelNode ret = seekRel;
final RelOptCluster cluster = seekRel.getCluster();
final RexBuilder rexBuilder = cluster.getRexBuilder();
final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
final int keyCount = ret.getRowType().getFieldCount();
if (!needsOuterJoin) {
return Pair.<RelNode, Boolean>of(
new AggregateRel(cluster, ret, BitSets.range(keyCount),
ImmutableList.<AggregateCall>of()),
false);
}
// for IN/NOT IN, it needs to output the fields
final List<RexNode> exprs = new ArrayList<RexNode>();
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 = createProject(ret, exprs, null);
final List<RelDataType> argTypes =
ImmutableList.of(typeFactory.createSqlType(SqlTypeName.BOOLEAN));
SqlAggFunction minFunction =
new SqlMinMaxAggFunction(argTypes, true,
SqlMinMaxAggFunction.MINMAX_COMPARABLE);
RelDataType returnType =
minFunction.inferReturnType(
new AggregateRelBase.AggCallBinding(
typeFactory, minFunction, argTypes, projectedKeyCount));
final AggregateCall aggCall =
new AggregateCall(
minFunction,
false,
ImmutableList.of(projectedKeyCount),
returnType,
null);
ret = new AggregateRel(
cluster,
ret,
BitSets.range(projectedKeyCount),
ImmutableList.of(aggCall));
switch (logic) {
case TRUE_FALSE_UNKNOWN:
case UNKNOWN_AS_TRUE:
return Pair.of(ret, true);
default:
return Pair.of(ret, false);
}
}
}
/**
* Creates a ProjectRel which accomplishes a rename.
*
* @param outputType a row type descriptor whose field names the generated
* ProjectRel must match
* @param rel the rel whose output is to be renamed; rel.getRowType()
* must be the same as outputType except for field names
* @return generated relational expression
*/
public static RelNode createRenameRel(
RelDataType outputType,
RelNode rel) {
RelDataType inputType = rel.getRowType();
List<RelDataTypeField> inputFields = inputType.getFieldList();
int n = inputFields.size();
List<RelDataTypeField> outputFields = outputType.getFieldList();
assert outputFields.size() == n : "rename: field count mismatch: in="
+ inputType
+ ", out" + outputType;
List<Pair<RexNode, String>> renames =
new ArrayList<Pair<RexNode, String>>();
for (Pair<RelDataTypeField, RelDataTypeField> pair
: Pair.zip(inputFields, outputFields)) {
final RelDataTypeField inputField = pair.left;
final RelDataTypeField outputField = pair.right;
assert inputField.getType().equals(outputField.getType());
renames.add(
Pair.of(
(RexNode) rel.getCluster().getRexBuilder().makeInputRef(
inputField.getType(),
inputField.getIndex()),
outputField.getName()));
}
return createProject(rel, Pair.left(renames), Pair.right(renames));
}
/**
* Creates a relational expression which filters according to a given
* condition, returning the same fields as its input.
*
* @param child Child relational expression
* @param condition Condition
* @return Relational expression
*/
public static RelNode createFilter(RelNode child, RexNode condition) {
return createFilter(child, condition, RelFactories.DEFAULT_FILTER_FACTORY);
}
/**
* Creates a relational expression which filters according to a given
* condition, returning the same fields as its input.
*
* @param child Child relational expression
* @param condition Condition
* @param filterFactory Filter factory
* @return Relational expression
*/
public static RelNode createFilter(RelNode child, RexNode condition,
RelFactories.FilterFactory filterFactory) {
return filterFactory.createFilter(child, condition);
}
/** 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, 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 createFilter(child, condition, filterFactory);
}
}
/**
* Creates a filter which will remove rows containing NULL values.
*
* @param rel the rel to be filtered
* @param fieldOrdinals array of 0-based field ordinals to filter, or null
* for all fields
* @return filtered rel
*/
public static RelNode createNullFilter(
RelNode rel,
Integer[] fieldOrdinals) {
RexNode condition = null;
RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
RelDataType rowType = rel.getRowType();
int n;
if (fieldOrdinals != null) {
n = fieldOrdinals.length;
} else {
n = rowType.getFieldCount();
}
List<RelDataTypeField> 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;
}
return createFilter(rel, condition, RelFactories.DEFAULT_FILTER_FACTORY);
}
/**
* Creates a projection which casts a rel's output to a desired row type.
*
* @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.
*
* @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;
}
List<RexNode> castExps =
RexUtil.generateCastExpressions(
rel.getCluster().getRexBuilder(), castRowType, rowType);
if (rename) {
// Use names and types from castRowType.
return projectFactory.createProject(
rel,
castExps,
castRowType.getFieldNames());
} else {
// Use names from rowType, types from castRowType.
return projectFactory.createProject(
rel,
castExps,
rowType.getFieldNames());
}
}
/**
* Creates an AggregateRel which removes all duplicates from the result of
* an underlying rel.
*
* @param rel underlying rel
* @return rel implementing SingleValueAgg
*/
public static RelNode createSingleValueAggRel(
RelOptCluster cluster,
RelNode rel) {
// assert (rel.getRowType().getFieldCount() == 1);
int aggCallCnt = rel.getRowType().getFieldCount();
List<AggregateCall> aggCalls = new ArrayList<AggregateCall>();
for (int i = 0; i < aggCallCnt; i++) {
RelDataType returnType =
SqlStdOperatorTable.SINGLE_VALUE.inferReturnType(
cluster.getRexBuilder().getTypeFactory(),
ImmutableList.of(
rel.getRowType().getFieldList().get(i).getType()));
aggCalls.add(
new AggregateCall(
SqlStdOperatorTable.SINGLE_VALUE,
false,
ImmutableList.of(i),
returnType,
null));
}
return new AggregateRel(
rel.getCluster(),
rel,
BitSets.of(),
aggCalls);
}
/**
* Creates an AggregateRel which removes all duplicates from the result of
* an underlying rel.
*
* @param rel underlying rel
* @return rel implementing DISTINCT
*/
public static RelNode createDistinctRel(
RelNode rel) {
return new AggregateRel(
rel.getCluster(),
rel,
BitSets.range(rel.getRowType().getFieldCount()),
ImmutableList.<AggregateCall>of());
}
public static boolean analyzeSimpleEquiJoin(
JoinRel joinRel,
int[] joinFieldOrdinals) {
RexNode joinExp = joinRel.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 =
joinRel.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
*
* <blockquote><code>L.A = R.X AND L.B = L.C AND (L.D = 5 OR L.E =
* R.Y)</code></blockquote>
*
* returns
*
* <ul>
* <li>leftKeys = {A}
* <li>rightKeys = {X}
* <li>rest = L.B = L.C AND (L.D = 5 OR L.E = R.Y)</li>
* </ul>
*
* @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
* @return remaining join filters that are not equijoins; may return a
* {@link RexLiteral} true, but never null
*/
public static RexNode splitJoinCondition(
RelNode left,
RelNode right,
RexNode condition,
List<Integer> leftKeys,
List<Integer> rightKeys) {
List<RexNode> nonEquiList = new ArrayList<RexNode>();
splitJoinCondition(
left.getRowType().getFieldCount(),
condition,
leftKeys,
rightKeys,
nonEquiList);
return RexUtil.composeConjunction(
left.getCluster().getRexBuilder(), nonEquiList, false);
}
/**
* Returns whether a join condition is an "equi-join" condition.
*
* @param left Left input of join
* @param right Right input of join
* @param condition Condition
* @return Whether condition is equi-join
*/
public static boolean isEqui(
RelNode left,
RelNode right,
RexNode condition) {
final List<Integer> leftKeys = new ArrayList<Integer>();
final List<Integer> rightKeys = new ArrayList<Integer>();
final List<RexNode> nonEquiList = new ArrayList<RexNode>();
splitJoinCondition(
left.getRowType().getFieldCount(),
condition,
leftKeys,
rightKeys,
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<RelDataTypeField> sysFieldList,
RelNode leftRel,
RelNode rightRel,
RexNode condition,
List<RexNode> leftJoinKeys,
List<RexNode> rightJoinKeys,
List<Integer> filterNulls,
List<SqlOperator> rangeOp) {
List<RexNode> nonEquiList = new ArrayList<RexNode>();
splitJoinCondition(
sysFieldList,
leftRel,
rightRel,
condition,
leftJoinKeys,
rightJoinKeys,
filterNulls,
rangeOp,
nonEquiList);
// Convert the remainders into a list that are AND'ed together.
return RexUtil.composeConjunction(
leftRel.getCluster().getRexBuilder(), nonEquiList, false);
}
public static RexNode splitCorrelatedFilterCondition(
FilterRel filterRel,
List<RexInputRef> joinKeys,
List<RexNode> correlatedJoinKeys) {
List<RexNode> nonEquiList = new ArrayList<RexNode>();
splitCorrelatedFilterCondition(
filterRel,
filterRel.getCondition(),
joinKeys,
correlatedJoinKeys,
nonEquiList);
// Convert the remainders into a list that are AND'ed together.
return RexUtil.composeConjunction(
filterRel.getCluster().getRexBuilder(), nonEquiList, true);
}
public static RexNode splitCorrelatedFilterCondition(
FilterRel filterRel,
List<RexNode> joinKeys,
List<RexNode> correlatedJoinKeys,
boolean extractCorrelatedFieldAccess) {
List<RexNode> nonEquiList = new ArrayList<RexNode>();
splitCorrelatedFilterCondition(
filterRel,
filterRel.getCondition(),
joinKeys,
correlatedJoinKeys,
nonEquiList,
extractCorrelatedFieldAccess);
// Convert the remainders into a list that are AND'ed together.
return RexUtil.composeConjunction(
filterRel.getCluster().getRexBuilder(), nonEquiList, true);
}
private static void splitJoinCondition(
List<RelDataTypeField> sysFieldList,
RelNode leftRel,
RelNode rightRel,
RexNode condition,
List<RexNode> leftJoinKeys,
List<RexNode> rightJoinKeys,
List<Integer> filterNulls,
List<SqlOperator> rangeOp,
List<RexNode> nonEquiList) {
final int sysFieldCount = sysFieldList.size();
final int leftFieldCount = leftRel.getRowType().getFieldCount();
final int rightFieldCount = rightRel.getRowType().getFieldCount();
final int firstLeftField = sysFieldCount;
final int firstRightField = sysFieldCount + leftFieldCount;
final int totalFieldCount = firstRightField + rightFieldCount;
final List<RelDataTypeField> leftFields =
leftRel.getRowType().getFieldList();
final List<RelDataTypeField> rightFields =
rightRel.getRowType().getFieldList();
RexBuilder rexBuilder = leftRel.getCluster().getRexBuilder();
RelDataTypeFactory typeFactory = leftRel.getCluster().getTypeFactory();
// adjustment array
int[] adjustments = new int[totalFieldCount];
for (int i = firstLeftField; i < firstRightField; i++) {
adjustments[i] = -firstLeftField;
}
for (int i = firstRightField; i < totalFieldCount; i++) {
adjustments[i] = -firstRightField;
}
if (condition instanceof RexCall) {
RexCall call = (RexCall) condition;
if (call.getOperator() == SqlStdOperatorTable.AND) {
for (RexNode operand : call.getOperands()) {
splitJoinCondition(
sysFieldList,
leftRel,
rightRel,
operand,
leftJoinKeys,
rightJoinKeys,
filterNulls,
rangeOp,
nonEquiList);
}
return;
}
RexNode leftKey = null;
RexNode rightKey = null;
boolean reverse = false;
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<RexNode> operands = call.getOperands();
RexNode op0 = operands.get(0);
RexNode op1 = operands.get(1);
final BitSet projRefs0 = RelOptUtil.InputFinder.bits(op0);
final BitSet projRefs1 = RelOptUtil.InputFinder.bits(op1);
if ((projRefs0.nextSetBit(firstRightField) < 0)
&& (projRefs1.nextSetBit(firstLeftField)
>= firstRightField)) {
leftKey = op0;
rightKey = op1;
} else if (
(projRefs1.nextSetBit(firstRightField) < 0)
&& (projRefs0.nextSetBit(firstLeftField)
>= firstRightField)) {
leftKey = op1;
rightKey = op0;
reverse = 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 Util.newInternal(
"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 BitSet projRefs = RelOptUtil.InputFinder.bits(condition);
leftKey = null;
rightKey = null;
if (projRefs.nextSetBit(firstRightField) < 0) {
leftKey = condition.accept(
new RelOptUtil.RexInputConverter(
rexBuilder,
leftFields,
leftFields,
adjustments));
rightKey = rexBuilder.makeLiteral(true);
// effectively performing an equality comparison
kind = SqlKind.EQUALS;
} else if (projRefs.nextSetBit(firstLeftField)
>= firstRightField) {
leftKey = rexBuilder.makeLiteral(true);
// replace right Key input ref
rightKey =
condition.accept(
new RelOptUtil.RexInputConverter(
rexBuilder,
rightFields,
rightFields,
adjustments));
// effectively performing an equality comparison
kind = SqlKind.EQUALS;
}
}
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(
leftJoinKeys,
leftKey,
(rangeOp != null) && !rangeOp.isEmpty());
addJoinKey(
rightJoinKeys,
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(leftJoinKeys.size() - 1);
}
if (rangeOp != null
&& kind != SqlKind.EQUALS
&& kind != SqlKind.IS_DISTINCT_FROM) {
if (reverse) {
kind = reverse(kind);
}
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 RexNode createEquiJoinCondition(
final RelNode left, final List<Integer> leftKeys,
final RelNode right, final List<Integer> rightKeys,
final RexBuilder rexBuilder) {
final List<RelDataType> leftTypes =
RelOptUtil.getFieldTypeList(left.getRowType());
final List<RelDataType> rightTypes =
RelOptUtil.getFieldTypeList(right.getRowType());
return RexUtil.composeConjunction(rexBuilder,
new AbstractList<RexNode>() {
@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();
}
},
false);
}
private static SqlKind reverse(SqlKind kind) {
switch (kind) {
case GREATER_THAN:
return SqlKind.LESS_THAN;
case GREATER_THAN_OR_EQUAL:
return SqlKind.LESS_THAN_OR_EQUAL;
case LESS_THAN:
return SqlKind.GREATER_THAN;
case LESS_THAN_OR_EQUAL:
return SqlKind.GREATER_THAN_OR_EQUAL;
default:
return kind;
}
}
private 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<RexNode> joinKeyList,
RexNode key,
boolean preserveLastElementInList) {
if (!joinKeyList.isEmpty() && preserveLastElementInList) {
joinKeyList.add(joinKeyList.size() - 1, key);
} else {
joinKeyList.add(key);
}
}
private static void splitCorrelatedFilterCondition(
FilterRel filterRel,
RexNode condition,
List<RexInputRef> joinKeys,
List<RexNode> correlatedJoinKeys,
List<RexNode> nonEquiList) {
if (condition instanceof RexCall) {
RexCall call = (RexCall) condition;
if (call.getOperator() == SqlStdOperatorTable.AND) {
for (RexNode operand : call.getOperands()) {
splitCorrelatedFilterCondition(
filterRel,
operand,
joinKeys,
correlatedJoinKeys,
nonEquiList);
}
return;
}
if (call.getOperator() == SqlStdOperatorTable.EQUALS) {
final List<RexNode> 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(
FilterRel filterRel,
RexNode condition,
List<RexNode> joinKeys,
List<RexNode> correlatedJoinKeys,
List<RexNode> nonEquiList,
boolean extractCorrelatedFieldAccess) {
if (condition instanceof RexCall) {
RexCall call = (RexCall) condition;
if (call.getOperator() == SqlStdOperatorTable.AND) {
for (RexNode operand : call.getOperands()) {
splitCorrelatedFilterCondition(
filterRel,
operand,
joinKeys,
correlatedJoinKeys,
nonEquiList,
extractCorrelatedFieldAccess);
}
return;
}
if (call.getOperator() == SqlStdOperatorTable.EQUALS) {
final List<RexNode> 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 int leftFieldCount,
RexNode condition,
List<Integer> leftKeys,
List<Integer> rightKeys,
List<RexNode> nonEquiList) {
if (condition instanceof RexCall) {
RexCall call = (RexCall) condition;
final SqlOperator operator = call.getOperator();
if (operator == SqlStdOperatorTable.AND) {
for (RexNode operand : call.getOperands()) {
splitJoinCondition(
leftFieldCount,
operand,
leftKeys,
rightKeys,
nonEquiList);
}
return;
}
// "=" and "IS NOT DISTINCT FROM" are the same except for how they
// treat nulls. TODO: record null treatment
if (operator == SqlStdOperatorTable.EQUALS
|| operator == SqlStdOperatorTable.IS_NOT_DISTINCT_FROM) {
final List<RexNode> 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);
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);
}
}
/**
* Adding projection to the inputs of a join to produce the required join
* keys.
*
* @param inputRels inputs to a join
* @param leftJoinKeys expressions for LHS of join key
* @param rightJoinKeys expressions for RHS of join key
* @param systemColCount number of system columns, usually zero. These
* columns are projected at the leading edge of the
* output row.
* @param leftKeys on return this contains the join key positions from
* the new project rel on the LHS.
* @param rightKeys on return this contains the join key positions from
* the new project rel on the RHS.
* @param outputProj on return this contains the positions of the original
* join output in the (to be formed by caller)
* LhxJoinRel. Caller needs to be responsible for adding
* projection on the new join output.
*/
public static void projectJoinInputs(
RelNode[] inputRels,
List<RexNode> leftJoinKeys,
List<RexNode> rightJoinKeys,
int systemColCount,
List<Integer> leftKeys,
List<Integer> rightKeys,
List<Integer> outputProj) {
RelNode leftRel = inputRels[0];
RelNode rightRel = inputRels[1];
RexBuilder rexBuilder = leftRel.getCluster().getRexBuilder();
int origLeftInputSize = leftRel.getRowType().getFieldCount();
int origRightInputSize = rightRel.getRowType().getFieldCount();
List<RexNode> newLeftFields = new ArrayList<RexNode>();
List<String> newLeftFieldNames = new ArrayList<String>();
List<RexNode> newRightFields = new ArrayList<RexNode>();
List<String> newRightFieldNames = new ArrayList<String>();
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++;
}
}
// added project if need to produce new keys than the original input
// fields
if (newLeftKeyCount > 0) {
leftRel = createProject(leftRel, newLeftFields,
SqlValidatorUtil.uniquify(newLeftFieldNames));
}
if (newRightKeyCount > 0) {
rightRel = createProject(rightRel, newRightFields,
SqlValidatorUtil.uniquify(newRightFieldNames));
}
inputRels[0] = leftRel;
inputRels[1] = rightRel;
}
/**
* Creates a projection on top of a join, if the desired projection is a
* subset of the join columns
*
* @param outputProj desired projection; if null, return original join node
* @param joinRel the join node
* @return projected join node or the original join if projection is
* unnecessary
*/
public static RelNode createProjectJoinRel(
List<Integer> outputProj,
RelNode joinRel) {
int newProjectOutputSize = outputProj.size();
List<RelDataTypeField> 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())) {
List<Pair<RexNode, String>> newProjects =
new ArrayList<Pair<RexNode, String>>();
RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
for (int fieldIndex : outputProj) {
final RelDataTypeField field = joinOutputFields.get(fieldIndex);
newProjects.add(
Pair.of(
(RexNode) rexBuilder.makeInputRef(
field.getType(), fieldIndex),
field.getName()));
}
// Create a project rel on the output of the join.
return createProject(
joinRel,
Pair.left(newProjects),
Pair.right(newProjects));
}
return joinRel;
}
public static void registerAbstractRels(RelOptPlanner planner) {
planner.addRule(PullConstantsThroughAggregatesRule.INSTANCE);
planner.addRule(RemoveEmptyRules.UNION_INSTANCE);
planner.addRule(RemoveEmptyRules.PROJECT_INSTANCE);
planner.addRule(RemoveEmptyRules.FILTER_INSTANCE);
planner.addRule(RemoveEmptyRules.SORT_INSTANCE);
planner.addRule(RemoveEmptyRules.AGGREGATE_INSTANCE);
planner.addRule(RemoveEmptyRules.JOIN_LEFT_INSTANCE);
planner.addRule(RemoveEmptyRules.JOIN_RIGHT_INSTANCE);
planner.addRule(RemoveEmptyRules.SORT_FETCH_ZERO_INSTANCE);
planner.addRule(WindowedAggSplitterRule.PROJECT);
planner.addRule(MergeFilterRule.INSTANCE);
}
/**
* 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 asXml Whether to format as XML.
* @param detailLevel Detail level.
* @return Plan
*/
public static String dumpPlan(
String header,
RelNode rel,
boolean asXml,
SqlExplainLevel detailLevel) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (!header.equals("")) {
pw.println(header);
}
RelWriter planWriter;
if (asXml) {
planWriter = new RelXmlWriter(pw, detailLevel);
} else {
planWriter = new RelWriterImpl(pw, detailLevel, false);
}
rel.explain(planWriter);
pw.flush();
return sw.toString();
}
/**
* 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:
return typeFactory.createStructType(
ImmutableList.of(
Pair.of(
"ROWCOUNT",
typeFactory.createSqlType(SqlTypeName.BIGINT))));
case EXPLAIN:
return typeFactory.createStructType(
ImmutableList.of(
Pair.of(
"PLAN",
typeFactory.createSqlType(
SqlTypeName.VARCHAR,
RelDataType.PRECISION_NOT_SPECIFIED))));
default:
throw Util.unexpected(kind);
}
}
/**
* Returns whether two types are equal using '='.
*
* @param desc1 Description of first type
* @param type1 First type
* @param desc2 Description of second type
* @param type2 Second type
* @param fail Whether to assert if they are not equal
* @return Whether the types are equal
*/
public static boolean eq(
final String desc1,
RelDataType type1,
final String desc2,
RelDataType type2,
boolean fail) {
// if any one of the types is ANY return true
if (type1.getSqlTypeName() == SqlTypeName.ANY
|| type2.getSqlTypeName() == SqlTypeName.ANY) {
return true;
}
if (type1 != type2) {
assert !fail : "type mismatch:\n"
+ desc1 + ":\n"
+ type1.getFullTypeString() + "\n"
+ desc2 + ":\n"
+ type2.getFullTypeString();
return false;
}
return true;
}
/**
* 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 fail 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,
boolean fail) {
if (!areRowTypesEqual(type1, type2, false)) {
if (fail) {
throw new AssertionError(
"Type mismatch:\n"
+ desc1 + ":\n"
+ type1.getFullTypeString() + "\n"
+ desc2 + ":\n"
+ type2.getFullTypeString());
}
return false;
}
return true;
}
/** Returns whether two relational expressions have the same row-type. */
public static boolean equalType(String desc0, RelNode rel0, String desc1,
RelNode rel1, boolean fail) {
// TODO: change 'equal' to 'eq', which is stronger.
return equal(desc0, rel0.getRowType(), desc1, rel1.getRowType(), fail);
}
/**
* Returns a translation of the <code>IS DISTINCT FROM</code> (or <code>IS
* NOT DISTINCT FROM</code>) 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<RelDataTypeField> xFields = x.getType().getFieldList();
List<RelDataTypeField> yFields = y.getType().getFieldList();
assert xFields.size() == yFields.size();
for (Pair<RelDataTypeField, RelDataTypeField> 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.
ret =
rexBuilder.makeCast(
rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BOOLEAN),
ret);
return ret;
}
private static RexNode isDistinctFromInternal(
RexBuilder rexBuilder,
RexNode x,
RexNode y,
boolean neg) {
SqlOperator nullOp;
SqlOperator eqOp;
if (neg) {
nullOp = SqlStdOperatorTable.IS_NULL;
eqOp = SqlStdOperatorTable.EQUALS;
} else {
nullOp = SqlStdOperatorTable.IS_NOT_NULL;
eqOp = SqlStdOperatorTable.NOT_EQUALS;
}
RexNode[] whenThenElse = {
// when x is null
rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, x),
// then return y is [not] null
rexBuilder.makeCall(nullOp, y),
// when y is null
rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, y),
// then return x is [not] null
rexBuilder.makeCall(nullOp, x),
// else return x compared to y
rexBuilder.makeCall(eqOp, x, y)
};
return rexBuilder.makeCall(
SqlStdOperatorTable.CASE,
whenThenElse);
}
/**
* 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();
}
/**
* Renames a relational expression to make its field names the same as
* another row type. If the row type is already identical, or if the row
* type is too different (the fields are different in number or type) does
* nothing.
*
* @param rel Relational expression
* @param desiredRowType Desired row type (including desired field names)
* @return Renamed relational expression, or the original expression if
* there is nothing to do or nothing we <em>can</em> do.
*/
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();
}
/**
* 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<RexNode> 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.
*
* <p>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]}.</p>
*
* <p>TRUE and NOT FALSE expressions are ignored. FALSE and NOT TRUE
* expressions are placed on {@code rexList} and {@code notList} as other
* expressions.</p>
*
* <p>For example, {@code a AND TRUE AND NOT TRUE} returns
* {@code rexList = [a], notList = [TRUE]}.</p>
*
* @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<RexNode> rexList,
List<RexNode> 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;
}
notList.add(e);
break;
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<RexNode> 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.
*
* <p>For example, {@code conjunctions(TRUE)} returns the empty list;
* {@code conjunctions(FALSE)} returns list {@code {FALSE}}.</p>
*/
public static List<RexNode> conjunctions(RexNode rexPredicate) {
final List<RexNode> list = new ArrayList<RexNode>();
decomposeConjunction(rexPredicate, list);
return list;
}
/**
* Returns a condition decomposed by OR.
*
* <p>For example, {@code disjunctions(FALSE)} returns the empty list.</p>
*/
public static List<RexNode> disjunctions(RexNode rexPredicate) {
final List<RexNode> list = new ArrayList<RexNode>();
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 org.eigenbase.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;
}
/**
* 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<Integer> adjustKeys(List<Integer> keys, int adjustment) {
if (adjustment == 0) {
return keys;
}
List<Integer> newKeys = new ArrayList<Integer>();
for (int key : keys) {
newKeys.add(key + adjustment);
}
return newKeys;
}
/**
* 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
* @param smart Whether to try to strengthen the join type
* @return whether at least one filter was pushed, or join type was
* strengthened
*/
public static boolean classifyFilters(
RelNode joinRel,
List<RexNode> filters,
JoinRelType joinType,
boolean pushInto,
boolean pushLeft,
boolean pushRight,
List<RexNode> joinFilters,
List<RexNode> leftFilters,
List<RexNode> rightFilters,
Holder<JoinRelType> joinTypeHolder,
boolean smart) {
RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
final JoinRelType oldJoinType = joinType;
List<RelDataTypeField> joinFields = joinRel.getRowType().getFieldList();
final int nTotalFields = joinFields.size();
final int nSysFields = 0; // joinRel.getSystemFieldList().size();
final List<RelDataTypeField> leftFields =
joinRel.getInputs().get(0).getRowType().getFieldList();
final int nFieldsLeft = leftFields.size();
final List<RelDataTypeField> rightFields =
joinRel.getInputs().get(1).getRowType().getFieldList();
final int nFieldsRight = rightFields.size();
assert nTotalFields == (joinRel instanceof SemiJoinRel
? nSysFields + nFieldsLeft
: nSysFields + nFieldsLeft + nFieldsRight);
// set the reference bitmaps for the left and right children
BitSet leftBitmap =
BitSets.range(nSysFields, nSysFields + nFieldsLeft);
BitSet rightBitmap =
BitSets.range(nSysFields + nFieldsLeft, nTotalFields);
final List<RexNode> filtersToRemove = Lists.newArrayList();
for (RexNode filter : filters) {
final InputFinder inputFinder = InputFinder.analyze(filter);
// 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 && BitSets.contains(leftBitmap, inputFinder.inputBitSet)) {
// 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
&& BitSets.contains(rightBitmap, inputFinder.inputBitSet)) {
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 == JoinRelType.INNER && pushInto) {
if (!joinFilters.contains(filter)) {
joinFilters.add(filter);
}
filtersToRemove.add(filter);
}
// If the filter will only evaluate to true if fields from the left
// are not null, and the left is null-generating, then we can make the
// left. Similarly for the right.
if (smart
&& joinType.generatesNullsOnRight()
&& Strong.is(filter, rightBitmap)) {
joinType = joinType.cancelNullsOnRight();
joinTypeHolder.set(joinType);
if (pushInto) {
filtersToRemove.add(filter);
if (!joinFilters.contains(filter)) {
joinFilters.add(filter);
}
}
}
if (smart
&& joinType.generatesNullsOnLeft()
&& Strong.is(filter, leftBitmap)) {
filtersToRemove.add(filter);
joinType = joinType.cancelNullsOnLeft();
joinTypeHolder.set(joinType);
if (pushInto) {
filtersToRemove.add(filter);
if (!joinFilters.contains(filter)) {
joinFilters.add(filter);
}
}
}
}
}
// Remove filters after the loop, to prevent concurrent modification.
if (!filtersToRemove.isEmpty()) {
filters.removeAll(filtersToRemove);
}
// Did anything change?
return !filtersToRemove.isEmpty() || joinType != oldJoinType;
}
private static RexNode shiftFilter(
int start,
int end,
int offset,
RexBuilder rexBuilder,
List<RelDataTypeField> joinFields,
int nTotalFields,
List<RelDataTypeField> 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(
BitSet childBitmap,
RexNode predicate,
List<RexNode> pushable,
List<RexNode> notPushable) {
// for each filter, if the filter only references the child inputs,
// then it can be pushed
for (RexNode filter : conjunctions(predicate)) {
BitSet filterRefs = RelOptUtil.InputFinder.bits(filter);
if (BitSets.contains(childBitmap, filterRefs)) {
pushable.add(filter);
} else {
notPushable.add(filter);
}
}
}
/**
* Splits a join condition.
*
* @param left Left input to the join
* @param right Right input to the join
* @param condition Join condition
* @return Array holding the output; neither element is null. Element 0 is
* the equi-join condition (or TRUE if empty); Element 1 is rest of the
* condition (or TRUE if empty).
*
* @deprecated Will be removed after 0.9.1
*/
public static RexNode[] splitJoinCondition(
RelNode left,
RelNode right,
RexNode condition) {
Bug.upgrade("remove after 0.9.1");
final RexBuilder rexBuilder = left.getCluster().getRexBuilder();
final List<Integer> leftKeys = new ArrayList<Integer>();
final List<Integer> rightKeys = new ArrayList<Integer>();
final RexNode nonEquiCondition =
splitJoinCondition(
left,
right,
condition,
leftKeys,
rightKeys);
assert nonEquiCondition != null;
RexNode equiCondition = rexBuilder.makeLiteral(true);
assert leftKeys.size() == rightKeys.size();
final int keyCount = leftKeys.size();
int offset = left.getRowType().getFieldCount();
for (int i = 0; i < keyCount; i++) {
int leftKey = leftKeys.get(i);
int rightKey = rightKeys.get(i);
RexNode equi =
rexBuilder.makeCall(
SqlStdOperatorTable.EQUALS,
rexBuilder.makeInputRef(left, leftKey),
rexBuilder.makeInputRef(
right.getRowType().getFieldList().get(rightKey)
.getType(),
rightKey + offset));
if (i == 0) {
equiCondition = equi;
} else {
equiCondition =
rexBuilder.makeCall(
SqlStdOperatorTable.AND,
equiCondition,
equi);
}
}
return new RexNode[]{equiCondition, nonEquiCondition};
}
/**
* Determines if a projection and its input reference identical input
* references.
*
* @param project projection being examined
* @param checkNames if true, also compare that the names of the project
* fields and its child fields
* @return if checkNames is false, true is returned if the project and its
* child reference the same input references, regardless of the names of the
* project and child fields; if checkNames is true, then true is returned if
* the input references are the same but the field names are different
*/
public static boolean checkProjAndChildInputs(
ProjectRelBase project,
boolean checkNames) {
if (!project.isBoxed()) {
return false;
}
int n = project.getProjects().size();
RelDataType inputType = project.getChild().getRowType();
if (inputType.getFieldList().size() != n) {
return false;
}
List<RelDataTypeField> projFields = project.getRowType().getFieldList();
List<RelDataTypeField> 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 JoinRel
* @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<RexNode> createSwappedJoinExprs(
RelNode newJoin,
JoinRelBase origJoin,
boolean origOrder) {
final List<RelDataTypeField> newJoinFields =
newJoin.getRowType().getFieldList();
final RexBuilder rexBuilder = newJoin.getCluster().getRexBuilder();
final List<RexNode> exps = new ArrayList<RexNode>();
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;
}
/**
* Converts a filter to the new filter that would result if the filter is
* pushed past a ProjectRel that it currently is referencing.
*
* @param filter the filter to be converted
* @param projRel project rel underneath the filter
* @return converted filter
*/
public static RexNode pushFilterPastProject(
RexNode filter,
ProjectRelBase projRel) {
// use RexPrograms to merge the filter and ProjectRel into a
// single program so we can convert the FilterRel condition to
// directly reference the ProjectRel's child
RexBuilder rexBuilder = projRel.getCluster().getRexBuilder();
RexProgram bottomProgram =
RexProgram.create(
projRel.getChild().getRowType(),
projRel.getProjects(),
null,
projRel.getRowType(),
rexBuilder);
RexProgramBuilder topProgramBuilder =
new RexProgramBuilder(
projRel.getRowType(),
rexBuilder);
topProgramBuilder.addIdentity();
topProgramBuilder.addCondition(filter);
RexProgram topProgram = topProgramBuilder.getProgram();
RexProgram mergedProgram =
RexProgramBuilder.mergePrograms(
topProgram,
bottomProgram,
rexBuilder);
return mergedProgram.expandLocalRef(
mergedProgram.getCondition());
}
/**
* Creates a new {@link MultiJoinRel} to reflect projection references from
* a {@link ProjectRel} that is on top of the {@link MultiJoinRel}.
*
* @param multiJoin the original MultiJoinRel
* @param project the ProjectRel on top of the MultiJoinRel
* @return the new MultiJoinRel
*/
public static MultiJoinRel projectMultiJoin(
MultiJoinRel multiJoin,
ProjectRel project) {
// Locate all input references in the projection expressions as well
// the post-join filter. Since the filter effectively sits in
// between the ProjectRel and the MultiJoinRel, the projection needs
// to include those filter references.
BitSet inputRefs = InputFinder.bits(
project.getProjects(), multiJoin.getPostJoinFilter());
// create new copies of the bitmaps
List<RelNode> multiJoinInputs = multiJoin.getInputs();
List<BitSet> newProjFields = new ArrayList<BitSet>();
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 : BitSets.toIter(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 MultiJoinRel containing the new field bitmaps
// for each input
return new MultiJoinRel(
multiJoin.getCluster(),
multiJoin.getInputs(),
multiJoin.getJoinFilter(),
multiJoin.getRowType(),
multiJoin.isFullOuterJoin(),
multiJoin.getOuterJoinConditions(),
multiJoin.getJoinTypes(),
newProjFields,
multiJoin.getJoinFieldRefCountsMap(),
multiJoin.getPostJoinFilter());
}
public static <T extends RelNode> T addTrait(
T rel, RelTrait trait) {
//noinspection unchecked
return (T) rel.copy(
rel.getTraitSet().replace(trait),
(List) 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<RelNode> inputs = new ArrayList<RelNode>(parent.getInputs());
if (inputs.get(ordinal) == newInput) {
return parent;
}
inputs.set(ordinal, newInput);
return parent.copy(parent.getTraitSet(), inputs);
}
/**
* Creates a {@link org.eigenbase.rel.ProjectRel} that projects particular
* fields of its input, according to a mapping.
*/
public static ProjectRel project(
RelNode child,
Mappings.TargetMapping mapping) {
List<RexNode> nodes = new ArrayList<RexNode>();
List<String> names = new ArrayList<String>();
final List<RelDataTypeField> fields = child.getRowType().getFieldList();
for (int i = 0; i < mapping.getTargetCount(); i++) {
int source = mapping.getSourceOpt(i);
RelDataTypeField field = fields.get(source);
nodes.add(new RexInputRef(source, field.getType()));
names.add(field.getName());
}
return new ProjectRel(
child.getCluster(), child, nodes, names, ProjectRel.Flags.BOXED);
}
/** 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, true);
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<RelNode> inputs = query.getInputs();
if (!inputs.isEmpty()) {
final List<RelNode> newInputs = new ArrayList<RelNode>();
for (RelNode input : inputs) {
newInputs.add(replaceRecurse(input, find, replace));
}
if (!newInputs.equals(inputs)) {
return query.copy(query.getTraitSet(), newInputs);
}
}
return query;
}
/** Returns a simple {@link org.eigenbase.relopt.RelOptTable.ToRelContext}. */
public static RelOptTable.ToRelContext getContext(
final RelOptCluster cluster) {
return new RelOptTable.ToRelContext() {
public RelOptCluster getCluster() {
return cluster;
}
public RelNode expandView(
RelDataType rowType,
String queryString,
List<String> schemaPath) {
throw new UnsupportedOperationException();
}
};
}
/** Returns the number of {@link org.eigenbase.rel.JoinRelBase} 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 JoinRelBase) {
++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()));
}
/**
* Creates a relational expression which projects a list of expressions.
*
* @param child input relational expression
* @param exprList list of expressions for the input columns
* @param fieldNameList aliases of the expressions, or null to generate
*/
public static RelNode createProject(
RelNode child,
List<? extends RexNode> exprList,
List<String> fieldNameList) {
return createProject(child, exprList, fieldNameList, false);
}
/**
* Creates a relational expression which projects a list of (expression, name)
* pairs.
*
* @param child input relational expression
* @param projectList list of (expression, name) pairs
* @param optimize Whether to optimize
*/
public static RelNode createProject(
RelNode child,
List<Pair<RexNode, String>> projectList,
boolean optimize) {
return createProject(child, Pair.left(projectList), Pair.right(projectList),
optimize);
}
/**
* Creates a relational expression that projects the given fields of the
* input.
*
* <p>Optimizes if the fields are the identity projection.</p>
*
* @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<Integer> posList) {
return createProject(RelFactories.DEFAULT_PROJECT_FACTORY,
child, posList);
}
/**
* Creates a relational expression which projects an array of expressions,
* and optionally optimizes.
*
* <p>The result may not be a {@link org.eigenbase.rel.ProjectRel}. If the
* projection is trivial, <code>child</code> is returned directly; and future
* versions may return other formulations of expressions, such as
* {@link org.eigenbase.rel.CalcRel}.
*
* @param child input relational expression
* @param exprs list of expressions for the input columns
* @param fieldNames aliases of the expressions, or null to generate
* @param optimize Whether to return <code>child</code> unchanged if the
* projections are trivial.
*/
public static RelNode createProject(
RelNode child,
List<? extends RexNode> exprs,
List<String> fieldNames,
boolean optimize) {
final RelOptCluster cluster = child.getCluster();
final RexProgram program =
RexProgram.create(
child.getRowType(), exprs, null, fieldNames,
cluster.getRexBuilder());
final List<RelCollation> collationList =
program.getCollations(child.getCollationList());
final RelDataType rowType =
RexUtil.createStructType(
cluster.getTypeFactory(),
exprs,
fieldNames == null
? null
: SqlValidatorUtil.uniquify(
fieldNames, SqlValidatorUtil.F_SUGGESTER));
if (optimize
&& RemoveTrivialProjectRule.isIdentity(exprs, rowType,
child.getRowType())) {
return child;
}
return new ProjectRel(cluster,
cluster.traitSetOf(collationList.isEmpty()
? RelCollationImpl.EMPTY
: collationList.get(0)),
child,
exprs,
rowType,
ProjectRelBase.Flags.BOXED);
}
/**
* Returns a relational expression which has the same fields as the
* underlying expression, but the fields have different names.
*
* @param rel Relational expression
* @param fieldNames Field names
* @return Renamed relational expression
*/
public static RelNode createRename(
RelNode rel,
List<String> fieldNames) {
final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
assert fieldNames.size() == fields.size();
final List<Pair<RexNode, String>> refs =
new AbstractList<Pair<RexNode, String>>() {
public int size() {
return fields.size();
}
public Pair<RexNode, String> get(int index) {
return RexInputRef.of2(index, fields);
}
};
return createProject(rel, refs, true);
}
/**
* Creates a relational expression which permutes the output fields of a
* relational expression according to a permutation.
*
* <p>Optimizations:</p>
*
* <ul>
* <li>If the relational expression is a {@link org.eigenbase.rel.CalcRel} or
* {@link org.eigenbase.rel.ProjectRel} that is already acting as a
* permutation, combines the new permutation with the old;</li>
*
* <li>If the permutation is the identity, returns the original relational
* expression.</li>
* </ul>
*
* <p>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<String> fieldNames) {
if (permutation.isIdentity()) {
return rel;
}
if (rel instanceof CalcRel) {
CalcRel calcRel = (CalcRel) rel;
Permutation permutation1 = calcRel.getProgram().getPermutation();
if (permutation1 != null) {
Permutation permutation2 = permutation.product(permutation1);
return permute(rel, permutation2, null);
}
}
if (rel instanceof ProjectRel) {
Permutation permutation1 = ((ProjectRel) rel).getPermutation();
if (permutation1 != null) {
Permutation permutation2 = permutation.product(permutation1);
return permute(rel, permutation2, null);
}
}
final List<RelDataType> outputTypeList = new ArrayList<RelDataType>();
final List<String> outputNameList = new ArrayList<String>();
final List<RexNode> exprList = new ArrayList<RexNode>();
final List<RexLocalRef> projectRefList = new ArrayList<RexLocalRef>();
final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
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(
rel.getCluster().getRexBuilder().makeInputRef(
fields.get(i).getType(),
i));
final int source = permutation.getSource(i);
projectRefList.add(
new RexLocalRef(
source,
fields.get(source).getType()));
}
final RexProgram program =
new RexProgram(
rel.getRowType(),
exprList,
projectRefList,
null,
rel.getCluster().getTypeFactory().createStructType(
outputTypeList,
outputNameList));
return new CalcRel(
rel.getCluster(),
rel.getTraitSet(),
rel,
program.getOutputRowType(),
program,
ImmutableList.<RelCollation>of());
}
/**
* Creates a relational expression that projects the given fields of the
* input.
*
* <p>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<Integer> posList) {
if (Mappings.isIdentity(posList, child.getRowType().getFieldCount())) {
return child;
}
final RexBuilder rexBuilder = child.getCluster().getRexBuilder();
return factory.createProject(child,
new AbstractList<RexNode>() {
public int size() {
return posList.size();
}
public RexNode get(int index) {
final int pos = posList.get(index);
return rexBuilder.makeInputRef(child, pos);
}
},
null);
}
/**
* Creates a relational expression which projects the output fields of a
* relational expression according to a partial mapping.
*
* <p>A partial mapping is weaker than a permutation: every target has one
* source, but a source may have 0, 1 or more than one targets. Usually the
* result will have fewer fields than the source, unless some source fields
* are projected multiple times.
*
* <p>This method could optimize the result as {@link #permute} does, but
* does not at present.
*
* @param rel Relational expression
* @param mapping Mapping from source fields to target fields. The mapping
* type must obey the constraints
* {@link org.eigenbase.util.mapping.MappingType#isMandatorySource()}
* and
* {@link org.eigenbase.util.mapping.MappingType#isSingleSource()},
* as does
* {@link org.eigenbase.util.mapping.MappingType#INVERSE_FUNCTION}.
* @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 projects a subset of the input fields
*/
public static RelNode projectMapping(
RelNode rel,
Mapping mapping,
List<String> fieldNames,
RelFactories.ProjectFactory projectFactory) {
assert mapping.getMappingType().isSingleSource();
assert mapping.getMappingType().isMandatorySource();
if (mapping.isIdentity()) {
return rel;
}
final List<String> outputNameList = Lists.newArrayList();
final List<RexNode> exprList = Lists.newArrayList();
final List<RelDataTypeField> 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, exprList, outputNameList);
}
/** 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.
*
* <p>"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.
*
* <p>"x IS FALSE" produces the same result, as does "WHERE NOT x", etc.
*
* <p>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;
public Logic negate() {
switch (this) {
case UNKNOWN_AS_FALSE:
return UNKNOWN_AS_TRUE;
case UNKNOWN_AS_TRUE:
return UNKNOWN_AS_FALSE;
default:
return this;
}
}
}
//~ Inner Classes ----------------------------------------------------------
/** Visitor that finds all variables used but not stopped in an expression. */
private static class VariableSetVisitor extends RelVisitor {
final Set<String> variables = new HashSet<String>();
// 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.getVariablesStopped());
}
}
/** Visitor that finds all variables used in an expression. */
public static class VariableUsedVisitor extends RexShuttle {
public final Set<String> variables = new LinkedHashSet<String>();
public RexNode visitCorrelVariable(RexCorrelVariable p) {
variables.add(p.getName());
return p;
}
}
/** Shuttle that finds the set of inputs that are used. */
public static class InputReferencedVisitor extends RexShuttle {
public final SortedSet<Integer> inputPosReferenced =
new TreeSet<Integer>();
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<RelDataTypeField> 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<RelDataTypeField> 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<Void> {
final BitSet inputBitSet;
private final Set<RelDataTypeField> extraFields;
public InputFinder(BitSet inputBitSet) {
this(inputBitSet, null);
}
public InputFinder(BitSet inputBitSet, Set<RelDataTypeField> extraFields) {
super(true);
this.inputBitSet = inputBitSet;
this.extraFields = extraFields;
}
/** Returns an input finder that has analyzed a given expression. */
public static InputFinder analyze(RexNode node) {
final InputFinder inputFinder = new InputFinder(new BitSet());
node.accept(inputFinder);
return inputFinder;
}
/**
* Returns a bit set describing the inputs used by an expression.
*/
public static BitSet bits(RexNode node) {
return analyze(node).inputBitSet;
}
/**
* Returns a bit set describing the inputs used by a collection of
* project expressions and an optional condition.
*/
public static BitSet bits(List<RexNode> exprs, RexNode expr) {
final BitSet inputBitSet = new BitSet();
RexProgram.apply(new InputFinder(inputBitSet), exprs, expr);
return inputBitSet;
}
public Void visitInputRef(RexInputRef inputRef) {
inputBitSet.set(inputRef.getIndex());
return null;
}
@Override
public Void visitCall(RexCall call) {
if (call.getOperator() == RexBuilder.GET_OPERATOR) {
RexLiteral literal = (RexLiteral) call.getOperands().get(1);
extraFields.add(
new RelDataTypeFieldImpl(
(String) literal.getValue2(),
-1,
call.getType()));
}
return super.visitCall(call);
}
}
/**
* Walks an expression tree, converting the index of RexInputRefs based on
* some adjustment factor.
*/
public static class RexInputConverter extends RexShuttle {
protected final RexBuilder rexBuilder;
private final List<RelDataTypeField> srcFields;
protected final List<RelDataTypeField> destFields;
private final List<RelDataTypeField> leftDestFields;
private final List<RelDataTypeField> rightDestFields;
private final int nLeftDestFields;
private final int[] adjustments;
/**
* @param rexBuilder builder for creating new RexInputRefs
* @param srcFields fields where the RexInputRefs originated
* from; if null, a new RexInputRef is always
* created, referencing the input from destFields
* corresponding to its current index value
* @param destFields fields that the new RexInputRefs will be
* referencing; if null, use the type information
* from the source field when creating the new
* RexInputRef
* @param leftDestFields in the case where the destination is a join,
* these are the fields from the left join input
* @param rightDestFields in the case where the destination is a join,
* these are the fields from the right join input
* @param adjustments the amount to adjust each field by
*/
private RexInputConverter(
RexBuilder rexBuilder,
List<RelDataTypeField> srcFields,
List<RelDataTypeField> destFields,
List<RelDataTypeField> leftDestFields,
List<RelDataTypeField> rightDestFields,
int[] adjustments) {
this.rexBuilder = rexBuilder;
this.srcFields = srcFields;
this.destFields = destFields;
this.adjustments = adjustments;
this.leftDestFields = leftDestFields;
this.rightDestFields = rightDestFields;
if (leftDestFields == null) {
nLeftDestFields = 0;
} else {
assert destFields == null;
nLeftDestFields = leftDestFields.size();
}
}
public RexInputConverter(
RexBuilder rexBuilder,
List<RelDataTypeField> srcFields,
List<RelDataTypeField> leftDestFields,
List<RelDataTypeField> rightDestFields,
int[] adjustments) {
this(
rexBuilder,
srcFields,
null,
leftDestFields,
rightDestFields,
adjustments);
}
public RexInputConverter(
RexBuilder rexBuilder,
List<RelDataTypeField> srcFields,
List<RelDataTypeField> destFields,
int[] adjustments) {
this(rexBuilder, srcFields, destFields, null, null, adjustments);
}
public RexInputConverter(
RexBuilder rexBuilder,
List<RelDataTypeField> srcFields,
int[] adjustments) {
this(rexBuilder, srcFields, null, null, null, adjustments);
}
public RexNode visitInputRef(RexInputRef var) {
int srcIndex = var.getIndex();
int destIndex = srcIndex + adjustments[srcIndex];
RelDataType type;
if (destFields != null) {
type = destFields.get(destIndex).getType();
} else if (leftDestFields != null) {
if (destIndex < nLeftDestFields) {
type = leftDestFields.get(destIndex).getType();
} else {
type =
rightDestFields.get(destIndex - nLeftDestFields).getType();
}
} else {
type = srcFields.get(srcIndex).getType();
}
if ((adjustments[srcIndex] != 0)
|| (srcFields == null)
|| (type != srcFields.get(srcIndex).getType())) {
return rexBuilder.makeInputRef(type, destIndex);
} else {
return var;
}
}
}
/** What kind of sub-query. */
public enum SubqueryType {
EXISTS,
IN,
SCALAR
}
}
// End RelOptUtil.java