/* 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.plannodes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json_voltpatches.JSONArray;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.json_voltpatches.JSONString;
import org.json_voltpatches.JSONStringer;
import org.voltdb.catalog.Database;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.AbstractSubqueryExpression;
import org.voltdb.types.PlanNodeType;
/**
*
*/
public class PlanNodeTree implements JSONString {
private static class Members {
private static final String PLAN_NODES = "PLAN_NODES";
private static final String PLAN_NODES_LISTS = "PLAN_NODES_LISTS";
private static final String STATEMENT_ID = "STATEMENT_ID";
}
// Subquery ID / subquery plan node list map. The top level statement always has id = 0
protected final Map<Integer, List<AbstractPlanNode>> m_planNodesListMap = new HashMap<Integer, List<AbstractPlanNode>>();
public PlanNodeTree() {
}
public PlanNodeTree(AbstractPlanNode root_node) {
try {
List<AbstractPlanNode> nodeList = new ArrayList<AbstractPlanNode>();
m_planNodesListMap.put(0, nodeList);
constructTree(nodeList, root_node);
}
catch (Exception e) {
e.printStackTrace();
}
}
public AbstractPlanNode getRootPlanNode() {
assert(!m_planNodesListMap.isEmpty() && !m_planNodesListMap.get(0).isEmpty());
return m_planNodesListMap.get(0).get(0);
}
private boolean constructTree(List<AbstractPlanNode> planNodes, AbstractPlanNode node) throws Exception {
planNodes.add(node);
extractSubqueries(node);
for (int i = 0; i < node.getChildCount(); i++) {
AbstractPlanNode child = node.getChild(i);
if (!constructTree(planNodes, child)) {
return false;
}
}
return true;
}
@Override
public String toJSONString() {
JSONStringer stringer = new JSONStringer();
try {
stringer.object();
toJSONString(stringer);
stringer.endObject();
}
catch (JSONException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return stringer.toString();
}
public void toJSONString(JSONStringer stringer) throws JSONException {
if (m_planNodesListMap.size() == 1) {
stringer.key(Members.PLAN_NODES).array(m_planNodesListMap.get(0));
}
else {
stringer.key(Members.PLAN_NODES_LISTS).array();
for (Map.Entry<Integer, List<AbstractPlanNode>> planNodes : m_planNodesListMap.entrySet()) {
stringer.object();
stringer.keySymbolValuePair(Members.STATEMENT_ID, planNodes.getKey());
stringer.key(Members.PLAN_NODES).array(planNodes.getValue());
stringer.endObject();
}
stringer.endArray(); //end entries
}
}
public List<AbstractPlanNode> getNodeList() {
return m_planNodesListMap.get(0);
}
/**
* Load json plan. The plan must have either "PLAN_NODE" array in case of a statement without
* subqueries or "PLAN_NODES_LISTS" array of "PLAN_NODE" arrays for each sub statement.
* @param jobj
* @param db
* @throws JSONException
*/
public void loadFromJSONPlan(JSONObject jobj, Database db) throws JSONException {
if (jobj.has(Members.PLAN_NODES_LISTS)) {
JSONArray jplanNodesArray = jobj.getJSONArray(Members.PLAN_NODES_LISTS);
for (int i = 0; i < jplanNodesArray.length(); ++i) {
JSONObject jplanNodesObj = jplanNodesArray.getJSONObject(i);
JSONArray jplanNodes = jplanNodesObj.getJSONArray(Members.PLAN_NODES);
int stmtId = jplanNodesObj.getInt(Members.STATEMENT_ID);
loadPlanNodesFromJSONArrays(stmtId, jplanNodes, db);
}
}
else {
// There is only one statement in the plan. Its id is set to 0 by default
int stmtId = 0;
JSONArray jplanNodes = jobj.getJSONArray(Members.PLAN_NODES);
loadPlanNodesFromJSONArrays(stmtId, jplanNodes, db);
}
// Connect the parent and child statements
for (List<AbstractPlanNode> nextPlanNodes : m_planNodesListMap.values()) {
for (AbstractPlanNode node : nextPlanNodes) {
findPlanNodeWithPredicate(node);
}
}
}
/**
* Scan node, join node can have predicate, so does the Aggregate node (Having clause).
*/
private void findPlanNodeWithPredicate(AbstractPlanNode node) {
NodeSchema outputSchema = node.getOutputSchema();
if (outputSchema != null) {
for (SchemaColumn col : outputSchema.getColumns()) {
connectPredicateStmt(col.getExpression());
}
}
if (node instanceof AbstractScanPlanNode) {
AbstractScanPlanNode scanNode = (AbstractScanPlanNode)node;
connectPredicateStmt(scanNode.getPredicate());
} else if (node instanceof AbstractJoinPlanNode) {
AbstractJoinPlanNode joinNode = (AbstractJoinPlanNode)node;
connectPredicateStmt(joinNode.getPreJoinPredicate());
connectPredicateStmt(joinNode.getJoinPredicate());
connectPredicateStmt(joinNode.getWherePredicate());
} else if (node instanceof AggregatePlanNode) {
AggregatePlanNode aggNode = (AggregatePlanNode)node;
connectPredicateStmt(aggNode.getPostPredicate());
}
for (AbstractPlanNode inlineNode: node.getInlinePlanNodes().values()) {
findPlanNodeWithPredicate(inlineNode);
}
}
private void connectPredicateStmt(AbstractExpression predicate) {
if (predicate == null) {
return;
}
List<AbstractExpression> subquerysExprs = predicate.findAllSubquerySubexpressions();
for (AbstractExpression expr : subquerysExprs) {
assert(expr instanceof AbstractSubqueryExpression);
AbstractSubqueryExpression subqueryExpr = (AbstractSubqueryExpression) expr;
int subqueryId = subqueryExpr.getSubqueryId();
int subqueryNodeId = subqueryExpr.getSubqueryNodeId();
List<AbstractPlanNode> subqueryNodes = m_planNodesListMap.get(subqueryId);
if (subqueryNodes == null) {
return;
}
AbstractPlanNode subqueryNode = getNodeofId(subqueryNodeId, subqueryNodes);
assert(subqueryNode != null);
subqueryExpr.setSubqueryNode(subqueryNode);
}
}
/**
* Load plan nodes from the "PLAN_NODE" array. It is assumed that
* these nodes are from the main statement and not from the substatement
* @param jArray - PLAN_NODES
* @param db
* @throws JSONException
*/
public void loadPlanNodesFromJSONArrays(JSONArray jArray, Database db) {
loadPlanNodesFromJSONArrays(0, jArray, db);
}
/**
* Load plan nodes from the "PLAN_NODE" array. All the nodes are from
* a substatement with the id = stmtId
* @param stmtId
* @param jArray - PLAN_NODES
* @param db
* @throws JSONException
*/
private void loadPlanNodesFromJSONArrays(int stmtId, JSONArray jArray, Database db) {
List<AbstractPlanNode> planNodes = new ArrayList<AbstractPlanNode>();
int size = jArray.length();
try {
for (int i = 0; i < size; i++) {
JSONObject jobj = jArray.getJSONObject(i);
String nodeTypeStr = jobj.getString("PLAN_NODE_TYPE");
PlanNodeType nodeType = PlanNodeType.get(nodeTypeStr);
AbstractPlanNode apn = nodeType.getPlanNodeClass().newInstance();
apn.loadFromJSONObject(jobj, db);
planNodes.add(apn);
}
//link children and parents
for (int i = 0; i < size; i++) {
JSONObject jobj = jArray.getJSONObject(i);
if (jobj.has("CHILDREN_IDS")) {
AbstractPlanNode parent = planNodes.get(i);
JSONArray children = jobj.getJSONArray("CHILDREN_IDS");
for (int j = 0; j < children.length(); j++) {
AbstractPlanNode child = getNodeofId(children.getInt(j), planNodes);
parent.addAndLinkChild(child);
}
}
}
m_planNodesListMap.put(stmtId, planNodes);
}
catch (JSONException | InstantiationException | IllegalAccessException e) {
System.err.println(e);
e.printStackTrace();
}
}
private AbstractPlanNode getNodeofId (int id, List<AbstractPlanNode> planNodes) {
int size = planNodes.size();
for (int i = 0; i < size; i++) {
if (planNodes.get(i).getPlanNodeId() == id) {
return planNodes.get(i);
}
}
return null;
}
/**
* Traverse down the plan extracting all the subquery plans. The potential places where
* the subqueries could be found are:
* - NestLoopInPlan
* - AbstractScanPlanNode predicate
* - AbstractJoinPlanNode predicates
* - IndexScan search keys and predicates
* - IndexJoin inline inner scan
* - Aggregate post-predicate(HAVING clause)
* - Projection, output schema (scalar subquery)
* @param node
* @throws Exception
*/
private void extractSubqueries(AbstractPlanNode node) throws Exception {
assert(node != null);
Collection<AbstractExpression> subexprs = node.findAllSubquerySubexpressions();
for (AbstractExpression nextexpr : subexprs) {
assert(nextexpr instanceof AbstractSubqueryExpression);
AbstractSubqueryExpression subqueryExpr = (AbstractSubqueryExpression) nextexpr;
int stmtId = subqueryExpr.getSubqueryId();
List<AbstractPlanNode> planNodes = new ArrayList<AbstractPlanNode>();
assert(!m_planNodesListMap.containsKey(stmtId));
m_planNodesListMap.put(stmtId, planNodes);
constructTree(planNodes, subqueryExpr.getSubqueryNode());
}
}
}