Package edu.brown.plannodes

Source Code of edu.brown.plannodes.PlanNodeUtil

package edu.brown.plannodes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.collections15.set.ListOrderedSet;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.voltdb.catalog.CatalogMap;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.PlanFragment;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.catalog.Table;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.planner.PlanColumn;
import org.voltdb.planner.PlannerContext;
import org.voltdb.plannodes.AbstractJoinPlanNode;
import org.voltdb.plannodes.AbstractOperationPlanNode;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.AbstractScanPlanNode;
import org.voltdb.plannodes.AggregatePlanNode;
import org.voltdb.plannodes.DeletePlanNode;
import org.voltdb.plannodes.DistinctPlanNode;
import org.voltdb.plannodes.IndexScanPlanNode;
import org.voltdb.plannodes.InsertPlanNode;
import org.voltdb.plannodes.LimitPlanNode;
import org.voltdb.plannodes.MaterializePlanNode;
import org.voltdb.plannodes.NestLoopIndexPlanNode;
import org.voltdb.plannodes.NestLoopPlanNode;
import org.voltdb.plannodes.OrderByPlanNode;
import org.voltdb.plannodes.PlanNodeList;
import org.voltdb.plannodes.PlanNodeTree;
import org.voltdb.plannodes.ProjectionPlanNode;
import org.voltdb.plannodes.ReceivePlanNode;
import org.voltdb.plannodes.SendPlanNode;
import org.voltdb.plannodes.SeqScanPlanNode;
import org.voltdb.plannodes.UnionPlanNode;
import org.voltdb.plannodes.UpdatePlanNode;
import org.voltdb.types.ExpressionType;
import org.voltdb.types.PlanNodeType;
import org.voltdb.types.QueryType;
import org.voltdb.utils.Encoder;

import edu.brown.catalog.CatalogKey;
import edu.brown.catalog.CatalogUtil;
import edu.brown.expressions.ExpressionUtil;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.ClassUtil;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.PredicatePairs;

/**
* Utility methods for extracting information from AbstractPlanNode trees/nodes
*
* @author pavlo
*/
public abstract class PlanNodeUtil {
    private static final Logger LOG = Logger.getLogger(PlanNodeUtil.class);
    private static final LoggerBoolean debug = new LoggerBoolean();
    private static final LoggerBoolean trace = new LoggerBoolean();
    static {
        LoggerUtil.attachObserver(LOG, debug, trace);
    }

    private static final String INLINE_SPACER_PREFIX = "\u2502";
    private static final String INLINE_INNER_PREFIX = "\u251C";

    private static final String NODE_PREFIX = "\u25B6 ";
    private static final String SPACER_PREFIX = "\u2503";
    private static final String INNER_PREFIX = "\u2523";

    // ------------------------------------------------------------
    // CACHES
    // ------------------------------------------------------------

    /**
     * PlanFragmentId -> AbstractPlanNode
     */
    private static final Map<String, AbstractPlanNode> CACHE_DESERIALIZE_FRAGMENT = new HashMap<String, AbstractPlanNode>();

    /**
     * Procedure.Statement -> AbstractPlanNode
     */
    private static final Map<String, AbstractPlanNode> CACHE_DESERIALIZE_SP_STATEMENT = new HashMap<String, AbstractPlanNode>();
    private static final Map<String, AbstractPlanNode> CACHE_DESERIALIZE_MP_STATEMENT = new HashMap<String, AbstractPlanNode>();

    /**
     * Statement -> Sorted List of PlanFragments
     */
    private static final Map<Statement, List<PlanFragment>> CACHE_SORTED_SP_FRAGMENTS = new HashMap<Statement, List<PlanFragment>>();
    private static final Map<Statement, List<PlanFragment>> CACHE_SORTED_MP_FRAGMENTS = new HashMap<Statement, List<PlanFragment>>();

    private static final Map<Statement, Collection<Column>> CACHE_OUTPUT_COLUMNS = new HashMap<Statement, Collection<Column>>();

    /**
     *
     */
    private static final Map<String, String> CACHE_STMTPARAMETER_COLUMN = new HashMap<String, String>();

    // ------------------------------------------------------------
    // UTILITY METHODS
    // ------------------------------------------------------------

    public static void clearCache() {
        CACHE_DESERIALIZE_FRAGMENT.clear();
        CACHE_DESERIALIZE_MP_STATEMENT.clear();
        CACHE_DESERIALIZE_SP_STATEMENT.clear();
        CACHE_SORTED_MP_FRAGMENTS.clear();
        CACHE_SORTED_SP_FRAGMENTS.clear();
        CACHE_OUTPUT_COLUMNS.clear();
        CACHE_STMTPARAMETER_COLUMN.clear();
    }

    /**
     * Returns the root node in the tree for the given node
     *
     * @param node
     * @return
     */
    public static AbstractPlanNode getRoot(AbstractPlanNode node) {
        return (node.getParentPlanNodeCount() > 0 ? getRoot(node.getParent(0)) : node);
    }

