com.hazelcast.org.apache.calcite.rex.RexProgramBuilder Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.org.apache.calcite.rex;
import com.hazelcast.org.apache.calcite.plan.RelOptPredicateList;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.util.Litmus;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Objects.requireNonNull;
/**
* Workspace for constructing a {@link RexProgram}.
*
* RexProgramBuilder is necessary because a {@link RexProgram} is immutable.
* (The {@link String} class has the same problem: it is immutable, so they
* introduced {@link StringBuilder}.)
*/
public class RexProgramBuilder {
//~ Instance fields --------------------------------------------------------
private final RexBuilder rexBuilder;
private final RelDataType inputRowType;
private final List exprList = new ArrayList<>();
private final Map, RexLocalRef> exprMap =
new HashMap<>();
private final List localRefList = new ArrayList<>();
private final List projectRefList = new ArrayList<>();
private final List<@Nullable String> projectNameList = new ArrayList<>();
@SuppressWarnings("unused")
private final @Nullable RexSimplify simplify;
private @Nullable RexLocalRef conditionRef = null;
private boolean validating;
//~ Constructors -----------------------------------------------------------
/**
* Creates a program-builder that will not simplify.
*/
public RexProgramBuilder(RelDataType inputRowType, RexBuilder rexBuilder) {
this(inputRowType, rexBuilder, null);
}
/**
* Creates a program-builder.
*/
@SuppressWarnings("method.invocation.invalid")
private RexProgramBuilder(RelDataType inputRowType, RexBuilder rexBuilder,
@Nullable RexSimplify simplify) {
this.inputRowType = requireNonNull(inputRowType, "inputRowType");
this.rexBuilder = requireNonNull(rexBuilder, "rexBuilder");
this.simplify = simplify; // may be null
this.validating = assertionsAreEnabled();
// Pre-create an expression for each input field.
if (inputRowType.isStruct()) {
final List fields = inputRowType.getFieldList();
for (int i = 0; i < fields.size(); i++) {
registerInternal(RexInputRef.of(i, fields), false);
}
}
}
/**
* Creates a program builder with the same contents as a program.
*
* @param rexBuilder Rex builder
* @param inputRowType Input row type
* @param exprList Common expressions
* @param projectList Projections
* @param condition Condition, or null
* @param outputRowType Output row type
* @param normalize Whether to normalize
* @param simplify Simplifier, or null to not simplify
*/
@SuppressWarnings("method.invocation.invalid")
private RexProgramBuilder(
RexBuilder rexBuilder,
final RelDataType inputRowType,
final List exprList,
final Iterable extends RexNode> projectList,
@Nullable RexNode condition,
final RelDataType outputRowType,
boolean normalize,
@Nullable RexSimplify simplify) {
this(inputRowType, rexBuilder, simplify);
// Create a shuttle for registering input expressions.
final RexShuttle shuttle =
new RegisterMidputShuttle(true, exprList);
// If we are not normalizing, register all internal expressions. If we
// are normalizing, expressions will be registered if and when they are
// first used.
if (!normalize) {
shuttle.visitEach(exprList);
}
final RexShuttle expander = new RexProgram.ExpansionShuttle(exprList);
// Register project expressions
// and create a named project item.
final List fieldList = outputRowType.getFieldList();
for (Pair extends RexNode, RelDataTypeField> pair
: Pair.zip(projectList, fieldList)) {
final RexNode project;
if (simplify != null) {
project = simplify.simplify(pair.left.accept(expander));
} else {
project = pair.left;
}
final String name = pair.right.getName();
final RexLocalRef ref = (RexLocalRef) project.accept(shuttle);
addProject(ref.getIndex(), name);
}
// Register the condition, if there is one.
if (condition != null) {
if (simplify != null) {
condition = simplify.simplify(
rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE,
condition.accept(expander)));
if (condition.isAlwaysTrue()) {
condition = null;
}
}
if (condition != null) {
final RexLocalRef ref = (RexLocalRef) condition.accept(shuttle);
addCondition(ref);
}
}
}
//~ Methods ----------------------------------------------------------------
/**
* Returns whether assertions are enabled in this class.
*/
private static boolean assertionsAreEnabled() {
boolean assertionsEnabled = false;
//noinspection AssertWithSideEffects
assert assertionsEnabled = true;
return assertionsEnabled;
}
private void validate(final RexNode expr, final int fieldOrdinal) {
final RexVisitor validator =
new RexVisitorImpl(true) {
@Override public Void visitInputRef(RexInputRef input) {
final int index = input.getIndex();
final List fields =
inputRowType.getFieldList();
if (index < fields.size()) {
final RelDataTypeField inputField = fields.get(index);
if (input.getType() != inputField.getType()) {
throw new AssertionError("in expression " + expr
+ ", field reference " + input + " has inconsistent type");
}
} else {
if (index >= fieldOrdinal) {
throw new AssertionError("in expression " + expr
+ ", field reference " + input + " is out of bounds");
}
RexNode refExpr = exprList.get(index);
if (refExpr.getType() != input.getType()) {
throw new AssertionError("in expression " + expr
+ ", field reference " + input + " has inconsistent type");
}
}
return null;
}
};
expr.accept(validator);
}
/**
* Adds a project expression to the program.
*
* The expression specified in terms of the input fields. If not, call
* {@link #registerOutput(RexNode)} first.
*
* @param expr Expression to add
* @param name Name of field in output row type; if null, a unique name will
* be generated when the program is created
* @return the ref created
*/
public RexLocalRef addProject(RexNode expr, @Nullable String name) {
final RexLocalRef ref = registerInput(expr);
return addProject(ref.getIndex(), name);
}
/**
* Adds a projection based upon the index
th expression.
*
* @param ordinal Index of expression to project
* @param name Name of field in output row type; if null, a unique name
* will be generated when the program is created
* @return the ref created
*/
public RexLocalRef addProject(int ordinal, final @Nullable String name) {
final RexLocalRef ref = localRefList.get(ordinal);
projectRefList.add(ref);
projectNameList.add(name);
return ref;
}
/**
* Adds a project expression to the program at a given position.
*
*
The expression specified in terms of the input fields. If not, call
* {@link #registerOutput(RexNode)} first.
*
* @param at Position in project list to add expression
* @param expr Expression to add
* @param name Name of field in output row type; if null, a unique name will
* be generated when the program is created
* @return the ref created
*/
public RexLocalRef addProject(int at, RexNode expr, String name) {
final RexLocalRef ref = registerInput(expr);
projectRefList.add(at, ref);
projectNameList.add(at, name);
return ref;
}
/**
* Adds a projection based upon the index
th expression at a
* given position.
*
* @param at Position in project list to add expression
* @param ordinal Index of expression to project
* @param name Name of field in output row type; if null, a unique name
* will be generated when the program is created
* @return the ref created
*/
public RexLocalRef addProject(int at, int ordinal, final String name) {
return addProject(
at,
localRefList.get(ordinal),
name);
}
/**
* Sets the condition of the program.
*
*
The expression must be specified in terms of the input fields. If
* not, call {@link #registerOutput(RexNode)} first.
*/
public void addCondition(RexNode expr) {
assert expr != null;
RexLocalRef conditionRef = this.conditionRef;
if (conditionRef == null) {
this.conditionRef = conditionRef = registerInput(expr);
} else {
// AND the new condition with the existing condition.
// If the new condition is identical to the existing condition, skip it.
RexLocalRef ref = registerInput(expr);
if (!ref.equals(conditionRef)) {
this.conditionRef =
registerInput(
rexBuilder.makeCall(
SqlStdOperatorTable.AND,
conditionRef,
ref));
}
}
}
/**
* Registers an expression in the list of common sub-expressions, and
* returns a reference to that expression.
* The expression must be expressed in terms of the inputs of
* this program.
*/
public RexLocalRef registerInput(RexNode expr) {
final RexShuttle shuttle = new RegisterInputShuttle(true);
final RexNode ref = expr.accept(shuttle);
return (RexLocalRef) ref;
}
/**
* Converts an expression expressed in terms of the outputs of this
* program into an expression expressed in terms of the inputs,
* registers it in the list of common sub-expressions, and returns a
* reference to that expression.
*
* @param expr Expression to register
*/
public RexLocalRef registerOutput(RexNode expr) {
final RexShuttle shuttle = new RegisterOutputShuttle(exprList);
final RexNode ref = expr.accept(shuttle);
return (RexLocalRef) ref;
}
/**
* Registers an expression in the list of common sub-expressions, and
* returns a reference to that expression.
*
* If an equivalent sub-expression already exists, creates another
* expression only if force
is true.
*
* @param expr Expression to register
* @param force Whether to create a new sub-expression if an equivalent
* sub-expression exists.
*/
private RexLocalRef registerInternal(RexNode expr, boolean force) {
final RexSimplify simplify =
new RexSimplify(rexBuilder, RelOptPredicateList.EMPTY, RexUtil.EXECUTOR);
expr = simplify.simplifyPreservingType(expr);
RexLocalRef ref;
final Pair key;
if (expr instanceof RexLocalRef) {
key = null;
ref = (RexLocalRef) expr;
} else {
key = RexUtil.makeKey(expr);
ref = exprMap.get(key);
}
if (ref == null) {
if (validating) {
validate(
expr,
exprList.size());
}
// Add expression to list, and return a new reference to it.
ref = addExpr(expr);
exprMap.put(requireNonNull(key, "key"), ref);
} else {
if (force) {
// Add expression to list, but return the previous ref.
addExpr(expr);
}
}
for (;;) {
int index = ref.index;
final RexNode expr2 = exprList.get(index);
if (expr2 instanceof RexLocalRef) {
ref = (RexLocalRef) expr2;
} else {
return ref;
}
}
}
/**
* Adds an expression to the list of common expressions, and returns a
* reference to the expression. DOES NOT CHECK WHETHER THE EXPRESSION
* ALREADY EXISTS.
*
* @param expr Expression
* @return Reference to expression
*/
public RexLocalRef addExpr(RexNode expr) {
RexLocalRef ref;
final int index = exprList.size();
exprList.add(expr);
ref =
new RexLocalRef(
index,
expr.getType());
localRefList.add(ref);
return ref;
}
/**
* Converts the state of the program builder to an immutable program,
* normalizing in the process.
*
* It is OK to call this method, modify the program specification (by
* adding projections, and so forth), and call this method again.
*/
public RexProgram getProgram() {
return getProgram(true);
}
/**
* Converts the state of the program builder to an immutable program.
*
*
It is OK to call this method, modify the program specification (by
* adding projections, and so forth), and call this method again.
*
* @param normalize Whether to normalize
*/
public RexProgram getProgram(boolean normalize) {
assert projectRefList.size() == projectNameList.size();
// Make sure all fields have a name.
generateMissingNames();
RelDataType outputRowType = computeOutputRowType();
if (normalize) {
return create(
rexBuilder,
inputRowType,
exprList,
projectRefList,
conditionRef,
outputRowType,
true)
.getProgram(false);
}
return new RexProgram(
inputRowType,
exprList,
projectRefList,
conditionRef,
outputRowType);
}
private RelDataType computeOutputRowType() {
return RexUtil.createStructType(rexBuilder.typeFactory, projectRefList,
projectNameList, null);
}
private void generateMissingNames() {
int i = -1;
int j = 0;
for (String projectName : projectNameList) {
++i;
if (projectName == null) {
while (true) {
final String candidateName = "$" + j++;
if (!projectNameList.contains(candidateName)) {
projectNameList.set(i, candidateName);
break;
}
}
}
}
}
/**
* Creates a program builder and initializes it from an existing program.
*
*
Calling {@link #getProgram()} immediately after creation will return a
* program equivalent (in terms of external behavior) to the existing
* program.
*
*
The existing program will not be changed. (It cannot: programs are
* immutable.)
*
* @param program Existing program
* @param rexBuilder Rex builder
* @param normalize Whether to normalize
* @return A program builder initialized with an equivalent program
*/
public static RexProgramBuilder forProgram(
RexProgram program,
RexBuilder rexBuilder,
boolean normalize) {
assert program.isValid(Litmus.THROW, null);
final RelDataType inputRowType = program.getInputRowType();
final List projectRefs = program.getProjectList();
final RexLocalRef conditionRef = program.getCondition();
final List exprs = program.getExprList();
final RelDataType outputRowType = program.getOutputRowType();
return create(
rexBuilder,
inputRowType,
exprs,
projectRefs,
conditionRef,
outputRowType,
normalize,
false);
}
/**
* Creates a program builder with the same contents as a program.
*
* If {@code normalize}, converts the program to canonical form. In
* canonical form, in addition to the usual constraints:
*
*
* - The first N internal expressions are {@link RexInputRef}s to the N
* input fields;
*
- Subsequent internal expressions reference only preceding expressions;
*
- Arguments to {@link RexCall}s must be {@link RexLocalRef}s (that is,
* expressions must have maximum depth 1)
*
*
* there are additional constraints:
*
*
* - Expressions appear in the left-deep order they are needed by
* the projections and (if present) the condition. Thus, expression N+1
* is the leftmost argument (literal or or call) in the expansion of
* projection #0.
*
- There are no duplicate expressions
*
- There are no unused expressions
*
*
* @param rexBuilder Rex builder
* @param inputRowType Input row type
* @param exprList Common expressions
* @param projectList Projections
* @param condition Condition, or null
* @param outputRowType Output row type
* @param normalize Whether to normalize
* @param simplify Whether to simplify expressions
* @return A program builder
*/
public static RexProgramBuilder create(
RexBuilder rexBuilder,
final RelDataType inputRowType,
final List exprList,
final List extends RexNode> projectList,
final @Nullable RexNode condition,
final RelDataType outputRowType,
boolean normalize,
@Nullable RexSimplify simplify) {
return new RexProgramBuilder(rexBuilder, inputRowType, exprList,
projectList, condition, outputRowType, normalize, simplify);
}
@Deprecated // to be removed before 2.0
public static RexProgramBuilder create(
RexBuilder rexBuilder,
final RelDataType inputRowType,
final List exprList,
final List extends RexNode> projectList,
final @Nullable RexNode condition,
final RelDataType outputRowType,
boolean normalize,
boolean simplify_) {
RexSimplify simplify = null;
if (simplify_) {
simplify = new RexSimplify(rexBuilder, RelOptPredicateList.EMPTY,
RexUtil.EXECUTOR);
}
return new RexProgramBuilder(rexBuilder, inputRowType, exprList,
projectList, condition, outputRowType, normalize, simplify);
}
@Deprecated // to be removed before 2.0
public static RexProgramBuilder create(
RexBuilder rexBuilder,
final RelDataType inputRowType,
final List exprList,
final List extends RexNode> projectList,
final @Nullable RexNode condition,
final RelDataType outputRowType,
boolean normalize) {
return create(rexBuilder, inputRowType, exprList, projectList, condition,
outputRowType, normalize, null);
}
/**
* Creates a program builder with the same contents as a program, applying a
* shuttle first.
*
* TODO: Refactor the above create method in terms of this one.
*
* @param rexBuilder Rex builder
* @param inputRowType Input row type
* @param exprList Common expressions
* @param projectRefList Projections
* @param conditionRef Condition, or null
* @param outputRowType Output row type
* @param shuttle Shuttle to apply to each expression before adding it
* to the program builder
* @param updateRefs Whether to update references that changes as a result
* of rewrites made by the shuttle
* @return A program builder
*/
public static RexProgramBuilder create(
RexBuilder rexBuilder,
final RelDataType inputRowType,
final List exprList,
final List projectRefList,
final @Nullable RexLocalRef conditionRef,
final RelDataType outputRowType,
final RexShuttle shuttle,
final boolean updateRefs) {
final RexProgramBuilder progBuilder =
new RexProgramBuilder(inputRowType, rexBuilder);
progBuilder.add(
exprList,
projectRefList,
conditionRef,
outputRowType,
shuttle,
updateRefs);
return progBuilder;
}
@Deprecated // to be removed before 2.0
public static RexProgram normalize(
RexBuilder rexBuilder,
RexProgram program) {
return program.normalize(rexBuilder, null);
}
/**
* Adds a set of expressions, projections and filters, applying a shuttle
* first.
*
* @param exprList Common expressions
* @param projectRefList Projections
* @param conditionRef Condition, or null
* @param outputRowType Output row type
* @param shuttle Shuttle to apply to each expression before adding it
* to the program builder
* @param updateRefs Whether to update references that changes as a result
* of rewrites made by the shuttle
*/
private void add(
List exprList,
List projectRefList,
@Nullable RexLocalRef conditionRef,
final RelDataType outputRowType,
RexShuttle shuttle,
boolean updateRefs) {
final List outFields = outputRowType.getFieldList();
final RexShuttle registerInputShuttle = new RegisterInputShuttle(false);
// For each common expression, first apply the user's shuttle, then
// register the result.
// REVIEW jpham 28-Apr-2006: if the user shuttle rewrites an input
// expression, then input references may change
List newRefs = new ArrayList<>(exprList.size());
RexShuttle refShuttle = new UpdateRefShuttle(newRefs);
int i = 0;
for (RexNode expr : exprList) {
RexNode newExpr = expr;
if (updateRefs) {
newExpr = expr.accept(refShuttle);
}
newExpr = newExpr.accept(shuttle);
newRefs.add(
i++,
(RexLocalRef) newExpr.accept(registerInputShuttle));
}
i = -1;
for (RexLocalRef oldRef : projectRefList) {
++i;
RexLocalRef ref = oldRef;
if (updateRefs) {
ref = (RexLocalRef) oldRef.accept(refShuttle);
}
ref = (RexLocalRef) ref.accept(shuttle);
this.projectRefList.add(ref);
final String name = outFields.get(i).getName();
assert name != null;
projectNameList.add(name);
}
if (conditionRef != null) {
if (updateRefs) {
conditionRef = (RexLocalRef) conditionRef.accept(refShuttle);
}
conditionRef = (RexLocalRef) conditionRef.accept(shuttle);
addCondition(conditionRef);
}
}
/**
* Merges two programs together, and normalizes the result.
*
* @param topProgram Top program. Its expressions are in terms of the
* outputs of the bottom program.
* @param bottomProgram Bottom program. Its expressions are in terms of the
* result fields of the relational expression's input
* @param rexBuilder Rex builder
* @return Merged program
* @see #mergePrograms(RexProgram, RexProgram, RexBuilder, boolean)
*/
public static RexProgram mergePrograms(
RexProgram topProgram,
RexProgram bottomProgram,
RexBuilder rexBuilder) {
return mergePrograms(topProgram, bottomProgram, rexBuilder, true);
}
/**
* Merges two programs together.
*
* All expressions become common sub-expressions. For example, the query
*
*
SELECT x + 1 AS p, x + y AS q FROM (
* SELECT a + b AS x, c AS y
* FROM t
* WHERE c = 6)}
*
* would be represented as the programs
*
*
* Calc:
* Projects={$2, $3},
* Condition=null,
* Exprs={$0, $1, $0 + 1, $0 + $1})
* Calc(
* Projects={$3, $2},
* Condition={$4}
* Exprs={$0, $1, $2, $0 + $1, $2 = 6}
*
*
* The merged program is
*
*
* Calc(
* Projects={$4, $5}
* Condition=$6
* Exprs={0: $0 // a
* 1: $1 // b
* 2: $2 // c
* 3: ($0 + $1) // x = a + b
* 4: ($3 + 1) // p = x + 1
* 5: ($3 + $2) // q = x + y
* 6: ($2 = 6) // c = 6
*
*
* Another example:
*
*
* SELECT *
* FROM (
* SELECT a + b AS x, c AS y
* FROM t
* WHERE c = 6)
* WHERE x = 5
*
*
* becomes
*
*
* SELECT a + b AS x, c AS y
* FROM t
* WHERE c = 6 AND (a + b) = 5
*
*
* @param topProgram Top program. Its expressions are in terms of the
* outputs of the bottom program.
* @param bottomProgram Bottom program. Its expressions are in terms of the
* result fields of the relational expression's input
* @param rexBuilder Rex builder
* @param normalize Whether to convert program to canonical form
* @return Merged program
*/
public static RexProgram mergePrograms(
RexProgram topProgram,
RexProgram bottomProgram,
RexBuilder rexBuilder,
boolean normalize) {
// Initialize a program builder with the same expressions, outputs
// and condition as the bottom program.
assert bottomProgram.isValid(Litmus.THROW, null);
assert topProgram.isValid(Litmus.THROW, null);
final RexProgramBuilder progBuilder =
RexProgramBuilder.forProgram(bottomProgram, rexBuilder, false);
// Drive from the outputs of the top program. Register each expression
// used as an output.
final List projectRefList =
progBuilder.registerProjectsAndCondition(topProgram);
// Switch to the projects needed by the top program. The original
// projects of the bottom program are no longer needed.
progBuilder.clearProjects();
final RelDataType outputRowType = topProgram.getOutputRowType();
for (Pair pair
: Pair.zip(projectRefList, outputRowType.getFieldNames(), true)) {
progBuilder.addProject(pair.left, pair.right);
}
RexProgram mergedProg = progBuilder.getProgram(normalize);
assert mergedProg.isValid(Litmus.THROW, null);
assert mergedProg.getOutputRowType() == topProgram.getOutputRowType();
return mergedProg;
}
private List registerProjectsAndCondition(RexProgram program) {
final List exprList = program.getExprList();
final List projectRefList = new ArrayList<>();
final RexShuttle shuttle = new RegisterOutputShuttle(exprList);
// For each project, lookup the expr and expand it so it is in terms of
// bottomCalc's input fields
for (RexLocalRef topProject : program.getProjectList()) {
final RexNode topExpr = exprList.get(topProject.getIndex());
final RexLocalRef expanded = (RexLocalRef) topExpr.accept(shuttle);
// Remember the expr, but don't add to the project list yet.
projectRefList.add(expanded);
}
// Similarly for the condition.
final RexLocalRef topCondition = program.getCondition();
if (topCondition != null) {
final RexNode topExpr = exprList.get(topCondition.getIndex());
final RexLocalRef expanded = (RexLocalRef) topExpr.accept(shuttle);
addCondition(registerInput(expanded));
}
return projectRefList;
}
/**
* Removes all project items.
*
* After calling this method, you may need to re-normalize.
*/
public void clearProjects() {
projectRefList.clear();
projectNameList.clear();
}
/**
* Clears the condition.
*
* After calling this method, you may need to re-normalize.
*/
public void clearCondition() {
conditionRef = null;
}
/**
* Adds a project item for every input field.
*
* You cannot call this method if there are other project items.
*/
public void addIdentity() {
assert projectRefList.isEmpty();
for (RelDataTypeField field : inputRowType.getFieldList()) {
addProject(
new RexInputRef(
field.getIndex(),
field.getType()),
field.getName());
}
}
/**
* Creates a reference to a given input field.
*
* @param index Ordinal of input field, must be less than the number of
* fields in the input type
* @return Reference to input field
*/
public RexLocalRef makeInputRef(int index) {
final List fields = inputRowType.getFieldList();
assert index < fields.size();
final RelDataTypeField field = fields.get(index);
return new RexLocalRef(
index,
field.getType());
}
/**
* Returns the row type of the input to the program.
*/
public RelDataType getInputRowType() {
return inputRowType;
}
/**
* Returns the list of project expressions.
*/
public List getProjectList() {
return projectRefList;
}
//~ Inner Classes ----------------------------------------------------------
/** Shuttle that visits a tree of {@link RexNode} and registers them
* in a program. */
private abstract class RegisterShuttle extends RexShuttle {
@Override public RexNode visitCall(RexCall call) {
final RexNode expr = super.visitCall(call);
return registerInternal(expr, false);
}
@Override public RexNode visitOver(RexOver over) {
final RexNode expr = super.visitOver(over);
return registerInternal(expr, false);
}
@Override public RexNode visitLiteral(RexLiteral literal) {
final RexNode expr = super.visitLiteral(literal);
return registerInternal(expr, false);
}
@Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) {
final RexNode expr = super.visitFieldAccess(fieldAccess);
return registerInternal(expr, false);
}
@Override public RexNode visitDynamicParam(RexDynamicParam dynamicParam) {
final RexNode expr = super.visitDynamicParam(dynamicParam);
return registerInternal(expr, false);
}
@Override public RexNode visitCorrelVariable(RexCorrelVariable variable) {
final RexNode expr = super.visitCorrelVariable(variable);
return registerInternal(expr, false);
}
}
/**
* Shuttle which walks over an expression, registering each sub-expression.
* Each {@link RexInputRef} is assumed to refer to an input of the
* program.
*/
private class RegisterInputShuttle extends RegisterShuttle {
private final boolean valid;
protected RegisterInputShuttle(boolean valid) {
this.valid = valid;
}
@Override public RexNode visitInputRef(RexInputRef input) {
final int index = input.getIndex();
if (valid) {
// The expression should already be valid. Check that its
// index is within bounds.
if ((index < 0) || (index >= inputRowType.getFieldCount())) {
assert false
: "RexInputRef index " + index + " out of range 0.."
+ (inputRowType.getFieldCount() - 1);
}
// Check that the type is consistent with the referenced
// field. If it is an object type, the rules are different, so
// skip the check.
assert input.getType().isStruct()
|| RelOptUtil.eq("type1", input.getType(),
"type2", inputRowType.getFieldList().get(index).getType(),
Litmus.THROW);
}
// Return a reference to the N'th expression, which should be
// equivalent.
final RexLocalRef ref = localRefList.get(index);
return ref;
}
@Override public RexNode visitLocalRef(RexLocalRef local) {
if (valid) {
// The expression should already be valid.
final int index = local.getIndex();
assert index >= 0 : index;
assert index < exprList.size()
: "index=" + index + ", exprList=" + exprList;
assert RelOptUtil.eq(
"expr type",
exprList.get(index).getType(),
"ref type",
local.getType(),
Litmus.THROW);
}
// Resolve the expression to an input.
while (true) {
final int index = local.getIndex();
final RexNode expr = exprList.get(index);
if (expr instanceof RexLocalRef) {
local = (RexLocalRef) expr;
if (local.index >= index) {
throw new AssertionError(
"expr " + local + " references later expr " + local.index);
}
} else {
// Add expression to the list, just so that subsequent
// expressions don't get screwed up. This expression is
// unused, so will be eliminated soon.
return registerInternal(local, false);
}
}
}
}
/**
* Extension to {@link RegisterInputShuttle} which allows expressions to be
* in terms of inputs or previous common sub-expressions.
*/
private class RegisterMidputShuttle extends RegisterInputShuttle {
private final List localExprList;
protected RegisterMidputShuttle(
boolean valid,
List localExprList) {
super(valid);
this.localExprList = localExprList;
}
@Override public RexNode visitLocalRef(RexLocalRef local) {
// Convert a local ref into the common-subexpression it references.
final int index = local.getIndex();
return localExprList.get(index).accept(this);
}
}
/**
* Shuttle which walks over an expression, registering each sub-expression.
* Each {@link RexInputRef} is assumed to refer to an output of the
* program.
*/
private class RegisterOutputShuttle extends RegisterShuttle {
private final List localExprList;
RegisterOutputShuttle(List localExprList) {
super();
this.localExprList = localExprList;
}
@Override public RexNode visitInputRef(RexInputRef input) {
// This expression refers to the Nth project column. Lookup that
// column and find out what common sub-expression IT refers to.
final int index = input.getIndex();
final RexLocalRef local = projectRefList.get(index);
assert RelOptUtil.eq(
"type1",
local.getType(),
"type2",
input.getType(),
Litmus.THROW);
return local;
}
@Override public RexNode visitLocalRef(RexLocalRef local) {
// Convert a local ref into the common-subexpression it references.
final int index = local.getIndex();
return localExprList.get(index).accept(this);
}
}
/**
* Shuttle that rewires {@link RexLocalRef} using a list of updated
* references.
*/
private static class UpdateRefShuttle extends RexShuttle {
private List newRefs;
private UpdateRefShuttle(List newRefs) {
this.newRefs = newRefs;
}
@Override public RexNode visitLocalRef(RexLocalRef localRef) {
return newRefs.get(localRef.getIndex());
}
}
}