com.hazelcast.org.apache.calcite.rel.rules.materialize.MaterializedViewRule Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in com.hazelcast.com.liance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.com.hazelcast.org.licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.org.apache.calcite.rel.rules.materialize;
import com.hazelcast.org.apache.calcite.plan.RelOptMaterialization;
import com.hazelcast.org.apache.calcite.plan.RelOptPlanner;
import com.hazelcast.org.apache.calcite.plan.RelOptPredicateList;
import com.hazelcast.org.apache.calcite.plan.RelOptRule;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleCall;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleOperand;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.plan.SubstitutionVisitor;
import com.hazelcast.org.apache.calcite.plan.hep.HepProgram;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.RelReferentialConstraint;
import com.hazelcast.org.apache.calcite.rel.core.Aggregate;
import com.hazelcast.org.apache.calcite.rel.core.Filter;
import com.hazelcast.org.apache.calcite.rel.core.Join;
import com.hazelcast.org.apache.calcite.rel.core.JoinRelType;
import com.hazelcast.org.apache.calcite.rel.core.Project;
import com.hazelcast.org.apache.calcite.rel.core.TableScan;
import com.hazelcast.org.apache.calcite.rel.metadata.RelMetadataQuery;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexExecutor;
import com.hazelcast.org.apache.calcite.rex.RexInputRef;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexShuttle;
import com.hazelcast.org.apache.calcite.rex.RexSimplify;
import com.hazelcast.org.apache.calcite.rex.RexTableInputRef;
import com.hazelcast.org.apache.calcite.rex.RexTableInputRef.RelTableRef;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.tools.RelBuilder;
import com.hazelcast.org.apache.calcite.tools.RelBuilderFactory;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.org.apache.calcite.util.graph.DefaultDirectedGraph;
import com.hazelcast.org.apache.calcite.util.graph.DefaultEdge;
import com.hazelcast.org.apache.calcite.util.graph.DirectedGraph;
import com.hazelcast.org.apache.calcite.util.mapping.Mapping;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ArrayListMultimap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.BiMap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.HashBiMap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableList;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableMap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Iterables;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Multimap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Planner rule that converts a {@link com.hazelcast.org.apache.calcite.rel.core.Project}
* followed by {@link com.hazelcast.org.apache.calcite.rel.core.Aggregate} or an
* {@link com.hazelcast.org.apache.calcite.rel.core.Aggregate} to a scan (and possibly
* other operations) over a materialized view.
*/
public abstract class MaterializedViewRule extends RelOptRule {
//~ Instance fields --------------------------------------------------------
/** Whether to generate rewritings containing union if the query results
* are contained within the view results. */
protected final boolean generateUnionRewriting;
/** If we generate union rewriting, we might want to pull up projections
* from the query itself to maximize rewriting opportunities. */
protected final HepProgram unionRewritingPullProgram;
/** Whether we should create the rewriting in the minimal subtree of plan
* operators. */
protected final boolean fastBailOut;
//~ Constructors -----------------------------------------------------------
/** Creates a AbstractMaterializedViewRule. */
protected MaterializedViewRule(RelOptRuleOperand operand,
RelBuilderFactory relBuilderFactory, String description,
boolean generateUnionRewriting, HepProgram unionRewritingPullProgram,
boolean fastBailOut) {
super(operand, relBuilderFactory, description);
this.generateUnionRewriting = generateUnionRewriting;
this.unionRewritingPullProgram = unionRewritingPullProgram;
this.fastBailOut = fastBailOut;
}
@Override public boolean matches(RelOptRuleCall call) {
return !call.getPlanner().getMaterializations().isEmpty();
}
/**
* Rewriting logic is based on "Optimizing Queries Using Materialized Views:
* A Practical, Scalable Solution" by Goldstein and Larson.
*
* On the query side, rules matches a Project-node chain or node, where node
* is either an Aggregate or a Join. Subplan rooted at the node operator must
* be com.hazelcast.com.osed of one or more of the following operators: TableScan, Project,
* Filter, and Join.
*
*
For each join MV, we need to check the following:
*
* - The plan rooted at the Join operator in the view produces all rows
* needed by the plan rooted at the Join operator in the query.
* - All columns required by com.hazelcast.com.ensating predicates, i.e., predicates that
* need to be enforced over the view, are available at the view output.
* - All output expressions can be com.hazelcast.com.uted from the output of the view.
* - All output rows occur with the correct duplication factor. We might
* rely on existing Unique-Key - Foreign-Key relationships to extract that
* information.
*
*
* In turn, for each aggregate MV, we need to check the following:
*
* - The plan rooted at the Aggregate operator in the view produces all rows
* needed by the plan rooted at the Aggregate operator in the query.
* - All columns required by com.hazelcast.com.ensating predicates, i.e., predicates that
* need to be enforced over the view, are available at the view output.
* - The grouping columns in the query are a subset of the grouping columns
* in the view.
* - All columns required to perform further grouping are available in the
* view output.
* - All columns required to com.hazelcast.com.ute output expressions are available in the
* view output.
*
*
* The rule contains multiple extensions com.hazelcast.com.ared to the original paper. One of
* them is the possibility of creating rewritings using Union operators, e.g., if
* the result of a query is partially contained in the materialized view.
*/
protected void perform(RelOptRuleCall call, Project topProject, RelNode node) {
final RexBuilder rexBuilder = node.getCluster().getRexBuilder();
final RelMetadataQuery mq = call.getMetadataQuery();
final RelOptPlanner planner = call.getPlanner();
final RexExecutor executor =
Util.first(planner.getExecutor(), RexUtil.EXECUTOR);
final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
final RexSimplify simplify =
new RexSimplify(rexBuilder, predicates, executor);
final List materializations =
planner.getMaterializations();
if (!materializations.isEmpty()) {
// 1. Explore query plan to recognize whether preconditions to
// try to generate a rewriting are met
if (!isValidPlan(topProject, node, mq)) {
return;
}
// 2. Initialize all query related auxiliary data structures
// that will be used throughout query rewriting process
// Generate query table references
final Set queryTableRefs = mq.getTableReferences(node);
if (queryTableRefs == null) {
// Bail out
return;
}
// Extract query predicates
final RelOptPredicateList queryPredicateList =
mq.getAllPredicates(node);
if (queryPredicateList == null) {
// Bail out
return;
}
final RexNode pred =
simplify.simplifyUnknownAsFalse(
RexUtil.com.hazelcast.com.oseConjunction(rexBuilder,
queryPredicateList.pulledUpPredicates));
final Pair queryPreds = splitPredicates(rexBuilder, pred);
// Extract query equivalence classes. An equivalence class is a set
// of columns in the query output that are known to be equal.
final EquivalenceClasses qEC = new EquivalenceClasses();
for (RexNode conj : RelOptUtil.conjunctions(queryPreds.left)) {
assert conj.isA(SqlKind.EQUALS);
RexCall equiCond = (RexCall) conj;
qEC.addEquivalenceClass(
(RexTableInputRef) equiCond.getOperands().get(0),
(RexTableInputRef) equiCond.getOperands().get(1));
}
// 3. We iterate through all applicable materializations trying to
// rewrite the given query
for (RelOptMaterialization materialization : materializations) {
RelNode view = materialization.tableRel;
Project topViewProject;
RelNode viewNode;
if (materialization.queryRel instanceof Project) {
topViewProject = (Project) materialization.queryRel;
viewNode = topViewProject.getInput();
} else {
topViewProject = null;
viewNode = materialization.queryRel;
}
// Extract view table references
final Set viewTableRefs = mq.getTableReferences(viewNode);
if (viewTableRefs == null) {
// Skip it
continue;
}
// Filter relevant materializations. Currently, we only check whether
// the materialization contains any table that is used by the query
// TODO: Filtering of relevant materializations can be improved to be more fine-grained.
boolean applicable = false;
for (RelTableRef tableRef : viewTableRefs) {
if (queryTableRefs.contains(tableRef)) {
applicable = true;
break;
}
}
if (!applicable) {
// Skip it
continue;
}
// 3.1. View checks before proceeding
if (!isValidPlan(topViewProject, viewNode, mq)) {
// Skip it
continue;
}
// 3.2. Initialize all query related auxiliary data structures
// that will be used throughout query rewriting process
// Extract view predicates
final RelOptPredicateList viewPredicateList =
mq.getAllPredicates(viewNode);
if (viewPredicateList == null) {
// Skip it
continue;
}
final RexNode viewPred = simplify.simplifyUnknownAsFalse(
RexUtil.com.hazelcast.com.oseConjunction(rexBuilder,
viewPredicateList.pulledUpPredicates));
final Pair viewPreds = splitPredicates(rexBuilder, viewPred);
// Extract view tables
MatchModality matchModality;
Multimap com.hazelcast.com.ensationEquiColumns =
ArrayListMultimap.create();
if (!queryTableRefs.equals(viewTableRefs)) {
// We try to com.hazelcast.com.ensate, e.g., for join queries it might be
// possible to join missing tables with view to com.hazelcast.com.ute result.
// Two supported cases: query tables are subset of view tables (we need to
// check whether they are cardinality-preserving joins), or view tables are
// subset of query tables (add additional tables through joins if possible)
if (viewTableRefs.containsAll(queryTableRefs)) {
matchModality = MatchModality.QUERY_PARTIAL;
final EquivalenceClasses vEC = new EquivalenceClasses();
for (RexNode conj : RelOptUtil.conjunctions(viewPreds.left)) {
assert conj.isA(SqlKind.EQUALS);
RexCall equiCond = (RexCall) conj;
vEC.addEquivalenceClass(
(RexTableInputRef) equiCond.getOperands().get(0),
(RexTableInputRef) equiCond.getOperands().get(1));
}
if (!com.hazelcast.com.ensatePartial(viewTableRefs, vEC, queryTableRefs,
com.hazelcast.com.ensationEquiColumns)) {
// Cannot rewrite, skip it
continue;
}
} else if (queryTableRefs.containsAll(viewTableRefs)) {
matchModality = MatchModality.VIEW_PARTIAL;
ViewPartialRewriting partialRewritingResult = com.hazelcast.com.ensateViewPartial(
call.builder(), rexBuilder, mq, view,
topProject, node, queryTableRefs, qEC,
topViewProject, viewNode, viewTableRefs);
if (partialRewritingResult == null) {
// Cannot rewrite, skip it
continue;
}
// Rewrite succeeded
view = partialRewritingResult.newView;
topViewProject = partialRewritingResult.newTopViewProject;
viewNode = partialRewritingResult.newViewNode;
} else {
// Skip it
continue;
}
} else {
matchModality = MatchModality.COMPLETE;
}
// 4. We map every table in the query to a table with the same qualified
// name (all query tables are contained in the view, thus this is equivalent
// to mapping every table in the query to a view table).
final Multimap multiMapTables = ArrayListMultimap.create();
for (RelTableRef queryTableRef1 : queryTableRefs) {
for (RelTableRef queryTableRef2 : queryTableRefs) {
if (queryTableRef1.getQualifiedName().equals(
queryTableRef2.getQualifiedName())) {
multiMapTables.put(queryTableRef1, queryTableRef2);
}
}
}
// If a table is used multiple times, we will create multiple mappings,
// and we will try to rewrite the query using each of the mappings.
// Then, we will try to map every source table (query) to a target
// table (view), and if we are successful, we will try to create
// com.hazelcast.com.ensation predicates to filter the view results further
// (if needed).
final List> flatListMappings =
generateTableMappings(multiMapTables);
for (BiMap queryToViewTableMapping : flatListMappings) {
// TableMapping : mapping query tables -> view tables
// 4.0. If com.hazelcast.com.ensation equivalence classes exist, we need to add
// the mapping to the query mapping
final EquivalenceClasses currQEC = EquivalenceClasses.copy(qEC);
if (matchModality == MatchModality.QUERY_PARTIAL) {
for (Entry e
: com.hazelcast.com.ensationEquiColumns.entries()) {
// Copy origin
RelTableRef queryTableRef = queryToViewTableMapping.inverse().get(
e.getKey().getTableRef());
RexTableInputRef queryColumnRef = RexTableInputRef.of(queryTableRef,
e.getKey().getIndex(), e.getKey().getType());
// Add to query equivalence classes and table mapping
currQEC.addEquivalenceClass(queryColumnRef, e.getValue());
queryToViewTableMapping.put(e.getValue().getTableRef(),
e.getValue().getTableRef()); // identity
}
}
// 4.1. Compute com.hazelcast.com.ensation predicates, i.e., predicates that need to be
// enforced over the view to retain query semantics. The resulting predicates
// are expressed using {@link RexTableInputRef} over the query.
// First, to establish relationship, we swap column references of the view
// predicates to point to query tables and com.hazelcast.com.ute equivalence classes.
final RexNode viewColumnsEquiPred = RexUtil.swapTableReferences(
rexBuilder, viewPreds.left, queryToViewTableMapping.inverse());
final EquivalenceClasses queryBasedVEC = new EquivalenceClasses();
for (RexNode conj : RelOptUtil.conjunctions(viewColumnsEquiPred)) {
assert conj.isA(SqlKind.EQUALS);
RexCall equiCond = (RexCall) conj;
queryBasedVEC.addEquivalenceClass(
(RexTableInputRef) equiCond.getOperands().get(0),
(RexTableInputRef) equiCond.getOperands().get(1));
}
Pair com.hazelcast.com.ensationPreds =
com.hazelcast.com.uteCompensationPredicates(rexBuilder, simplify,
currQEC, queryPreds, queryBasedVEC, viewPreds,
queryToViewTableMapping);
if (com.hazelcast.com.ensationPreds == null && generateUnionRewriting) {
// Attempt partial rewriting using union operator. This rewriting
// will read some data from the view and the rest of the data from
// the query com.hazelcast.com.utation. The resulting predicates are expressed
// using {@link RexTableInputRef} over the view.
com.hazelcast.com.ensationPreds = com.hazelcast.com.uteCompensationPredicates(rexBuilder, simplify,
queryBasedVEC, viewPreds, currQEC, queryPreds,
queryToViewTableMapping.inverse());
if (com.hazelcast.com.ensationPreds == null) {
// This was our last chance to use the view, skip it
continue;
}
RexNode com.hazelcast.com.ensationColumnsEquiPred = com.hazelcast.com.ensationPreds.left;
RexNode otherCompensationPred = com.hazelcast.com.ensationPreds.right;
assert !com.hazelcast.com.ensationColumnsEquiPred.isAlwaysTrue()
|| !otherCompensationPred.isAlwaysTrue();
// b. Generate union branch (query).
final RelNode unionInputQuery = rewriteQuery(call.builder(), rexBuilder,
simplify, mq, com.hazelcast.com.ensationColumnsEquiPred, otherCompensationPred,
topProject, node, queryToViewTableMapping, queryBasedVEC, currQEC);
if (unionInputQuery == null) {
// Skip it
continue;
}
// c. Generate union branch (view).
// We trigger the unifying method. This method will either create a Project
// or an Aggregate operator on top of the view. It will also com.hazelcast.com.ute the
// output expressions for the query.
final RelNode unionInputView = rewriteView(call.builder(), rexBuilder, simplify, mq,
matchModality, true, view, topProject, node, topViewProject, viewNode,
queryToViewTableMapping, currQEC);
if (unionInputView == null) {
// Skip it
continue;
}
// d. Generate final rewriting (union).
final RelNode result = createUnion(call.builder(), rexBuilder,
topProject, unionInputQuery, unionInputView);
if (result == null) {
// Skip it
continue;
}
call.transformTo(result);
} else if (com.hazelcast.com.ensationPreds != null) {
RexNode com.hazelcast.com.ensationColumnsEquiPred = com.hazelcast.com.ensationPreds.left;
RexNode otherCompensationPred = com.hazelcast.com.ensationPreds.right;
// a. Compute final com.hazelcast.com.ensation predicate.
if (!com.hazelcast.com.ensationColumnsEquiPred.isAlwaysTrue()
|| !otherCompensationPred.isAlwaysTrue()) {
// All columns required by com.hazelcast.com.ensating predicates must be contained
// in the view output (condition 2).
List viewExprs = topViewProject == null
? extractReferences(rexBuilder, view)
: topViewProject.getChildExps();
// For com.hazelcast.com.ensationColumnsEquiPred, we use the view equivalence classes,
// since we want to enforce the rest
if (!com.hazelcast.com.ensationColumnsEquiPred.isAlwaysTrue()) {
com.hazelcast.com.ensationColumnsEquiPred = rewriteExpression(rexBuilder, mq,
view, viewNode, viewExprs, queryToViewTableMapping.inverse(), queryBasedVEC,
false, com.hazelcast.com.ensationColumnsEquiPred);
if (com.hazelcast.com.ensationColumnsEquiPred == null) {
// Skip it
continue;
}
}
// For the rest, we use the query equivalence classes
if (!otherCompensationPred.isAlwaysTrue()) {
otherCompensationPred = rewriteExpression(rexBuilder, mq,
view, viewNode, viewExprs, queryToViewTableMapping.inverse(), currQEC,
true, otherCompensationPred);
if (otherCompensationPred == null) {
// Skip it
continue;
}
}
}
final RexNode viewCompensationPred =
RexUtil.com.hazelcast.com.oseConjunction(rexBuilder,
ImmutableList.of(com.hazelcast.com.ensationColumnsEquiPred,
otherCompensationPred));
// b. Generate final rewriting if possible.
// First, we add the com.hazelcast.com.ensation predicate (if any) on top of the view.
// Then, we trigger the unifying method. This method will either create a
// Project or an Aggregate operator on top of the view. It will also com.hazelcast.com.ute
// the output expressions for the query.
RelBuilder builder = call.builder().transform(c -> c.withPruneInputOfAggregate(false));
RelNode viewWithFilter;
if (!viewCompensationPred.isAlwaysTrue()) {
RexNode newPred =
simplify.simplifyUnknownAsFalse(viewCompensationPred);
viewWithFilter = builder.push(view).filter(newPred).build();
// No need to do anything if it's a leaf node.
if (viewWithFilter.getInputs().isEmpty()) {
call.transformTo(viewWithFilter);
return;
}
// We add (and push) the filter to the view plan before triggering the rewriting.
// This is useful in case some of the columns can be folded to same value after
// filter is added.
Pair pushedNodes =
pushFilterToOriginalViewPlan(builder, topViewProject, viewNode, newPred);
topViewProject = (Project) pushedNodes.left;
viewNode = pushedNodes.right;
} else {
viewWithFilter = builder.push(view).build();
}
final RelNode result = rewriteView(builder, rexBuilder, simplify, mq, matchModality,
false, viewWithFilter, topProject, node, topViewProject, viewNode,
queryToViewTableMapping, currQEC);
if (result == null) {
// Skip it
continue;
}
call.transformTo(result);
} // end else
}
}
}
}
protected abstract boolean isValidPlan(Project topProject, RelNode node,
RelMetadataQuery mq);
/**
* It checks whether the query can be rewritten using the view even though the
* query uses additional tables.
*
* Rules implementing the method should follow different approaches depending on the
* operators they rewrite.
*/
protected abstract ViewPartialRewriting com.hazelcast.com.ensateViewPartial(
RelBuilder relBuilder, RexBuilder rexBuilder, RelMetadataQuery mq, RelNode input,
Project topProject, RelNode node, Set queryTableRefs, EquivalenceClasses queryEC,
Project topViewProject, RelNode viewNode, Set viewTableRefs);
/**
* If the view will be used in a union rewriting, this method is responsible for
* rewriting the query branch of the union using the given com.hazelcast.com.ensation predicate.
*
* If a rewriting can be produced, we return that rewriting. If it cannot
* be produced, we will return null.
*/
protected abstract RelNode rewriteQuery(
RelBuilder relBuilder, RexBuilder rexBuilder, RexSimplify simplify, RelMetadataQuery mq,
RexNode com.hazelcast.com.ensationColumnsEquiPred, RexNode otherCompensationPred,
Project topProject, RelNode node,
BiMap viewToQueryTableMapping,
EquivalenceClasses viewEC, EquivalenceClasses queryEC);
/**
* If the view will be used in a union rewriting, this method is responsible for
* generating the union and any other operator needed on top of it, e.g., a Project
* operator.
*/
protected abstract RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
RelNode topProject, RelNode unionInputQuery, RelNode unionInputView);
/**
* Rewrites the query using the given view query.
*
* The input node is a Scan on the view table and possibly a com.hazelcast.com.ensation Filter
* on top. If a rewriting can be produced, we return that rewriting. If it cannot
* be produced, we will return null.
*/
protected abstract RelNode rewriteView(RelBuilder relBuilder, RexBuilder rexBuilder,
RexSimplify simplify, RelMetadataQuery mq, MatchModality matchModality,
boolean unionRewriting, RelNode input,
Project topProject, RelNode node,
Project topViewProject, RelNode viewNode,
BiMap queryToViewTableMapping,
EquivalenceClasses queryEC);
/**
* Once we create a com.hazelcast.com.ensation predicate, this method is responsible for pushing
* the resulting filter through the view nodes. This might be useful for rewritings
* containing Aggregate operators, as some of the grouping columns might be removed,
* which results in additional matching possibilities.
*
* The method will return a pair of nodes: the new top project on the left and
* the new node on the right.
*/
protected abstract Pair pushFilterToOriginalViewPlan(RelBuilder builder,
RelNode topViewProject, RelNode viewNode, RexNode cond);
//~ Methods ----------------------------------------------------------------
/**
* If the node is an Aggregate, it returns a list of references to the grouping columns.
* Otherwise, it returns a list of references to all columns in the node.
* The returned list is immutable.
*/
protected List extractReferences(RexBuilder rexBuilder, RelNode node) {
ImmutableList.Builder exprs = ImmutableList.builder();
if (node instanceof Aggregate) {
Aggregate aggregate = (Aggregate) node;
for (int i = 0; i < aggregate.getGroupCount(); i++) {
exprs.add(rexBuilder.makeInputRef(aggregate, i));
}
} else {
for (int i = 0; i < node.getRowType().getFieldCount(); i++) {
exprs.add(rexBuilder.makeInputRef(node, i));
}
}
return exprs.build();
}
/**
* It will flatten a multimap containing table references to table references,
* producing all possible com.hazelcast.com.inations of mappings. Each of the mappings will
* be bi-directional.
*/
protected List> generateTableMappings(
Multimap multiMapTables) {
if (multiMapTables.isEmpty()) {
return ImmutableList.of();
}
List> result =
ImmutableList.of(
HashBiMap.create());
for (Entry> e : multiMapTables.asMap().entrySet()) {
if (e.getValue().size() == 1) {
// Only one reference, we can just add it to every map
RelTableRef target = e.getValue().iterator().next();
for (BiMap m : result) {
m.put(e.getKey(), target);
}
continue;
}
// Multiple references: flatten
ImmutableList.Builder> newResult =
ImmutableList.builder();
for (RelTableRef target : e.getValue()) {
for (BiMap m : result) {
if (!m.containsValue(target)) {
final BiMap newM =
HashBiMap.create(m);
newM.put(e.getKey(), target);
newResult.add(newM);
}
}
}
result = newResult.build();
}
return result;
}
/** Currently we only support TableScan - Project - Filter - Inner Join */
protected boolean isValidRelNodePlan(RelNode node, RelMetadataQuery mq) {
final Multimap, RelNode> m =
mq.getNodeTypes(node);
if (m == null) {
return false;
}
for (Entry, Collection> e : m.asMap().entrySet()) {
Class c = e.getKey();
if (!TableScan.class.isAssignableFrom(c)
&& !Project.class.isAssignableFrom(c)
&& !Filter.class.isAssignableFrom(c)
&& (!Join.class.isAssignableFrom(c))) {
// Skip it
return false;
}
if (Join.class.isAssignableFrom(c)) {
for (RelNode n : e.getValue()) {
final Join join = (Join) n;
if (join.getJoinType() != JoinRelType.INNER && !join.isSemiJoin()) {
// Skip it
return false;
}
}
}
}
return true;
}
/**
* Classifies each of the predicates in the list into one of these two
* categories:
*
*
* - 1-l) column equality predicates, or
*
- 2-r) residual predicates, all the rest
*
*
* For each category, it creates the conjunction of the predicates. The
* result is an pair of RexNode objects corresponding to each category.
*/
protected Pair splitPredicates(
RexBuilder rexBuilder, RexNode pred) {
List equiColumnsPreds = new ArrayList<>();
List residualPreds = new ArrayList<>();
for (RexNode e : RelOptUtil.conjunctions(pred)) {
switch (e.getKind()) {
case EQUALS:
RexCall eqCall = (RexCall) e;
if (RexUtil.isReferenceOrAccess(eqCall.getOperands().get(0), false)
&& RexUtil.isReferenceOrAccess(eqCall.getOperands().get(1), false)) {
equiColumnsPreds.add(e);
} else {
residualPreds.add(e);
}
break;
default:
residualPreds.add(e);
}
}
return Pair.of(
RexUtil.com.hazelcast.com.oseConjunction(rexBuilder, equiColumnsPreds),
RexUtil.com.hazelcast.com.oseConjunction(rexBuilder, residualPreds));
}
/**
* It checks whether the target can be rewritten using the source even though the
* source uses additional tables. In order to do that, we need to double-check
* that every join that exists in the source and is not in the target is a
* cardinality-preserving join, i.e., it only appends columns to the row
* without changing its multiplicity. Thus, the join needs to be:
*
* - Equi-join
* - Between all columns in the keys
* - Foreign-key columns do not allow NULL values
* - Foreign-key
* - Unique-key
*
*
* If it can be rewritten, it returns true. Further, it inserts the missing equi-join
* predicates in the input {@code com.hazelcast.com.ensationEquiColumns} multimap if it is provided.
* If it cannot be rewritten, it returns false.
*/
protected boolean com.hazelcast.com.ensatePartial(
Set sourceTableRefs,
EquivalenceClasses sourceEC,
Set targetTableRefs,
Multimap com.hazelcast.com.ensationEquiColumns) {
// Create UK-FK graph with view tables
final DirectedGraph graph =
DefaultDirectedGraph.create(Edge::new);
final Multimap, RelTableRef> tableVNameToTableRefs =
ArrayListMultimap.create();
final Set extraTableRefs = new HashSet<>();
for (RelTableRef tRef : sourceTableRefs) {
// Add tables in view as vertices
graph.addVertex(tRef);
tableVNameToTableRefs.put(tRef.getQualifiedName(), tRef);
if (!targetTableRefs.contains(tRef)) {
// Add to extra tables if table is not part of the query
extraTableRefs.add(tRef);
}
}
for (RelTableRef tRef : graph.vertexSet()) {
// Add edges between tables
List constraints =
tRef.getTable().getReferentialConstraints();
for (RelReferentialConstraint constraint : constraints) {
Collection parentTableRefs =
tableVNameToTableRefs.get(constraint.getTargetQualifiedName());
for (RelTableRef parentTRef : parentTableRefs) {
boolean canBeRewritten = true;
Multimap equiColumns =
ArrayListMultimap.create();
for (int pos = 0; pos < constraint.getNumColumns(); pos++) {
int foreignKeyPos = constraint.getColumnPairs().get(pos).source;
RelDataType foreignKeyColumnType =
tRef.getTable().getRowType().getFieldList().get(foreignKeyPos).getType();
RexTableInputRef foreignKeyColumnRef =
RexTableInputRef.of(tRef, foreignKeyPos, foreignKeyColumnType);
int uniqueKeyPos = constraint.getColumnPairs().get(pos).target;
RexTableInputRef uniqueKeyColumnRef = RexTableInputRef.of(parentTRef, uniqueKeyPos,
parentTRef.getTable().getRowType().getFieldList().get(uniqueKeyPos).getType());
if (!foreignKeyColumnType.isNullable()
&& sourceEC.getEquivalenceClassesMap().containsKey(uniqueKeyColumnRef)
&& sourceEC.getEquivalenceClassesMap().get(uniqueKeyColumnRef)
.contains(foreignKeyColumnRef)) {
equiColumns.put(foreignKeyColumnRef, uniqueKeyColumnRef);
} else {
canBeRewritten = false;
break;
}
}
if (canBeRewritten) {
// Add edge FK -> UK
Edge edge = graph.getEdge(tRef, parentTRef);
if (edge == null) {
edge = graph.addEdge(tRef, parentTRef);
}
edge.equiColumns.putAll(equiColumns);
}
}
}
}
// Try to eliminate tables from graph: if we can do it, it means extra tables in
// view are cardinality-preserving joins
boolean done = false;
do {
List nodesToRemove = new ArrayList<>();
for (RelTableRef tRef : graph.vertexSet()) {
if (graph.getInwardEdges(tRef).size() == 1
&& graph.getOutwardEdges(tRef).isEmpty()) {
// UK-FK join
nodesToRemove.add(tRef);
if (com.hazelcast.com.ensationEquiColumns != null && extraTableRefs.contains(tRef)) {
// We need to add to com.hazelcast.com.ensation columns as the table is not present in the query
com.hazelcast.com.ensationEquiColumns.putAll(graph.getInwardEdges(tRef).get(0).equiColumns);
}
}
}
if (!nodesToRemove.isEmpty()) {
graph.removeAllVertices(nodesToRemove);
} else {
done = true;
}
} while (!done);
// After removing them, we check whether all the remaining tables in the graph
// are tables present in the query: if they are, we can try to rewrite
if (!Collections.disjoint(graph.vertexSet(), extraTableRefs)) {
return false;
}
return true;
}
/**
* We check whether the predicates in the source are contained in the predicates
* in the target. The method treats separately the equi-column predicates, the
* range predicates, and the rest of predicates.
*
* If the containment is confirmed, we produce com.hazelcast.com.ensation predicates that
* need to be added to the target to produce the results in the source. Thus,
* if source and target expressions are equivalent, those predicates will be the
* true constant.
*
*
In turn, if containment cannot be confirmed, the method returns null.
*/
protected Pair com.hazelcast.com.uteCompensationPredicates(
RexBuilder rexBuilder,
RexSimplify simplify,
EquivalenceClasses sourceEC,
Pair sourcePreds,
EquivalenceClasses targetEC,
Pair targetPreds,
BiMap sourceToTargetTableMapping) {
final RexNode com.hazelcast.com.ensationColumnsEquiPred;
final RexNode com.hazelcast.com.ensationPred;
// 1. Establish relationship between source and target equivalence classes.
// If every target equivalence class is not a subset of a source
// equivalence class, we bail out.
com.hazelcast.com.ensationColumnsEquiPred = generateEquivalenceClasses(
rexBuilder, sourceEC, targetEC);
if (com.hazelcast.com.ensationColumnsEquiPred == null) {
// Cannot rewrite
return null;
}
// 2. We check that that residual predicates of the source are satisfied within the target.
// Compute com.hazelcast.com.ensating predicates.
final RexNode queryPred = RexUtil.swapColumnReferences(
rexBuilder, sourcePreds.right, sourceEC.getEquivalenceClassesMap());
final RexNode viewPred = RexUtil.swapTableColumnReferences(
rexBuilder, targetPreds.right, sourceToTargetTableMapping.inverse(),
sourceEC.getEquivalenceClassesMap());
com.hazelcast.com.ensationPred = SubstitutionVisitor.splitFilter(
simplify, queryPred, viewPred);
if (com.hazelcast.com.ensationPred == null) {
// Cannot rewrite
return null;
}
return Pair.of(com.hazelcast.com.ensationColumnsEquiPred, com.hazelcast.com.ensationPred);
}
/**
* Given the equi-column predicates of the source and the target and the
* com.hazelcast.com.uted equivalence classes, it extracts possible mappings between
* the equivalence classes.
*
* If there is no mapping, it returns null. If there is a exact match,
* it will return a com.hazelcast.com.ensation predicate that evaluates to true.
* Finally, if a com.hazelcast.com.ensation predicate needs to be enforced on top of
* the target to make the equivalences classes match, it returns that
* com.hazelcast.com.ensation predicate.
*/
protected RexNode generateEquivalenceClasses(RexBuilder rexBuilder,
EquivalenceClasses sourceEC, EquivalenceClasses targetEC) {
if (sourceEC.getEquivalenceClasses().isEmpty() && targetEC.getEquivalenceClasses().isEmpty()) {
// No column equality predicates in query and view
// Empty mapping and com.hazelcast.com.ensation predicate
return rexBuilder.makeLiteral(true);
}
if (sourceEC.getEquivalenceClasses().isEmpty() && !targetEC.getEquivalenceClasses().isEmpty()) {
// No column equality predicates in source, but column equality predicates in target
return null;
}
final List> sourceEquivalenceClasses = sourceEC.getEquivalenceClasses();
final List> targetEquivalenceClasses = targetEC.getEquivalenceClasses();
final Multimap mapping = extractPossibleMapping(
sourceEquivalenceClasses, targetEquivalenceClasses);
if (mapping == null) {
// Did not find mapping between the equivalence classes,
// bail out
return null;
}
// Create the com.hazelcast.com.ensation predicate
RexNode com.hazelcast.com.ensationPredicate = rexBuilder.makeLiteral(true);
for (int i = 0; i < sourceEquivalenceClasses.size(); i++) {
if (!mapping.containsKey(i)) {
// Add all predicates
Iterator it = sourceEquivalenceClasses.get(i).iterator();
RexTableInputRef e0 = it.next();
while (it.hasNext()) {
RexNode equals = rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
e0, it.next());
com.hazelcast.com.ensationPredicate = rexBuilder.makeCall(SqlStdOperatorTable.AND,
com.hazelcast.com.ensationPredicate, equals);
}
} else {
// Add only predicates that are not there
for (int j : mapping.get(i)) {
Set difference = new HashSet<>(
sourceEquivalenceClasses.get(i));
difference.removeAll(targetEquivalenceClasses.get(j));
for (RexTableInputRef e : difference) {
RexNode equals = rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
e, targetEquivalenceClasses.get(j).iterator().next());
com.hazelcast.com.ensationPredicate = rexBuilder.makeCall(SqlStdOperatorTable.AND,
com.hazelcast.com.ensationPredicate, equals);
}
}
}
}
return com.hazelcast.com.ensationPredicate;
}
/**
* Given the source and target equivalence classes, it extracts the possible mappings
* from each source equivalence class to each target equivalence class.
*
* If any of the source equivalence classes cannot be mapped to a target equivalence
* class, it returns null.
*/
protected Multimap extractPossibleMapping(
List> sourceEquivalenceClasses,
List> targetEquivalenceClasses) {
Multimap mapping = ArrayListMultimap.create();
for (int i = 0; i < targetEquivalenceClasses.size(); i++) {
boolean foundQueryEquivalenceClass = false;
final Set viewEquivalenceClass = targetEquivalenceClasses.get(i);
for (int j = 0; j < sourceEquivalenceClasses.size(); j++) {
final Set queryEquivalenceClass = sourceEquivalenceClasses.get(j);
if (queryEquivalenceClass.containsAll(viewEquivalenceClass)) {
mapping.put(j, i);
foundQueryEquivalenceClass = true;
break;
}
} // end for
if (!foundQueryEquivalenceClass) {
// Target equivalence class not found in source equivalence class
return null;
}
} // end for
return mapping;
}
/**
* First, the method takes the node expressions {@code nodeExprs} and swaps the table
* and column references using the table mapping and the equivalence classes.
* If {@code swapTableColumn} is true, it swaps the table reference and then the column reference,
* otherwise it swaps the column reference and then the table reference.
*
* Then, the method will rewrite the input expression {@code exprToRewrite}, replacing the
* {@link RexTableInputRef} by references to the positions in {@code nodeExprs}.
*
*
The method will return the rewritten expression. If any of the expressions in the input
* expression cannot be mapped, it will return null.
*/
protected RexNode rewriteExpression(
RexBuilder rexBuilder,
RelMetadataQuery mq,
RelNode targetNode,
RelNode node,
List nodeExprs,
BiMap tableMapping,
EquivalenceClasses ec,
boolean swapTableColumn,
RexNode exprToRewrite) {
List rewrittenExprs = rewriteExpressions(rexBuilder, mq, targetNode, node, nodeExprs,
tableMapping, ec, swapTableColumn, ImmutableList.of(exprToRewrite));
if (rewrittenExprs == null) {
return null;
}
assert rewrittenExprs.size() == 1;
return rewrittenExprs.get(0);
}
/**
* First, the method takes the node expressions {@code nodeExprs} and swaps the table
* and column references using the table mapping and the equivalence classes.
* If {@code swapTableColumn} is true, it swaps the table reference and then the column reference,
* otherwise it swaps the column reference and then the table reference.
*
* Then, the method will rewrite the input expressions {@code exprsToRewrite}, replacing the
* {@link RexTableInputRef} by references to the positions in {@code nodeExprs}.
*
*
The method will return the rewritten expressions. If any of the subexpressions in the input
* expressions cannot be mapped, it will return null.
*/
protected List rewriteExpressions(
RexBuilder rexBuilder,
RelMetadataQuery mq,
RelNode targetNode,
RelNode node,
List nodeExprs,
BiMap tableMapping,
EquivalenceClasses ec,
boolean swapTableColumn,
List exprsToRewrite) {
NodeLineage nodeLineage;
if (swapTableColumn) {
nodeLineage = generateSwapTableColumnReferencesLineage(rexBuilder, mq, node,
tableMapping, ec, nodeExprs);
} else {
nodeLineage = generateSwapColumnTableReferencesLineage(rexBuilder, mq, node,
tableMapping, ec, nodeExprs);
}
List rewrittenExprs = new ArrayList<>(exprsToRewrite.size());
for (RexNode exprToRewrite : exprsToRewrite) {
RexNode rewrittenExpr = replaceWithOriginalReferences(
rexBuilder, targetNode, nodeLineage, exprToRewrite);
if (RexUtil.containsTableInputRef(rewrittenExpr) != null) {
// Some expressions were not present in view output
return null;
}
rewrittenExprs.add(rewrittenExpr);
}
return rewrittenExprs;
}
/**
* It swaps the table references and then the column references of the input
* expressions using the table mapping and the equivalence classes.
*/
protected NodeLineage generateSwapTableColumnReferencesLineage(
RexBuilder rexBuilder,
RelMetadataQuery mq,
RelNode node,
BiMap tableMapping,
EquivalenceClasses ec,
List nodeExprs) {
final Map exprsLineage = new HashMap<>();
final Map exprsLineageLosslessCasts = new HashMap<>();
for (int i = 0; i < nodeExprs.size(); i++) {
final Set s = mq.getExpressionLineage(node, nodeExprs.get(i));
if (s == null) {
// Next expression
continue;
}
// We only support project - filter - join, thus it should map to
// a single expression
assert s.size() == 1;
// Rewrite expr. First we swap the table references following the table
// mapping, then we take first element from the corresponding equivalence class
final RexNode e = RexUtil.swapTableColumnReferences(rexBuilder,
s.iterator().next(), tableMapping, ec.getEquivalenceClassesMap());
exprsLineage.put(e, i);
if (RexUtil.isLosslessCast(e)) {
exprsLineageLosslessCasts.put(((RexCall) e).getOperands().get(0), i);
}
}
return new NodeLineage(exprsLineage, exprsLineageLosslessCasts);
}
/**
* It swaps the column references and then the table references of the input
* expressions using the equivalence classes and the table mapping.
*/
protected NodeLineage generateSwapColumnTableReferencesLineage(
RexBuilder rexBuilder,
RelMetadataQuery mq,
RelNode node,
BiMap tableMapping,
EquivalenceClasses ec,
List nodeExprs) {
final Map exprsLineage = new HashMap<>();
final Map exprsLineageLosslessCasts = new HashMap<>();
for (int i = 0; i < nodeExprs.size(); i++) {
final Set s = mq.getExpressionLineage(node, nodeExprs.get(i));
if (s == null) {
// Next expression
continue;
}
// We only support project - filter - join, thus it should map to
// a single expression
final RexNode node2 = Iterables.getOnlyElement(s);
// Rewrite expr. First we take first element from the corresponding equivalence class,
// then we swap the table references following the table mapping
final RexNode e = RexUtil.swapColumnTableReferences(rexBuilder, node2,
ec.getEquivalenceClassesMap(), tableMapping);
exprsLineage.put(e, i);
if (RexUtil.isLosslessCast(e)) {
exprsLineageLosslessCasts.put(((RexCall) e).getOperands().get(0), i);
}
}
return new NodeLineage(exprsLineage, exprsLineageLosslessCasts);
}
/**
* Given the input expression, it will replace (sub)expressions when possible
* using the content of the mapping. In particular, the mapping contains the
* digest of the expression and the index that the replacement input ref should
* point to.
*/
protected RexNode replaceWithOriginalReferences(final RexBuilder rexBuilder,
final RelNode node, final NodeLineage nodeLineage, final RexNode exprToRewrite) {
// Currently we allow the following:
// 1) com.hazelcast.com.ensation pred can be directly map to expression
// 2) all references in com.hazelcast.com.ensation pred can be map to expressions
// We support bypassing lossless casts.
RexShuttle visitor =
new RexShuttle() {
@Override public RexNode visitCall(RexCall call) {
RexNode rw = replace(call);
return rw != null ? rw : super.visitCall(call);
}
@Override public RexNode visitTableInputRef(RexTableInputRef inputRef) {
RexNode rw = replace(inputRef);
return rw != null ? rw : super.visitTableInputRef(inputRef);
}
private RexNode replace(RexNode e) {
Integer pos = nodeLineage.exprsLineage.get(e);
if (pos != null) {
// Found it
return rexBuilder.makeInputRef(node, pos);
}
pos = nodeLineage.exprsLineageLosslessCasts.get(e);
if (pos != null) {
// Found it
return rexBuilder.makeCast(
e.getType(), rexBuilder.makeInputRef(node, pos));
}
return null;
}
};
return visitor.apply(exprToRewrite);
}
/**
* Replaces all the input references by the position in the
* input column set. If a reference index cannot be found in
* the input set, then we return null.
*/
protected RexNode shuttleReferences(final RexBuilder rexBuilder,
final RexNode node, final Mapping mapping) {
try {
RexShuttle visitor =
new RexShuttle() {
@Override public RexNode visitInputRef(RexInputRef inputRef) {
int pos = mapping.getTargetOpt(inputRef.getIndex());
if (pos != -1) {
// Found it
return rexBuilder.makeInputRef(inputRef.getType(), pos);
}
throw Util.FoundOne.NULL;
}
};
return visitor.apply(node);
} catch (Util.FoundOne ex) {
Util.swallow(ex, null);
return null;
}
}
/**
* Replaces all the possible sub-expressions by input references
* to the input node.
*/
protected RexNode shuttleReferences(final RexBuilder rexBuilder,
final RexNode expr, final Multimap exprsLineage) {
return shuttleReferences(rexBuilder, expr,
exprsLineage, null, null);
}
/**
* Replaces all the possible sub-expressions by input references
* to the input node. If available, it uses the rewriting mapping
* to change the position to reference. Takes the reference type
* from the input node.
*/
protected RexNode shuttleReferences(final RexBuilder rexBuilder,
final RexNode expr, final Multimap exprsLineage,
final RelNode node, final Multimap rewritingMapping) {
try {
RexShuttle visitor =
new RexShuttle() {
@Override public RexNode visitTableInputRef(RexTableInputRef ref) {
Collection c = exprsLineage.get(ref);
if (c.isEmpty()) {
// Cannot map expression
throw Util.FoundOne.NULL;
}
int pos = c.iterator().next();
if (rewritingMapping != null) {
if (!rewritingMapping.containsKey(pos)) {
// Cannot map expression
throw Util.FoundOne.NULL;
}
pos = rewritingMapping.get(pos).iterator().next();
}
if (node != null) {
return rexBuilder.makeInputRef(node, pos);
}
return rexBuilder.makeInputRef(ref.getType(), pos);
}
@Override public RexNode visitInputRef(RexInputRef inputRef) {
Collection c = exprsLineage.get(inputRef);
if (c.isEmpty()) {
// Cannot map expression
throw Util.FoundOne.NULL;
}
int pos = c.iterator().next();
if (rewritingMapping != null) {
if (!rewritingMapping.containsKey(pos)) {
// Cannot map expression
throw Util.FoundOne.NULL;
}
pos = rewritingMapping.get(pos).iterator().next();
}
if (node != null) {
return rexBuilder.makeInputRef(node, pos);
}
return rexBuilder.makeInputRef(inputRef.getType(), pos);
}
@Override public RexNode visitCall(final RexCall call) {
Collection c = exprsLineage.get(call);
if (c.isEmpty()) {
// Cannot map expression
return super.visitCall(call);
}
int pos = c.iterator().next();
if (rewritingMapping != null) {
if (!rewritingMapping.containsKey(pos)) {
// Cannot map expression
return super.visitCall(call);
}
pos = rewritingMapping.get(pos).iterator().next();
}
if (node != null) {
return rexBuilder.makeInputRef(node, pos);
}
return rexBuilder.makeInputRef(call.getType(), pos);
}
};
return visitor.apply(expr);
} catch (Util.FoundOne ex) {
Util.swallow(ex, null);
return null;
}
}
/**
* Class representing an equivalence class, i.e., a set of equivalent columns
*/
protected static class EquivalenceClasses {
private final Map> nodeToEquivalenceClass;
private Map> cacheEquivalenceClassesMap;
private List> cacheEquivalenceClasses;
protected EquivalenceClasses() {
nodeToEquivalenceClass = new HashMap<>();
cacheEquivalenceClassesMap = ImmutableMap.of();
cacheEquivalenceClasses = ImmutableList.of();
}
protected void addEquivalenceClass(RexTableInputRef p1, RexTableInputRef p2) {
// Clear cache
cacheEquivalenceClassesMap = null;
cacheEquivalenceClasses = null;
Set c1 = nodeToEquivalenceClass.get(p1);
Set c2 = nodeToEquivalenceClass.get(p2);
if (c1 != null && c2 != null) {
// Both present, we need to merge
if (c1.size() < c2.size()) {
// We swap them to merge
Set c2Temp = c2;
c2 = c1;
c1 = c2Temp;
}
for (RexTableInputRef newRef : c2) {
c1.add(newRef);
nodeToEquivalenceClass.put(newRef, c1);
}
} else if (c1 != null) {
// p1 present, we need to merge into it
c1.add(p2);
nodeToEquivalenceClass.put(p2, c1);
} else if (c2 != null) {
// p2 present, we need to merge into it
c2.add(p1);
nodeToEquivalenceClass.put(p1, c2);
} else {
// None are present, add to same equivalence class
Set equivalenceClass = new LinkedHashSet<>();
equivalenceClass.add(p1);
equivalenceClass.add(p2);
nodeToEquivalenceClass.put(p1, equivalenceClass);
nodeToEquivalenceClass.put(p2, equivalenceClass);
}
}
protected Map> getEquivalenceClassesMap() {
if (cacheEquivalenceClassesMap == null) {
cacheEquivalenceClassesMap = ImmutableMap.copyOf(nodeToEquivalenceClass);
}
return cacheEquivalenceClassesMap;
}
protected List> getEquivalenceClasses() {
if (cacheEquivalenceClasses == null) {
Set visited = new HashSet<>();
ImmutableList.Builder> builder =
ImmutableList.builder();
for (Set set : nodeToEquivalenceClass.values()) {
if (Collections.disjoint(visited, set)) {
builder.add(set);
visited.addAll(set);
}
}
cacheEquivalenceClasses = builder.build();
}
return cacheEquivalenceClasses;
}
protected static EquivalenceClasses copy(EquivalenceClasses ec) {
final EquivalenceClasses newEc = new EquivalenceClasses();
for (Entry> e
: ec.nodeToEquivalenceClass.entrySet()) {
newEc.nodeToEquivalenceClass.put(
e.getKey(), Sets.newLinkedHashSet(e.getValue()));
}
newEc.cacheEquivalenceClassesMap = null;
newEc.cacheEquivalenceClasses = null;
return newEc;
}
}
/** Expression lineage details. */
protected static class NodeLineage {
private final Map exprsLineage;
private final Map exprsLineageLosslessCasts;
private NodeLineage(Map exprsLineage,
Map exprsLineageLosslessCasts) {
this.exprsLineage = ImmutableMap.copyOf(exprsLineage);
this.exprsLineageLosslessCasts =
ImmutableMap.copyOf(exprsLineageLosslessCasts);
}
}
/** Edge for graph */
protected static class Edge extends DefaultEdge {
final Multimap equiColumns =
ArrayListMultimap.create();
Edge(RelTableRef source, RelTableRef target) {
super(source, target);
}
public String toString() {
return "{" + source + " -> " + target + "}";
}
}
/** View partitioning result */
protected static class ViewPartialRewriting {
private final RelNode newView;
private final Project newTopViewProject;
private final RelNode newViewNode;
private ViewPartialRewriting(RelNode newView, Project newTopViewProject, RelNode newViewNode) {
this.newView = newView;
this.newTopViewProject = newTopViewProject;
this.newViewNode = newViewNode;
}
protected static ViewPartialRewriting of(
RelNode newView, Project newTopViewProject, RelNode newViewNode) {
return new ViewPartialRewriting(newView, newTopViewProject, newViewNode);
}
}
/** Complete, view partial, or query partial. */
protected enum MatchModality {
COMPLETE,
VIEW_PARTIAL,
QUERY_PARTIAL
}
}