com.splicemachine.db.impl.ast.PlanPrinter.java Source code

Java tutorial

Introduction

Here is the source code for com.splicemachine.db.impl.ast.PlanPrinter.java

Source

/*
 * Apache Derby is a subproject of the Apache DB project, and is licensed under
 * the Apache License, Version 2.0 (the "License"); you may not use these files
 * except in compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * Splice Machine, Inc. has modified this file.
 *
 * All Splice Machine modifications are Copyright 2012 - 2016 Splice Machine, Inc.,
 * and are licensed to you under the License; you may not use this file except in
 * compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 */

package com.splicemachine.db.impl.ast;

import org.spark_project.guava.base.Function;
import com.splicemachine.db.iapi.error.StandardException;
import com.splicemachine.db.iapi.sql.compile.CompilerContext;
import com.splicemachine.db.iapi.sql.compile.CostEstimate;
import com.splicemachine.db.iapi.sql.compile.Visitable;
import com.splicemachine.db.iapi.sql.dictionary.ConglomerateDescriptor;
import com.splicemachine.db.iapi.types.DataValueDescriptor;
import com.splicemachine.db.impl.sql.compile.*;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.spark_project.guava.base.Strings;
import org.spark_project.guava.collect.Iterators;
import org.spark_project.guava.collect.Lists;
import java.util.*;

/**
 * @author P Trolard
 *         Date: 30/09/2013
 */
public class PlanPrinter extends AbstractSpliceVisitor {
    public static Logger LOG = Logger.getLogger(PlanPrinter.class);
    public static final String spaces = "  ";
    private boolean explain = false;
    public static ThreadLocal<Map<String, Collection<QueryTreeNode>>> planMap = new ThreadLocal<Map<String, Collection<QueryTreeNode>>>() {
        @Override
        protected Map<String, Collection<QueryTreeNode>> initialValue() {
            return new HashMap<>();
        }
    };

    // Only visit root node
    @Override
    public boolean isPostOrder() {
        return false;
    }

    @Override
    public boolean skipChildren(Visitable node) {
        return true;
    }

    @Override
    public Visitable visit(ExplainNode node) throws StandardException {
        explain = true;
        return visit(node.getPlanRoot());
    }

    @Override
    public Visitable defaultVisit(Visitable node) throws StandardException {
        DMLStatementNode rsn;
        if ((explain || LOG.isDebugEnabled()) && node instanceof DMLStatementNode
                && (((DMLStatementNode) node).getResultSetNode()) != null) {
            rsn = (DMLStatementNode) node;
            List<QueryTreeNode> orderedNodes = new ArrayList<QueryTreeNode>();
            rsn.buildTree(orderedNodes, 0);
            Map<String, Collection<QueryTreeNode>> m = planMap.get();
            m.put(query, orderedNodes);
            if (LOG.isDebugEnabled()) {
                CompilerContext.DataSetProcessorType currentType = rsn.getCompilerContext()
                        .getDataSetProcessorType();
                boolean useSpark = shouldUseSpark(orderedNodes);
                Iterator<String> nodes = planToIterator(orderedNodes, useSpark);
                StringBuffer sb = new StringBuffer();
                while (nodes.hasNext())
                    sb.append(nodes.next() + "\n");
                LOG.info(String.format("Plan nodes for query <<\n\t%s\n>>%s\n", query, sb.toString()));
            }
        }
        explain = false;
        return node;
    }

    public static boolean shouldUseSpark(Collection<QueryTreeNode> opPlanMap) {
        boolean useSpark = false;
        for (QueryTreeNode node : opPlanMap) {
            if (node instanceof FromBaseTable) {
                CompilerContext.DataSetProcessorType dataSetProcessorType = ((FromBaseTable) node)
                        .getDataSetProcessorType();
                if (dataSetProcessorType == CompilerContext.DataSetProcessorType.FORCED_SPARK
                        || dataSetProcessorType == CompilerContext.DataSetProcessorType.SPARK) {
                    useSpark = true;
                    break;
                }
            }
        }

        return useSpark;
    }

    public static Map without(Map m, Object... keys) {
        for (Object k : keys) {
            m.remove(k);
        }
        return m;
    }

