/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.eigenbase.relopt;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eigenbase.rel.*;
import org.eigenbase.rel.rules.RemoveTrivialProjectRule;
import org.eigenbase.reltype.RelDataTypeField;
import org.eigenbase.rex.*;
import org.eigenbase.sql.SqlKind;
import org.eigenbase.sql.fun.SqlStdOperatorTable;
import org.eigenbase.trace.EigenbaseTrace;
import org.eigenbase.util.ControlFlowException;
import org.eigenbase.util.IntList;
import org.eigenbase.util.Pair;
import org.eigenbase.util.mapping.Mapping;
import org.eigenbase.util.mapping.Mappings;
import net.hydromatic.linq4j.Ord;
import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
import net.hydromatic.optiq.util.BitSets;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* 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 TableAccessRel},
* {@link FilterRel},
* {@link ProjectRel},
* {@link JoinRel},
* {@link UnionRel},
* {@link AggregateRel}.</p>
*/
public class SubstitutionVisitor {
private static final boolean DEBUG = OptiqPrepareImpl.DEBUG;
private static final Logger LOGGER = EigenbaseTrace.getPlannerTracer();
private static final List<UnifyRule> RULES =
ImmutableList.<UnifyRule>of(
TrivialRule.INSTANCE,
ProjectToProjectUnifyRule.INSTANCE,
FilterToProjectUnifyRule.INSTANCE,
// ProjectToFilterUnifyRule.INSTANCE,
FilterToFilterUnifyRule.INSTANCE,
AggregateToAggregateUnifyRule.INSTANCE,
AggregateOnProjectToAggregateUnifyRule.INSTANCE);
private static final Map<Pair<Class, Class>, List<UnifyRule>> RULE_MAP =
new HashMap<Pair<Class, Class>, List<UnifyRule>>();
private final RelVisitor registrar =
new RelVisitor() {
public void visit(RelNode node, int ordinal, RelNode parent) {
if (parent != null) {
parentMap.put(node, new Parentage(parent, ordinal));
}
super.visit(node, ordinal, parent);
}
};
private final RelNode query;
private final RelNode target;
/**
* Map from each node in the query and the materialization query
* to its parent.
*/
final Map<RelNode, Parentage> parentMap =
new IdentityHashMap<RelNode, Parentage>();
/**
* Nodes in {@link #target} that have no children.
*/
final List<RelNode> targetLeaves;
/**
* Nodes in {@link #query} that have no children.
*/
final List<RelNode> queryLeaves;
final Map<RelNode, RelNode> replacementMap =
new HashMap<RelNode, RelNode>();
public SubstitutionVisitor(RelNode target, RelNode query) {
this.query = query;
this.target = target;
final Set<RelNode> parents = new HashSet<RelNode>();
final List<RelNode> allNodes = new ArrayList<RelNode>();
final RelVisitor visitor =
new RelVisitor() {
public void visit(RelNode node, int ordinal, RelNode parent) {
parentMap.put(node, new Parentage(parent, ordinal));
parents.add(parent);
allNodes.add(node);
super.visit(node, ordinal, parent);
}
};
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();
visitor.go(query);
allNodes.removeAll(parents);
queryLeaves = ImmutableList.copyOf(allNodes);
}
void register(RelNode result, RelNode query) {
equiv(result, query);
registrar.go(result);
}
/** Marks that {@code result} is equivalent to (existing node) {@code query},
* and inherits its parentage. */
void equiv(RelNode result, RelNode query) {
if (result != query && parentMap.get(result) == null) {
final Parentage parentage = parentMap.get(query);
if (parentage != null) {
parentMap.put(result, parentage);
}
}
}
/**
* 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, not(rexBuilder)), false);
}
/** Returns a function that applies NOT to its argument. */
public static Function<RexNode, RexNode> not(final RexBuilder rexBuilder) {
return new Function<RexNode, RexNode>() {
public RexNode apply(RexNode input) {
return input.isAlwaysTrue()
? rexBuilder.makeLiteral(false)
: input.isAlwaysFalse()
? rexBuilder.makeLiteral(true)
: input.getKind() == SqlKind.NOT
? ((RexCall) input).operands.get(0)
: rexBuilder.makeCall(SqlStdOperatorTable.NOT, input);
}
};
}
/** Removes all expressions from a list that are equivalent to a given
* expression. Returns whether any were removed. */
private static boolean removeAll(List<RexNode> targets, RexNode e) {
int count = 0;
Iterator<RexNode> iterator = targets.iterator();
while (iterator.hasNext()) {
RexNode next = iterator.next();
if (equivalent(next, e)) {
++count;
iterator.remove();
}
}
return count > 0;
}
/** Returns whether two expressions are equivalent. */
private static boolean equivalent(RexNode e1, RexNode e2) {
// TODO: make broader;
// 1. 'x = y' should be equivalent to 'y = x'.
// 2. 'c2 and c1' should be equivalent to 'c1 and c2'.
return e1 == e2 || e1.toString().equals(e2.toString());
}
/**
* 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<RexNode>();
final List<RexNode> notDisjunctions = new ArrayList<RexNode>();
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;
}
/**
* Simplifies a boolean expression.
*
* <p>In particular:</p>
* <ul>
* <li>{@code simplify(x = 1 AND y = 2 AND NOT x = 1)}
* returns {@code y = 2}</li>
* <li>{@code simplify(x = 1 AND FALSE)}
* returns {@code FALSE}</li>
* </ul>
*/
public static RexNode simplify(RexBuilder rexBuilder, RexNode e) {
final List<RexNode> disjunctions = RelOptUtil.conjunctions(e);
final List<RexNode> notDisjunctions = new ArrayList<RexNode>();
for (int i = 0; i < disjunctions.size(); i++) {
final RexNode disjunction = disjunctions.get(i);
final SqlKind kind = disjunction.getKind();
switch (kind) {
case NOT:
notDisjunctions.add(
((RexCall) disjunction).getOperands().get(0));
disjunctions.remove(i);
--i;
break;
case LITERAL:
if (!RexLiteral.booleanValue(disjunction)) {
return disjunction; // false
} else {
disjunctions.remove(i);
--i;
}
}
}
if (disjunctions.isEmpty() && notDisjunctions.isEmpty()) {
return rexBuilder.makeLiteral(true);
}
// 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 rexBuilder.makeLiteral(false);
}
}
// Add the NOT disjunctions back in.
for (RexNode notDisjunction : notDisjunctions) {
disjunctions.add(
rexBuilder.makeCall(
SqlStdOperatorTable.NOT,
notDisjunction));
}
return RexUtil.composeConjunction(rexBuilder, disjunctions, false);
}
/**
* Creates the expression {@code e1 AND NOT e2}.
*/
static RexNode andNot(RexBuilder rexBuilder, RexNode e1, RexNode e2) {
return rexBuilder.makeCall(
SqlStdOperatorTable.AND,
e1,
rexBuilder.makeCall(
SqlStdOperatorTable.NOT,
e2));
}
public RelNode go(RelNode replacement) {
assert RelOptUtil.equalType("target", target, "replacement", replacement,
true);
replacementMap.put(target, replacement);
final UnifyResult unifyResult = matchRecurse(target);
if (unifyResult == null) {
return null;
}
final RelNode node0 = unifyResult.result;
RelNode node = replaceAncestors(node0);
if (DEBUG) {
System.out.println(
"Convert: query:\n" + RelOptUtil.toString(query)
+ "\nunify.query:\n" + RelOptUtil.toString(unifyResult.call.query)
+ "\nunify.result:\n" + RelOptUtil.toString(unifyResult.result)
+ "\nunify.target:\n" + RelOptUtil.toString(unifyResult.call.target)
+ "\nnode0:\n" + RelOptUtil.toString(node0)
+ "\nnode:\n" + RelOptUtil.toString(node));
}
return node;
}
private RelNode replaceAncestors(RelNode node) {
for (;;) {
Parentage parentage = parentMap.get(node);
if (parentage == null || parentage.parent == null) {
return node;
}
node = RelOptUtil.replaceInput(parentage.parent, parentage.ordinal, node);
}
}
private UnifyResult matchRecurse(RelNode target) {
final List<RelNode> targetInputs = target.getInputs();
RelNode queryParent = null;
for (RelNode targetInput : targetInputs) {
UnifyResult unifyResult = matchRecurse(targetInput);
if (unifyResult == null) {
return null;
}
Parentage parentage = parentMap.get(unifyResult.call.query);
parentMap.put(unifyResult.result, parentage);
queryParent = RelOptUtil.replaceInput(parentage.parent, parentage.ordinal,
unifyResult.result);
equiv(queryParent, parentage.parent);
}
if (targetInputs.isEmpty()) {
for (RelNode queryLeaf : queryLeaves) {
for (UnifyRule rule : applicableRules(queryLeaf, target)) {
final UnifyResult x = apply(rule, queryLeaf, target);
if (x != null) {
if (DEBUG) {
System.out.println(
"Rule: " + rule
+ "\nQuery:\n"
+ RelOptUtil.toString(queryParent)
+ (x.call.query != queryParent
? "\nQuery (original):\n"
+ RelOptUtil.toString(queryParent)
: "")
+ "\nTarget:\n"
+ RelOptUtil.toString(target)
+ "\nResult:\n"
+ RelOptUtil.toString(x.result)
+ "\n");
}
return x;
}
}
}
} else {
for (UnifyRule rule : applicableRules(queryParent, target)) {
final UnifyResult x = apply(rule, queryParent, target);
if (x != null) {
if (DEBUG) {
System.out.println(
"Rule: " + rule
+ "\nQuery:\n"
+ RelOptUtil.toString(queryParent)
+ (x.call.query != queryParent
? "\nQuery (original):\n"
+ RelOptUtil.toString(queryParent)
: "")
+ "\nTarget:\n"
+ RelOptUtil.toString(target)
+ "\nResult:\n"
+ RelOptUtil.toString(x.result)
+ "\n");
}
return x;
}
}
}
if (DEBUG) {
System.out.println(
"Unify failed:"
+ "\nQuery:\n"
+ RelOptUtil.toString(queryParent)
+ "\nTarget:\n"
+ RelOptUtil.toString(target)
+ "\n");
}
return null;
}
private UnifyResult apply(UnifyRule rule, RelNode query, RelNode target) {
final UnifyRuleCall call = new UnifyRuleCall(rule, query, target);
return rule.apply(call);
}
private static List<UnifyRule> applicableRules(RelNode query,
RelNode target) {
final Class queryClass = query.getClass();
final Class targetClass = target.getClass();
final Pair<Class, Class> key = Pair.of(queryClass, targetClass);
List<UnifyRule> list = RULE_MAP.get(key);
if (list == null) {
final ImmutableList.Builder<UnifyRule> builder =
ImmutableList.builder();
for (UnifyRule rule : RULES) {
//noinspection unchecked
if (rule.mightMatch(queryClass, targetClass)) {
builder.add(rule);
}
}
list = builder.build();
RULE_MAP.put(key, list);
}
return list;
}
/** Exception thrown to exit a matcher. Not really an error. */
private static class MatchFailed extends ControlFlowException {
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>
*/
private interface UnifyRule {
/**
* <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, Optiq 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
*/
UnifyResult apply(UnifyRuleCall call);
boolean mightMatch(Class queryClass, Class targetClass);
}
/**
* Arguments to an application of a {@link UnifyRule}.
*/
private class UnifyRuleCall {
final UnifyRule rule;
final RelNode query;
final RelNode target;
public UnifyRuleCall(UnifyRule rule, RelNode query, RelNode target) {
this.rule = rule;
this.query = query;
this.target = target;
}
/** Returns the parent of a node, which child it is, and a bitmap of which
* columns are used by the parent. */
public Parentage parent(RelNode node) {
return parentMap.get(node);
}
UnifyResult result(RelNode result) {
assert RelOptUtil.contains(result, target);
assert RelOptUtil.equalType("result", result, "query", query, true);
equiv(result, query);
RelNode replace = replacementMap.get(target);
if (replace != null) {
result = RelOptUtil.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(RelNode query) {
return new UnifyRuleCall(rule, query, target);
}
}
/**
* 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}.
*/
private static class UnifyResult {
private final UnifyRuleCall call;
// equivalent to "query", contains "result"
private final RelNode result;
UnifyResult(UnifyRuleCall call, RelNode result) {
this.call = call;
assert RelOptUtil.equalType("query", call.query, "result", result, true);
this.result = result;
}
}
/** Abstract base class for implementing {@link UnifyRule}. */
private abstract static class AbstractUnifyRule implements UnifyRule {
private final Class<? extends RelNode> queryClass;
private final Class<? extends RelNode> targetClass;
public AbstractUnifyRule(Class<? extends RelNode> queryClass,
Class<? extends RelNode> targetClass) {
this.queryClass = queryClass;
this.targetClass = targetClass;
}
public boolean mightMatch(Class queryClass, Class targetClass) {
return this.queryClass.isAssignableFrom(queryClass)
&& this.targetClass.isAssignableFrom(targetClass);
}
}
/** Implementation of {@link UnifyRule} that matches if the query is already
* equal to the target (using {@code ==}).
*
* <p>Matches scans to the same table, because these will be canonized to
* the same {@link org.eigenbase.rel.TableAccessRel} instance.</p>
*/
private static class TrivialRule extends AbstractUnifyRule {
private static final TrivialRule INSTANCE = new TrivialRule();
private TrivialRule() {
super(RelNode.class, RelNode.class);
}
public UnifyResult apply(UnifyRuleCall call) {
if (call.query == call.target) {
return call.result(call.query);
}
return null;
}
}
/** Implementation of {@link UnifyRule} that matches {@link ProjectRel}. */
private static class ProjectToProjectUnifyRule extends AbstractUnifyRule {
public static final ProjectToProjectUnifyRule INSTANCE =
new ProjectToProjectUnifyRule();
private ProjectToProjectUnifyRule() {
super(ProjectRel.class, ProjectRel.class);
}
public UnifyResult apply(UnifyRuleCall call) {
final ProjectRel target = (ProjectRel) call.target;
final ProjectRel query = (ProjectRel) call.query;
final RexShuttle shuttle = getRexShuttle(target);
final List<RexNode> newProjects;
try {
newProjects = shuttle.apply(query.getProjects());
} catch (MatchFailed e) {
return null;
}
final ProjectRel newProject =
new ProjectRel(
target.getCluster(),
target.getCluster().traitSetOf(
query.getCollationList().isEmpty()
? RelCollationImpl.EMPTY
: query.getCollationList().get(0)),
target,
newProjects,
query.getRowType(),
query.getFlags());
final RelNode newProject2 =
RemoveTrivialProjectRule.strip(newProject);
return call.result(newProject2);
}
}
/** Implementation of {@link UnifyRule} that matches a {@link FilterRel}
* to a {@link ProjectRel}. */
private static class FilterToProjectUnifyRule extends AbstractUnifyRule {
public static final FilterToProjectUnifyRule INSTANCE =
new FilterToProjectUnifyRule();
private FilterToProjectUnifyRule() {
super(FilterRel.class, ProjectRel.class);
}
public UnifyResult apply(UnifyRuleCall call) {
// Child of projectTarget is equivalent to child of filterQuery.
try {
// TODO: make sure that constants are ok
final ProjectRel target = (ProjectRel) call.target;
final RexShuttle shuttle = getRexShuttle(target);
final RexNode newCondition;
final FilterRel query = (FilterRel) call.query;
try {
newCondition = query.getCondition().accept(shuttle);
} catch (MatchFailed e) {
return null;
}
final FilterRel newFilter =
new FilterRel(
query.getCluster(),
target,
newCondition);
final RelNode inverse = invert(query, newFilter, target);
return call.result(inverse);
} catch (MatchFailed e) {
return null;
}
}
private RelNode invert(RelNode model, RelNode input, ProjectRel project) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("SubstitutionVisitor: invert:\n"
+ "model: " + model + "\n"
+ "input: " + input + "\n"
+ "project: " + project + "\n");
}
final List<RexNode> exprList = new ArrayList<RexNode>();
final RexBuilder rexBuilder = model.getCluster().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(expr.i,
rexBuilder.makeInputRef(input, target));
}
}
return new ProjectRel(model.getCluster(), model.getTraitSet(), input,
exprList, model.getRowType(), ProjectRelBase.Flags.BOXED);
}
}
/** Implementation of {@link UnifyRule} that matches a {@link FilterRel}. */
private static class FilterToFilterUnifyRule extends AbstractUnifyRule {
public static final FilterToFilterUnifyRule INSTANCE =
new FilterToFilterUnifyRule();
private FilterToFilterUnifyRule() {
super(FilterRel.class, FilterRel.class);
}
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 FilterRel query = (FilterRel) call.query;
final FilterRel target = (FilterRel) call.target;
final FilterRel newFilter = createFilter(query, target);
if (newFilter == null) {
return null;
}
return call.result(newFilter);
}
FilterRel createFilter(FilterRel query, FilterRel target) {
final RelOptCluster cluster = query.getCluster();
final RexNode newCondition =
splitFilter(
cluster.getRexBuilder(), query.getCondition(),
target.getCondition());
if (newCondition == null) {
// Could not map query onto target.
return null;
}
if (newCondition.isAlwaysTrue()) {
return target;
}
return new FilterRel(cluster, target, newCondition);
}
}
/** Implementation of {@link UnifyRule} that matches a {@link ProjectRel} to
* a {@link FilterRel}. */
private static class ProjectToFilterUnifyRule extends AbstractUnifyRule {
public static final ProjectToFilterUnifyRule INSTANCE =
new ProjectToFilterUnifyRule();
private ProjectToFilterUnifyRule() {
super(ProjectRel.class, FilterRel.class);
}
public UnifyResult apply(UnifyRuleCall call) {
final Parentage queryParent = call.parent(call.query);
if (queryParent.parent instanceof FilterRel) {
final UnifyRuleCall in2 = call.create(queryParent.parent);
final FilterRel query = (FilterRel) in2.query;
final FilterRel target = (FilterRel) in2.target;
final FilterRel newFilter =
FilterToFilterUnifyRule.INSTANCE.createFilter(query, target);
if (newFilter == null) {
return null;
}
return in2.result(
call.query.copy(
call.query.getTraitSet(),
ImmutableList.<RelNode>of(newFilter)));
}
return null;
}
}
/** Implementation of {@link UnifyRule} that matches a {@link AggregateRel} to
* a {@link AggregateRel}, provided that they have the same child. */
private static class AggregateToAggregateUnifyRule extends AbstractUnifyRule {
public static final AggregateToAggregateUnifyRule INSTANCE =
new AggregateToAggregateUnifyRule();
private AggregateToAggregateUnifyRule() {
super(AggregateRel.class, AggregateRel.class);
}
public UnifyResult apply(UnifyRuleCall call) {
final AggregateRel query = (AggregateRel) call.query;
final AggregateRel target = (AggregateRel) call.target;
assert query != target;
if (query.getChild() != target.getChild()) {
return null;
}
// 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 (!BitSets.contains(target.getGroupSet(), query.getGroupSet())) {
return null;
}
RelNode result = unifyAggregates(query, target);
if (result == null) {
return null;
}
return call.result(result);
}
}
public static AggregateRel permute(AggregateRel aggregate, RelNode input,
Mapping mapping) {
BitSet groupSet = Mappings.apply(mapping, aggregate.getGroupSet());
List<AggregateCall> aggregateCalls =
apply(mapping, aggregate.getAggCallList());
return aggregate.copy(aggregate.getTraitSet(), input, groupSet,
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 new AggregateCall(call.getAggregation(), call.isDistinct(),
Mappings.apply2(mapping, call.getArgList()), call.getType(),
call.name);
}
});
}
public static RelNode unifyAggregates(
AggregateRel query, AggregateRel target) {
RelNode 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 = CalcRel.createProject(target, projects);
} else {
// Target is coarser level of aggregation. Generate an aggregate.
final BitSet groupSet = new BitSet();
final IntList targetGroupList = BitSets.toList(target.getGroupSet());
for (int c : BitSets.toIter(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(
new AggregateCall(getRollup(aggregateCall.getAggregation()),
aggregateCall.isDistinct(),
ImmutableList.of(groupSet.cardinality() + i),
aggregateCall.type, aggregateCall.name));
}
result = new AggregateRel(target.getCluster(), target, groupSet,
aggregateCalls);
}
return RelOptUtil.createCastRel(result, query.getRowType(), true);
}
/** Implementation of {@link UnifyRule} that matches a {@link AggregateRel} on
* a {@link ProjectRel} to an {@link AggregateRel} target. */
private static class AggregateOnProjectToAggregateUnifyRule
extends AbstractUnifyRule {
public static final AggregateOnProjectToAggregateUnifyRule INSTANCE =
new AggregateOnProjectToAggregateUnifyRule();
private AggregateOnProjectToAggregateUnifyRule() {
super(AggregateRel.class, AggregateRel.class);
}
public UnifyResult apply(UnifyRuleCall call) {
final AggregateRel query = (AggregateRel) call.query;
final AggregateRel target = (AggregateRel) call.target;
if (!(query.getChild() instanceof ProjectRel)) {
return null;
}
final ProjectRel project = (ProjectRel) query.getChild();
if (project.getChild() != target.getChild()) {
return null;
}
final Mappings.TargetMapping mapping = project.getMapping();
if (mapping == null) {
return null;
}
final AggregateRel aggregate2 =
permute(query, project.getChild(), mapping.inverse());
final RelNode result = unifyAggregates(aggregate2, target);
return result == null ? null : call.result(result);
}
}
public static Aggregation getRollup(Aggregation aggregation) {
// TODO: count rolls up using sum; etc.
return aggregation;
}
private static RexShuttle getRexShuttle(ProjectRel target) {
final Map<String, Integer> map = new HashMap<String, Integer>();
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);
}
};
}
/** Information about a node's parent and its position among its siblings. */
private static class Parentage {
final RelNode parent;
final int ordinal;
private Parentage(RelNode parent, int ordinal) {
this.parent = parent;
this.ordinal = ordinal;
}
}
}
// End SubstitutionVisitor.java