blob: 6ac717d7a678d3975459c859df709bbf900ec994 [file] [log] [blame]
/*
* 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.apache.calcite.rel.rules;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.Strong;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Correlate;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.SetOp;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.BitSets;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Pair;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static java.util.Objects.requireNonNull;
/**
* PushProjector is a utility class used to perform operations used in push
* projection rules.
*
* <p>Pushing is particularly interesting in the case of join, because there
* are multiple inputs. Generally an expression can be pushed down to a
* particular input if it depends upon no other inputs. If it can be pushed
* down to both sides, it is pushed down to the left.
*
* <p>Sometimes an expression needs to be split before it can be pushed down.
* To flag that an expression cannot be split, specify a rule that it must be
* <dfn>preserved</dfn>. Such an expression will be pushed down intact to one
* of the inputs, or not pushed down at all.</p>
*/
public class PushProjector {
//~ Instance fields --------------------------------------------------------
private final @Nullable Project origProj;
private final @Nullable RexNode origFilter;
private final RelNode childRel;
private final ExprCondition preserveExprCondition;
private final RelBuilder relBuilder;
/**
* Original projection expressions.
*/
final List<RexNode> origProjExprs;
/**
* Fields from the RelNode that the projection is being pushed past.
*/
final List<RelDataTypeField> childFields;
/**
* Number of fields in the RelNode that the projection is being pushed past.
*/
final int nChildFields;
/**
* Bitmap containing the references in the original projection.
*/
final BitSet projRefs;
/**
* Bitmap containing the fields in the RelNode that the projection is being
* pushed past, if the RelNode is not a join. If the RelNode is a join, then
* the fields correspond to the left hand side of the join.
*/
final ImmutableBitSet childBitmap;
/**
* Bitmap containing the fields in the right hand side of a join, in the
* case where the projection is being pushed past a join. Not used
* otherwise.
*/
final @Nullable ImmutableBitSet rightBitmap;
/**
* Bitmap containing the fields that should be strong, i.e. when preserving expressions
* we can only preserve them if the expressions if it is null when these fields are null.
*/
final @Nullable ImmutableBitSet strongBitmap;
/**
* Number of fields in the RelNode that the projection is being pushed past,
* if the RelNode is not a join. If the RelNode is a join, then this is the
* number of fields in the left hand side of the join.
*
* <p>The identity
* {@code nChildFields == nSysFields + nFields + nFieldsRight}
* holds. {@code nFields} does not include {@code nSysFields}.
* The output of a join looks like this:
*
* <blockquote><pre>
* | nSysFields | nFields | nFieldsRight |
* </pre></blockquote>
*
* <p>The output of a single-input rel looks like this:
*
* <blockquote><pre>
* | nSysFields | nFields |
* </pre></blockquote>
*/
final int nFields;
/**
* Number of fields in the right hand side of a join, in the case where the
* projection is being pushed past a join. Always 0 otherwise.
*/
final int nFieldsRight;
/**
* Number of system fields. System fields appear at the start of a join,
* before the first field from the left input.
*/
private final int nSysFields;
/**
* Expressions referenced in the projection/filter that should be preserved.
* In the case where the projection is being pushed past a join, then the
* list only contains the expressions corresponding to the left hand side of
* the join.
*/
final List<RexNode> childPreserveExprs;
/**
* Expressions referenced in the projection/filter that should be preserved,
* corresponding to expressions on the right hand side of the join, if the
* projection is being pushed past a join. Empty list otherwise.
*/
final List<RexNode> rightPreserveExprs;
/**
* Number of system fields being projected.
*/
int nSystemProject;
/**
* Number of fields being projected. In the case where the projection is
* being pushed past a join, the number of fields being projected from the
* left hand side of the join.
*/
int nProject;
/**
* Number of fields being projected from the right hand side of a join, in
* the case where the projection is being pushed past a join. 0 otherwise.
*/
int nRightProject;
/**
* Rex builder used to create new expressions.
*/
final RexBuilder rexBuilder;
//~ Constructors -----------------------------------------------------------
/**
* Creates a PushProjector object for pushing projects past a RelNode.
*
* @param origProj the original projection that is being pushed;
* may be null if the projection is implied as a
* result of a projection having been trivially
* removed
* @param origFilter the filter that the projection must also be
* pushed past, if applicable
* @param childRel the RelNode that the projection is being
* pushed past
* @param preserveExprCondition condition for whether an expression should
* be preserved in the projection
*/
public PushProjector(
@Nullable Project origProj,
@Nullable RexNode origFilter,
RelNode childRel,
ExprCondition preserveExprCondition,
RelBuilder relBuilder) {
this.origProj = origProj;
this.origFilter = origFilter;
this.childRel = childRel;
this.preserveExprCondition = preserveExprCondition;
this.relBuilder = requireNonNull(relBuilder, "relBuilder");
if (origProj == null) {
origProjExprs = ImmutableList.of();
} else {
origProjExprs = origProj.getProjects();
}
if (childRel instanceof Join) {
Join join = (Join) childRel;
childFields = Lists.newArrayList(join.getLeft().getRowType().getFieldList());
childFields.addAll(join.getRight().getRowType().getFieldList());
} else {
childFields = childRel.getRowType().getFieldList();
}
nChildFields = childFields.size();
projRefs = new BitSet(nChildFields);
if (childRel instanceof Join) {
Join joinRel = (Join) childRel;
List<RelDataTypeField> leftFields =
joinRel.getLeft().getRowType().getFieldList();
List<RelDataTypeField> rightFields =
joinRel.getRight().getRowType().getFieldList();
nFields = leftFields.size();
nFieldsRight = rightFields.size();
nSysFields = joinRel.getSystemFieldList().size();
childBitmap =
ImmutableBitSet.range(nSysFields, nFields + nSysFields);
rightBitmap =
ImmutableBitSet.range(nFields + nSysFields, nChildFields);
switch (joinRel.getJoinType()) {
case INNER:
strongBitmap = ImmutableBitSet.of();
break;
case RIGHT: // All the left-input's columns must be strong
strongBitmap = ImmutableBitSet.range(nSysFields, nFields + nSysFields);
break;
case LEFT: // All the right-input's columns must be strong
strongBitmap = ImmutableBitSet.range(nFields + nSysFields, nChildFields);
break;
case FULL:
default:
strongBitmap = ImmutableBitSet.range(nSysFields, nChildFields);
}
} else if (childRel instanceof Correlate) {
Correlate corrRel = (Correlate) childRel;
List<RelDataTypeField> leftFields =
corrRel.getLeft().getRowType().getFieldList();
List<RelDataTypeField> rightFields =
corrRel.getRight().getRowType().getFieldList();
nFields = leftFields.size();
JoinRelType joinType = corrRel.getJoinType();
switch (joinType) {
case SEMI:
case ANTI:
nFieldsRight = 0;
break;
default:
nFieldsRight = rightFields.size();
}
nSysFields = 0;
childBitmap =
ImmutableBitSet.range(0, nFields);
rightBitmap =
ImmutableBitSet.range(nFields, nChildFields);
// Required columns need to be included in project
projRefs.or(BitSets.of(corrRel.getRequiredColumns()));
switch (joinType) {
case INNER:
strongBitmap = ImmutableBitSet.of();
break;
case ANTI:
case SEMI: // All the left-input's columns must be strong
strongBitmap = ImmutableBitSet.range(0, nFields);
break;
case LEFT: // All the right-input's columns must be strong
strongBitmap = ImmutableBitSet.range(nFields, nChildFields);
break;
default:
strongBitmap = ImmutableBitSet.range(0, nChildFields);
}
} else {
nFields = nChildFields;
nFieldsRight = 0;
childBitmap = ImmutableBitSet.range(nChildFields);
rightBitmap = null;
nSysFields = 0;
strongBitmap = ImmutableBitSet.of();
}
assert nChildFields == nSysFields + nFields + nFieldsRight;
childPreserveExprs = new ArrayList<>();
rightPreserveExprs = new ArrayList<>();
rexBuilder = childRel.getCluster().getRexBuilder();
}
//~ Methods ----------------------------------------------------------------
/**
* Decomposes a projection to the input references referenced by a
* projection and a filter, either of which is optional. If both are
* provided, the filter is underneath the project.
*
* <p>Creates a projection containing all input references as well as
* preserving any special expressions. Converts the original projection
* and/or filter to reference the new projection. Then, finally puts on top,
* a final projection corresponding to the original projection.
*
* @param defaultExpr expression to be used in the projection if no fields
* or special columns are selected
* @return the converted projection if it makes sense to push elements of
* the projection; otherwise returns null
*/
public @Nullable RelNode convertProject(@Nullable RexNode defaultExpr) {
// locate all fields referenced in the projection and filter
locateAllRefs();
// if all columns are being selected (either explicitly in the
// projection) or via a "select *", then there needs to be some
// special expressions to preserve in the projection; otherwise,
// there's no point in proceeding any further
if (origProj == null) {
if (childPreserveExprs.size() == 0) {
return null;
}
// even though there is no projection, this is the same as
// selecting all fields
if (nChildFields > 0) {
// Calling with nChildFields == 0 should be safe but hits
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6222207
projRefs.set(0, nChildFields);
}
nProject = nChildFields;
} else if (
(projRefs.cardinality() == nChildFields)
&& (childPreserveExprs.size() == 0)) {
return null;
}
// if nothing is being selected from the underlying rel, just
// project the default expression passed in as a parameter or the
// first column if there is no default expression
if ((projRefs.cardinality() == 0) && (childPreserveExprs.size() == 0)) {
if (defaultExpr != null) {
childPreserveExprs.add(defaultExpr);
} else if (nChildFields == 1) {
return null;
} else {
projRefs.set(0);
nProject = 1;
}
}
// create a new projection referencing all fields referenced in
// either the project or the filter
RelNode newProject = createProjectRefsAndExprs(childRel, false, false);
int[] adjustments = getAdjustments();
// if a filter was passed in, convert it to reference the projected
// columns, placing it on top of the project just created
RelNode projChild;
if (origFilter != null) {
RexNode newFilter =
convertRefsAndExprs(
origFilter,
newProject.getRowType().getFieldList(),
adjustments);
relBuilder.push(newProject);
relBuilder.filter(newFilter);
projChild = relBuilder.build();
} else {
projChild = newProject;
}
// put the original project on top of the filter/project, converting
// it to reference the modified projection list; otherwise, create
// a projection that essentially selects all fields
return createNewProject(projChild, adjustments);
}
/**
* Locates all references found in either the projection expressions a
* filter, as well as references to expressions that should be preserved.
* Based on that, determines whether pushing the projection makes sense.
*
* @return true if all inputs from the child that the projection is being
* pushed past are referenced in the projection/filter and no special
* preserve expressions are referenced; in that case, it does not make sense
* to push the projection
*/
public boolean locateAllRefs() {
RexUtil.apply(
new InputSpecialOpFinder(
projRefs,
childBitmap,
rightBitmap,
requireNonNull(strongBitmap, "strongBitmap"),
preserveExprCondition,
childPreserveExprs,
rightPreserveExprs),
origProjExprs,
origFilter);
// The system fields of each child are always used by the join, even if
// they are not projected out of it.
projRefs.set(
nSysFields,
nSysFields + nSysFields,
true);
projRefs.set(
nSysFields + nFields,
nSysFields + nFields + nSysFields,
true);
// Count how many fields are projected.
nSystemProject = 0;
nProject = 0;
nRightProject = 0;
for (int bit : BitSets.toIter(projRefs)) {
if (bit < nSysFields) {
nSystemProject++;
} else if (bit < nSysFields + nFields) {
nProject++;
} else {
nRightProject++;
}
}
assert nSystemProject + nProject + nRightProject
== projRefs.cardinality();
if ((childRel instanceof Join)
|| (childRel instanceof SetOp)) {
// if nothing is projected from the children, arbitrarily project
// the first columns; this is necessary since Fennel doesn't
// handle 0-column projections
if ((nProject == 0) && (childPreserveExprs.size() == 0)) {
projRefs.set(0);
nProject = 1;
}
if (childRel instanceof Join) {
if ((nRightProject == 0) && (rightPreserveExprs.size() == 0)) {
projRefs.set(nFields);
nRightProject = 1;
}
}
}
// no need to push projections if all children fields are being
// referenced and there are no special preserve expressions; note
// that we need to do this check after we've handled the 0-column
// project cases
boolean allFieldsReferenced = IntStream.range(0, nChildFields).allMatch(i -> projRefs.get(i));
if (allFieldsReferenced
&& childPreserveExprs.size() == 0
&& rightPreserveExprs.size() == 0) {
return true;
}
return false;
}
/**
* Creates a projection based on the inputs specified in a bitmap and the
* expressions that need to be preserved. The expressions are appended after
* the input references.
*
* @param projChild child that the projection will be created on top of
* @param adjust if true, need to create new projection expressions;
* otherwise, the existing ones are reused
* @param rightSide if true, creating a projection for the right hand side
* of a join
* @return created projection
*/
public Project createProjectRefsAndExprs(
RelNode projChild,
boolean adjust,
boolean rightSide) {
List<RexNode> preserveExprs;
int nInputRefs;
int offset;
if (rightSide) {
preserveExprs = rightPreserveExprs;
nInputRefs = nRightProject;
offset = nSysFields + nFields;
} else {
preserveExprs = childPreserveExprs;
nInputRefs = nProject;
offset = nSysFields;
}
int refIdx = offset - 1;
List<Pair<RexNode, String>> newProjects =
new ArrayList<>();
List<RelDataTypeField> destFields =
projChild.getRowType().getFieldList();
// add on the input references
for (int i = 0; i < nInputRefs; i++) {
refIdx = projRefs.nextSetBit(refIdx + 1);
assert refIdx >= 0;
final RelDataTypeField destField = destFields.get(refIdx - offset);
newProjects.add(
Pair.of(
rexBuilder.makeInputRef(
destField.getType(), refIdx - offset),
destField.getName()));
}
// add on the expressions that need to be preserved, converting the
// arguments to reference the projected columns (if necessary)
int[] adjustments = {};
if ((preserveExprs.size() > 0) && adjust) {
adjustments = new int[childFields.size()];
for (int idx = offset; idx < childFields.size(); idx++) {
adjustments[idx] = -offset;
}
}
int preserveExpOrdinal = 0;
for (RexNode projExpr : preserveExprs) {
RexNode newExpr;
if (adjust) {
newExpr =
projExpr.accept(
new RelOptUtil.RexInputConverter(
rexBuilder,
childFields,
destFields,
adjustments));
} else {
newExpr = projExpr;
}
List<RelDataType> typeList = projChild.getRowType().getFieldList()
.stream().map(field -> field.getType()).collect(Collectors.toList());
RexUtil.FixNullabilityShuttle fixer =
new RexUtil.FixNullabilityShuttle(
projChild.getCluster().getRexBuilder(), typeList);
newExpr = newExpr.accept(fixer);
final String originalFieldName = findOriginalFieldName(projExpr);
final String newAlias;
if (originalFieldName != null) {
newAlias = originalFieldName;
} else {
newAlias = SqlUtil.deriveAliasFromOrdinal(preserveExpOrdinal);
}
newProjects.add(Pair.of(newExpr, newAlias));
preserveExpOrdinal++;
}
return (Project) relBuilder.push(projChild)
.projectNamed(Pair.left(newProjects), Pair.right(newProjects), true)
.build();
}
private @Nullable String findOriginalFieldName(RexNode originRexNode) {
if (origProj == null) {
return null;
}
int idx = origProj.getProjects().indexOf(originRexNode);
if (idx < 0) {
return null;
}
return origProj.getRowType().getFieldList().get(idx).getName();
}
/**
* Determines how much each input reference needs to be adjusted as a result
* of projection.
*
* @return array indicating how much each input needs to be adjusted by
*/
public int[] getAdjustments() {
int[] adjustments = new int[nChildFields];
int newIdx = 0;
int rightOffset = childPreserveExprs.size();
for (int pos : BitSets.toIter(projRefs)) {
adjustments[pos] = -(pos - newIdx);
if (pos >= nSysFields + nFields) {
adjustments[pos] += rightOffset;
}
newIdx++;
}
return adjustments;
}
/**
* Clones an expression tree and walks through it, adjusting each
* RexInputRef index by some amount, and converting expressions that need to
* be preserved to field references.
*
* @param rex the expression
* @param destFields fields that the new expressions will be referencing
* @param adjustments the amount each input reference index needs to be
* adjusted by
* @return modified expression tree
*/
public RexNode convertRefsAndExprs(
RexNode rex,
List<RelDataTypeField> destFields,
int[] adjustments) {
return rex.accept(
new RefAndExprConverter(
rexBuilder,
childFields,
destFields,
adjustments,
childPreserveExprs,
nProject,
rightPreserveExprs,
nProject + childPreserveExprs.size() + nRightProject));
}
/**
* Creates a new projection based on the original projection, adjusting all
* input refs using an adjustment array passed in. If there was no original
* projection, create a new one that selects every field from the underlying
* rel.
*
* <p>If the resulting projection would be trivial, return the child.
*
* @param projChild child of the new project
* @param adjustments array indicating how much each input reference should
* be adjusted by
* @return the created projection
*/
public RelNode createNewProject(RelNode projChild, int[] adjustments) {
final List<Pair<RexNode, String>> projects = new ArrayList<>();
if (origProj != null) {
for (Pair<RexNode, String> p : origProj.getNamedProjects()) {
projects.add(
Pair.of(
convertRefsAndExprs(
p.left,
projChild.getRowType().getFieldList(),
adjustments),
p.right));
}
} else {
for (Ord<RelDataTypeField> field : Ord.zip(childFields)) {
projects.add(
Pair.of(
rexBuilder.makeInputRef(
field.e.getType(), field.i), field.e.getName()));
}
}
return relBuilder.push(projChild)
.project(Pair.left(projects), Pair.right(projects))
.build();
}
//~ Inner Classes ----------------------------------------------------------
/**
* Visitor which builds a bitmap of the inputs used by an expressions, as
* well as locating expressions corresponding to special operators.
*/
private static class InputSpecialOpFinder extends RexVisitorImpl<Void> {
private final BitSet rexRefs;
private final ImmutableBitSet leftFields;
private final @Nullable ImmutableBitSet rightFields;
private final ImmutableBitSet strongFields;
private final ExprCondition preserveExprCondition;
private final List<RexNode> preserveLeft;
private final List<RexNode> preserveRight;
private final Strong strong;
InputSpecialOpFinder(
BitSet rexRefs,
ImmutableBitSet leftFields,
@Nullable ImmutableBitSet rightFields,
final ImmutableBitSet strongFields,
ExprCondition preserveExprCondition,
List<RexNode> preserveLeft,
List<RexNode> preserveRight) {
super(true);
this.rexRefs = rexRefs;
this.leftFields = leftFields;
this.rightFields = rightFields;
this.preserveExprCondition = preserveExprCondition;
this.preserveLeft = preserveLeft;
this.preserveRight = preserveRight;
this.strongFields = strongFields;
this.strong = Strong.of(strongFields);
}
@Override public Void visitCall(RexCall call) {
if (preserve(call)) {
return null;
}
super.visitCall(call);
return null;
}
private boolean isStrong(final ImmutableBitSet exprArgs, final RexNode call) {
// If the expressions do not use any of the inputs that require output to be null,
// no need to check. Otherwise, check that the expression is null.
// For example, in an "left outer join", we don't require that expressions
// pushed down into the left input to be strong. On the other hand,
// expressions pushed into the right input must be. In that case,
// strongFields == right input fields.
return !strongFields.intersects(exprArgs) || strong.isNull(call);
}
private boolean preserve(RexNode call) {
if (preserveExprCondition.test(call)) {
// if the arguments of the expression only reference the
// left hand side, preserve it on the left; similarly, if
// it only references expressions on the right
final ImmutableBitSet exprArgs = RelOptUtil.InputFinder.bits(call);
if (exprArgs.cardinality() > 0) {
if (leftFields.contains(exprArgs) && isStrong(exprArgs, call)) {
if (!preserveLeft.contains(call)) {
preserveLeft.add(call);
}
return true;
} else if (requireNonNull(rightFields, "rightFields").contains(exprArgs)
&& isStrong(exprArgs, call)) {
assert preserveRight != null;
if (!preserveRight.contains(call)) {
preserveRight.add(call);
}
return true;
}
}
// if the expression arguments reference both the left and
// right, fall through and don't attempt to preserve the
// expression, but instead locate references and special
// ops in the call operands
}
return false;
}
@Override public Void visitInputRef(RexInputRef inputRef) {
rexRefs.set(inputRef.getIndex());
return null;
}
}
/**
* Walks an expression tree, replacing input refs with new values to reflect
* projection and converting special expressions to field references.
*/
private static class RefAndExprConverter extends RelOptUtil.RexInputConverter {
private final List<RexNode> preserveLeft;
private final int firstLeftRef;
private final List<RexNode> preserveRight;
private final int firstRightRef;
RefAndExprConverter(
RexBuilder rexBuilder,
List<RelDataTypeField> srcFields,
List<RelDataTypeField> destFields,
int[] adjustments,
List<RexNode> preserveLeft,
int firstLeftRef,
List<RexNode> preserveRight,
int firstRightRef) {
super(rexBuilder, srcFields, destFields, adjustments);
this.preserveLeft = preserveLeft;
this.firstLeftRef = firstLeftRef;
this.preserveRight = preserveRight;
this.firstRightRef = firstRightRef;
}
@Override public RexNode visitCall(RexCall call) {
// if the expression corresponds to one that needs to be preserved,
// convert it to a field reference; otherwise, convert the entire
// expression
int match =
findExprInLists(
call,
preserveLeft,
firstLeftRef,
preserveRight,
firstRightRef);
if (match >= 0) {
return rexBuilder.makeInputRef(
requireNonNull(destFields, "destFields").get(match).getType(),
match);
}
return super.visitCall(call);
}
/**
* Looks for a matching RexNode from among two lists of RexNodes and
* returns the offset into the list corresponding to the match, adjusted
* by an amount, depending on whether the match was from the first or
* second list.
*
* @param rex RexNode that is being matched against
* @param rexList1 first list of RexNodes
* @param adjust1 adjustment if match occurred in first list
* @param rexList2 second list of RexNodes
* @param adjust2 adjustment if match occurred in the second list
* @return index in the list corresponding to the matching RexNode; -1
* if no match
*/
private static int findExprInLists(
RexNode rex,
List<RexNode> rexList1,
int adjust1,
List<RexNode> rexList2,
int adjust2) {
int match = rexList1.indexOf(rex);
if (match >= 0) {
return match + adjust1;
}
if (rexList2 != null) {
match = rexList2.indexOf(rex);
if (match >= 0) {
return match + adjust2;
}
}
return -1;
}
}
/**
* A functor that replies true or false for a given expression.
*
* @see org.apache.calcite.rel.rules.PushProjector.OperatorExprCondition
*/
public interface ExprCondition extends Predicate<RexNode> {
/**
* Evaluates a condition for a given expression.
*
* @param expr Expression
* @return result of evaluating the condition
*/
@Override boolean test(RexNode expr);
/**
* Constant condition that replies {@code false} for all expressions.
*/
ExprCondition FALSE = expr -> false;
/**
* Constant condition that replies {@code true} for all expressions.
*/
ExprCondition TRUE = expr -> true;
}
/**
* An expression condition that evaluates to true if the expression is
* a call to one of a set of operators.
*/
static class OperatorExprCondition implements ExprCondition {
private final Set<SqlOperator> operatorSet;
/**
* Creates an OperatorExprCondition.
*
* @param operatorSet Set of operators
*/
OperatorExprCondition(Iterable<? extends SqlOperator> operatorSet) {
this.operatorSet = ImmutableSet.copyOf(operatorSet);
}
@Override public boolean test(RexNode expr) {
return expr instanceof RexCall
&& operatorSet.contains(((RexCall) expr).getOperator());
}
}
}