    public static Map prune(Map m) {
        List<Object> toPrune = new LinkedList<>();
        for (Map.Entry e : (Set<Map.Entry<Object, Object>>) m.entrySet()) {
            Object val = e.getValue();
            if (val == null || (val instanceof List && ((List) val).size() == 0)) {
                toPrune.add(e.getKey());
            }
        }
        for (Object k : toPrune) {
            m.remove(k);
        }
        return m;
    }

    public static String infoToString(Map<String, Object> info, boolean addSpaces) throws StandardException {
        Map<String, Object> copy = new HashMap<>(info);
        Object clazz = copy.get("class");
        Object results = copy.get("results");
        int level = (Integer) copy.get("level");
        String space;
        if (addSpaces)
            space = Strings.repeat(spaces, level);
        else
            space = "";
        return String.format("%s%s (%s) %s", space, clazz,
                prune(without(copy, "class", "results", "level", "subqueries", "children")),
                results != null ? printResults(level + 2, (List<Map<String, Object>>) results) : "");
    }

    private static String printResults(int nTimes, List<Map<String, Object>> results) {
        String indent = Strings.repeat(spaces, nTimes);
        StringBuilder buf = new StringBuilder("\n");
        for (Map<String, Object> row : results) {
            buf.append(indent).append('{');
            for (Map.Entry<String, Object> entry : row.entrySet()) {
                buf.append(entry).append(", ");
            }
            if (buf.length() > 2 && buf.charAt(buf.length() - 2) == ',') {
                buf.setLength(buf.length() - 2);
                buf.append("}\n");
            }
        }
        if (buf.length() > 0) {
            // remove last '\n'
            buf.setLength(buf.length() - 1);
        }
        return buf.toString();
    }

    public static Map<String, Object> nodeInfo(final ResultSetNode rsn, final int level) throws StandardException {
        Map<String, Object> info = new HashMap<>();
        CostEstimate co = rsn.getFinalCostEstimate().getBase();
        info.put("class", JoinInfo.className.apply(rsn));
        info.put("n", rsn.getResultSetNumber());
        info.put("level", level);
        info.put("cost", co.prettyProcessingString());
        if (Level.TRACE.equals(LOG.getLevel())) {
            //        if(LOG.isTraceEnabled()){
            // FIXME: FIND OUT WHY LOG.isTraceEnabled() always returns false

            // we only want to see exec row info when THIS logger is set to trace:
            // com.splicemachine.db.impl.ast.PlanPrinter=TRACE
            // or
            // call SYSCS_UTIL.SYSCS_SET_LOGGER_LEVEL('com.splicemachine.db.impl.ast.PlanPrinter', 'TRACE');
            info.put("results", getResultColumnInfo(rsn));
        }
        List<ResultSetNode> children = RSUtils.getChildren(rsn);
        info.put("children",
                Lists.reverse(Lists.transform(children, new Function<ResultSetNode, Map<String, Object>>() {
                    @Override
                    public Map<String, Object> apply(ResultSetNode child) {
                        try {
                            return nodeInfo(child, level + 1);
                        } catch (StandardException e) {
                            throw new RuntimeException(e);
                        }
                    }
                })));
        List<SubqueryNode> subs = RSUtils.collectExpressionNodes(rsn, SubqueryNode.class);
        info.put("subqueries", Lists.transform(subs, new Function<SubqueryNode, Map>() {
            @Override
            public Map apply(SubqueryNode subq) {
                try {
                    HashMap<String, Object> subInfo = new HashMap<>();
                    subInfo.put("node", nodeInfo(subq.getResultSet(), 1));
                    subInfo.put("expression?", subq.getSubqueryType() == SubqueryNode.EXPRESSION_SUBQUERY);
                    subInfo.put("correlated?", subq.hasCorrelatedCRs());
                    subInfo.put("invariant?", subq.isInvariant());
                    return subInfo;
                } catch (StandardException e) {
                    throw new RuntimeException(e);
                }
            }
        }));
        if (rsn instanceof JoinNode) {
            JoinNode j = (JoinNode) rsn;
            info.put("exe", RSUtils.ap(j).getJoinStrategy().getName());
            info.put("preds",
                    Lists.transform(PredicateUtils.PLtoList(j.joinPredicates), PredicateUtils.predToString));
        }
        if (rsn instanceof FromBaseTable) {
            FromBaseTable fbt = (FromBaseTable) rsn;
            ConglomerateDescriptor cd = fbt.getTrulyTheBestAccessPath().getConglomerateDescriptor();
            info.put("table", String.format("%s(%s)", fbt.getTableDescriptor().getName(),
                    fbt.getTableDescriptor().getHeapConglomerateId()));
            info.put("quals", Lists.transform(preds(rsn), PredicateUtils.predToString));
            if (cd.isIndex()) {
                info.put("using-index",
                        String.format("%s(%s)", cd.getConglomerateName(), cd.getConglomerateNumber()));
            }

        }
        if (rsn instanceof IndexToBaseRowNode) {
            IndexToBaseRowNode idx = (IndexToBaseRowNode) rsn;
            //info.put("name", idx.getName());
        }
        if (rsn instanceof ProjectRestrictNode) {
            info.put("quals", Lists.transform(preds(rsn), PredicateUtils.predToString));
        }
        if (rsn instanceof WindowResultSetNode) {
            info.put("functions", ((WindowResultSetNode) rsn).getFunctionNames());
        }
        return info;
    }

