Package org.voltdb.plannodes

Source Code of org.voltdb.plannodes.AbstractPlanNode

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 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.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

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.Cluster;
import org.voltdb.catalog.Database;
import org.voltdb.compiler.DatabaseEstimates;
import org.voltdb.compiler.ScalarValueHints;
import org.voltdb.planner.PlanStatistics;
import org.voltdb.planner.StatsField;
import org.voltdb.planner.parseinfo.StmtTableScan;
import org.voltdb.planner.parseinfo.StmtTargetTableScan;
import org.voltdb.types.PlanNodeType;

public abstract class AbstractPlanNode implements JSONString, Comparable<AbstractPlanNode> {

    /**
     * Internal PlanNodeId counter. Note that this member is static, which means
     * all PlanNodes will have a unique id
     */
    private static int NEXT_PLAN_NODE_ID = 1;

    // Keep this flag turned off in production or when testing user-accessible EXPLAIN output or when
    // using EXPLAIN output to validate plans.
    protected static boolean m_verboseExplainForDebugging = false; // CODE REVIEWER! this SHOULD be false!
    public static void enableVerboseExplainForDebugging() { m_verboseExplainForDebugging = true; }
    public static boolean disableVerboseExplainForDebugging()
    {
        boolean was = m_verboseExplainForDebugging;
        m_verboseExplainForDebugging = false;
        return was;
    }
    public static void restoreVerboseExplainForDebugging(boolean was) { m_verboseExplainForDebugging = was; }

    /*
     * IDs only need to be unique for a single plan.
     * Reset between plans
     */
    public static final void resetPlanNodeIds() {
        NEXT_PLAN_NODE_ID = 1;
    }

    public enum Members {
        ID,
        PLAN_NODE_TYPE,
        INLINE_NODES,
        CHILDREN_IDS,
        PARENT_IDS,
        OUTPUT_SCHEMA;
    }

    protected int m_id = -1;
    protected List<AbstractPlanNode> m_children = new ArrayList<AbstractPlanNode>();
    protected List<AbstractPlanNode> m_parents = new ArrayList<AbstractPlanNode>();
    protected Set<AbstractPlanNode> m_dominators = new HashSet<AbstractPlanNode>();

    // TODO: planner accesses this data directly. Should be protected.
    protected List<ScalarValueHints> m_outputColumnHints = new ArrayList<ScalarValueHints>();
    protected long m_estimatedOutputTupleCount = 0;
    protected long m_estimatedProcessedTupleCount = 0;
    protected boolean m_hasComputedEstimates = false;

    // The output schema for this node
    protected boolean m_hasSignificantOutputSchema;
    protected NodeSchema m_outputSchema;

    /**
     * Some PlanNodes can take advantage of inline PlanNodes to perform
     * certain additional tasks while performing their main operation, rather than
     * having to re-read tuples from intermediate results
     */
    protected Map<PlanNodeType, AbstractPlanNode> m_inlineNodes =
        new HashMap<PlanNodeType, AbstractPlanNode>();
    protected boolean m_isInline = false;

    /**
     * The textual explanation of why the plan may fail to have a deterministic result or effect when replayed.
     */
    protected String  m_nondeterminismDetail = "the query result does not guarantee a consistent ordering";

    /**
     * Instantiates a new plan node.
     */
    protected AbstractPlanNode() {
        m_id = NEXT_PLAN_NODE_ID++;
    }

    public void overrideId(int newId) {
        m_id = newId;
    }

    /**
     * Create a PlanNode that clones the configuration information but
     * is not inserted in the plan graph and has a unique plan node id.
     */
    protected void produceCopyForTransformation(AbstractPlanNode copy) {
        copy.m_outputSchema = m_outputSchema;
        copy.m_hasSignificantOutputSchema = m_hasSignificantOutputSchema;
        copy.m_outputColumnHints = m_outputColumnHints;
        copy.m_estimatedOutputTupleCount = m_estimatedOutputTupleCount;
        copy.m_estimatedProcessedTupleCount = m_estimatedProcessedTupleCount;

        // clone is not yet implemented for every node.
        assert(m_inlineNodes.size() == 0);
        assert(m_isInline == false);

        // the api requires the copy is not (yet) connected
        assert (copy.m_parents.size() == 0);
        assert (copy.m_children.size() == 0);
    }

