/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.planner.parseinfo;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.ConstantValueExpression;
import org.voltdb.expressions.ExpressionUtil;
import org.voltdb.expressions.TupleValueExpression;
import org.voltdb.planner.AccessPath;
import org.voltdb.types.ExpressionType;
import org.voltdb.types.JoinType;
/**
* JoinNode class captures the hierarchical data model of a given SQl join.
* Each node in the tree can be either a leaf representing a joined table (m_table != null)
* or a node representing the actual join (m_leftNode!=0 and m_rightNode!=0)
* filter expressions
*/
public abstract class JoinNode implements Cloneable {
// Node id. Must be unique within a given tree
protected int m_id;
// Join expression associated with this node
protected AbstractExpression m_joinExpr = null;
// Additional filter expression (WHERE) associated with this node
protected AbstractExpression m_whereExpr = null;
// Buckets for children expression classification
public final ArrayList<AbstractExpression> m_joinOuterList = new ArrayList<>();
public final ArrayList<AbstractExpression> m_joinInnerList = new ArrayList<>();
public final ArrayList<AbstractExpression> m_joinInnerOuterList = new ArrayList<>();
public final ArrayList<AbstractExpression> m_whereOuterList = new ArrayList<>();
public final ArrayList<AbstractExpression> m_whereInnerList = new ArrayList<>();
public final ArrayList<AbstractExpression> m_whereInnerOuterList = new ArrayList<>();
// All possible access paths for this node
public List<AccessPath> m_accessPaths = new ArrayList<>();
// Access path under the evaluation
public AccessPath m_currentAccessPath = null;
/**
* Construct an empty join node
*/
protected JoinNode(int id) {
m_id = id;
}
/**
* Deep clone
*/
@Override
abstract public Object clone();
public int getId() {
return m_id;
}
public void setId(int id) {
m_id = id;
}
@SuppressWarnings("static-method")
public JoinNode getLeftNode() {
return null;
}
@SuppressWarnings("static-method")
public JoinNode getRightNode() {
return null;
}
public AbstractExpression getJoinExpression() {
return m_joinExpr;
}
public void setJoinExpression(AbstractExpression expr) {
m_joinExpr = expr;
}
public AbstractExpression getWhereExpression() {
return m_whereExpr;
}
public void setWhereExpression(AbstractExpression expr) {
m_whereExpr = expr;
}
/// For debug purposes:
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
explain_recurse(sb, "");
return sb.toString();
}
public void explain_recurse(StringBuilder sb, String indent) {
// Node id. Must be unique within a given tree
sb.append(indent).append("JOIN NODE id: " + m_id).append("\n");
// Table
sb.append(indent).append("table alias: ").append(getTableAlias()).append("\n");
// Join expression associated with this node
if (m_joinExpr != null) {
sb.append(indent).append(m_joinExpr.explain("be explicit")).append("\n");
}
// Additional filter expression (WHERE) associated with this node
if (m_whereExpr != null) {
sb.append(indent).append(m_whereExpr.explain("be explicit")).append("\n");
}
// Buckets for children expression classification
explain_filter_list(sb, indent, "join outer:", m_joinOuterList);
explain_filter_list(sb, indent, "join inner:", m_joinInnerList);
explain_filter_list(sb, indent, "join inner outer:", m_joinInnerOuterList);
explain_filter_list(sb, indent, "where outer:", m_whereOuterList);
explain_filter_list(sb, indent, "where inner:", m_whereInnerList);
explain_filter_list(sb, indent, "where inner outer:", m_whereInnerOuterList);
}
protected static void explain_filter_list(StringBuilder sb, String indent, String label,
Collection<AbstractExpression> filterListMember) {
String prefix = label + "\n" + indent;
for (AbstractExpression filter : filterListMember) {
sb.append(prefix).append(filter.explain("be explicit")).append("\n");
prefix = indent;
}
}
/**
* Summarize the WHERE clause expressions of type COMPARE_EQUAL for the entire statement,
* that can be used to determine query statement partitioning.
* This is tricky in the case of JOIN filters because "partition_column = constant"
* does not exclude joined result rows from occurring on all partitions.
* This generally still requires a multi-partition plan.
* But a join filter of the form "outer.partition_column = inner.partition_column" DOES allow
* a multi-partition join to be executed in parallel on each partition -- each tuple on the OUTER
* side will either get properly matched OR get a null-padded tuple on its local partition and any
* differently-valued inner side rows that may exist on other partitions have no bearing on this.
* So ALL manner of where clause but ONLY inner-outer column=column JOIN clauses can
* influence partitioning.
*/
public HashMap<AbstractExpression, Set<AbstractExpression> > getAllEquivalenceFilters()
{
HashMap<AbstractExpression, Set<AbstractExpression> > equivalenceSet =
new HashMap< >();
ArrayDeque<JoinNode> joinNodes = new ArrayDeque<>();
// Iterate over the join nodes to collect their join and where equivalence filter expressions
joinNodes.add(this);
while ( ! joinNodes.isEmpty()) {
JoinNode joinNode = joinNodes.poll();
joinNode.collectEquivalenceFilters(equivalenceSet, joinNodes);
}
return equivalenceSet;
}
protected abstract void collectEquivalenceFilters(
HashMap<AbstractExpression, Set<AbstractExpression>> equivalenceSet,
ArrayDeque<JoinNode> joinNodes);
/**
* Collect all JOIN and WHERE expressions combined with AND for the entire tree.
*/
public AbstractExpression getAllFilters() {
ArrayDeque<JoinNode> joinNodes = new ArrayDeque<>();
ArrayDeque<AbstractExpression> in = new ArrayDeque<>();
ArrayDeque<AbstractExpression> out = new ArrayDeque<>();
// Iterate over the join nodes to collect their join and where expressions
joinNodes.add(this);
while (!joinNodes.isEmpty()) {
JoinNode joinNode = joinNodes.poll();
if (joinNode.m_joinExpr != null) {
in.add(joinNode.m_joinExpr);
}
if (joinNode.m_whereExpr != null) {
in.add(joinNode.m_whereExpr);
}
joinNode.queueChildren(joinNodes);
}
// this chunk of code breaks the code into a list of expression that
// all have to be true for the where clause to be true
AbstractExpression inExpr = null;
while ((inExpr = in.poll()) != null) {
if (inExpr.getExpressionType() == ExpressionType.CONJUNCTION_AND) {
in.add(inExpr.getLeft());
in.add(inExpr.getRight());
}
else {
out.add(inExpr);
}
}
return ExpressionUtil.combinePredicates(out);
}
protected void queueChildren(ArrayDeque<JoinNode> joinNodes) { }
/**
* Get the WHERE expression for a single-table statement.
*/
public AbstractExpression getSimpleFilterExpression()
{
if (m_whereExpr != null) {
if (m_joinExpr != null) {
return ExpressionUtil.combine(m_whereExpr, m_joinExpr);
}
return m_whereExpr;
}
return m_joinExpr;
}
/**
* Returns tables in the order they are joined in the tree by iterating the tree depth-first
*/
public List<JoinNode> generateLeafNodesJoinOrder() {
ArrayList<JoinNode> leafNodes = new ArrayList<>();
listNodesJoinOrderRecursive(leafNodes, false);
return leafNodes;
}
/**
* Returns tables in the order they are joined in the tree by iterating the tree depth-first
*/
public Collection<String> generateTableJoinOrder() {
List<JoinNode> leafNodes = generateLeafNodesJoinOrder();
Collection<String> tables = new ArrayList<>();
for (JoinNode node : leafNodes) {
tables.add(node.getTableAlias());
}
return tables;
}
@SuppressWarnings("static-method")
public String getTableAlias() {
return null;
}
/**
* Returns nodes in the order they are joined in the tree by iterating the tree depth-first
*/
public List<JoinNode> generateAllNodesJoinOrder() {
ArrayList<JoinNode> nodes = new ArrayList<>();
listNodesJoinOrderRecursive(nodes, true);
return nodes;
}
protected void listNodesJoinOrderRecursive(ArrayList<JoinNode> nodes, boolean appendBranchNodes) {
nodes.add(this);
}
/**
* Returns true if one of the tree nodes has outer join
*/
@SuppressWarnings("static-method")
public boolean hasOuterJoin() { return false; }
/**
* Returns a list of immediate sub-queries which are part of this query.
* @return List<AbstractParsedStmt> - list of sub-queries from this query
*/
public void extractSubQueries(List<StmtSubqueryScan> subQueries) { }
/**
* Split a join tree into one or more sub-trees. Each sub-tree has the same join type
* for all join nodes. The root of the child tree in the parent tree is replaced with a 'dummy' node
* which id is negated id of the child root node.
* @param tree - The join tree
* @return the list of sub-trees from the input tree
*/
public List<JoinNode> extractSubTrees() {
List<JoinNode> subTrees = new ArrayList<>();
// Extract the first sub-tree starting at the root
subTrees.add(this);
List<JoinNode> leafNodes = new ArrayList<>();
extractSubTree(leafNodes);
// Continue with the leafs
for (JoinNode leaf : leafNodes) {
subTrees.addAll(leaf.extractSubTrees());
}
return subTrees;
}
/**
* Starting from the root recurse to its children stopping at the first join node
* of the different type and discontinue the tree at this point by replacing the join node with
* the temporary node which id matches the join node id. This join node is the root of the next
* sub-tree.
* @param root - The root of the join tree
* @param leafNodes - the list of the root nodes of the next sub-trees
*/
protected void extractSubTree(List<JoinNode> leafNodes) { }
/**
* Reconstruct a join tree from the list of tables always appending the next node to the right.
*
* @param tableNodes the list of tables to build the tree from.
* @param JoinType the join type for all the joins
* @return The reconstructed tree
*/
public static JoinNode reconstructJoinTreeFromTableNodes(List<JoinNode> tableNodes, JoinType joinType) {
JoinNode root = null;
for (JoinNode leafNode : tableNodes) {
JoinNode node = leafNode.cloneWithoutFilters();
if (root == null) {
root = node;
} else {
// We only care about the root node id to be able to reconnect the sub-trees
// The intermediate node id can be anything. For the final root node its id
// will be set later to the original tree's root id
root = new BranchNode(-node.m_id, joinType, root, node);
}
}
return root;
}
@SuppressWarnings("static-method")
protected JoinNode cloneWithoutFilters() { assert(false); return null; }
/**
* Reconstruct a join tree from the list of sub-trees connecting the sub-trees in the order
* they appear in the list. The list of sub-trees must be initially obtained by calling the extractSubTrees
* method on the original tree.
*
* @param subTrees the list of sub trees.
* @return The reconstructed tree
*/
public static JoinNode reconstructJoinTreeFromSubTrees(List<JoinNode> subTrees) {
if (subTrees == null || subTrees.isEmpty()) {
return null;
}
// Reconstruct the tree. The first element is the first sub-tree and so on
JoinNode joinNode = subTrees.get(0);
for (int i = 1; i < subTrees.size(); ++i) {
JoinNode nextNode = subTrees.get(i);
boolean replaced = joinNode.replaceChild(nextNode);
// There must be a node in the current tree to be replaced
assert(replaced);
}
return joinNode;
}
protected boolean replaceChild(JoinNode node) {
// can't replace self
assert (Math.abs(m_id) != Math.abs(node.m_id));
return false;
}
public abstract void analyzeJoinExpressions(List<AbstractExpression> noneList);
/**
* Apply implied transitive constant filter to join expressions
* outer.partkey = ? and outer.partkey = inner.partkey is equivalent to
* outer.partkey = ? and inner.partkey = ?
* @param innerTableExprs inner table expressions
* @param outerTableExprs outer table expressions
* @param innerOuterTableExprs inner-outer tables expressions
*/
protected static void applyTransitiveEquivalence(List<AbstractExpression> outerTableExprs,
List<AbstractExpression> innerTableExprs,
List<AbstractExpression> innerOuterTableExprs)
{
List<AbstractExpression> simplifiedOuterExprs = applyTransitiveEquivalence(innerTableExprs, innerOuterTableExprs);
List<AbstractExpression> simplifiedInnerExprs = applyTransitiveEquivalence(outerTableExprs, innerOuterTableExprs);
outerTableExprs.addAll(simplifiedOuterExprs);
innerTableExprs.addAll(simplifiedInnerExprs);
}
private static List<AbstractExpression>
applyTransitiveEquivalence(List<AbstractExpression> singleTableExprs,
List<AbstractExpression> twoTableExprs)
{
ArrayList<AbstractExpression> simplifiedExprs = new ArrayList<>();
HashMap<AbstractExpression, Set<AbstractExpression> > eqMap1 =
new HashMap< >();
ExpressionUtil.collectPartitioningFilters(singleTableExprs, eqMap1);
for (AbstractExpression expr : twoTableExprs) {
if (! expr.isColumnEquivalenceFilter()) {
continue;
}
AbstractExpression leftExpr = expr.getLeft();
AbstractExpression rightExpr = expr.getRight();
assert(leftExpr instanceof TupleValueExpression && rightExpr instanceof TupleValueExpression);
Set<AbstractExpression> eqSet1 = eqMap1.get(leftExpr);
AbstractExpression singleExpr = leftExpr;
if (eqSet1 == null) {
eqSet1 = eqMap1.get(rightExpr);
if (eqSet1 == null) {
continue;
}
singleExpr = rightExpr;
}
for (AbstractExpression eqExpr : eqSet1) {
if (eqExpr instanceof ConstantValueExpression) {
if (singleExpr == leftExpr) {
expr.setLeft(eqExpr);
} else {
expr.setRight(eqExpr);
}
simplifiedExprs.add(expr);
// Having more than one const value for a single column doesn't make
// much sense, right?
break;
}
}
}
twoTableExprs.removeAll(simplifiedExprs);
return simplifiedExprs;
}
/**
* Split the input expression list into the three categories
* 1. TVE expressions with outer tables only
* 2. TVE expressions with inner tables only
* 3. TVE expressions with inner and outer tables
* The outer tables are the tables reachable from the outer node of the join
* The inner tables are the tables reachable from the inner node of the join
* @param exprList expression list to split
* @param outerTables outer table
* @param innerTable outer table
* @param outerList expressions with outer table only
* @param innerList expressions with inner table only
* @param innerOuterList with inner and outer tables
*/
protected static void classifyJoinExpressions(Collection<AbstractExpression> exprList,
Collection<String> outerTables, Collection<String> innerTables,
List<AbstractExpression> outerList, List<AbstractExpression> innerList,
List<AbstractExpression> innerOuterList, List<AbstractExpression> noneList)
{
HashSet<String> tableAliasSet = new HashSet<>();
HashSet<String> outerSet = new HashSet<>(outerTables);
HashSet<String> innerSet = new HashSet<>(innerTables);
for (AbstractExpression expr : exprList) {
tableAliasSet.clear();
getTablesForExpression(expr, tableAliasSet);
String tableAliases[] = tableAliasSet.toArray(new String[0]);
if (tableAliasSet.isEmpty()) {
noneList.add(expr);
} else {
boolean outer = false;
boolean inner = false;
for (String alias : tableAliases) {
outer = outer || outerSet.contains(alias);
inner = inner || innerSet.contains(alias);
}
if (outer && inner) {
innerOuterList.add(expr);
} else if (outer) {
outerList.add(expr);
} else if (inner) {
innerList.add(expr);
} else {
// can not be, right?
assert(false);
}
}
}
}
private static void getTablesForExpression(AbstractExpression expr, HashSet<String> tableAliasSet)
{
List<TupleValueExpression> tves = ExpressionUtil.getTupleValueExpressions(expr);
for (TupleValueExpression tupleExpr : tves) {
String tableAlias = tupleExpr.getTableAlias();
tableAliasSet.add(tableAlias);
}
}
@SuppressWarnings("static-method")
public StmtTableScan getTableScan() {
return null;
}
private String m_contentDeterminismMessage = null;
public String getContentDeterminismMessage() {
return m_contentDeterminismMessage;
}
public void updateContentDeterminismMessage(String msg) {
if (m_contentDeterminismMessage == null) {
m_contentDeterminismMessage = msg;
}
}
/**
* Returns if all the join operations within this join tree are inner joins.
* @return true or false.
*/
public boolean allInnerJoins() {
return true;
}
public void gatherJoinExpressions(List<AbstractExpression> checkExpressions) {
if (m_joinExpr != null) {
checkExpressions.add(m_joinExpr);
}
if (m_whereExpr != null) {
checkExpressions.addAll(checkExpressions);
}
}
}