    public static List<Map<String, Object>> getResultColumnInfo(ResultSetNode rsn) throws StandardException {
        List<Map<String, Object>> resultColumns = new ArrayList<Map<String, Object>>();
        ResultColumnList resultColumnList = rsn.getResultColumns();
        if (resultColumnList != null && resultColumnList.size() > 0) {

            for (ResultColumn resultColumn : resultColumnList) {
                Map<String, Object> columnInfo = new LinkedHashMap<String, Object>();
                if (resultColumn != null) {
                    columnInfo.put("column", resultColumn.getName());
                    columnInfo.put("position", resultColumn.getColumnPosition());
                    columnInfo.put("type", resultColumn.getType());
                    ValueNode exp = resultColumn.getExpression();
                    if (exp != null) {
                        String columnName = exp.getColumnName();
                        if (columnName != null) {
                            columnInfo.put("exp", columnName);
                        }
                        String src = "null";
                        if (exp instanceof VirtualColumnNode) {
                            ResultColumn rc = ((VirtualColumnNode) exp).getSourceColumn();
                            if (rc != null) {
                                src = rc.getName() + "(from RS " + rc.getResultSetNumber() + ")";
                            }
                        } else if (exp instanceof ColumnReference) {
                            ResultColumn rc = ((ColumnReference) exp).getSource();
                            if (rc != null) {
                                src = rc.getName() + "(from RS " + rc.getResultSetNumber() + ")";
                            }
                        } else if (exp instanceof ConstantNode) {
                            DataValueDescriptor dvd = ((ConstantNode) exp).getValue();
                            if (dvd != null) {
                                src = dvd.getString();
                            }
                        }
                        columnInfo.put("src", src);
                        columnInfo.put("exp node", exp.getClass().getSimpleName());
                    }
                }
                resultColumns.add(columnInfo);
            }
        }
        return resultColumns;
    }

    public static List<Map<String, Object>> linearizeNodeInfoTree(Map<String, Object> info)
            throws StandardException {
        List<Map<String, Object>> children = (List<Map<String, Object>>) info.get("children");
        List<Map<String, Object>> nodes = new LinkedList<Map<String, Object>>();
        nodes.add(info);
        for (Map<String, Object> child : Lists.reverse(children)) {
            nodes.addAll(linearizeNodeInfoTree(child));
        }
        return nodes;
    }

    public static String treeToString(Map<String, Object> nodeInfo) throws StandardException {
        List<Pair<Integer, Map>> subs = new LinkedList<>();
        StringBuilder sb = new StringBuilder();
        List<Map<String, Object>> nodes = linearizeNodeInfoTree(nodeInfo);
        for (Map<String, Object> node : nodes) {
            List<Map> subqs = (List<Map>) node.get("subqueries");
            if (subqs != null) {
                for (Map subInfo : subqs) {
                    subs.add(Pair.of((Integer) node.get("n"), subInfo));
                }
            }
            sb.append(infoToString(node, true));
            sb.append("\n");
        }
        for (Pair<Integer, Map> sub : subs) {
            Map subInfo = sub.getRight();
            Map<String, Object> subqInfoNode = (Map<String, Object>) subInfo.get("node");
            sb.append(subqueryToString(subInfo, subqInfoNode));
            sb.append(treeToString(subqInfoNode));
        }
        return sb.toString();
    }