    public abstract PlanNodeType getPlanNodeType();

    /**
     * Generate the output schema for this node based on the
     * output schemas of its children.  The generated schema consists of
     * the complete set of columns but is not yet ordered.
     *
     * Right now it's best to call this on every node after it gets added
     * and linked to the top of the current plan graph.
     * FIXME: "it's best to call this" means "to be on the paranoid safe side".
     * It used to be that there was a hacky dependency in some non-critical aggregate code,
     * so it would crash if generateOutputSchema had not been run earlier on its subtree.
     * Historically, there may have been other dependencies like this, too,
     * but they are mostly gone or otherwise completely avoidable.
     * This means that one definitive depth-first recursive call that combines the effects
     * of resolveColumnIndexes and then generateOutputSchema should suffice,
     * if applied to a complete plan tree just before it gets fragmentized.
     * The newest twist is that most of this repeated effort goes into generating
     * redundant pass-thorugh structures that get ignored by the serializer.
     *
     * Many nodes will need to override this method in order to take whatever
     * action is appropriate (so, joins will combine two schemas, projections
     * will already have schemas defined and do nothing, etc).
     * They should set m_hasSignificantOutputSchema to true so that the serialization knows
     * not to ignore their work.
     *
     * @param db  A reference to the Database object from the catalog.
     */
    public void generateOutputSchema(Database db)
    {
        // default behavior: just copy the input schema
        // to the output schema
        assert(m_children.size() == 1);
        m_children.get(0).generateOutputSchema(db);
        // Replace the expressions in our children's columns with TVEs.  When
        // we resolve the indexes in these TVEs they will point back at the
        // correct input column, which we are assuming that the child node
        // has filled in with whatever expression was here before the replacement.
        // Output schemas defined using this standard algorithm
        // are just cached "fillers" that satisfy the legacy
        // resolveColumnIndexes/generateOutputSchema/getOutputSchema protocol
        // until it can be fixed up  -- see the FIXME comment on generateOutputSchema.
        m_hasSignificantOutputSchema = false;
        m_outputSchema =
            m_children.get(0).getOutputSchema().copyAndReplaceWithTVE();
    }

    /**
     * Recursively iterate through the plan and resolve the column_idx value for
     * every TupleValueExpression in every AbstractExpression in every PlanNode.
     * Few enough common cases so we force every AbstractPlanNode subclass to
     * implement this.  After index resolution, this method also sorts
     * the columns in the output schema appropriately, depending upon what
     * sort of node it is, so that its parent will be able to resolve
     * its indexes successfully.
     *
     * Should get called on the plan graph after any optimizations but before
     * the plan gets fragmented.
     * FIXME: This needs to be reworked with generateOutputSchema to eliminate redundancies.
     */
    public abstract void resolveColumnIndexes();

    public void validate() throws Exception {
        //
        // Make sure our children have us listed as their parents
        //
        for (AbstractPlanNode child : m_children) {
            if (!child.m_parents.contains(this)) {
                throw new Exception("ERROR: The child PlanNode '" + child.toString() + "' does not " +
                                    "have its parent PlanNode '" + toString() + "' in its parents list");
            }
            child.validate();
        }
        //
        // Inline PlanNodes
        //
        if (!m_inlineNodes.isEmpty()) {
            for (AbstractPlanNode node : m_inlineNodes.values()) {
                //
                // Make sure that we're not attached to some kind of tree somewhere...
                //
                if (!node.m_children.isEmpty()) {
                    throw new Exception("ERROR: The inline PlanNode '" + node + "' has children inside of PlanNode '" + this + "'");
                } else if (!node.m_parents.isEmpty()) {
                    throw new Exception("ERROR: The inline PlanNode '" + node + "' has parents inside of PlanNode '" + this + "'");
                } else if (!node.isInline()) {
                    throw new Exception("ERROR: The inline PlanNode '" + node + "' was not marked as inline for PlanNode '" + this + "'");
                } else if (!node.getInlinePlanNodes().isEmpty()) {
                    throw new Exception("ERROR: The inline PlanNode '" + node + "' has its own inline PlanNodes inside of PlanNode '" + this + "'");
                }
                node.validate();
            }
        }
    }