    /**
     * Return a set of all the PlanNodeTypes in the tree
     *
     * @param node
     * @return
     */
    public static Collection<PlanNodeType> getPlanNodeTypes(AbstractPlanNode node) {
        final Set<PlanNodeType> types = new HashSet<PlanNodeType>();
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                types.add(element.getPlanNodeType());
            }
        }.traverse(node);
        assert (types.size() > 0);
        return (types);
    }
   
    /**
     * Returns true if the AbstractPlanNode contains a range query
     * @param rootNode
     * @return
     */
    public static boolean isRangeQuery(AbstractPlanNode rootNode) {
        for (ExpressionType expType : getScanExpressionTypes(rootNode)) {
            switch (expType) {
                case COMPARE_GREATERTHAN:
                case COMPARE_GREATERTHANOREQUALTO:
                case COMPARE_IN:
                case COMPARE_LESSTHAN:
                case COMPARE_LESSTHANOREQUALTO:
                case COMPARE_LIKE:
                case CONJUNCTION_OR:
                    return (true);
            } // SWITCH
        } // FOR
        return (false);
    }

    /**
     * Returns true if the tree at the given root node is for a distributed
     * query plan
     *
     * @param rootNode
     * @return
     */
    public static boolean isDistributedQuery(AbstractPlanNode rootNode) {
        return (PlanNodeUtil.getPlanNodeTypes(rootNode).contains(PlanNodeType.RECEIVE));
    }

    /**
     * Get all the AbstractExpression roots used in the given AbstractPlanNode.
     * Non-recursive
     *
     * @param node
     * @return
     */
    public static Collection<AbstractExpression> getExpressionsForPlanNode(AbstractPlanNode node, PlanNodeType... exclude) {
        return PlanNodeUtil.getExpressionsForPlanNode(node, new ListOrderedSet<AbstractExpression>(), exclude);
    }

    /**
     * Get all the AbstractExpression roots used in the given AbstractPlanNode.
     * Non-recursive
     *
     * @param node
     * @param exps
     * @return
     */
    public static Collection<AbstractExpression> getExpressionsForPlanNode(AbstractPlanNode node, Set<AbstractExpression> exps, PlanNodeType... exclude) {
        final PlannerContext plannerContext = PlannerContext.singleton();
        final PlanNodeType node_type = node.getPlanNodeType();
        for (PlanNodeType e : exclude) {
            if (node_type == e)
                return (exps);
        } // FOR

        switch (node_type) {
            // ---------------------------------------------------
            // SCANS
            // ---------------------------------------------------
            case INDEXSCAN: {
                IndexScanPlanNode idx_node = (IndexScanPlanNode) node;
                if (idx_node.getEndExpression() != null)
                    exps.add(idx_node.getEndExpression());
                for (AbstractExpression exp : idx_node.getSearchKeyExpressions()) {
                    if (exp != null)
                        exps.add(exp);
                } // FOR

                // Fall through down into SEQSCAN....
            }
            case SEQSCAN: {
                AbstractScanPlanNode scan_node = (AbstractScanPlanNode) node;
                if (scan_node.getPredicate() != null)
                    exps.add(scan_node.getPredicate());
                break;
            }
            // ---------------------------------------------------
            // JOINS
            // ---------------------------------------------------
            case NESTLOOP:
            case NESTLOOPINDEX: {
                AbstractJoinPlanNode cast_node = (AbstractJoinPlanNode) node;
                if (cast_node.getPredicate() != null)
                    exps.add(cast_node.getPredicate());

                // We always need to look at the inline scan nodes for joins
                for (AbstractPlanNode inline_node : cast_node.getInlinePlanNodes().values()) {
                    if (inline_node instanceof AbstractScanPlanNode) {
                        PlanNodeUtil.getExpressionsForPlanNode(inline_node, exps);
                    }
                } // FOR
                break;
            }
            // ---------------------------------------------------
            // PROJECTION
            // ---------------------------------------------------
            case MATERIALIZE:
            case PROJECTION: {
                for (Integer col_guid : node.getOutputColumnGUIDs()) {
                    PlanColumn col = plannerContext.get(col_guid);
                    assert (col != null) : "Invalid PlanColumn #" + col_guid;
                    if (col.getExpression() != null)
                        exps.add(col.getExpression());
                } // FOR
                break;
            }
            // ---------------------------------------------------
            // AGGREGATE
            // ---------------------------------------------------
            case AGGREGATE:
            case HASHAGGREGATE: {
                AggregatePlanNode agg_node = (AggregatePlanNode) node;
                for (Integer col_guid : agg_node.getAggregateColumnGuids()) {
                    PlanColumn col = plannerContext.get(col_guid);
                    assert (col != null) : "Invalid PlanColumn #" + col_guid;
                    if (col.getExpression() != null)
                        exps.add(col.getExpression());
                } // FOR
                for (Integer col_guid : agg_node.getGroupByColumnGuids()) {
                    PlanColumn col = plannerContext.get(col_guid);
                    assert (col != null) : "Invalid PlanColumn #" + col_guid;
                    if (col.getExpression() != null)
                        exps.add(col.getExpression());
                } // FOR
                break;
            }
            // ---------------------------------------------------
            // ORDERBY
            // ---------------------------------------------------
            case ORDERBY: {
                OrderByPlanNode orby_node = (OrderByPlanNode) node;
                for (Integer col_guid : orby_node.getSortColumnGuids()) {
                    PlanColumn col = plannerContext.get(col_guid);
                    assert (col != null) : "Invalid PlanColumn #" + col_guid;
                    if (col.getExpression() != null)
                        exps.add(col.getExpression());
                } // FOR
                break;
            }
            default:
                // Do nothing...
        } // SWITCH
        return (exps);
    }

    /**
     * Return all the ExpressionTypes used for scan predicates in the given PlanNode
     * @param node
     * @return
     */
    public static Collection<ExpressionType> getScanExpressionTypes(AbstractPlanNode root) {
        final Set<ExpressionType> found = new HashSet<ExpressionType>();
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode node) {
                Set<AbstractExpression> exps = new HashSet<AbstractExpression>();
                switch (node.getPlanNodeType()) {
                    // SCANS
                    case INDEXSCAN: {
                        IndexScanPlanNode idx_node = (IndexScanPlanNode) node;
                        exps.add(idx_node.getEndExpression());
                        exps.addAll(idx_node.getSearchKeyExpressions());
                    }
                    case SEQSCAN: {
                        AbstractScanPlanNode scan_node = (AbstractScanPlanNode) node;
                        exps.add(scan_node.getPredicate());
                        break;
                    }
                    // JOINS
                    case NESTLOOP:
                    case NESTLOOPINDEX: {
                        AbstractJoinPlanNode cast_node = (AbstractJoinPlanNode) node;
                        exps.add(cast_node.getPredicate());
                        break;
                    }
                    default:
                        // Do nothing...
                } // SWITCH

                for (AbstractExpression exp : exps) {
                    if (exp == null)
                        continue;
                    found.addAll(ExpressionUtil.getExpressionTypes(exp));
                } // FOR
                return;
            }
        }.traverse(root);
        return (found);
    }
   
    /**
     * Get the Columns referenced in the output portion of a SELECT query
     * @param catalog_stmt
     * @return A collection of the output Columns
     * @throws Exception
     */
    public static Collection<Column> getOutputColumnsForStatement(Statement catalog_stmt) throws Exception {
        Collection<Column> ret = CACHE_OUTPUT_COLUMNS.get(catalog_stmt);
        if (ret == null && catalog_stmt.getQuerytype() == QueryType.SELECT.getValue()) {
            // It's easier to figure things out if we use the single-partition
            // query plan
            final Database catalog_db = CatalogUtil.getDatabase(catalog_stmt);
            final AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, true);
            assert (root != null);
            assert (root instanceof SendPlanNode) : "Unexpected PlanNode root " + root + " for " + catalog_stmt.fullName();

            // We need to examine down the tree to figure out what this thing
            // shoving out to the outside world
            assert (root.getChildPlanNodeCount() == 1) : "Unexpected one child for " + root + " for " + catalog_stmt.fullName() + " but it has " + root.getChildPlanNodeCount();
            ret = Collections.unmodifiableCollection(PlanNodeUtil.getOutputColumnsForPlanNode(catalog_db, root.getChild(0)));
            CACHE_OUTPUT_COLUMNS.put(catalog_stmt, ret);
        }
        return (ret);
    }

    /**
     * Get the set of columns
     *
     * @param catalog_db
     * @param node
     * @return
     */
    public static Collection<Column> getOutputColumnsForPlanNode(final Database catalog_db, AbstractPlanNode node) {
        final PlannerContext pcontext = PlannerContext.singleton();
        final Collection<Integer> planColumnIds = getOutputColumnIdsForPlanNode(node);

        final Set<Column> columns = new ListOrderedSet<Column>();
        for (Integer column_guid : planColumnIds) {
            PlanColumn planColumn = pcontext.get(column_guid);
            assert (planColumn != null);
            AbstractExpression exp = planColumn.getExpression();
            assert (exp != null);
            Collection<Column> exp_cols = ExpressionUtil.getReferencedColumns(catalog_db, exp);
            if (debug.val)
                LOG.debug(planColumn.toString() + " => " + exp_cols);
            columns.addAll(exp_cols);
        } // FOR

        return (columns);
    }

    /**
     * Get the set of columns
     *
     * @param catalogContext
     * @param node
     * @return
     */
    public static Collection<AbstractExpression> getOutputExpressionsForPlanNode(AbstractPlanNode node) {
        final PlannerContext pcontext = PlannerContext.singleton();
        final Collection<Integer> planColumnIds = getOutputColumnIdsForPlanNode(node);

        final Collection<AbstractExpression> exps = new ListOrderedSet<AbstractExpression>();
        for (Integer column_guid : planColumnIds) {
            PlanColumn planColumn = pcontext.get(column_guid);
            assert (planColumn != null);
            AbstractExpression exp = planColumn.getExpression();
            assert (exp != null);
            exps.add(exp);
        } // FOR

        return (exps);
    }

    /**
     * @param node
     * @return
     */
    public static Collection<Integer> getOutputColumnIdsForPlanNode(AbstractPlanNode node) {
        final Collection<Integer> planColumnIds = new ListOrderedSet<Integer>();

        // 2011-07-20: Using the AbstractExpressions is the more accurate way of
        // getting the
        // Columns referenced in the output
        // If this is Scan that has an inline Projection, grab those too
        if ((node instanceof AbstractScanPlanNode) && node.getInlinePlanNode(PlanNodeType.PROJECTION) != null) {
            ProjectionPlanNode prj_node = node.getInlinePlanNode(PlanNodeType.PROJECTION);
            planColumnIds.addAll(prj_node.getOutputColumnGUIDs());
            if (debug.val)
                LOG.debug(prj_node.getPlanNodeType() + ": " + planColumnIds);
        } else {
            planColumnIds.addAll(node.getOutputColumnGUIDs());
            if (debug.val)
                LOG.debug(node.getPlanNodeType() + ": " + planColumnIds);
        }

        // If this is an AggregatePlanNode, then we also need to include columns
        // computed in the aggregates
        if (node instanceof AggregatePlanNode) {
            AggregatePlanNode agg_node = (AggregatePlanNode) node;
            planColumnIds.addAll(agg_node.getAggregateColumnGuids());
            if (debug.val)
                LOG.debug(node.getPlanNodeType() + ": " + agg_node.getAggregateColumnGuids());
        }

        return (planColumnIds);
    }

    /**
     * Get the set of columns that
     *
     * @param catalog_db
     * @param node
     * @return
     */
    public static Collection<Column> getUpdatedColumnsForPlanNode(final Database catalog_db, AbstractPlanNode node) {
        Set<Column> columns = new ListOrderedSet<Column>();
        for (int ctr = 0, cnt = node.getOutputColumnGUIDs().size(); ctr < cnt; ctr++) {
            int column_guid = node.getOutputColumnGUIDs().get(ctr);
            PlanColumn column = PlannerContext.singleton().get(column_guid);
            assert (column != null);

            final String column_name = column.getDisplayName();
            String table_name = column.originTableName();

            // If there is no table name, then check whether this is a scan
            // node.
            // If it is, then we can try to get the table name from the node's
            // target
            if (table_name == null && node instanceof AbstractScanPlanNode) {
                table_name = ((AbstractScanPlanNode) node).getTargetTableName();
            }

            // If this is a TupleAddressExpression or there is no target table
            // name, then we have
            // to skip this output column
            if (column_name.equalsIgnoreCase("tuple_address") || table_name == null)
                continue;

            Table catalog_tbl = null;
            try {
                catalog_tbl = catalog_db.getTables().get(table_name);
            } catch (Exception ex) {
                LOG.fatal("Failed to retrieve table '" + table_name + "'", ex);
                LOG.fatal(CatalogUtil.debug(catalog_db.getTables()));
                throw new RuntimeException(ex);
            }
            assert (catalog_tbl != null) : "Invalid table '" + table_name + "'";

            Column catalog_col = catalog_tbl.getColumns().get(column_name);
            assert (catalog_col != null) : "Invalid column '" + table_name + "." + column_name;

            columns.add(catalog_col);
        } // FOR
        return (columns);
    }

    /**
     * Returns all the PlanNodes in the given tree that of a specific type
     * @param root
     * @param search_class
     * @return
     */
    public static <T extends AbstractPlanNode> Collection<T> getPlanNodes(AbstractPlanNode root, final Class<? extends T> search_class) {
        final Set<T> found = new HashSet<T>();
        new PlanNodeTreeWalker() {
            @SuppressWarnings("unchecked")
            @Override
            protected void callback(AbstractPlanNode element) {
                Class<? extends AbstractPlanNode> element_class = element.getClass();
                if (ClassUtil.getSuperClasses(element_class).contains(search_class)) {
                    found.add((T) element);
                }
                return;
            }
        }.traverse(root);
        return (found);
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractPlanNode> Collection<T> getChildren(AbstractPlanNode node, Class<T> search_class) {
        final Set<T> found = new HashSet<T>();
        for (int i = 0, cnt = node.getChildPlanNodeCount(); i < cnt; i++) {
            AbstractPlanNode child = node.getChild(i);
            Class<? extends AbstractPlanNode> child_class = child.getClass();
            if (ClassUtil.getSuperClasses(child_class).contains(search_class)) {
                found.add((T) child);
            }
        } // FOR
        return (found);
    }

    /**
     * Get the total depth of the tree
     * @param root
     * @return
     */
    public static int getDepth(AbstractPlanNode root) {
        final int depth[] = { 0 };
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                int current_depth = this.getDepth();
                if (current_depth > depth[0])
                    depth[0] = current_depth;
            }
        }.traverse(root);
        return (depth[0]);
    }
   
    /**
     * Get the depth of an element in the tree
     * @param root
     * @param node
     * @return
     */
    public static int getDepth(AbstractPlanNode root, final AbstractPlanNode node) {
        final int depth[] = { 0 };
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                if (element.equals(node)) {
                    depth[0] = this.getDepth();
                    this.stop();
                }
            }
        }.traverse(root);
        return (depth[0]);
    }

    /**
     * Return all the nodes in an tree that reference a particular table This
     * can be either scan nodes or operation nodes
     *
     * @param root
     * @param catalog_tbl
     * @return
     */
    public static Collection<AbstractPlanNode> getPlanNodesReferencingTable(AbstractPlanNode root, final Table catalog_tbl) {
        final Set<AbstractPlanNode> found = new HashSet<AbstractPlanNode>();
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                // AbstractScanNode
                if (element instanceof AbstractScanPlanNode) {
                    AbstractScanPlanNode cast_node = (AbstractScanPlanNode) element;
                    if (cast_node.getTargetTableName().equals(catalog_tbl.getName()))
                        found.add(cast_node);
                    // AbstractOperationPlanNode
                } else if (element instanceof AbstractOperationPlanNode) {
                    AbstractOperationPlanNode cast_node = (AbstractOperationPlanNode) element;
                    if (cast_node.getTargetTableName().equals(catalog_tbl.getName()))
                        found.add(cast_node);
                }
                return;
            }
        }.traverse(root);
        return (found);
    }

    /**
     * Return all of the PlanColumn guids used in this query plan tree
     * (including inline nodes)
     *
     * @param root
     * @return
     */
    public static Collection<Integer> getAllPlanColumnGuids(AbstractPlanNode root) {
        final Set<Integer> guids = new HashSet<Integer>();
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                guids.addAll(element.getOutputColumnGUIDs());
            }
        }.traverse(root);
        return (guids);
    }

    public static String debug(AbstractPlanNode node) {
        return (PlanNodeUtil.debug(node, ""));
    }

    /**
     * @param label
     * @param guids
     * @param spacer
     * @return
     */
    private static String debugOutputColumns(String label, List<Integer> guids, String spacer) {
        String ret = "";

        ret += label + "[" + guids.size() + "]:\n";
        for (int ctr = 0, cnt = guids.size(); ctr < cnt; ctr++) {
            int column_guid = guids.get(ctr);
            String name = "???";
            PlanColumn column = PlannerContext.singleton().get(column_guid);
            String inner = " : guid=" + column_guid;
            if (column != null) {
                assert (column_guid == column.guid());
                name = column.getDisplayName();
                inner += " : type=" + column.type() + " : size=" + column.width() + " : sort=" + column.getSortOrder() + " : storage=" + column.getStorage();
            }

            ret += String.format("%s   [%02d] %s%s\n", spacer, ctr, name, inner);

            if (column != null && column.getExpression() != null) { // && (true
                                                                    // || node
                                                                    // instanceof
                                                                    // ProjectionPlanNode))
                                                                    // {
                ret += ExpressionUtil.debug(column.getExpression(), spacer + "    ");
            }
        } // FOR
        return (ret);
    }

    private static String debug(AbstractPlanNode node, String spacer) {
        if (node == null) return (null);
        String ret = debugNode(node, spacer);

        // Print out all of our children
        spacer += "  ";
        for (int ctr = 0, cnt = node.getChildPlanNodeCount(); ctr < cnt; ctr++) {
            ret += PlanNodeUtil.debug(node.getChild(ctr), spacer);
        }

        return (ret);
    }

    public static String debugNode(AbstractPlanNode node) {
        return (debugNode(node, ""));
    }

    public static String debugNode(AbstractPlanNode node, final String orig_spacer) {
        StringBuilder sb = new StringBuilder();

        final String inner_prefix = (node.isInline() ? INLINE_INNER_PREFIX : INNER_PREFIX) + " ";
        final String spacer_prefix = (node.isInline() ? INLINE_SPACER_PREFIX : SPACER_PREFIX) + " ";
        // final String last_prefix = (node.isInline() ? INLINE_LAST_PREFIX :
        // LAST_PREFIX);

        String spacer = orig_spacer + "  ";
        String inner_spacer = spacer + inner_prefix;
        String line_spacer = spacer + spacer_prefix;

        // General Information
        if (node.isInline() == false)
            sb.append(orig_spacer).append(NODE_PREFIX + node.toString() + "\n");
        sb.append(inner_spacer).append("Inline[" + node.isInline() + "]\n");

        // AbstractJoinPlanNode
        if (node instanceof AbstractJoinPlanNode) {
            AbstractJoinPlanNode cast_node = (AbstractJoinPlanNode) node;
            sb.append(inner_spacer).append("JoinType[" + cast_node.getJoinType() + "]\n");
            sb.append(inner_spacer).append("Join Expression: " + (cast_node.getPredicate() != null ? "\n" + ExpressionUtil.debug(cast_node.getPredicate(), line_spacer) : null + "\n"));

            // AbstractOperationPlanNode
        } else if (node instanceof AbstractOperationPlanNode) {
            sb.append(inner_spacer).append("TargetTableId[" + ((AbstractOperationPlanNode) node).getTargetTableName() + "]\n");

            // AbstractScanPlanNode
        } else if (node instanceof AbstractScanPlanNode) {
            AbstractScanPlanNode cast_node = (AbstractScanPlanNode) node;
            sb.append(inner_spacer).append("TargetTableName[" + cast_node.getTargetTableName() + "]\n");
            sb.append(inner_spacer).append("TargetTableAlias[" + cast_node.getTargetTableAlias() + "]\n");
            sb.append(inner_spacer).append("TargetTableId[" + cast_node.getTargetTableName() + "]\n");
        }

        // AggregatePlanNode
        if (node instanceof AggregatePlanNode) {
            AggregatePlanNode cast_node = (AggregatePlanNode) node;
            sb.append(inner_spacer).append("AggregateTypes[" + cast_node.getAggregateTypes().size() + "]: " + cast_node.getAggregateTypes() + "\n");
            sb.append(inner_spacer).append("AggregateColumnOffsets[" + cast_node.getAggregateOutputColumns().size() + "]: " + cast_node.getAggregateOutputColumns() + "\n");
            sb.append(inner_spacer).append(PlanNodeUtil.debugOutputColumns("AggregateColumns", cast_node.getAggregateColumnGuids(), line_spacer));
            sb.append(inner_spacer).append(PlanNodeUtil.debugOutputColumns("GroupByColumns", cast_node.getGroupByColumnGuids(), line_spacer));

            // DeletePlanNode
        } else if (node instanceof DeletePlanNode) {
            sb.append(inner_spacer).append("Truncate[" + ((DeletePlanNode) node).isTruncate() + "\n");

            // DistinctPlanNode
        } else if (node instanceof DistinctPlanNode) {
            DistinctPlanNode dist_node = (DistinctPlanNode) node;
            PlanColumn col = PlannerContext.singleton().get(dist_node.getDistinctColumnGuid());
            sb.append(inner_spacer).append("DistinctColumn[" + col + "]\n");

            // IndexScanPlanNode
        } else if (node instanceof IndexScanPlanNode) {
            IndexScanPlanNode cast_node = (IndexScanPlanNode) node;
            sb.append(inner_spacer).append("TargetIndexName[" + cast_node.getTargetIndexName() + "]\n");
            sb.append(inner_spacer).append("EnableKeyIteration[" + cast_node.getKeyIterate() + "]\n");
            sb.append(inner_spacer).append("IndexLookupType[" + cast_node.getLookupType() + "]\n");
            sb.append(inner_spacer).append("SearchKey Expressions:\n");
            for (AbstractExpression search_key : cast_node.getSearchKeyExpressions()) {
                sb.append(ExpressionUtil.debug(search_key, line_spacer));
            }
            sb.append(inner_spacer).append("End Expression: " + (cast_node.getEndExpression() != null ? "\n" + ExpressionUtil.debug(cast_node.getEndExpression(), line_spacer) : null + "\n"));
            sb.append(inner_spacer).append("Post-Scan Expression: " + (cast_node.getPredicate() != null ? "\n" + ExpressionUtil.debug(cast_node.getPredicate(), line_spacer) : null + "\n"));

            // InsertPlanNode
        } else if (node instanceof InsertPlanNode) {
            sb.append(inner_spacer).append("MultiPartition[" + ((InsertPlanNode) node).getMultiPartition() + "]\n");

            // LimitPlanNode
        } else if (node instanceof LimitPlanNode) {
            sb.append(inner_spacer).append("Limit[" + ((LimitPlanNode) node).getLimit() + "]\n");
            sb.append(inner_spacer).append("Offset[" + ((LimitPlanNode) node).getOffset() + "]\n");

            // NestLoopIndexPlanNode
        } else if (node instanceof NestLoopIndexPlanNode) {
            // Nothing

            // NestLoopPlanNode
        } else if (node instanceof NestLoopPlanNode) {
            // Nothing

        } else if (node instanceof OrderByPlanNode) {
            OrderByPlanNode cast_node = (OrderByPlanNode) node;
            sb.append(inner_spacer).append(PlanNodeUtil.debugOutputColumns("SortColumns", cast_node.getSortColumnGuids(), line_spacer));

        } else if (node instanceof ProjectionPlanNode) {
            // ProjectionPlanNode cast_node = (ProjectionPlanNode)node;
            if (node instanceof MaterializePlanNode) {
                sb.append(line_spacer).append("Batched[" + ((MaterializePlanNode) node).isBatched() + "]\n");
            }

        } else if (node instanceof ReceivePlanNode) {
            // Nothing

        } else if (node instanceof SendPlanNode) {
            sb.append(inner_spacer).append("Fake[" + ((SendPlanNode) node).getFake() + "]\n");

        } else if (node instanceof SeqScanPlanNode) {
            sb.append(inner_spacer).append(
                    "Scan Expression: " + (((SeqScanPlanNode) node).getPredicate() != null ? "\n" + ExpressionUtil.debug(((SeqScanPlanNode) node).getPredicate(), line_spacer) : null + "\n"));

        } else if (node instanceof UnionPlanNode) {
            // Nothing
        } else if (node instanceof UpdatePlanNode) {
            sb.append(inner_spacer).append("UpdateIndexes[" + ((UpdatePlanNode) node).doesUpdateIndexes() + "]\n");
        } else {
            throw new RuntimeException("Unsupported PlanNode type: " + node.getClass().getSimpleName());
        }

        // Output Columns
        // if (false && node.getInlinePlanNode(PlanNodeType.PROJECTION) != null)
        // {
        // sb.append(inner_spacer).append(PlanNodeUtil.debugOutputColumns("OutputColumns (Inline Projection)",
        // node.getInlinePlanNode(PlanNodeType.PROJECTION), line_spacer));
        // } else {
        sb.append(inner_spacer).append(PlanNodeUtil.debugOutputColumns("OutputColumns", node.getOutputColumnGUIDs(), line_spacer));
        // }

        // Inline PlanNodes
        if (!node.getInlinePlanNodes().isEmpty()) {
            for (AbstractPlanNode inline_node : node.getInlinePlanNodes().values()) {
                sb.append(inner_spacer).append("Inline " + inline_node + ":\n");
                sb.append(PlanNodeUtil.debug(inline_node, line_spacer));
            }
        }
        return (sb.toString());
    }

    /**
     * For the given StmtParameter object, return the column that it is used
     * against in the Statement
     *
     * @param catalog_stmt_param
     * @return
     * @throws Exception
     */
    public static Column getColumnForStmtParameter(StmtParameter catalog_stmt_param) {
        String param_key = CatalogKey.createKey(catalog_stmt_param);
        String col_key = PlanNodeUtil.CACHE_STMTPARAMETER_COLUMN.get(param_key);

        if (col_key == null) {
            Statement catalog_stmt = catalog_stmt_param.getParent();
            PredicatePairs cset = null;
            try {
                cset = CatalogUtil.extractStatementPredicates(catalog_stmt, false);
            } catch (Throwable ex) {
                throw new RuntimeException("Failed to extract ColumnSet for " + catalog_stmt_param.fullName(), ex);
            }
            assert (cset != null);
            // System.err.println(cset.debug());
            Collection<Column> matches = cset.findAllForOther(Column.class, catalog_stmt_param);
            // System.err.println("MATCHES: " + matches);
            if (matches.isEmpty()) {
                LOG.warn("Unable to find any column with param #" + catalog_stmt_param.getIndex() + " in " + catalog_stmt);
            } else {
                col_key = CatalogKey.createKey(CollectionUtil.first(matches));
            }
            PlanNodeUtil.CACHE_STMTPARAMETER_COLUMN.put(param_key, col_key);
        }
        return (col_key != null ? CatalogKey.getFromKey(CatalogUtil.getDatabase(catalog_stmt_param), col_key, Column.class) : null);
    }

    /**
     * For a given list of PlanFragments, return them in a sorted list based on
     * how they must be executed. The first element in the list will be the
     * first PlanFragment that must be executed (i.e., the one at the bottom of
     * the PlanNode tree).
     *
     * @param catalog_frags
     * @return
     * @throws Exception
     */
    public static List<PlanFragment> sortPlanFragments(List<PlanFragment> catalog_frags) {
        Collections.sort(catalog_frags, PlanNodeUtil.PLANFRAGMENT_EXECUTION_ORDER);
        return (catalog_frags);
    }

    /**
     * Return a list of the PlanFragments for the given Statement that are sorted in
     * the order that they must be executed
     * @param catalog_stmt
     * @param singlePartition
     * @return
     */
    public static List<PlanFragment> getSortedPlanFragments(Statement catalog_stmt, boolean singlePartition) {
        Map<Statement, List<PlanFragment>> cache = (singlePartition ? PlanNodeUtil.CACHE_SORTED_SP_FRAGMENTS : PlanNodeUtil.CACHE_SORTED_MP_FRAGMENTS);
        List<PlanFragment> ret = cache.get(catalog_stmt);
        if (ret == null) {
            CatalogMap<PlanFragment> catalog_frags = null;
            if (singlePartition && catalog_stmt.getHas_singlesited()) {
                catalog_frags = catalog_stmt.getFragments();
            } else if (catalog_stmt.getHas_multisited()) {
                catalog_frags = catalog_stmt.getMs_fragments();
            }

            if (catalog_frags != null) {
                List<PlanFragment> fragments = (List<PlanFragment>) CollectionUtil.addAll(new ArrayList<PlanFragment>(), catalog_frags);
                sortPlanFragments(fragments);
                ret = Collections.unmodifiableList(fragments);
                cache.put(catalog_stmt, ret);
            }
        }
        return (ret);
    }

    /**
     * @param nodes
     * @param singlePartition
     *            TODO
     * @return
     */
    public static AbstractPlanNode reconstructPlanNodeTree(Statement catalog_stmt, List<AbstractPlanNode> nodes, boolean singlePartition) throws Exception {
        if (debug.val)
            LOG.debug("reconstructPlanNodeTree(" + catalog_stmt + ", " + nodes + ", true)");

        // HACK: We should have all SendPlanNodes here, so we just need to order
        // them
        // by their Node ids from lowest to highest (where the root has id = 1)
        TreeSet<AbstractPlanNode> sorted_nodes = new TreeSet<AbstractPlanNode>(new Comparator<AbstractPlanNode>() {
            @Override
            public int compare(AbstractPlanNode o1, AbstractPlanNode o2) {
                // o1 < o2
                return o1.getPlanNodeId() - o2.getPlanNodeId();
            }
        });
        sorted_nodes.addAll(nodes);
        if (debug.val)
            LOG.debug("SORTED NODES: " + sorted_nodes);
        AbstractPlanNode last_node = null;
        for (AbstractPlanNode node : sorted_nodes) {
            final AbstractPlanNode walker_last_node = last_node;
            final List<AbstractPlanNode> next_last_node = new ArrayList<AbstractPlanNode>();
            new PlanNodeTreeWalker() {
                @Override
                protected void callback(AbstractPlanNode element) {
                    if (element instanceof SendPlanNode && walker_last_node != null) {
                        walker_last_node.addAndLinkChild(element);
                    } else if (element instanceof ReceivePlanNode) {
                        assert (next_last_node.isEmpty());
                        next_last_node.add(element);
                    }
                }
            }.traverse(node);

            if (!next_last_node.isEmpty())
                last_node = next_last_node.remove(0);
        } // FOR
        return (CollectionUtil.first(sorted_nodes));
    }

    /**
     * Get the root AbstractPlanNode for the given Statement's query plan If the
     * singlePartition flag is true, then it will return the tree for the
     * single-partition query Otherwise, it will return the distributed query
     * plan tree
     * <B>IMPORTANT:</B> Note that the AbstractPlanNodes returned by this method will
     *                   be different than the ones returned by getPlanNodeTreeForPlanFragment()
     *                   because the roots of the PlanFragment trees will not have any parents,
     *                   whereas in this tree they could.
     * @param catalog_stmt
     * @param singlePartition
     * @return
     */
    public static AbstractPlanNode getRootPlanNodeForStatement(Statement catalog_stmt, boolean singlePartition) {
        if (singlePartition && !catalog_stmt.getHas_singlesited()) {
            String msg = "No single-partition plan is available for " + catalog_stmt + ". ";
            if (catalog_stmt.getHas_multisited()) {
                if (debug.val)
                    LOG.debug(msg + "Going to try to use multi-partition plan");
                return (getRootPlanNodeForStatement(catalog_stmt, false));
            } else {
                LOG.fatal(msg + "No other plan is available");
                return (null);
            }
        } else if (!singlePartition && !catalog_stmt.getHas_multisited()) {
            String msg = "No multi-sited plan is available for " + catalog_stmt + ". ";
            if (catalog_stmt.getHas_singlesited()) {
                if (debug.val)
                    LOG.warn(msg + "Going to try to use single-partition plan");
                return (getRootPlanNodeForStatement(catalog_stmt, true));
            } else {
                LOG.fatal(msg + "No other plan is available");
                return (null);
            }
        }

        // Check whether we have this cached already
        // This is probably not thread-safe because the AbstractPlanNode tree
        // has pointers to specific table catalog objects
        String cache_key = CatalogKey.createKey(catalog_stmt);
        Map<String, AbstractPlanNode> cache = (singlePartition ? PlanNodeUtil.CACHE_DESERIALIZE_SP_STATEMENT : PlanNodeUtil.CACHE_DESERIALIZE_MP_STATEMENT);
        AbstractPlanNode ret = cache.get(cache_key);
        if (ret != null)
            return (ret);

        // Otherwise construct the AbstractPlanNode tree
        Database catalog_db = CatalogUtil.getDatabase(catalog_stmt);
        String fullPlan = (singlePartition ? catalog_stmt.getFullplan() : catalog_stmt.getMs_fullplan());
        if (fullPlan == null || fullPlan.isEmpty()) {
            throw new RuntimeException("Unable to deserialize full query plan tree for " + catalog_stmt + ": The plan attribute is empty");
        }

        try {
            String jsonString = Encoder.hexDecodeToString(fullPlan);
            JSONObject jsonObject = new JSONObject(jsonString);
            PlanNodeList list = (PlanNodeList) PlanNodeTree.fromJSONObject(jsonObject, catalog_db);
            ret = list.getRootPlanNode();
            // } else {
            // //
            // // FIXME: If it's an INSERT query, then we have to use the plan
            // fragments instead of
            // // the full query plan tree because the full plan is missing the
            // MaterializePlanNode
            // // part for some reason.
            // // NEVER TRUST THE FULL PLAN!
            // //
            // JSONObject jsonObject = null;
            // List<AbstractPlanNode> nodes = new ArrayList<AbstractPlanNode>();
            // CatalogMap<PlanFragment> fragments = (singlePartition ?
            // catalog_stmt.getFragments() : catalog_stmt.getMs_fragments());
            // for (PlanFragment catalog_frag : fragments) {
            // String jsonString =
            // Encoder.hexDecodeToString(catalog_frag.getPlannodetree());
            // jsonObject = new JSONObject(jsonString);
            // PlanNodeList list =
            // (PlanNodeList)PlanNodeTree.fromJSONObject(jsonObject,
            // catalog_db);
            // nodes.add(list.getRootPlanNode());
            // } // FOR
            // if (nodes.isEmpty()) {
            // throw new
            // Exception("Failed to retrieve query plan nodes from catalog for "
            // + catalog_stmt + " in " + catalog_stmt.getParent());
            // }
            // try {
            // ret = reconstructPlanNodeTree(catalog_stmt, nodes, true);
            // } catch (Exception ex) {
            // System.out.println("ORIGINAL NODES: " + nodes);
            // throw ex;
            // }
            // }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        if (ret == null) {
            throw new RuntimeException("Unable to deserialize full query plan tree for " + catalog_stmt + ": The deserializer returned a null root node");
        }

        cache.put(cache_key, ret);
        return (ret);
    }

    /**
     * Return all the leaf nodes in the query plan tree
     *
     * @param root
     * @return
     */
    public static Collection<AbstractPlanNode> getLeafPlanNodes(AbstractPlanNode root) {
        final Set<AbstractPlanNode> leaves = new HashSet<AbstractPlanNode>();
        new PlanNodeTreeWalker(false) {
            @Override
            protected void callback(AbstractPlanNode element) {
                if (element.getChildPlanNodeCount() == 0)
                    leaves.add(element);
            }
        }.traverse(root);
        return (leaves);
    }

    /**
     * Returns the PlanNode for the given PlanFragment
     *
     * @param catalog_frag
     * @return
     */
    public static AbstractPlanNode getPlanNodeTreeForPlanFragment(PlanFragment catalog_frag) {
        String id = catalog_frag.getName();
        AbstractPlanNode ret = PlanNodeUtil.CACHE_DESERIALIZE_FRAGMENT.get(id);
        if (ret == null) {
            if (debug.val)
                LOG.warn("No cached object for " + catalog_frag.fullName());
            Database catalog_db = CatalogUtil.getDatabase(catalog_frag);
            String jsonString = Encoder.hexDecodeToString(catalog_frag.getPlannodetree());
            PlanNodeList list = null;
            try {
                JSONObject jsonObject = new JSONObject(jsonString);
                list = (PlanNodeList) PlanNodeTree.fromJSONObject(jsonObject, catalog_db);
            } catch (JSONException ex) {
                String msg = String.format("Invalid PlanNodeTree for %s [plantreeLength=%d, jsonLength=%d]",
                                           catalog_frag.fullName(),
                                           catalog_frag.getPlannodetree().length(),
                                           jsonString.length());
                throw new RuntimeException(msg, ex);
            }
            ret = list.getRootPlanNode();
            PlanNodeUtil.CACHE_DESERIALIZE_FRAGMENT.put(id, ret);
        }
        return (ret);
    }
   
    /**
     * Returns true if the given AbstractPlaNode exists in plan tree for the given PlanFragment
     * This includes inline nodes
     * @param catalog_frag
     * @param node
     * @return
     */
    public static boolean containsPlanNode(final PlanFragment catalog_frag, final AbstractPlanNode node) {
        final AbstractPlanNode root = getPlanNodeTreeForPlanFragment(catalog_frag);
        final boolean found[] = { false };
        new PlanNodeTreeWalker(true) {
            @Override
            protected void callback(AbstractPlanNode element) {
                if (element.equals(node)) {
                    found[0] = true;
                    this.stop();
                }
            }
        }.traverse(root);
        return (found[0]);
    }

    /**
     * Pre-load the cache for all of the PlanFragments
     *
     * @param catalog_db
     * @throws Exception
     */
    public static void preload(Database catalog_db) throws Exception {
        for (Procedure catalog_proc : catalog_db.getProcedures()) {
            if (catalog_proc.getSystemproc() || catalog_proc.getHasjava() == false)
                continue;
            for (Statement catalog_stmt : catalog_proc.getStatements()) {
                if (catalog_stmt.getHas_singlesited()) {
                    getRootPlanNodeForStatement(catalog_stmt, true);
                    getSortedPlanFragments(catalog_stmt, true);
                }
                if (catalog_stmt.getHas_multisited()) {
                    getRootPlanNodeForStatement(catalog_stmt, false);
                    getSortedPlanFragments(catalog_stmt, false);
                }

                for (PlanFragment catalog_frag : catalog_stmt.getFragments()) {
                    getPlanNodeTreeForPlanFragment(catalog_frag);
                } // FOR
                for (PlanFragment catalog_frag : catalog_stmt.getMs_fragments()) {
                    getPlanNodeTreeForPlanFragment(catalog_frag);
                } // FOR
            } // FOR
        } // FOR
    }

    /**
     * Using this Comparator will sort a list of PlanFragments by their
     * execution order
     */
    public static final Comparator<PlanFragment> PLANFRAGMENT_EXECUTION_ORDER = new Comparator<PlanFragment>() {
        @Override
        public int compare(PlanFragment o1, PlanFragment o2) {
            AbstractPlanNode node1 = null;
            AbstractPlanNode node2 = null;
            try {
                node1 = getPlanNodeTreeForPlanFragment(o1);
                node2 = getPlanNodeTreeForPlanFragment(o2);
            } catch (Exception ex) {
                LOG.fatal(ex);
                throw new RuntimeException(ex);
            }
            // o1 > o2
            return (node2.getPlanNodeId() - node1.getPlanNodeId());
        }
    };

}
TOP

Related Classes of edu.brown.plannodes.PlanNodeUtil

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.