    private static String subqueryToString(Map subInfo, Map<String, Object> subqInfoNode) {
        return String.format("\nSubquery n=%s: expression?=%s, invariant?=%s, correlated?=%s\n",
                subqInfoNode.get("n"), subInfo.get("expression?"), subInfo.get("invariant?"),
                subInfo.get("correlated?"));
    }

    public static String treeToString(ResultSetNode rsn, int initLevel) throws StandardException {
        return treeToString(nodeInfo(rsn, initLevel));
    }

    public static String treeToString(ResultSetNode rsn) throws StandardException {
        return treeToString(rsn, 0);
    }

    private static List<Predicate> preds(ResultSetNode t) throws StandardException {
        PredicateList pl;
        if (t instanceof FromBaseTable)
            pl = RSUtils.getPreds((FromBaseTable) t);
        else if (t instanceof ProjectRestrictNode)
            pl = RSUtils.getPreds((ProjectRestrictNode) t);
        else if (t instanceof IndexToBaseRowNode)
            pl = RSUtils.getPreds((IndexToBaseRowNode) t);
        else
            throw new IllegalArgumentException(
                    "Programmer error: Unable to determine class with predicates:" + t.getClass());

        return PredicateUtils.PLtoList(pl);
    }

    private static List<Pair<String, Integer>> planToString(ResultSetNode rsn) throws StandardException {
        Map<String, Object> nodeInfo = nodeInfo(rsn, 0);
        List<Pair<String, Integer>> flattenedPlanMap = new LinkedList<>();
        //        Map<Integer,String> planMap = new HashMap<>();
        pushPlanInfo(nodeInfo, flattenedPlanMap);
        return flattenedPlanMap;
    }

    private static void pushPlanInfo(Map<String, Object> nodeInfo, List<Pair<String, Integer>> planMap)
            throws StandardException {
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> children = (List<Map<String, Object>>) nodeInfo.get("children");
        String thisNodeInfo = infoToString(nodeInfo, false);
        Integer level = (Integer) nodeInfo.get("level");
        planMap.add(Pair.of(thisNodeInfo, level));
        for (Map<String, Object> child : children) {
            pushPlanInfo(child, planMap);
        }

        if (!nodeInfo.containsKey("subqueries"))
            return; //nothing to work with
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> subqueries = (List<Map<String, Object>>) nodeInfo.get("subqueries");
        for (Map<String, Object> subquery : subqueries) {
            Map<String, Object> subqueryNodeInfo = (Map<String, Object>) subquery.get("node");
            pushPlanInfo(subqueryNodeInfo, planMap);
            String subqueryInfo = subqueryToString(subquery, subqueryNodeInfo);
            planMap.add(Pair.of(subqueryInfo, level));
        }
    }

    public class PlanToString implements Function<QueryTreeNode, String> {
        int size;
        int counter = 0;

        public PlanToString(int size) {
            this.size = size;
        }

        @Override
        public String apply(QueryTreeNode o) {
            try {
                return o.printExplainInformation(size - counter);
            } catch (StandardException se) {
                throw new RuntimeException(se);
            } finally {
                counter++;
            }
        }
    }

    public static Iterator<String> planToIterator(final Collection<QueryTreeNode> orderedNodes,
            final boolean useSpark) throws StandardException {
        return Iterators.transform(orderedNodes.iterator(), new Function<QueryTreeNode, String>() {
            int i = 0;

            @Override
            public String apply(QueryTreeNode queryTreeNode) {
                try {
                    return queryTreeNode.printExplainInformation(orderedNodes.size(), i, useSpark);
                } catch (StandardException se) {
                    throw new RuntimeException(se);
                } finally {
                    i++;
                }
            }
        });
    }

}