    public boolean hasReplicatedResult()
    {
        Map<String, StmtTargetTableScan> tablesRead = new TreeMap<String, StmtTargetTableScan>();
        getTablesAndIndexes(tablesRead, null);
        for (StmtTableScan tableScan : tablesRead.values()) {
            if ( ! tableScan.getIsReplicated()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Recursively build sets of read tables read and index names used.
     *
     * @param tablesRead Set of table aliases read potentially added to at each recursive level.
     * @param indexes Set of index names used in the plan tree
     * Only the current fragment is of interest.
     */
    public void getTablesAndIndexes(Map<String, StmtTargetTableScan> tablesRead,
            Collection<String> indexes)
    {
        for (AbstractPlanNode node : m_inlineNodes.values()) {
            node.getTablesAndIndexes(tablesRead, indexes);
        }
        for (AbstractPlanNode node : m_children) {
            node.getTablesAndIndexes(tablesRead, indexes);
        }
    }

    /**
     * Recursively find the target table name for a DML statement.
     * The name will be attached to the AbstractOperationNode child
     * of a Send Node, in all cases, so the "recursion" can be very limited.
     * Most plan nodes can quickly stub out this recursion and return null.
     * @return
     */
    @SuppressWarnings("static-method")
    public String getUpdatedTable() {
        return null;
    }

    /**
     * Does the (sub)plan guarantee an identical result/effect when "replayed"
     * against the same database state, such as during replication or CL recovery.
     * @return
     */
    public boolean isOrderDeterministic() {
        // Leaf nodes need to re-implement this test.
        assert(m_children != null);
        for (AbstractPlanNode child : m_children) {
            if (! child.isOrderDeterministic()) {
                m_nondeterminismDetail = child.m_nondeterminismDetail;
                return false;
            }
        }
        return true;
    }

    /**
     * Accessor for description of plan non-determinism.
     * @return the field
     */
    public String nondeterminismDetail() {
        return m_nondeterminismDetail;
    }

    @Override
    public final String toString() {
        return getPlanNodeType() + "[" + m_id + "]";
    }

    /**
     * Called to compute cost estimates and statistics on a plan graph. Computing of the costs
     * should be idempotent, but updating the PlanStatistics instance isn't, so this should
     * be called once per finished graph, and once per PlanStatistics instance.
     * TODO(XIN): It takes at least 14% planner CPU. Optimize it.
     */
    public final void computeEstimatesRecursively(PlanStatistics stats,
                                                  Cluster cluster,
                                                  Database db,
                                                  DatabaseEstimates estimates,
                                                  ScalarValueHints[] paramHints)
    {
        assert(stats != null);

        m_outputColumnHints.clear();
        m_estimatedOutputTupleCount = 0;

        // recursively compute and collect stats from children
        long childOutputTupleCountEstimate = 0;
        for (AbstractPlanNode child : m_children) {
            child.computeEstimatesRecursively(stats, cluster, db, estimates, paramHints);
            m_outputColumnHints.addAll(child.m_outputColumnHints);
            childOutputTupleCountEstimate += child.m_estimatedOutputTupleCount;
        }

        // make sure any inlined scans (for NLIJ mostly) are costed as well
        for (Entry<PlanNodeType, AbstractPlanNode> entry : m_inlineNodes.entrySet()) {
            AbstractPlanNode inlineNode = entry.getValue();
            if (inlineNode instanceof AbstractScanPlanNode) {
                inlineNode.computeCostEstimates(0, cluster, db, estimates, paramHints);
            }
        }

        computeCostEstimates(childOutputTupleCountEstimate, cluster, db, estimates, paramHints);
        stats.incrementStatistic(0, StatsField.TUPLES_READ, m_estimatedProcessedTupleCount);
    }

    /**
     * Given the number of tuples expected as input to this node, compute an estimate
     * of the number of tuples read/processed and the number of tuples output.
     * This will be called by
     * {@see AbstractPlanNode#computeEstimatesRecursively(PlanStatistics, Cluster, Database, DatabaseEstimates, ScalarValueHints[])}.
     */
    protected void computeCostEstimates(long childOutputTupleCountEstimate,
                                        Cluster cluster,
                                        Database db,
                                        DatabaseEstimates estimates,
                                        ScalarValueHints[] paramHints)
    {
        m_estimatedOutputTupleCount = childOutputTupleCountEstimate;
        m_estimatedProcessedTupleCount = childOutputTupleCountEstimate;
    }

    public long getEstimatedOutputTupleCount() {
        return m_estimatedOutputTupleCount;
    }

    public long getEstimatedProcessedTupleCount() {
        return m_estimatedProcessedTupleCount;
    }

    /**
     * Gets the id.
     *
     * @return the id
     */
    public Integer getPlanNodeId() {
        return m_id;
    }

    /**
     * Get this PlanNode's output schema
     * FIXME: This needs to be reworked with generateOutputSchema to eliminate redundancies.
     * In short, if generateOutputSchema was called definitively ONCE and returned the child's
     * effective outputSchema to its parent -- possibly without even caching it as m_outputSchema,
     * m_outputSchema could be used to cache only significant non-redundant output schemas.
     * For now, the m_hasSignificantOutputSchema flag is checked separately to determine whether
     * m_outputSchema is worth looking at.
     * @return the NodeSchema which represents this node's output schema
     */
    public NodeSchema getOutputSchema()
    {
        return m_outputSchema;
    }

    /**
     * Add a child and link this node child's parent.
     * @param child The node to add.
     */
    public void addAndLinkChild(AbstractPlanNode child) {
        m_children.add(child);
        child.m_parents.add(this);
    }

    // called by PushDownLimit, re-link the child without changing the order
    public void setAndLinkChild(int index, AbstractPlanNode child) {
        m_children.set(index, child);
        child.m_parents.add(this);
    }

    /** Remove child from this node.
     * @param child to remove.
     */
    public void unlinkChild(AbstractPlanNode child) {
        m_children.remove(child);
        child.m_parents.remove(this);
    }

    /**
     * Replace an existing child with a new one preserving the child's position.
     * @param oldChild The node to replace.
     * @param newChild The new node.
     * @return true if the child was replaced
     */
    public boolean replaceChild(AbstractPlanNode oldChild, AbstractPlanNode newChild) {
        int idx = 0;
        for (AbstractPlanNode child : m_children) {
            if (child.equals(oldChild)) {
                oldChild.m_parents.clear();
                setAndLinkChild(idx, newChild);
                return true;
            }
            ++idx;
        }
        return false;
    }

    public boolean replaceChild(int oldChildIdx, AbstractPlanNode newChild) {
        if (oldChildIdx < 0 || oldChildIdx >= getChildCount()) {
            return false;
        }

        AbstractPlanNode oldChild = m_children.get(oldChildIdx);
        oldChild.m_parents.clear();
        setAndLinkChild(oldChildIdx, newChild);
        return true;
    }


    /**
     * Gets the children.
     * @return the children
     */
    public int getChildCount() {
        return m_children.size();
    }

    /**
     * @param index
     * @return The child node of this node at a given index or null if none exists.
     */
    public AbstractPlanNode getChild(int index) {
        return m_children.get(index);
    }

    public void clearChildren() {
        m_children.clear();
    }

    public boolean hasChild(AbstractPlanNode receive) {
        return m_children.contains(receive);
    }

    /**
     * Gets the parents.
     * @return the parents
     */
    public int getParentCount() {
        return m_parents.size();
    }

    public AbstractPlanNode getParent(int index) {
        return m_parents.get(index);
    }

    public void clearParents() {
        m_parents.clear();
    }

    public void removeFromGraph() {
        disconnectParents();
        disconnectChildren();
    }

    public void disconnectParents() {
        for (AbstractPlanNode parent : m_parents)
            parent.m_children.remove(this);
        m_parents.clear();
     }

    public void disconnectChildren() {
        for (AbstractPlanNode child : m_children)
            child.m_parents.remove(this);
        m_children.clear();
     }

    /** Interject the provided node between this node and this node's current children */
    public void addIntermediary(AbstractPlanNode node) {

        // transfer this node's children to node
        Iterator<AbstractPlanNode> it = m_children.iterator();
        while (it.hasNext()) {
            AbstractPlanNode child = it.next();
            it.remove();                          // remove this.child from m_children
            assert child.getParentCount() == 1;
            child.clearParents();                 // and reset child's parents list
            node.addAndLinkChild(child);          // set node.child and child.parent
        }

        // and add node to this node's children
        assert(m_children.size() == 0);
        addAndLinkChild(node);
    }

    /**
     * @return The map of inlined nodes.
     */
    public Map<PlanNodeType, AbstractPlanNode> getInlinePlanNodes() {
        return m_inlineNodes;
    }

    /**
     * @param node
     */
    public void addInlinePlanNode(AbstractPlanNode node) {
        node.m_isInline = true;
        m_inlineNodes.put(node.getPlanNodeType(), node);
        node.m_children.clear();
        node.m_parents.clear();
    }

    /**
     *
     * @param type
     */
    public void removeInlinePlanNode(PlanNodeType type) {
        if (m_inlineNodes.containsKey(type)) {
            m_inlineNodes.remove(type);
        }
    }

    /**
     *
     * @param type
     * @return An inlined node of the given type or null if none.
     */
    public AbstractPlanNode getInlinePlanNode(PlanNodeType type) {
        return m_inlineNodes.get(type);
    }

    /**
     *
     * @return Is this node inlined in another node.
     */
    public Boolean isInline() {
        return m_isInline;
    }

    public boolean isSubQuery() {
        return false;
    }

    public boolean hasSubquery() {
        if (isSubQuery()) {
            return true;
        }
        for (AbstractPlanNode n : m_children) {
            if (n.hasSubquery()) {
                return true;
            }
        }
        for (AbstractPlanNode inlined : m_inlineNodes.values()) {
            if (inlined.hasSubquery()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Refer to the override implementation on NestLoopIndexJoin node.
     * @param tableName
     * @return whether this node has an inlined index scan node or not.
     */
    public boolean hasInlinedIndexScanOfTable(String tableName) {
        for (int i = 0; i < getChildCount(); i++) {
            AbstractPlanNode child = getChild(i);
            if (child.hasInlinedIndexScanOfTable(tableName) == true) {
                return true;
            }
        }

        return false;
    }


    /**
     * @return the dominator list for a node
     */
    public Set<AbstractPlanNode> getDominators() {
        return m_dominators;
    }

    /**
    *   Initialize a hashset for each node containing that node's dominators
    *   (the set of predecessors that *always* precede this node in a traversal
    *   of the plan-graph in reverse-execution order (from root to leaves)).
    */
    public void calculateDominators() {
        HashSet<AbstractPlanNode> visited = new HashSet<AbstractPlanNode>();
        calculateDominators_recurse(visited);
    }

    private void calculateDominators_recurse(HashSet<AbstractPlanNode> visited) {
        if (visited.contains(this)) {
            assert(false): "do not expect loops in plangraph.";
            return;
        }

        visited.add(this);
        m_dominators.clear();
        m_dominators.add(this);

        // find nodes that are in every parent's dominator set.

        HashMap<AbstractPlanNode, Integer> union = new HashMap<AbstractPlanNode, Integer>();
        for (AbstractPlanNode n : m_parents) {
            for (AbstractPlanNode d : n.getDominators()) {
                if (union.containsKey(d))
                    union.put(d, union.get(d) + 1);
                else
                    union.put(d, 1);
            }
        }

        for (AbstractPlanNode pd : union.keySet() ) {
            if (union.get(pd) == m_parents.size())
                m_dominators.add(pd);
        }

        for (AbstractPlanNode n : m_children)
            n.calculateDominators_recurse(visited);
    }

    /**
     * @param type plan node type to search for
     * @return a list of nodes that are eventual successors of this node of the desired type
     */
    public ArrayList<AbstractPlanNode> findAllNodesOfType(PlanNodeType type) {
        HashSet<AbstractPlanNode> visited = new HashSet<AbstractPlanNode>();
        ArrayList<AbstractPlanNode> collected = new ArrayList<AbstractPlanNode>();
        findAllNodesOfType_recurse(type, collected, visited);
        return collected;
    }

    private void findAllNodesOfType_recurse(PlanNodeType type,ArrayList<AbstractPlanNode> collected,
        HashSet<AbstractPlanNode> visited)
    {
        if (visited.contains(this)) {
            assert(false): "do not expect loops in plangraph.";
            return;
        }
        visited.add(this);
        if (getPlanNodeType() == type)
            collected.add(this);

        for (AbstractPlanNode child : m_children)
            child.findAllNodesOfType_recurse(type, collected, visited);

        for (AbstractPlanNode inlined : m_inlineNodes.values())
            inlined.findAllNodesOfType_recurse(type, collected, visited);
    }

    /**
     * @param type plan node type to search for
     * @return whether a node of that type is contained in the plan tree
     */
    public boolean hasAnyNodeOfType(PlanNodeType type) {
        if (getPlanNodeType() == type)
            return true;

        for (AbstractPlanNode n : m_children) {
            if (n.hasAnyNodeOfType(type)) {
                return true;
            }
        }

        for (AbstractPlanNode inlined : m_inlineNodes.values()) {
            if (inlined.hasAnyNodeOfType(type)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public int compareTo(AbstractPlanNode other) {
        int diff = 0;

        // compare child nodes
        HashMap<Integer, AbstractPlanNode> nodesById = new HashMap<Integer, AbstractPlanNode>();
        for (AbstractPlanNode node : m_children)
            nodesById.put(node.getPlanNodeId(), node);
        for (AbstractPlanNode node : other.m_children) {
            AbstractPlanNode myNode = nodesById.get(node.getPlanNodeId());
            diff = myNode.compareTo(node);
            if (diff != 0) return diff;
        }

        // compare inline nodes
        HashMap<Integer, Entry<PlanNodeType, AbstractPlanNode>> inlineNodesById =
               new HashMap<Integer, Entry<PlanNodeType, AbstractPlanNode>>();
        for (Entry<PlanNodeType, AbstractPlanNode> e : m_inlineNodes.entrySet())
            inlineNodesById.put(e.getValue().getPlanNodeId(), e);
        for (Entry<PlanNodeType, AbstractPlanNode> e : other.m_inlineNodes.entrySet()) {
            Entry<PlanNodeType, AbstractPlanNode> myE = inlineNodesById.get(e.getValue().getPlanNodeId());
            if (myE.getKey() != e.getKey()) return -1;

            diff = myE.getValue().compareTo(e.getValue());
            if (diff != 0) return diff;
        }

        diff = m_id - other.m_id;
        return diff;
    }

    // produce a file that can imported into graphviz for easier visualization
    public String toDOTString() {
        StringBuilder sb = new StringBuilder();

        // id [label=id: value-type <value-type-attributes>];
        // id -> child_id;
        // id -> child_id;

        sb.append(m_id).append(" [label=\"").append(m_id).append(": ").append(getPlanNodeType()).append("\" ");
        sb.append(getValueTypeDotString());
        sb.append("];\n");
        for (AbstractPlanNode node : m_inlineNodes.values()) {
            sb.append(m_id).append(" -> ").append(node.getPlanNodeId().intValue()).append(";\n");
            sb.append(node.toDOTString());
        }
        for (AbstractPlanNode node : m_children) {
           sb.append(m_id).append(" -> ").append(node.getPlanNodeId().intValue()).append(";\n");
        }

        return sb.toString();
    }

    // maybe not worth polluting
    private String getValueTypeDotString() {
        PlanNodeType pnt = getPlanNodeType();
        if (isInline()) {
            return "fontcolor=\"white\" style=\"filled\" fillcolor=\"red\"";
        }
        if (pnt == PlanNodeType.SEND || pnt == PlanNodeType.RECEIVE) {
            return "fontcolor=\"white\" style=\"filled\" fillcolor=\"black\"";
        }
        return "";
    }

    @Override
    public String toJSONString() {
        JSONStringer stringer = new JSONStringer();
        try
        {
            stringer.object();
            toJSONString(stringer);
            stringer.endObject();
        }
        catch (JSONException e)
        {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
        return stringer.toString();
    }

    public void toJSONString(JSONStringer stringer) throws JSONException {
        stringer.key(Members.ID.name()).value(m_id);
        stringer.key(Members.PLAN_NODE_TYPE.name()).value(getPlanNodeType().toString());

        if (m_inlineNodes.size() > 0) {
            stringer.key(Members.INLINE_NODES.name()).array();

            PlanNodeType types[] = new PlanNodeType[m_inlineNodes.size()];
            int i = 0;
            for (PlanNodeType type : m_inlineNodes.keySet()) {
                types[i++] = type;
            }
            Arrays.sort(types);
            for (PlanNodeType type : types) {
                AbstractPlanNode node = m_inlineNodes.get(type);
                assert(node != null);
                assert(node instanceof JSONString);
                stringer.value(node);
            }
            stringer.endArray();
        }

        if (m_children.size() > 0) {
            stringer.key(Members.CHILDREN_IDS.name()).array();
            for (AbstractPlanNode node : m_children) {
                stringer.value(node.getPlanNodeId().intValue());
            }
            stringer.endArray();
        }

        outputSchemaToJSON(stringer);
    }

    private void outputSchemaToJSON(JSONStringer stringer) throws JSONException {
        if (m_hasSignificantOutputSchema) {
            stringer.key(Members.OUTPUT_SCHEMA.name());
            stringer.array();
            for (SchemaColumn column : m_outputSchema.getColumns()) {
                column.toJSONString(stringer, true);
            }
            stringer.endArray();
        }
    }

    public String toExplainPlanString() {
        StringBuilder sb = new StringBuilder();
        explainPlan_recurse(sb, "");
        return sb.toString();
    }

    public void explainPlan_recurse(StringBuilder sb, String indent) {
        if (m_verboseExplainForDebugging && m_hasSignificantOutputSchema) {
            sb.append(indent + "Detailed Output Schema: ");
            JSONStringer stringer = new JSONStringer();
            try
            {
                stringer.object();
                outputSchemaToJSON(stringer);
                stringer.endObject();
                sb.append(stringer.toString());
            }
            catch (Exception e)
            {
                sb.append(indent + "CORRUPTED beyond the ability to format? " + e);
                e.printStackTrace();
            }
            sb.append(indent + "from\n");
        }
        String extraIndent = " ";
        // Except when verbosely debugging,
        // skip projection nodes basically (they're boring as all get out)
        if (( ! m_verboseExplainForDebugging) && (getPlanNodeType() == PlanNodeType.PROJECTION)) {
            extraIndent = "";
        }
        else {
            if ( ! m_skipInitalIndentationForExplain) {
                sb.append(indent);
            }
            String nodePlan = explainPlanForNode(indent);
            sb.append(nodePlan + "\n");
        }

        // Agg < Proj < Limit < Scan
        // Order the inline nodes with integer in ascending order
        TreeMap<Integer, AbstractPlanNode> sort_inlineNodes =
                new TreeMap<Integer, AbstractPlanNode>();

        // every inline plan node is unique
        int ii = 4;
        for (AbstractPlanNode inlineNode : m_inlineNodes.values()) {
            if (inlineNode instanceof AggregatePlanNode) {
                sort_inlineNodes.put(0, inlineNode);
            } else if (inlineNode instanceof ProjectionPlanNode) {
                sort_inlineNodes.put(1, inlineNode);
            } else if (inlineNode instanceof LimitPlanNode) {
                sort_inlineNodes.put(2, inlineNode);
            } else if (inlineNode instanceof AbstractScanPlanNode) {
                sort_inlineNodes.put(3, inlineNode);
            } else {
                // any other inline nodes currently ?  --xin
                sort_inlineNodes.put(ii++, inlineNode);
            }
        }
        // inline nodes with ascending order as their integer keys
        for (AbstractPlanNode inlineNode : sort_inlineNodes.values()) {
            // don't bother with inlined projections
            if (( ! m_verboseExplainForDebugging) &&
                (inlineNode.getPlanNodeType() == PlanNodeType.PROJECTION)) {
                continue;
            }
            inlineNode.setSkipInitalIndentationForExplain(true);

            sb.append(indent + extraIndent + "inline ");
            inlineNode.explainPlan_recurse(sb, indent + extraIndent);
        }

        for (AbstractPlanNode node : m_children) {
            // inline nodes shouldn't have children I hope
            assert(m_isInline == false);
            node.explainPlan_recurse(sb, indent + extraIndent);
        }
    }

    private boolean m_skipInitalIndentationForExplain = false;
    public void setSkipInitalIndentationForExplain(boolean skip) {
        m_skipInitalIndentationForExplain = skip;
    }

    protected abstract String explainPlanForNode(String indent);

    public ArrayList<AbstractScanPlanNode> getScanNodeList () {
        HashSet<AbstractPlanNode> visited = new HashSet<AbstractPlanNode>();
        ArrayList<AbstractScanPlanNode> collected = new ArrayList<AbstractScanPlanNode>();
        getScanNodeList_recurse( collected, visited);
        return collected;
    }

    //postorder adding scan nodes
    public void getScanNodeList_recurse(ArrayList<AbstractScanPlanNode> collected,
            HashSet<AbstractPlanNode> visited) {
        if (visited.contains(this)) {
            assert(false): "do not expect loops in plangraph.";
            return;
        }
        visited.add(this);
        for (AbstractPlanNode n : m_children) {
            n.getScanNodeList_recurse(collected, visited);
        }

        for (AbstractPlanNode node : m_inlineNodes.values()) {
            node.getScanNodeList_recurse(collected, visited);
        }
    }

    public ArrayList<AbstractPlanNode> getPlanNodeList () {
        HashSet<AbstractPlanNode> visited = new HashSet<AbstractPlanNode>();
        ArrayList<AbstractPlanNode> collected = new ArrayList<AbstractPlanNode>();
        getPlanNodeList_recurse( collected, visited);
        return collected;
    }

    //postorder add nodes
    public void getPlanNodeList_recurse(ArrayList<AbstractPlanNode> collected,
            HashSet<AbstractPlanNode> visited) {
        if (visited.contains(this)) {
            assert(false): "do not expect loops in plangraph.";
            return;
        }
        visited.add(this);

        for (AbstractPlanNode n : m_children) {
            n.getPlanNodeList_recurse(collected, visited);
        }
        collected.add(this);
    }

    abstract protected void loadFromJSONObject(JSONObject obj, Database db) throws JSONException;

    protected final void helpLoadFromJSONObject( JSONObject jobj, Database db ) throws JSONException {
        assert( jobj != null );
        m_id = jobj.getInt( Members.ID.name() );

        JSONArray jarray = null;
        //load inline nodes
        if( !jobj.isNull( Members.INLINE_NODES.name() ) ){
            jarray = jobj.getJSONArray( Members.INLINE_NODES.name() );
            PlanNodeTree pnt = new PlanNodeTree();
            pnt.loadFromJSONArray(jarray, db);
            List<AbstractPlanNode> list = pnt.getNodeList();
            for( AbstractPlanNode pn : list ) {
                m_inlineNodes.put( pn.getPlanNodeType(), pn);
            }
        }
        //children and parents list loading implemented in planNodeTree.loadFromJsonArray

        // load the output schema if it was marked significant.
        if ( !jobj.isNull( Members.OUTPUT_SCHEMA.name() ) ) {
            m_outputSchema = new NodeSchema();
            m_hasSignificantOutputSchema = true;
            jarray = jobj.getJSONArray( Members.OUTPUT_SCHEMA.name() );
            int size = jarray.length();
            for( int i = 0; i < size; i++ ) {
                m_outputSchema.addColumn( SchemaColumn.fromJSONObject(jarray.getJSONObject(i)) );
            }
        }
    }

    public boolean reattachFragment( SendPlanNode child ) {
        for( AbstractPlanNode pn : m_inlineNodes.values() ) {
            if( pn.reattachFragment( child) )
                return true;
        }
        for( AbstractPlanNode pn : m_children ) {
            if( pn.reattachFragment( child) )
                return true;
        }
        return false;
    }
}
TOP

Related Classes of org.voltdb.plannodes.AbstractPlanNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.