/**
* 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.hadoop.hive.ql.optimizer.calcite.rules.views;
import static org.apache.calcite.rex.RexUtil.andNot;
import static org.apache.calcite.rex.RexUtil.removeAll;
import static org.apache.calcite.rex.RexUtil.simplify;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.calcite.avatica.util.Spaces;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RexImplicationChecker;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Filter;
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.RelFactories;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.logical.LogicalSort;
import org.apache.calcite.rel.logical.LogicalUnion;
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.RexExecutorImpl;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.Bug;
import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.mapping.Mapping;
import org.apache.calcite.util.mapping.Mappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Equivalence;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
/**
* Substitutes part of a tree of relational expressions with another tree.
*
* <p>The call {@code new SubstitutionVisitor(target, query).go(replacement))}
* will return {@code query} with every occurrence of {@code target} replaced
* by {@code replacement}.</p>
*
* <p>The following example shows how {@code SubstitutionVisitor} can be used
* for materialized view recognition.</p>
*
* <ul>
* <li>query = SELECT a, c FROM t WHERE x = 5 AND b = 4</li>
* <li>target = SELECT a, b, c FROM t WHERE x = 5</li>
* <li>replacement = SELECT * FROM mv</li>
* <li>result = SELECT a, c FROM mv WHERE b = 4</li>
* </ul>
*
* <p>Note that {@code result} uses the materialized view table {@code mv} and a
* simplified condition {@code b = 4}.</p>
*
* <p>Uses a bottom-up matching algorithm. Nodes do not need to be identical.
* At each level, returns the residue.</p>
*
* <p>The inputs must only include the core relational operators:
* {@link org.apache.calcite.rel.logical.LogicalTableScan},
* {@link org.apache.calcite.rel.logical.LogicalFilter},
* {@link org.apache.calcite.rel.logical.LogicalProject},
* {@link org.apache.calcite.rel.logical.LogicalJoin},
* {@link org.apache.calcite.rel.logical.LogicalUnion},
* {@link org.apache.calcite.rel.logical.LogicalAggregate}.</p>
*
* TODO: Remove when we upgrade to Calcite version using builders.
*/
public class SubstitutionVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(SubstitutionVisitor.class);
/** Equivalence that compares objects by their {@link Object#toString()}
* method. */
private static final Equivalence<Object> STRING_EQUIVALENCE =
new Equivalence<Object>() {
@Override protected boolean doEquivalent(Object o, Object o2) {
return o.toString().equals(o2.toString());
}
@Override protected int doHash(Object o) {
return o.toString().hashCode();
}
};
/** Equivalence that compares {@link Lists}s by the
* {@link Object#toString()} of their elements. */
@SuppressWarnings("unchecked")
private static final Equivalence<List<?>> PAIRWISE_STRING_EQUIVALENCE =
(Equivalence) STRING_EQUIVALENCE.pairwise();
protected static final ImmutableList<UnifyRule> DEFAULT_RULES =
ImmutableList.<UnifyRule>of(
TrivialRule.INSTANCE,
ScanToProjectUnifyRule.INSTANCE,
ProjectToProjectUnifyRule.INSTANCE,
FilterToProjectUnifyRule.INSTANCE,
// ProjectToFilterUnifyRule.INSTANCE,
// FilterToFilterUnifyRule.INSTANCE,
AggregateToAggregateUnifyRule.INSTANCE,
AggregateOnProjectToAggregateUnifyRule.INSTANCE);
/**
* Factory for a builder for relational expressions.
* <p>The actual builder is available via {@link RelOptRuleCall#builder()}.
*/
protected final RelBuilder relBuilder;
private final ImmutableList<UnifyRule> rules;
private final Map<Pair<Class, Class>, List<UnifyRule>> ruleMap =
new HashMap<>();
private final RelOptCluster cluster;
private final Holder query;
private final MutableRel target;
/**
* Nodes in {@link #target} that have no children.
*/
final List<MutableRel> targetLeaves;
/**
* Nodes in {@link #query} that have no children.
*/
final List<MutableRel> queryLeaves;
final Map<MutableRel, MutableRel> replacementMap = new HashMap<>();
final Multimap<MutableRel, MutableRel> equivalents =
LinkedHashMultimap.create();
/** Workspace while rule is being matched.
* Careful, re-entrant!
* Assumes no rule needs more than 2 slots. */
protected final MutableRel[] slots = new MutableRel[2];
/** Creates a SubstitutionVisitor with the default rule set. */
public SubstitutionVisitor(RelNode target_, RelNode query_) {
this(target_, query_, DEFAULT_RULES);
}
public SubstitutionVisitor(RelNode target_, RelNode query_,
ImmutableList<UnifyRule> rules) {
this(target_, query_, rules, RelFactories.LOGICAL_BUILDER);
}
/** Creates a SubstitutionVisitor. */
public SubstitutionVisitor(RelNode target_, RelNode query_,
ImmutableList<UnifyRule> rules, RelBuilderFactory relBuilderFactory) {
this.cluster = target_.getCluster();
this.rules = rules;
this.query = Holder.of(toMutable(query_));
this.target = toMutable(target_);
this.relBuilder = relBuilderFactory.create(cluster, null);
final Set<MutableRel> parents = Sets.newIdentityHashSet();
final List<MutableRel> allNodes = new ArrayList<>();
final MutableRelVisitor visitor =
new MutableRelVisitor() {
public void visit(MutableRel node) {
parents.add(node.parent);
allNodes.add(node);
super.visit(node);
}
};
visitor.go(target);
// Populate the list of leaves in the tree under "target".
// Leaves are all nodes that are not parents.
// For determinism, it is important that the list is in scan order.
allNodes.removeAll(parents);
targetLeaves = ImmutableList.copyOf(allNodes);
allNodes.clear();
parents.clear();
visitor.go(query);
allNodes.removeAll(parents);
queryLeaves = ImmutableList.copyOf(allNodes);
}
private static MutableRel toMutable(RelNode rel) {
if (rel instanceof TableScan) {
return MutableScan.of((TableScan) rel);
}
if (rel instanceof Values) {
return MutableValues.of((Values) rel);
}
if (rel instanceof Project) {
final Project project = (Project) rel;
final MutableRel input = toMutable(project.getInput());
return MutableProject.of(input, project.getProjects(),
project.getRowType().getFieldNames());
}
if (rel instanceof Filter) {
final Filter filter = (Filter) rel;
final MutableRel input = toMutable(filter.getInput());
return MutableFilter.of(input, filter.getCondition());
}
if (rel instanceof Aggregate) {
final Aggregate aggregate = (Aggregate) rel;
final MutableRel input = toMutable(aggregate.getInput());
return MutableAggregate.of(input, aggregate.indicator,
aggregate.getGroupSet(), aggregate.getGroupSets(),
aggregate.getAggCallList());
}
if (rel instanceof Join) {
final Join join = (Join) rel;
final MutableRel left = toMutable(join.getLeft());
final MutableRel right = toMutable(join.getRight());
return MutableJoin.of(join.getCluster(), left, right,
join.getCondition(), join.getJoinType(), join.getVariablesSet());
}
if (rel instanceof Sort) {
final Sort sort = (Sort) rel;
final MutableRel input = toMutable(sort.getInput());
return MutableSort.of(input, sort.getCollation(), sort.offset, sort.fetch);
}
throw new RuntimeException("cannot translate " + rel + " to MutableRel");
}
void register(MutableRel result, MutableRel query) {
}
/**
* Maps a condition onto a target.
*
* <p>If condition is stronger than target, returns the residue.
* If it is equal to target, returns the expression that evaluates to
* the constant {@code true}. If it is weaker than target, returns
* {@code null}.</p>
*
* <p>The terms satisfy the relation</p>
*
* <pre>
* {@code condition = target AND residue}
* </pre>
*
* <p>and {@code residue} must be as weak as possible.</p>
*
* <p>Example #1: condition stronger than target</p>
* <ul>
* <li>condition: x = 1 AND y = 2</li>
* <li>target: x = 1</li>
* <li>residue: y = 2</li>
* </ul>
*
* <p>Note that residue {@code x > 0 AND y = 2} would also satisfy the
* relation {@code condition = target AND residue} but is stronger than
* necessary, so we prefer {@code y = 2}.</p>
*
* <p>Example #2: target weaker than condition (valid, but not currently
* implemented)</p>
* <ul>
* <li>condition: x = 1</li>
* <li>target: x = 1 OR z = 3</li>
* <li>residue: NOT (z = 3)</li>
* </ul>
*
* <p>Example #3: condition and target are equivalent</p>
* <ul>
* <li>condition: x = 1 AND y = 2</li>
* <li>target: y = 2 AND x = 1</li>
* <li>residue: TRUE</li>
* </ul>
*
* <p>Example #4: condition weaker than target</p>
* <ul>
* <li>condition: x = 1</li>
* <li>target: x = 1 AND y = 2</li>
* <li>residue: null (i.e. no match)</li>
* </ul>
*
* <p>There are many other possible examples. It amounts to solving
* whether {@code condition AND NOT target} can ever evaluate to
* true, and therefore is a form of the NP-complete
* <a href="http://en.wikipedia.org/wiki/Satisfiability">Satisfiability</a>
* problem.</p>
*/
@VisibleForTesting
public static RexNode splitFilter(
final RexBuilder rexBuilder, RexNode condition, RexNode target) {
// First, try splitting into ORs.
// Given target c1 OR c2 OR c3 OR c4
// and condition c2 OR c4
// residue is NOT c1 AND NOT c3
// Also deals with case target [x] condition [x] yields residue [true].
RexNode z = splitOr(rexBuilder, condition, target);
if (z != null) {
return z;
}
RexNode x = andNot(rexBuilder, target, condition);
if (mayBeSatisfiable(x)) {
RexNode x2 = andNot(rexBuilder, condition, target);
return simplify(rexBuilder, x2);
}
return null;
}
private static RexNode splitOr(
final RexBuilder rexBuilder, RexNode condition, RexNode target) {
List<RexNode> targets = RelOptUtil.disjunctions(target);
for (RexNode e : RelOptUtil.disjunctions(condition)) {
boolean found = removeAll(targets, e);
if (!found) {
return null;
}
}
return RexUtil.composeConjunction(rexBuilder,
Lists.transform(targets, RexUtil.notFn(rexBuilder)), false);
}
/**
* Returns whether a boolean expression ever returns true.
*
* <p>This method may give false positives. For instance, it will say
* that {@code x = 5 AND x > 10} is satisfiable, because at present it
* cannot prove that it is not.</p>
*/
public static boolean mayBeSatisfiable(RexNode e) {
// Example:
// e: x = 1 AND y = 2 AND z = 3 AND NOT (x = 1 AND y = 2)
// disjunctions: {x = 1, y = 2, z = 3}
// notDisjunctions: {x = 1 AND y = 2}
final List<RexNode> disjunctions = new ArrayList<>();
final List<RexNode> notDisjunctions = new ArrayList<>();
RelOptUtil.decomposeConjunction(e, disjunctions, notDisjunctions);
// If there is a single FALSE or NOT TRUE, the whole expression is
// always false.
for (RexNode disjunction : disjunctions) {
switch (disjunction.getKind()) {
case LITERAL:
if (!RexLiteral.booleanValue(disjunction)) {
return false;
}
}
}
for (RexNode disjunction : notDisjunctions) {
switch (disjunction.getKind()) {
case LITERAL:
if (RexLiteral.booleanValue(disjunction)) {
return false;
}
}
}
// If one of the not-disjunctions is a disjunction that is wholly
// contained in the disjunctions list, the expression is not
// satisfiable.
//
// Example #1. x AND y AND z AND NOT (x AND y) - not satisfiable
// Example #2. x AND y AND NOT (x AND y) - not satisfiable
// Example #3. x AND y AND NOT (x AND y AND z) - may be satisfiable
for (RexNode notDisjunction : notDisjunctions) {
final List<RexNode> disjunctions2 =
RelOptUtil.conjunctions(notDisjunction);
if (disjunctions.containsAll(disjunctions2)) {
return false;
}
}
return true;
}
public RelNode go0(RelNode replacement_) {
assert false; // not called
MutableRel replacement = toMutable(replacement_);
assert MutableRels.equalType(
"target", target, "replacement", replacement, Litmus.THROW);
replacementMap.put(target, replacement);
final UnifyResult unifyResult = matchRecurse(target);
if (unifyResult == null) {
return null;
}
final MutableRel node0 = unifyResult.result;
MutableRel node = node0; // replaceAncestors(node0);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Convert: query:\n"
+ query.deep()
+ "\nunify.query:\n"
+ unifyResult.call.query.deep()
+ "\nunify.result:\n"
+ unifyResult.result.deep()
+ "\nunify.target:\n"
+ unifyResult.call.target.deep()
+ "\nnode0:\n"
+ node0.deep()
+ "\nnode:\n"
+ node.deep());
}
return fromMutable(node);
}
/**
* Returns a list of all possible rels that result from substituting the
* matched RelNode with the replacement RelNode within the query.
*
* <p>For example, the substitution result of A join B, while A and B
* are both a qualified match for replacement R, is R join B, R join R,
* A join R.
*/
public List<RelNode> go(RelNode replacement_) {
List<List<Replacement>> matches = go(toMutable(replacement_));
if (matches.isEmpty()) {
return ImmutableList.of();
}
List<RelNode> sub = Lists.newArrayList();
sub.add(fromMutable(query.input));
reverseSubstitute(query, matches, sub, 0, matches.size());
return sub;
}
/**
* Substitutes the query with replacement whenever possible but meanwhile
* keeps track of all the substitutions and their original rel before
* replacement, so that in later processing stage, the replacement can be
* recovered individually to produce a list of all possible rels with
* substitution in different places.
*/
private List<List<Replacement>> go(MutableRel replacement) {
assert MutableRels.equalType(
"target", target, "replacement", replacement, Litmus.THROW);
final List<MutableRel> queryDescendants = MutableRels.descendants(query);
final List<MutableRel> targetDescendants = MutableRels.descendants(target);
// Populate "equivalents" with (q, t) for each query descendant q and
// target descendant t that are equal.
final Map<MutableRel, MutableRel> map = Maps.newHashMap();
for (MutableRel queryDescendant : queryDescendants) {
map.put(queryDescendant, queryDescendant);
}
for (MutableRel targetDescendant : targetDescendants) {
MutableRel queryDescendant = map.get(targetDescendant);
if (queryDescendant != null) {
assert queryDescendant.rowType.equals(targetDescendant.rowType);
equivalents.put(queryDescendant, targetDescendant);
}
}
map.clear();
final List<Replacement> attempted = Lists.newArrayList();
List<List<Replacement>> substitutions = Lists.newArrayList();
for (;;) {
int count = 0;
MutableRel queryDescendant = query;
outer:
while (queryDescendant != null) {
for (Replacement r : attempted) {
if (queryDescendant == r.after) {
// This node has been replaced by previous iterations in the
// hope to match its ancestors, so the node itself should not
// be matched again.
queryDescendant = MutableRels.preOrderTraverseNext(queryDescendant);
continue outer;
}
}
final MutableRel next = MutableRels.preOrderTraverseNext(queryDescendant);
final MutableRel childOrNext =
queryDescendant.getInputs().isEmpty()
? next : queryDescendant.getInputs().get(0);
for (MutableRel targetDescendant : targetDescendants) {
for (UnifyRule rule
: applicableRules(queryDescendant, targetDescendant)) {
UnifyRuleCall call =
rule.match(this, queryDescendant, targetDescendant);
if (call != null) {
final UnifyResult result = rule.apply(call);
if (result != null) {
++count;
attempted.add(new Replacement(result.call.query, result.result));
MutableRel parent = result.call.query.replaceInParent(result.result);
// Replace previous equivalents with new equivalents, higher up
// the tree.
for (int i = 0; i < rule.slotCount; i++) {
Collection<MutableRel> equi = equivalents.get(slots[i]);
if (!equi.isEmpty()) {
equivalents.remove(slots[i], equi.iterator().next());
}
}
assert result.result.rowType.equals(result.call.query.rowType)
: Pair.of(result.result, result.call.query);
equivalents.put(result.result, result.call.query);
if (targetDescendant == target) {
// A real substitution happens. We purge the attempted
// replacement list and add them into substitution list.
// Meanwhile we stop matching the descendants and jump
// to the next subtree in pre-order traversal.
if (!target.equals(replacement)) {
Replacement r = MutableRels.replace(
query.input, target, copyMutable(replacement));
assert r != null
: rule + "should have returned a result containing the target.";
attempted.add(r);
}
substitutions.add(ImmutableList.copyOf(attempted));
attempted.clear();
queryDescendant = next;
continue outer;
}
// We will try walking the query tree all over again to see
// if there can be any substitutions after the replacement
// attempt.
break outer;
}
}
}
}
queryDescendant = childOrNext;
}
// Quit the entire loop if:
// 1) we have walked the entire query tree with one or more successful
// substitutions, thus count != 0 && attempted.isEmpty();
// 2) we have walked the entire query tree but have made no replacement
// attempt, thus count == 0 && attempted.isEmpty();
// 3) we had done some replacement attempt in a previous walk, but in
// this one we have not found any potential matches or substitutions,
// thus count == 0 && !attempted.isEmpty().
if (count == 0 || attempted.isEmpty()) {
break;
}
}
if (!attempted.isEmpty()) {
// We had done some replacement attempt in the previous walk, but that
// did not lead to any substitutions in this walk, so we need to recover
// the replacement.
undoReplacement(attempted);
}
return substitutions;
}
/**
* Represents a replacement action: before → after.
*/
private static class Replacement {
final MutableRel before;
final MutableRel after;
Replacement(MutableRel before, MutableRel after) {
this.before = before;
this.after = after;
}
}
private static void undoReplacement(List<Replacement> replacement) {
for (int i = replacement.size() - 1; i >= 0; i--) {
Replacement r = replacement.get(i);
r.after.replaceInParent(r.before);
}
}
private static void redoReplacement(List<Replacement> replacement) {
for (Replacement r : replacement) {
r.before.replaceInParent(r.after);
}
}
private void reverseSubstitute(Holder query,
List<List<Replacement>> matches, List<RelNode> sub,
int replaceCount, int maxCount) {
if (matches.isEmpty()) {
return;
}
final List<List<Replacement>> rem = matches.subList(1, matches.size());
reverseSubstitute(query, rem, sub, replaceCount, maxCount);
undoReplacement(matches.get(0));
if (++replaceCount < maxCount) {
sub.add(fromMutable(query.input));
}
reverseSubstitute(query, rem, sub, replaceCount, maxCount);
redoReplacement(matches.get(0));
}
private List<RelNode> fromMutables(List<MutableRel> nodes) {
return Lists.transform(nodes,
new Function<MutableRel, RelNode>() {
public RelNode apply(MutableRel mutableRel) {
return fromMutable(mutableRel);
}
});
}
private RelNode fromMutable(MutableRel node) {
switch (node.type) {
case SCAN:
case VALUES:
return ((MutableLeafRel) node).rel;
case PROJECT:
final MutableProject project = (MutableProject) node;
relBuilder.push(fromMutable(project.input));
relBuilder.project(project.projects);
return relBuilder.build();
case FILTER:
final MutableFilter filter = (MutableFilter) node;
relBuilder.push(fromMutable(filter.input));
relBuilder.filter(filter.condition);
return relBuilder.build();
case AGGREGATE:
final MutableAggregate aggregate = (MutableAggregate) node;
return LogicalAggregate.create(fromMutable(aggregate.input),
aggregate.indicator, aggregate.groupSet, aggregate.groupSets,
aggregate.aggCalls);
case SORT:
final MutableSort sort = (MutableSort) node;
return LogicalSort.create(fromMutable(sort.input), sort.collation,
sort.offset, sort.fetch);
case UNION:
final MutableUnion union = (MutableUnion) node;
return LogicalUnion.create(fromMutables(union.inputs), union.all);
case JOIN:
final MutableJoin join = (MutableJoin) node;
return LogicalJoin.create(fromMutable(join.getLeft()), fromMutable(join.getRight()),
join.getCondition(), join.getVariablesSet(), join.getJoinType());
default:
throw new AssertionError(node.deep());
}
}
private static List<MutableRel> copyMutables(List<MutableRel> nodes) {
return Lists.transform(nodes,
new Function<MutableRel, MutableRel>() {
public MutableRel apply(MutableRel mutableRel) {
return copyMutable(mutableRel);
}
});
}
private static MutableRel copyMutable(MutableRel node) {
switch (node.type) {
case SCAN:
return MutableScan.of((TableScan) ((MutableScan) node).rel);
case VALUES:
return MutableValues.of((Values) ((MutableValues) node).rel);
case PROJECT:
final MutableProject project = (MutableProject) node;
return MutableProject.of(project.rowType,
copyMutable(project.input), project.projects);
case FILTER:
final MutableFilter filter = (MutableFilter) node;
return MutableFilter.of(copyMutable(filter.input), filter.condition);
case AGGREGATE:
final MutableAggregate aggregate = (MutableAggregate) node;
return MutableAggregate.of(copyMutable(aggregate.input),
aggregate.indicator, aggregate.groupSet, aggregate.groupSets,
aggregate.aggCalls);
case SORT:
final MutableSort sort = (MutableSort) node;
return MutableSort.of(copyMutable(sort.input), sort.collation,
sort.offset, sort.fetch);
case UNION:
final MutableUnion union = (MutableUnion) node;
return MutableUnion.of(copyMutables(union.inputs), union.all);
case JOIN:
final MutableJoin join = (MutableJoin) node;
return MutableJoin.of(join.cluster, copyMutable(join.getLeft()),
copyMutable(join.getRight()), join.getCondition(), join.getJoinType(),
join.getVariablesSet());
default:
throw new AssertionError(node.deep());
}
}
private UnifyResult matchRecurse(MutableRel target) {
assert false; // not called
final List<MutableRel> targetInputs = target.getInputs();
MutableRel queryParent = null;
for (MutableRel targetInput : targetInputs) {
UnifyResult unifyResult = matchRecurse(targetInput);
if (unifyResult == null) {
return null;
}
queryParent = unifyResult.call.query.replaceInParent(unifyResult.result);
}
if (targetInputs.isEmpty()) {
for (MutableRel queryLeaf : queryLeaves) {
for (UnifyRule rule : applicableRules(queryLeaf, target)) {
final UnifyResult x = apply(rule, queryLeaf, target);
if (x != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rule: " + rule
+ "\nQuery:\n"
+ queryParent
+ (x.call.query != queryParent
? "\nQuery (original):\n"
+ queryParent
: "")
+ "\nTarget:\n"
+ target.deep()
+ "\nResult:\n"
+ x.result.deep()
+ "\n");
}
return x;
}
}
}
} else {
assert queryParent != null;
for (UnifyRule rule : applicableRules(queryParent, target)) {
final UnifyResult x = apply(rule, queryParent, target);
if (x != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Rule: " + rule
+ "\nQuery:\n"
+ queryParent.deep()
+ (x.call.query != queryParent
? "\nQuery (original):\n"
+ queryParent.toString()
: "")
+ "\nTarget:\n"
+ target.deep()
+ "\nResult:\n"
+ x.result.deep()
+ "\n");
}
return x;
}
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Unify failed:"
+ "\nQuery:\n"
+ queryParent.toString()
+ "\nTarget:\n"
+ target.toString()
+ "\n");
}
return null;
}
private UnifyResult apply(UnifyRule rule, MutableRel query,
MutableRel target) {
final UnifyRuleCall call = new UnifyRuleCall(rule, query, target, null);
return rule.apply(call);
}
private List<UnifyRule> applicableRules(MutableRel query,
MutableRel target) {
final Class queryClass = query.getClass();
final Class targetClass = target.getClass();
final Pair<Class, Class> key = Pair.of(queryClass, targetClass);
List<UnifyRule> list = ruleMap.get(key);
if (list == null) {
final ImmutableList.Builder<UnifyRule> builder =
ImmutableList.builder();
for (UnifyRule rule : rules) {
//noinspection unchecked
if (mightMatch(rule, queryClass, targetClass)) {
builder.add(rule);
}
}
list = builder.build();
ruleMap.put(key, list);
}
return list;
}
private static boolean mightMatch(UnifyRule rule,
Class queryClass, Class targetClass) {
return rule.queryOperand.clazz.isAssignableFrom(queryClass)
&& rule.targetOperand.clazz.isAssignableFrom(targetClass);
}
/** Exception thrown to exit a matcher. Not really an error. */
protected static class MatchFailed extends ControlFlowException {
@SuppressWarnings("ThrowableInstanceNeverThrown")
public static final MatchFailed INSTANCE = new MatchFailed();
}
/** Rule that attempts to match a query relational expression
* against a target relational expression.
*
* <p>The rule declares the query and target types; this allows the
* engine to fire only a few rules in a given context.</p>
*/
protected abstract static class UnifyRule {
protected final int slotCount;
protected final Operand queryOperand;
protected final Operand targetOperand;
protected UnifyRule(int slotCount, Operand queryOperand,
Operand targetOperand) {
this.slotCount = slotCount;
this.queryOperand = queryOperand;
this.targetOperand = targetOperand;
}
/**
* <p>Applies this rule to a particular node in a query. The goal is
* to convert {@code query} into {@code target}. Before the rule is
* invoked, Calcite has made sure that query's children are equivalent
* to target's children.
*
* <p>There are 3 possible outcomes:</p>
*
* <ul>
*
* <li>{@code query} already exactly matches {@code target}; returns
* {@code target}</li>
*
* <li>{@code query} is sufficiently close to a match for
* {@code target}; returns {@code target}</li>
*
* <li>{@code query} cannot be made to match {@code target}; returns
* null</li>
*
* </ul>
*
* <p>REVIEW: Is possible that we match query PLUS one or more of its
* ancestors?</p>
*
* @param call Input parameters
*/
protected abstract UnifyResult apply(UnifyRuleCall call);
protected UnifyRuleCall match(SubstitutionVisitor visitor, MutableRel query,
MutableRel target) {
if (queryOperand.matches(visitor, query)) {
if (targetOperand.matches(visitor, target)) {
return visitor.new UnifyRuleCall(this, query, target,
copy(visitor.slots, slotCount));
}
}
return null;
}
protected <E> ImmutableList<E> copy(E[] slots, int slotCount) {
// Optimize if there are 0 or 1 slots.
switch (slotCount) {
case 0:
return ImmutableList.of();
case 1:
return ImmutableList.of(slots[0]);
default:
return ImmutableList.copyOf(slots).subList(0, slotCount);
}
}
}
/**
* Arguments to an application of a {@link UnifyRule}.
*/
protected class UnifyRuleCall {
protected final UnifyRule rule;
public final MutableRel query;
public final MutableRel target;
protected final ImmutableList<MutableRel> slots;
public UnifyRuleCall(UnifyRule rule, MutableRel query, MutableRel target,
ImmutableList<MutableRel> slots) {
this.rule = Preconditions.checkNotNull(rule);
this.query = Preconditions.checkNotNull(query);
this.target = Preconditions.checkNotNull(target);
this.slots = Preconditions.checkNotNull(slots);
}
public UnifyResult result(MutableRel result) {
assert MutableRels.contains(result, target);
assert MutableRels.equalType("result", result, "query", query,
Litmus.THROW);
MutableRel replace = replacementMap.get(target);
if (replace != null) {
assert false; // replacementMap is always empty
// result =
MutableRels.replace(result, target, replace);
}
register(result, query);
return new UnifyResult(this, result);
}
/**
* Creates a {@link UnifyRuleCall} based on the parent of {@code query}.
*/
public UnifyRuleCall create(MutableRel query) {
return new UnifyRuleCall(rule, query, target, slots);
}
public RelOptCluster getCluster() {
return cluster;
}
}
/**
* Result of an application of a {@link UnifyRule} indicating that the
* rule successfully matched {@code query} against {@code target} and
* generated a {@code result} that is equivalent to {@code query} and
* contains {@code target}.
*/
protected static class UnifyResult {
private final UnifyRuleCall call;
// equivalent to "query", contains "result"
private final MutableRel result;
UnifyResult(UnifyRuleCall call, MutableRel result) {
this.call = call;
assert MutableRels.equalType("query", call.query, "result", result,
Litmus.THROW);
this.result = result;
}
}
/** Abstract base class for implementing {@link UnifyRule}. */
protected abstract static class AbstractUnifyRule extends UnifyRule {
public AbstractUnifyRule(Operand queryOperand, Operand targetOperand,
int slotCount) {
super(slotCount, queryOperand, targetOperand);
//noinspection AssertWithSideEffects
assert isValid();
}
protected boolean isValid() {
final SlotCounter slotCounter = new SlotCounter();
slotCounter.visit(queryOperand);
assert slotCounter.queryCount == slotCount;
assert slotCounter.targetCount == 0;
slotCounter.queryCount = 0;
slotCounter.visit(targetOperand);
assert slotCounter.queryCount == 0;
assert slotCounter.targetCount == slotCount;
return true;
}
/** Creates an operand with given inputs. */
protected static Operand operand(Class<? extends MutableRel> clazz,
Operand... inputOperands) {
return new InternalOperand(clazz, ImmutableList.copyOf(inputOperands));
}
/** Creates an operand that doesn't check inputs. */
protected static Operand any(Class<? extends MutableRel> clazz) {
return new AnyOperand(clazz);
}
/** Creates an operand that matches a relational expression in the query. */
protected static Operand query(int ordinal) {
return new QueryOperand(ordinal);
}
/** Creates an operand that matches a relational expression in the
* target. */
protected static Operand target(int ordinal) {
return new TargetOperand(ordinal);
}
}
/** Implementation of {@link UnifyRule} that matches if the query is already
* equal to the target.
*
* <p>Matches scans to the same table, because these will be
* {@link MutableScan}s with the same
* {@link org.apache.calcite.rel.logical.LogicalTableScan} instance.</p>
*/
private static class TrivialRule extends AbstractUnifyRule {
private static final TrivialRule INSTANCE = new TrivialRule();
private TrivialRule() {
super(any(MutableRel.class), any(MutableRel.class), 0);
}
public UnifyResult apply(UnifyRuleCall call) {
if (call.query.equals(call.target)) {
return call.result(call.query);
}
return null;
}
}
/** Implementation of {@link UnifyRule} that matches
* {@link org.apache.calcite.rel.logical.LogicalTableScan}. */
private static class ScanToProjectUnifyRule extends AbstractUnifyRule {
public static final ScanToProjectUnifyRule INSTANCE =
new ScanToProjectUnifyRule();
private ScanToProjectUnifyRule() {
super(any(MutableScan.class),
any(MutableProject.class), 0);
}
public UnifyResult apply(UnifyRuleCall call) {
final MutableProject target = (MutableProject) call.target;
final MutableScan query = (MutableScan) call.query;
// We do not need to check query's parent type to avoid duplication
// of ProjectToProjectUnifyRule or FilterToProjectUnifyRule, since
// SubstitutionVisitor performs a top-down match.
if (!query.equals(target.getInput())) {
return null;
}
final RexShuttle shuttle = getRexShuttle(target);
final RexBuilder rexBuilder = target.cluster.getRexBuilder();
final List<RexNode> newProjects;
try {
newProjects = (List<RexNode>)
shuttle.apply(rexBuilder.identityProjects(query.getRowType()));
} catch (MatchFailed e) {
return null;
}
final MutableProject newProject =
MutableProject.of(
query.getRowType(), target, newProjects);
final MutableRel newProject2 = MutableRels.strip(newProject);
return call.result(newProject2);
}
}
/** Implementation of {@link UnifyRule} that matches
* {@link org.apache.calcite.rel.logical.LogicalProject}. */
private static class ProjectToProjectUnifyRule extends AbstractUnifyRule {
public static final ProjectToProjectUnifyRule INSTANCE =
new ProjectToProjectUnifyRule();
private ProjectToProjectUnifyRule() {
super(operand(MutableProject.class, query(0)),
operand(MutableProject.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
final MutableProject target = (MutableProject) call.target;
final MutableProject query = (MutableProject) call.query;
final RexShuttle shuttle = getRexShuttle(target);
final List<RexNode> newProjects;
try {
newProjects = shuttle.apply(query.getProjects());
} catch (MatchFailed e) {
return null;
}
final MutableProject newProject =
MutableProject.of(
query.getRowType(), target, newProjects);
final MutableRel newProject2 = MutableRels.strip(newProject);
return call.result(newProject2);
}
}
/** Implementation of {@link UnifyRule} that matches a {@link MutableFilter}
* to a {@link MutableProject}. */
private static class FilterToProjectUnifyRule extends AbstractUnifyRule {
public static final FilterToProjectUnifyRule INSTANCE =
new FilterToProjectUnifyRule();
private FilterToProjectUnifyRule() {
super(operand(MutableFilter.class, query(0)),
operand(MutableProject.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
// Child of projectTarget is equivalent to child of filterQuery.
try {
// TODO: make sure that constants are ok
final MutableProject target = (MutableProject) call.target;
final RexShuttle shuttle = getRexShuttle(target);
final RexNode newCondition;
final MutableFilter query = (MutableFilter) call.query;
try {
newCondition = query.getCondition().accept(shuttle);
} catch (MatchFailed e) {
return null;
}
final MutableFilter newFilter = MutableFilter.of(target, newCondition);
if (query.parent instanceof MutableProject) {
final MutableRel inverse =
invert(((MutableProject) query.parent).getNamedProjects(),
newFilter, shuttle);
return call.create(query.parent).result(inverse);
} else {
final MutableRel inverse = invert(query, newFilter, target);
return call.result(inverse);
}
} catch (MatchFailed e) {
return null;
}
}
protected MutableRel invert(List<Pair<RexNode, String>> namedProjects,
MutableRel input,
RexShuttle shuttle) {
LOGGER.trace("SubstitutionVisitor: invert:\nprojects: {}\ninput: {}\nproject: {}\n",
namedProjects, input, shuttle);
final List<RexNode> exprList = new ArrayList<>();
final RexBuilder rexBuilder = input.cluster.getRexBuilder();
final List<RexNode> projects = Pair.left(namedProjects);
for (RexNode expr : projects) {
exprList.add(rexBuilder.makeZeroLiteral(expr.getType()));
}
for (Ord<RexNode> expr : Ord.zip(projects)) {
final RexNode node = expr.e.accept(shuttle);
if (node == null) {
throw MatchFailed.INSTANCE;
}
exprList.set(expr.i, node);
}
return MutableProject.of(input, exprList, Pair.right(namedProjects));
}
protected MutableRel invert(MutableRel model, MutableRel input,
MutableProject project) {
LOGGER.trace("SubstitutionVisitor: invert:\nmodel: {}\ninput: {}\nproject: {}\n",
model, input, project);
if (project.getProjects().size() < model.getRowType().getFieldCount()) {
throw MatchFailed.INSTANCE;
}
final List<RexNode> exprList = new ArrayList<>();
final RexBuilder rexBuilder = model.cluster.getRexBuilder();
for (RelDataTypeField field : model.getRowType().getFieldList()) {
exprList.add(rexBuilder.makeZeroLiteral(field.getType()));
}
for (Ord<RexNode> expr : Ord.zip(project.getProjects())) {
if (expr.e instanceof RexInputRef) {
final int target = ((RexInputRef) expr.e).getIndex();
exprList.set(target,
rexBuilder.ensureType(expr.e.getType(),
RexInputRef.of(expr.i, input.rowType),
false));
} else {
throw MatchFailed.INSTANCE;
}
}
return MutableProject.of(model.rowType, input, exprList);
}
}
/** Implementation of {@link UnifyRule} that matches a
* {@link MutableFilter}. */
private static class FilterToFilterUnifyRule extends AbstractUnifyRule {
public static final FilterToFilterUnifyRule INSTANCE =
new FilterToFilterUnifyRule();
private FilterToFilterUnifyRule() {
super(operand(MutableFilter.class, query(0)),
operand(MutableFilter.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
// in.query can be rewritten in terms of in.target if its condition
// is weaker. For example:
// query: SELECT * FROM t WHERE x = 1 AND y = 2
// target: SELECT * FROM t WHERE x = 1
// transforms to
// result: SELECT * FROM (target) WHERE y = 2
final MutableFilter query = (MutableFilter) call.query;
final MutableFilter target = (MutableFilter) call.target;
final MutableFilter newFilter =
createFilter(query, target);
if (newFilter == null) {
return null;
}
return call.result(newFilter);
}
MutableFilter createFilter(MutableFilter query, MutableFilter target) {
final RexNode newCondition =
splitFilter(query.cluster.getRexBuilder(), query.getCondition(),
target.getCondition());
if (newCondition == null) {
// Could not map query onto target.
return null;
}
if (newCondition.isAlwaysTrue()) {
return target;
}
return MutableFilter.of(target, newCondition);
}
}
/** Implementation of {@link UnifyRule} that matches a {@link MutableProject}
* to a {@link MutableFilter}. */
private static class ProjectToFilterUnifyRule extends AbstractUnifyRule {
public static final ProjectToFilterUnifyRule INSTANCE =
new ProjectToFilterUnifyRule();
private ProjectToFilterUnifyRule() {
super(operand(MutableProject.class, query(0)),
operand(MutableFilter.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
if (call.query.parent instanceof MutableFilter) {
final UnifyRuleCall in2 = call.create(call.query.parent);
final MutableFilter query = (MutableFilter) in2.query;
final MutableFilter target = (MutableFilter) in2.target;
final MutableFilter newFilter =
FilterToFilterUnifyRule.INSTANCE.createFilter(
query, target);
if (newFilter == null) {
return null;
}
return in2.result(query.replaceInParent(newFilter));
}
return null;
}
}
/** Implementation of {@link UnifyRule} that matches a
* {@link org.apache.calcite.rel.logical.LogicalAggregate} to a
* {@link org.apache.calcite.rel.logical.LogicalAggregate}, provided
* that they have the same child. */
private static class AggregateToAggregateUnifyRule extends AbstractUnifyRule {
public static final AggregateToAggregateUnifyRule INSTANCE =
new AggregateToAggregateUnifyRule();
private AggregateToAggregateUnifyRule() {
super(operand(MutableAggregate.class, query(0)),
operand(MutableAggregate.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
final MutableAggregate query = (MutableAggregate) call.query;
final MutableAggregate target = (MutableAggregate) call.target;
assert query != target;
// in.query can be rewritten in terms of in.target if its groupSet is
// a subset, and its aggCalls are a superset. For example:
// query: SELECT x, COUNT(b) FROM t GROUP BY x
// target: SELECT x, y, SUM(a) AS s, COUNT(b) AS cb FROM t GROUP BY x, y
// transforms to
// result: SELECT x, SUM(cb) FROM (target) GROUP BY x
if (!target.getGroupSet().contains(query.getGroupSet())) {
return null;
}
MutableRel result = unifyAggregates(query, target);
if (result == null) {
return null;
}
return call.result(result);
}
}
public static MutableAggregate permute(MutableAggregate aggregate,
MutableRel input, Mapping mapping) {
ImmutableBitSet groupSet = Mappings.apply(mapping, aggregate.getGroupSet());
ImmutableList<ImmutableBitSet> groupSets =
Mappings.apply2(mapping, aggregate.getGroupSets());
List<AggregateCall> aggregateCalls =
apply(mapping, aggregate.getAggCallList());
return MutableAggregate.of(input, aggregate.indicator, groupSet, groupSets,
aggregateCalls);
}
private static List<AggregateCall> apply(final Mapping mapping,
List<AggregateCall> aggCallList) {
return Lists.transform(aggCallList,
new Function<AggregateCall, AggregateCall>() {
public AggregateCall apply(AggregateCall call) {
return call.copy(Mappings.apply2(mapping, call.getArgList()),
Mappings.apply(mapping, call.filterArg));
}
});
}
public static MutableRel unifyAggregates(MutableAggregate query,
MutableAggregate target) {
if (query.getGroupType() != Aggregate.Group.SIMPLE
|| target.getGroupType() != Aggregate.Group.SIMPLE) {
throw new AssertionError(Bug.CALCITE_461_FIXED);
}
MutableRel result;
if (query.getGroupSet().equals(target.getGroupSet())) {
// Same level of aggregation. Generate a project.
final List<Integer> projects = Lists.newArrayList();
final int groupCount = query.getGroupSet().cardinality();
for (int i = 0; i < groupCount; i++) {
projects.add(i);
}
for (AggregateCall aggregateCall : query.getAggCallList()) {
int i = target.getAggCallList().indexOf(aggregateCall);
if (i < 0) {
return null;
}
projects.add(groupCount + i);
}
result = MutableRels.createProject(target, projects);
} else {
// Target is coarser level of aggregation. Generate an aggregate.
final ImmutableBitSet.Builder groupSet = ImmutableBitSet.builder();
final List<Integer> targetGroupList = target.getGroupSet().asList();
for (int c : query.getGroupSet()) {
int c2 = targetGroupList.indexOf(c);
if (c2 < 0) {
return null;
}
groupSet.set(c2);
}
final List<AggregateCall> aggregateCalls = Lists.newArrayList();
for (AggregateCall aggregateCall : query.getAggCallList()) {
if (aggregateCall.isDistinct()) {
return null;
}
int i = target.getAggCallList().indexOf(aggregateCall);
if (i < 0) {
return null;
}
aggregateCalls.add(
AggregateCall.create(getRollup(aggregateCall.getAggregation()),
aggregateCall.isDistinct(),
ImmutableList.of(target.groupSet.cardinality() + i), -1,
aggregateCall.type, aggregateCall.name));
}
result = MutableAggregate.of(target, false, groupSet.build(), null,
aggregateCalls);
}
return MutableRels.createCastRel(result, query.getRowType(), true);
}
/** Implementation of {@link UnifyRule} that matches a
* {@link MutableAggregate} on
* a {@link MutableProject} query to an {@link MutableAggregate} target.
*
* <p>The rule is necessary when we unify query=Aggregate(x) with
* target=Aggregate(x, y). Query will tend to have an extra Project(x) on its
* input, which this rule knows is safe to ignore.</p> */
private static class AggregateOnProjectToAggregateUnifyRule
extends AbstractUnifyRule {
public static final AggregateOnProjectToAggregateUnifyRule INSTANCE =
new AggregateOnProjectToAggregateUnifyRule();
private AggregateOnProjectToAggregateUnifyRule() {
super(
operand(MutableAggregate.class,
operand(MutableProject.class, query(0))),
operand(MutableAggregate.class, target(0)), 1);
}
public UnifyResult apply(UnifyRuleCall call) {
final MutableAggregate query = (MutableAggregate) call.query;
final MutableAggregate target = (MutableAggregate) call.target;
if (!(query.getInput() instanceof MutableProject)) {
return null;
}
final MutableProject project = (MutableProject) query.getInput();
if (project.getInput() != target.getInput()) {
return null;
}
final Mappings.TargetMapping mapping = project.getMapping();
if (mapping == null) {
return null;
}
final MutableAggregate aggregate2 =
permute(query, project.getInput(), mapping.inverse());
final MutableRel result = unifyAggregates(aggregate2, target);
return result == null ? null : call.result(result);
}
}
public static SqlAggFunction getRollup(SqlAggFunction aggregation) {
if (aggregation == SqlStdOperatorTable.SUM
|| aggregation == SqlStdOperatorTable.MIN
|| aggregation == SqlStdOperatorTable.MAX
|| aggregation == SqlStdOperatorTable.SUM0) {
return aggregation;
} else if (aggregation == SqlStdOperatorTable.COUNT) {
return SqlStdOperatorTable.SUM0;
} else {
return null;
}
}
/** Builds a shuttle that stores a list of expressions, and can map incoming
* expressions to references to them. */
protected static RexShuttle getRexShuttle(MutableProject target) {
final Map<String, Integer> map = new HashMap<>();
for (RexNode e : target.getProjects()) {
map.put(e.toString(), map.size());
}
return new RexShuttle() {
@Override public RexNode visitInputRef(RexInputRef ref) {
final Integer integer = map.get(ref.getName());
if (integer != null) {
return new RexInputRef(integer, ref.getType());
}
throw MatchFailed.INSTANCE;
}
@Override public RexNode visitCall(RexCall call) {
final Integer integer = map.get(call.toString());
if (integer != null) {
return new RexInputRef(integer, call.getType());
}
return super.visitCall(call);
}
};
}
/** Type of {@code MutableRel}. */
private enum MutableRelType {
SCAN,
PROJECT,
FILTER,
AGGREGATE,
SORT,
UNION,
JOIN,
HOLDER,
VALUES
}
/** Visitor over {@link MutableRel}. */
private static class MutableRelVisitor {
private MutableRel root;
public void visit(MutableRel node) {
node.childrenAccept(this);
}
public MutableRel go(MutableRel p) {
this.root = p;
visit(p);
return root;
}
}
/** Mutable equivalent of {@link RelNode}.
*
* <p>Each node has mutable state, and keeps track of its parent and position
* within parent.
* It doesn't make sense to canonize {@code MutableRels},
* otherwise one node could end up with multiple parents.
* It follows that {@code #hashCode} and {@code #equals} are less efficient
* than their {@code RelNode} counterparts.
* But, you don't need to copy a {@code MutableRel} in order to change it.
* For this reason, you should use {@code MutableRel} for short-lived
* operations, and transcribe back to {@code RelNode} when you are done.</p>
*/
protected abstract static class MutableRel {
MutableRel parent;
int ordinalInParent;
public final RelOptCluster cluster;
final RelDataType rowType;
final MutableRelType type;
private MutableRel(RelOptCluster cluster, RelDataType rowType,
MutableRelType type) {
this.cluster = cluster;
this.rowType = rowType;
this.type = type;
}
public RelDataType getRowType() {
return rowType;
}
public abstract void setInput(int ordinalInParent, MutableRel input);
public abstract List<MutableRel> getInputs();
public abstract void childrenAccept(MutableRelVisitor visitor);
/** Replaces this {@code MutableRel} in its parent with another node at the
* same position.
*
* <p>Before the method, {@code child} must be an orphan (have null parent)
* and after this method, this {@code MutableRel} is an orphan.
*
* @return The parent
*/
public MutableRel replaceInParent(MutableRel child) {
final MutableRel parent = this.parent;
if (this != child) {
/*
if (child.parent != null) {
child.parent.setInput(child.ordinalInParent, null);
child.parent = null;
}
*/
if (parent != null) {
parent.setInput(ordinalInParent, child);
this.parent = null;
this.ordinalInParent = 0;
}
}
return parent;
}
public abstract StringBuilder digest(StringBuilder buf);
public final String deep() {
return new MutableRelDumper().apply(this);
}
@Override public final String toString() {
return deep();
}
public MutableRel getParent() { return parent; }
}
/** Implementation of {@link MutableRel} whose only purpose is to have a
* child. Used as the root of a tree. */
private static class Holder extends MutableSingleRel {
private Holder(MutableRelType type, RelDataType rowType, MutableRel input) {
super(type, rowType, input);
}
static Holder of(MutableRel input) {
return new Holder(MutableRelType.HOLDER, input.rowType, input);
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Holder");
}
}
/** Abstract base class for implementations of {@link MutableRel} that have
* no inputs. */
protected abstract static class MutableLeafRel extends MutableRel {
protected final RelNode rel;
MutableLeafRel(MutableRelType type, RelNode rel) {
super(rel.getCluster(), rel.getRowType(), type);
this.rel = rel;
}
public void setInput(int ordinalInParent, MutableRel input) {
throw new IllegalArgumentException();
}
public List<MutableRel> getInputs() {
return ImmutableList.of();
}
public void childrenAccept(MutableRelVisitor visitor) {
// no children - nothing to do
}
}
/** Mutable equivalent of {@link SingleRel}. */
protected abstract static class MutableSingleRel extends MutableRel {
protected MutableRel input;
MutableSingleRel(MutableRelType type, RelDataType rowType,
MutableRel input) {
super(input.cluster, rowType, type);
this.input = input;
input.parent = this;
input.ordinalInParent = 0;
}
public void setInput(int ordinalInParent, MutableRel input) {
if (ordinalInParent >= 1) {
throw new IllegalArgumentException();
}
this.input = input;
if (input != null) {
input.parent = this;
input.ordinalInParent = 0;
}
}
public List<MutableRel> getInputs() {
return ImmutableList.of(input);
}
public void childrenAccept(MutableRelVisitor visitor) {
visitor.visit(input);
}
public MutableRel getInput() {
return input;
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalTableScan}. */
protected static class MutableScan extends MutableLeafRel {
private MutableScan(TableScan rel) {
super(MutableRelType.SCAN, rel);
}
static MutableScan of(TableScan rel) {
return new MutableScan(rel);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableScan
&& rel.getTable().equals(((MutableScan) obj).rel.getTable());
}
@Override public int hashCode() {
return rel.getTable().hashCode();
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Scan(table: ")
.append(rel.getTable().getQualifiedName()).append(")");
}
}
/** Mutable equivalent of {@link org.apache.calcite.rel.core.Values}. */
protected static class MutableValues extends MutableLeafRel {
private MutableValues(Values rel) {
super(MutableRelType.VALUES, rel);
}
static MutableValues of(Values rel) {
return new MutableValues(rel);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableValues
&& rel == ((MutableValues) obj).rel;
}
@Override public int hashCode() {
return rel.hashCode();
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Values(tuples: ")
.append(((Values) rel).getTuples()).append(")");
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalProject}. */
protected static class MutableProject extends MutableSingleRel {
private final List<RexNode> projects;
private MutableProject(RelDataType rowType, MutableRel input,
List<RexNode> projects) {
super(MutableRelType.PROJECT, rowType, input);
this.projects = projects;
assert RexUtil.compatibleTypes(projects, rowType, Litmus.THROW);
}
public static MutableProject of(RelDataType rowType, MutableRel input,
List<RexNode> projects) {
return new MutableProject(rowType, input, projects);
}
/** Equivalent to
* {@link RelOptUtil#createProject(org.apache.calcite.rel.RelNode, java.util.List, java.util.List)}
* for {@link MutableRel}. */
public static MutableRel of(MutableRel child, List<RexNode> exprList,
List<String> fieldNameList) {
final RelDataType rowType =
RexUtil.createStructType(child.cluster.getTypeFactory(), exprList,
fieldNameList, SqlValidatorUtil.F_SUGGESTER);
return of(rowType, child, exprList);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableProject
&& PAIRWISE_STRING_EQUIVALENCE.equivalent(
projects, ((MutableProject) obj).projects)
&& input.equals(((MutableProject) obj).input);
}
@Override public int hashCode() {
return Objects.hash(input,
PAIRWISE_STRING_EQUIVALENCE.hash(projects));
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Project(projects: ").append(projects).append(")");
}
public List<RexNode> getProjects() {
return projects;
}
/** Returns a list of (expression, name) pairs. */
public final List<Pair<RexNode, String>> getNamedProjects() {
return Pair.zip(getProjects(), getRowType().getFieldNames());
}
public Mappings.TargetMapping getMapping() {
return Project.getMapping(
input.getRowType().getFieldCount(), projects);
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalFilter}. */
protected static class MutableFilter extends MutableSingleRel {
private final RexNode condition;
private MutableFilter(MutableRel input, RexNode condition) {
super(MutableRelType.FILTER, input.rowType, input);
this.condition = condition;
}
public static MutableFilter of(MutableRel input, RexNode condition) {
return new MutableFilter(input, condition);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableFilter
&& condition.toString().equals(
((MutableFilter) obj).condition.toString())
&& input.equals(((MutableFilter) obj).input);
}
@Override public int hashCode() {
return Objects.hash(input, condition.toString());
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Filter(condition: ").append(condition).append(")");
}
public RexNode getCondition() {
return condition;
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalAggregate}. */
protected static class MutableAggregate extends MutableSingleRel {
public final boolean indicator;
private final ImmutableBitSet groupSet;
private final ImmutableList<ImmutableBitSet> groupSets;
private final List<AggregateCall> aggCalls;
private MutableAggregate(MutableRel input, RelDataType rowType,
boolean indicator, ImmutableBitSet groupSet,
List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) {
super(MutableRelType.AGGREGATE, rowType, input);
this.indicator = indicator;
this.groupSet = groupSet;
this.groupSets = groupSets == null
? ImmutableList.of(groupSet)
: ImmutableList.copyOf(groupSets);
this.aggCalls = aggCalls;
}
static MutableAggregate of(MutableRel input, boolean indicator,
ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
List<AggregateCall> aggCalls) {
RelDataType rowType =
Aggregate.deriveRowType(input.cluster.getTypeFactory(),
input.getRowType(), indicator, groupSet, groupSets, aggCalls);
return new MutableAggregate(input, rowType, indicator, groupSet,
groupSets, aggCalls);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableAggregate
&& groupSet.equals(((MutableAggregate) obj).groupSet)
&& aggCalls.equals(((MutableAggregate) obj).aggCalls)
&& input.equals(((MutableAggregate) obj).input);
}
@Override public int hashCode() {
return Objects.hash(input, groupSet, aggCalls);
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Aggregate(groupSet: ").append(groupSet)
.append(", groupSets: ").append(groupSets)
.append(", calls: ").append(aggCalls).append(")");
}
public ImmutableBitSet getGroupSet() {
return groupSet;
}
public ImmutableList<ImmutableBitSet> getGroupSets() {
return groupSets;
}
public List<AggregateCall> getAggCallList() {
return aggCalls;
}
public Aggregate.Group getGroupType() {
return Aggregate.Group.induce(groupSet, groupSets);
}
}
/** Mutable equivalent of {@link org.apache.calcite.rel.core.Sort}. */
protected static class MutableSort extends MutableSingleRel {
private final RelCollation collation;
private final RexNode offset;
private final RexNode fetch;
private MutableSort(MutableRel input, RelCollation collation,
RexNode offset, RexNode fetch) {
super(MutableRelType.SORT, input.rowType, input);
this.collation = collation;
this.offset = offset;
this.fetch = fetch;
}
static MutableSort of(MutableRel input, RelCollation collation,
RexNode offset, RexNode fetch) {
return new MutableSort(input, collation, offset, fetch);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableSort
&& collation.equals(((MutableSort) obj).collation)
&& Objects.equals(offset, ((MutableSort) obj).offset)
&& Objects.equals(fetch, ((MutableSort) obj).fetch)
&& input.equals(((MutableSort) obj).input);
}
@Override public int hashCode() {
return Objects.hash(input, collation, offset, fetch);
}
@Override public StringBuilder digest(StringBuilder buf) {
buf.append("Sort(collation: ").append(collation);
if (offset != null) {
buf.append(", offset: ").append(offset);
}
if (fetch != null) {
buf.append(", fetch: ").append(fetch);
}
return buf.append(")");
}
}
/** Base class for set-operations. */
protected abstract static class MutableSetOp extends MutableRel {
protected final List<MutableRel> inputs;
private MutableSetOp(RelOptCluster cluster, RelDataType rowType,
MutableRelType type, List<MutableRel> inputs) {
super(cluster, rowType, type);
this.inputs = inputs;
}
@Override public void setInput(int ordinalInParent, MutableRel input) {
inputs.set(ordinalInParent, input);
if (input != null) {
input.parent = this;
input.ordinalInParent = ordinalInParent;
}
}
@Override public List<MutableRel> getInputs() {
return inputs;
}
@Override public void childrenAccept(MutableRelVisitor visitor) {
for (MutableRel input : inputs) {
visitor.visit(input);
}
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalUnion}. */
protected static class MutableUnion extends MutableSetOp {
public boolean all;
private MutableUnion(RelOptCluster cluster, RelDataType rowType,
List<MutableRel> inputs, boolean all) {
super(cluster, rowType, MutableRelType.UNION, inputs);
this.all = all;
}
static MutableUnion of(List<MutableRel> inputs, boolean all) {
assert inputs.size() >= 2;
final MutableRel input0 = inputs.get(0);
return new MutableUnion(input0.cluster, input0.rowType, inputs, all);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableUnion
&& inputs.equals(((MutableUnion) obj).getInputs());
}
@Override public int hashCode() {
return Objects.hash(type, inputs);
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Union");
}
}
/** Base Class for relations with two inputs */
private abstract static class MutableBiRel extends MutableRel {
protected MutableRel left;
protected MutableRel right;
MutableBiRel(MutableRelType type, RelOptCluster cluster, RelDataType rowType,
MutableRel left, MutableRel right) {
super(cluster, rowType, type);
this.left = left;
left.parent = this;
left.ordinalInParent = 0;
this.right = right;
right.parent = this;
right.ordinalInParent = 1;
}
public void setInput(int ordinalInParent, MutableRel input) {
if (ordinalInParent > 1) {
throw new IllegalArgumentException();
}
if (ordinalInParent == 0) {
this.left = input;
} else {
this.right = input;
}
if (input != null) {
input.parent = this;
input.ordinalInParent = ordinalInParent;
}
}
public List<MutableRel> getInputs() {
return ImmutableList.of(left, right);
}
public MutableRel getLeft() {
return left;
}
public MutableRel getRight() {
return right;
}
public void childrenAccept(MutableRelVisitor visitor) {
visitor.visit(left);
visitor.visit(right);
}
}
/** Mutable equivalent of
* {@link org.apache.calcite.rel.logical.LogicalJoin}. */
private static class MutableJoin extends MutableBiRel {
//~ Instance fields --------------------------------------------------------
protected final RexNode condition;
protected final ImmutableSet<CorrelationId> variablesSet;
/**
* Values must be of enumeration {@link JoinRelType}, except that
* {@link JoinRelType#RIGHT} is disallowed.
*/
protected JoinRelType joinType;
private MutableJoin(
RelDataType rowType,
MutableRel left,
MutableRel right,
RexNode condition,
JoinRelType joinType,
Set<CorrelationId> variablesSet) {
super(MutableRelType.JOIN, left.cluster, rowType, left, right);
this.condition = Preconditions.checkNotNull(condition);
this.variablesSet = ImmutableSet.copyOf(variablesSet);
this.joinType = Preconditions.checkNotNull(joinType);
}
public RexNode getCondition() {
return condition;
}
public JoinRelType getJoinType() {
return joinType;
}
public ImmutableSet<CorrelationId> getVariablesSet() {
return variablesSet;
}
static MutableJoin of(RelOptCluster cluster, MutableRel left,
MutableRel right, RexNode condition, JoinRelType joinType,
Set<CorrelationId> variablesStopped) {
List<RelDataTypeField> fieldList = Collections.emptyList();
RelDataType rowType =
SqlValidatorUtil.deriveJoinRowType(left.getRowType(),
right.getRowType(), joinType, cluster.getTypeFactory(), null,
fieldList);
return new MutableJoin(rowType, left, right, condition, joinType,
variablesStopped);
}
@Override public boolean equals(Object obj) {
return obj == this
|| obj instanceof MutableJoin
&& joinType == ((MutableJoin) obj).joinType
&& condition.toString().equals(
((MutableJoin) obj).condition.toString())
&& left.equals(((MutableJoin) obj).left)
&& right.equals(((MutableJoin) obj).right);
}
@Override public int hashCode() {
return Objects.hash(left, right, condition.toString(), joinType);
}
@Override public StringBuilder digest(StringBuilder buf) {
return buf.append("Join(left: ").append(left)
.append(", right:").append(right)
.append(")");
}
}
/** Utilities for dealing with {@link MutableRel}s. */
protected static class MutableRels {
public static boolean contains(MutableRel ancestor,
final MutableRel target) {
if (ancestor.equals(target)) {
// Short-cut common case.
return true;
}
try {
new MutableRelVisitor() {
@Override public void visit(MutableRel node) {
if (node.equals(target)) {
throw Util.FoundOne.NULL;
}
super.visit(node);
}
// CHECKSTYLE: IGNORE 1
}.go(ancestor);
return false;
} catch (Util.FoundOne e) {
return true;
}
}
public static MutableRel preOrderTraverseNext(MutableRel node) {
MutableRel parent = node.getParent();
int ordinal = node.ordinalInParent + 1;
while (parent != null) {
if (parent.getInputs().size() > ordinal) {
return parent.getInputs().get(ordinal);
}
node = parent;
parent = node.getParent();
ordinal = node.ordinalInParent + 1;
}
return null;
}
private static List<MutableRel> descendants(MutableRel query) {
final List<MutableRel> list = new ArrayList<>();
descendantsRecurse(list, query);
return list;
}
private static void descendantsRecurse(List<MutableRel> list,
MutableRel rel) {
list.add(rel);
for (MutableRel input : rel.getInputs()) {
descendantsRecurse(list, input);
}
}
/** Returns whether two relational expressions have the same row-type. */
public static boolean equalType(String desc0, MutableRel rel0, String desc1,
MutableRel rel1, Litmus litmus) {
return RelOptUtil.equal(desc0, rel0.getRowType(),
desc1, rel1.getRowType(), litmus);
}
/** Within a relational expression {@code query}, replaces occurrences of
* {@code find} with {@code replace}.
*
* <p>Assumes relational expressions (and their descendants) are not null.
* Does not handle cycles. */
public static Replacement replace(MutableRel query, MutableRel find,
MutableRel replace) {
if (find.equals(replace)) {
// Short-cut common case.
return null;
}
assert equalType("find", find, "replace", replace, Litmus.THROW);
return replaceRecurse(query, find, replace);
}
/** Helper for {@link #replace}. */
private static Replacement replaceRecurse(MutableRel query,
MutableRel find, MutableRel replace) {
if (find.equals(query)) {
query.replaceInParent(replace);
return new Replacement(query, replace);
}
for (MutableRel input : query.getInputs()) {
Replacement r = replaceRecurse(input, find, replace);
if (r != null) {
return r;
}
}
return null;
}
/** Based on
* {@link org.apache.calcite.rel.rules.ProjectRemoveRule#strip}. */
public static MutableRel strip(MutableProject project) {
return isTrivial(project) ? project.getInput() : project;
}
/** Based on
* {@link org.apache.calcite.rel.rules.ProjectRemoveRule#isTrivial(org.apache.calcite.rel.core.Project)}. */
public static boolean isTrivial(MutableProject project) {
MutableRel child = project.getInput();
final RelDataType childRowType = child.getRowType();
return RexUtil.isIdentity(project.getProjects(), childRowType);
}
/** Equivalent to
* {@link RelOptUtil#createProject(org.apache.calcite.rel.RelNode, java.util.List)}
* for {@link MutableRel}. */
public static MutableRel createProject(final MutableRel child,
final List<Integer> posList) {
final RelDataType rowType = child.getRowType();
if (Mappings.isIdentity(posList, rowType.getFieldCount())) {
return child;
}
return MutableProject.of(
RelOptUtil.permute(child.cluster.getTypeFactory(), rowType,
Mappings.bijection(posList)),
child,
new AbstractList<RexNode>() {
public int size() {
return posList.size();
}
public RexNode get(int index) {
final int pos = posList.get(index);
return RexInputRef.of(pos, rowType);
}
});
}
/** Equivalence to {@link org.apache.calcite.plan.RelOptUtil#createCastRel}
* for {@link MutableRel}. */
public static MutableRel createCastRel(MutableRel rel,
RelDataType castRowType, boolean rename) {
RelDataType rowType = rel.getRowType();
if (RelOptUtil.areRowTypesEqual(rowType, castRowType, rename)) {
// nothing to do
return rel;
}
List<RexNode> castExps =
RexUtil.generateCastExpressions(rel.cluster.getRexBuilder(),
castRowType, rowType);
final List<String> fieldNames =
rename ? castRowType.getFieldNames() : rowType.getFieldNames();
return MutableProject.of(rel, castExps, fieldNames);
}
}
/** Visitor that prints an indented tree of {@link MutableRel}s. */
protected static class MutableRelDumper extends MutableRelVisitor {
private final StringBuilder buf = new StringBuilder();
private int level;
@Override public void visit(MutableRel node) {
Spaces.append(buf, level * 2);
if (node == null) {
buf.append("null");
} else {
node.digest(buf);
buf.append("\n");
++level;
super.visit(node);
--level;
}
}
public String apply(MutableRel rel) {
go(rel);
return buf.toString();
}
}
/** Returns if one rel is weaker than another. */
protected boolean isWeaker(MutableRel rel0, MutableRel rel) {
if (rel0 == rel || equivalents.get(rel0).contains(rel)) {
return false;
}
if (!(rel0 instanceof MutableFilter)
|| !(rel instanceof MutableFilter)) {
return false;
}
if (!rel.getRowType().equals(rel0.getRowType())) {
return false;
}
final MutableRel rel0input = ((MutableFilter) rel0).getInput();
final MutableRel relinput = ((MutableFilter) rel).getInput();
if (rel0input != relinput
&& !equivalents.get(rel0input).contains(relinput)) {
return false;
}
RexExecutorImpl rexImpl =
(RexExecutorImpl) (rel.cluster.getPlanner().getExecutor());
RexImplicationChecker rexImplicationChecker = new RexImplicationChecker(
rel.cluster.getRexBuilder(),
rexImpl, rel.getRowType());
return rexImplicationChecker.implies(((MutableFilter) rel0).getCondition(),
((MutableFilter) rel).getCondition());
}
/** Operand to a {@link UnifyRule}. */
protected abstract static class Operand {
protected final Class<? extends MutableRel> clazz;
protected Operand(Class<? extends MutableRel> clazz) {
this.clazz = clazz;
}
public abstract boolean matches(SubstitutionVisitor visitor, MutableRel rel);
public boolean isWeaker(SubstitutionVisitor visitor, MutableRel rel) {
return false;
}
}
/** Operand to a {@link UnifyRule} that matches a relational expression of a
* given type. It has zero or more child operands. */
private static class InternalOperand extends Operand {
private final List<Operand> inputs;
InternalOperand(Class<? extends MutableRel> clazz, List<Operand> inputs) {
super(clazz);
this.inputs = inputs;
}
@Override public boolean matches(SubstitutionVisitor visitor, MutableRel rel) {
return clazz.isInstance(rel)
&& allMatch(visitor, inputs, rel.getInputs());
}
@Override public boolean isWeaker(SubstitutionVisitor visitor, MutableRel rel) {
return clazz.isInstance(rel)
&& allWeaker(visitor, inputs, rel.getInputs());
}
private static boolean allMatch(SubstitutionVisitor visitor,
List<Operand> operands, List<MutableRel> rels) {
if (operands.size() != rels.size()) {
return false;
}
for (Pair<Operand, MutableRel> pair : Pair.zip(operands, rels)) {
if (!pair.left.matches(visitor, pair.right)) {
return false;
}
}
return true;
}
private static boolean allWeaker(
SubstitutionVisitor visitor,
List<Operand> operands, List<MutableRel> rels) {
if (operands.size() != rels.size()) {
return false;
}
for (Pair<Operand, MutableRel> pair : Pair.zip(operands, rels)) {
if (!pair.left.isWeaker(visitor, pair.right)) {
return false;
}
}
return true;
}
}
/** Operand to a {@link UnifyRule} that matches a relational expression of a
* given type. */
private static class AnyOperand extends Operand {
AnyOperand(Class<? extends MutableRel> clazz) {
super(clazz);
}
@Override public boolean matches(SubstitutionVisitor visitor, MutableRel rel) {
return clazz.isInstance(rel);
}
}
/** Operand that assigns a particular relational expression to a variable.
*
* <p>It is applied to a descendant of the query, writes the operand into the
* slots array, and always matches.
* There is a corresponding operand of type {@link TargetOperand} that checks
* whether its relational expression, a descendant of the target, is
* equivalent to this {@code QueryOperand}'s relational expression.
*/
private static class QueryOperand extends Operand {
private final int ordinal;
protected QueryOperand(int ordinal) {
super(MutableRel.class);
this.ordinal = ordinal;
}
@Override public boolean matches(SubstitutionVisitor visitor, MutableRel rel) {
visitor.slots[ordinal] = rel;
return true;
}
}
/** Operand that checks that a relational expression matches the corresponding
* relational expression that was passed to a {@link QueryOperand}. */
private static class TargetOperand extends Operand {
private final int ordinal;
protected TargetOperand(int ordinal) {
super(MutableRel.class);
this.ordinal = ordinal;
}
@Override public boolean matches(SubstitutionVisitor visitor, MutableRel rel) {
final MutableRel rel0 = visitor.slots[ordinal];
assert rel0 != null : "QueryOperand should have been called first";
return rel0 == rel || visitor.equivalents.get(rel0).contains(rel);
}
@Override public boolean isWeaker(SubstitutionVisitor visitor, MutableRel rel) {
final MutableRel rel0 = visitor.slots[ordinal];
assert rel0 != null : "QueryOperand should have been called first";
return visitor.isWeaker(rel0, rel);
}
}
/** Visitor that counts how many {@link QueryOperand} and
* {@link TargetOperand} in an operand tree. */
private static class SlotCounter {
int queryCount;
int targetCount;
void visit(Operand operand) {
if (operand instanceof QueryOperand) {
++queryCount;
} else if (operand instanceof TargetOperand) {
++targetCount;
} else if (operand instanceof AnyOperand) {
// nothing
} else {
for (Operand input : ((InternalOperand) operand).inputs) {
visit(input);
}
}
}
}
/**
* Rule that converts a {@link org.apache.calcite.rel.logical.LogicalFilter}
* on top of a {@link org.apache.calcite.rel.logical.LogicalProject} into a
* trivial filter (on a boolean column).
*/
public static class FilterOnProjectRule extends RelOptRule {
private static final Predicate<Filter> PREDICATE =
new Predicate<Filter>() {
public boolean apply(Filter input) {
return input.getCondition() instanceof RexInputRef;
}
};
public static final FilterOnProjectRule INSTANCE =
new FilterOnProjectRule();
private FilterOnProjectRule() {
super(
operand(Filter.class, null, PREDICATE,
some(operand(Project.class, any()))));
}
public void onMatch(RelOptRuleCall call) {
final Filter filter = call.rel(0);
final Project project = call.rel(1);
final List<RexNode> newProjects = new ArrayList<>(project.getProjects());
newProjects.add(filter.getCondition());
final RelOptCluster cluster = filter.getCluster();
RelDataType newRowType =
cluster.getTypeFactory().builder()
.addAll(project.getRowType().getFieldList())
.add("condition", Util.last(newProjects).getType())
.build();
final RelNode newProject =
project.copy(project.getTraitSet(),
project.getInput(),
newProjects,
newRowType);
final RexInputRef newCondition =
cluster.getRexBuilder().makeInputRef(newProject,
newProjects.size() - 1);
call.transformTo(filter.copy(filter.getTraitSet(), newProject, newCondition));
}
}
}
// End SubstitutionVisitor.java