org.apache.drill.PlanTestBase.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.drill.PlanTestBase.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file 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.
 */

package org.apache.drill;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.exec.record.RecordBatchLoader;
import org.apache.drill.exec.record.VectorWrapper;
import org.apache.drill.exec.rpc.user.QueryDataBatch;
import org.apache.drill.exec.vector.NullableVarCharVector;
import org.apache.drill.exec.vector.ValueVector;
import org.apache.calcite.sql.SqlExplain.Depth;
import org.apache.calcite.sql.SqlExplainLevel;

import com.google.common.base.Strings;

public class PlanTestBase extends BaseTestQuery {

    protected static final String OPTIQ_FORMAT = "text";
    protected static final String JSON_FORMAT = "json";
    protected static final String EXPECTED_NOT_FOUND = "Did not find expected pattern in plan: ";
    protected static final String UNEXPECTED_FOUND = "Found unwanted pattern in plan: ";

    // TODO - enhance this to support regex, and excluded patterns like the
    // check method below for optiq format plans
    /**
     * This method will take a SQL string statement, get the PHYSICAL plan in json
     * format. Then check the physical plan against the list expected substrs.
     * Verify all the expected strings are contained in the physical plan string.
     */
    public static void testPhysicalPlan(String sql, String... expectedSubstrs) throws Exception {
        sql = "EXPLAIN PLAN for " + QueryTestUtil.normalizeQuery(sql);

        final String planStr = getPlanInString(sql, JSON_FORMAT);

        for (final String colNames : expectedSubstrs) {
            assertTrue(String.format("Unable to find expected string %s in plan: %s!", colNames, planStr),
                    planStr.contains(colNames));
        }
    }

    /**
     * Runs an explain plan query and check for expected regex patterns (in optiq
     * text format), also ensure excluded patterns are not found. Either list can
     * be empty or null to skip that part of the check.
     *
     * See the convenience methods for passing a single string in either the
     * excluded list, included list or both.
     *
     * @param query - an explain query, this method does not add it for you
     * @param expectedPatterns - list of patterns that should appear in the plan
     * @param excludedPatterns - list of patterns that should not appear in the plan
     * @throws Exception - if an inclusion or exclusion check fails, or the
     *                     planning process throws an exception
     */
    public static void testPlanMatchingPatterns(String query, String[] expectedPatterns, String[] excludedPatterns)
            throws Exception {
        final String plan = getPlanInString("EXPLAIN PLAN for " + QueryTestUtil.normalizeQuery(query),
                OPTIQ_FORMAT);

        // Check and make sure all expected patterns are in the plan
        if (expectedPatterns != null) {
            for (final String s : expectedPatterns) {
                final Pattern p = Pattern.compile(s);
                final Matcher m = p.matcher(plan);
                assertTrue(EXPECTED_NOT_FOUND + s, m.find());
            }
        }

        // Check and make sure all excluded patterns are not in the plan
        if (excludedPatterns != null) {
            for (final String s : excludedPatterns) {
                final Pattern p = Pattern.compile(s);
                final Matcher m = p.matcher(plan);
                assertFalse(UNEXPECTED_FOUND + s, m.find());
            }
        }
    }

    /**
     * Runs an explain plan query and check for expected substring patterns (in optiq
     * text format), also ensure excluded patterns are not found. Either list can
     * be empty or null to skip that part of the check.
     *
     * This is different from testPlanMatchingPatterns in that this one use substring contains,
     * in stead of regex pattern matching. This one is useful when the pattern contains
     * many regex reserved chars, and you do not want to put the escape char.
     *
     * See the convenience methods for passing a single string in either the
     * excluded list, included list or both.
     *
     * @param query - an explain query, this method does not add it for you
     * @param expectedPatterns - list of patterns that should appear in the plan
     * @param excludedPatterns - list of patterns that should not appear in the plan
     * @throws Exception - if an inclusion or exclusion check fails, or the
     *                     planning process throws an exception
     */
    public static void testPlanSubstrPatterns(String query, String[] expectedPatterns, String[] excludedPatterns)
            throws Exception {
        final String plan = getPlanInString("EXPLAIN PLAN for " + QueryTestUtil.normalizeQuery(query),
                OPTIQ_FORMAT);

        // Check and make sure all expected patterns are in the plan
        if (expectedPatterns != null) {
            for (final String s : expectedPatterns) {
                assertTrue(EXPECTED_NOT_FOUND + s, plan.contains(s));
            }
        }

        // Check and make sure all excluded patterns are not in the plan
        if (excludedPatterns != null) {
            for (final String s : excludedPatterns) {
                assertFalse(UNEXPECTED_FOUND + s, plan.contains(s));
            }
        }
    }

    public static void testPlanOneExpectedPatternOneExcluded(String query, String expectedPattern,
            String excludedPattern) throws Exception {
        testPlanMatchingPatterns(query, new String[] { expectedPattern }, new String[] { excludedPattern });
    }

    public static void testPlanOneExpectedPattern(String query, String expectedPattern) throws Exception {
        testPlanMatchingPatterns(query, new String[] { expectedPattern }, new String[] {});
    }

    public static void testPlanOneExcludedPattern(String query, String excludedPattern) throws Exception {
        testPlanMatchingPatterns(query, new String[] {}, new String[] { excludedPattern });
    }

    /**
     * This method will take a SQL string statement, get the LOGICAL plan in Optiq
     * RelNode format. Then check the physical plan against the list expected
     * substrs. Verify all the expected strings are contained in the physical plan
     * string.
     */
    public static void testRelLogicalJoinOrder(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.EXPPLAN_ATTRIBUTES, Depth.LOGICAL);
        final String prefixJoinOrder = getLogicalPrefixJoinOrderFromPlan(planStr);
        System.out.println(" prefix Join order = \n" + prefixJoinOrder);
        for (final String substr : expectedSubstrs) {
            assertTrue(
                    String.format("Expected string %s is not in the prefixJoinOrder %s!", substr, prefixJoinOrder),
                    prefixJoinOrder.contains(substr));
        }
    }

    /**
     * This method will take a SQL string statement, get the LOGICAL plan in Optiq
     * RelNode format. Then check the physical plan against the list expected
     * substrs. Verify all the expected strings are contained in the physical plan
     * string.
     */
    public static void testRelPhysicalJoinOrder(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.EXPPLAN_ATTRIBUTES, Depth.PHYSICAL);
        final String prefixJoinOrder = getPhysicalPrefixJoinOrderFromPlan(planStr);
        System.out.println(" prefix Join order = \n" + prefixJoinOrder);
        for (final String substr : expectedSubstrs) {
            assertTrue(
                    String.format("Expected string %s is not in the prefixJoinOrder %s!", substr, prefixJoinOrder),
                    prefixJoinOrder.contains(substr));
        }
    }

    /**
     * This method will take a SQL string statement, get the PHYSICAL plan in
     * Optiq RelNode format. Then check the physical plan against the list
     * expected substrs. Verify all the expected strings are contained in the
     * physical plan string.
     */
    public static void testRelPhysicalPlanLevDigest(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.DIGEST_ATTRIBUTES, Depth.PHYSICAL);
        for (final String substr : expectedSubstrs) {
            assertTrue(planStr.contains(substr));
        }
    }

    /**
     * This method will take a SQL string statement, get the LOGICAL plan in Optiq
     * RelNode format. Then check the physical plan against the list expected
     * substrs. Verify all the expected strings are contained in the physical plan
     * string.
     */
    public static void testRelLogicalPlanLevDigest(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.DIGEST_ATTRIBUTES, Depth.LOGICAL);

        for (final String substr : expectedSubstrs) {
            assertTrue(planStr.contains(substr));
        }
    }

    /**
     * This method will take a SQL string statement, get the PHYSICAL plan in
     * Optiq RelNode format. Then check the physical plan against the list
     * expected substrs. Verify all the expected strings are contained in the
     * physical plan string.
     */
    public static void testRelPhysicalPlanLevExplain(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.EXPPLAN_ATTRIBUTES, Depth.PHYSICAL);

        for (final String substr : expectedSubstrs) {
            assertTrue(planStr.contains(substr));
        }
    }

    /**
     * This method will take a SQL string statement, get the LOGICAL plan in Optiq
     * RelNode format. Then check the physical plan against the list expected
     * substrs. Verify all the expected strings are contained in the physical plan
     * string.
     */
    public static void testRelLogicalPlanLevExplain(String sql, String... expectedSubstrs) throws Exception {
        final String planStr = getDrillRelPlanInString(sql, SqlExplainLevel.EXPPLAN_ATTRIBUTES, Depth.LOGICAL);

        for (final String substr : expectedSubstrs) {
            assertTrue(planStr.contains(substr));
        }
    }

    /*
     * This will get the plan (either logical or physical) in Optiq RelNode
     * format, based on SqlExplainLevel and Depth.
     */
    private static String getDrillRelPlanInString(String sql, SqlExplainLevel level, Depth depth) throws Exception {
        String levelStr = " ", depthStr = " ";

        switch (level) {
        case NO_ATTRIBUTES:
            levelStr = "EXCLUDING ATTRIBUTES";
            break;
        case EXPPLAN_ATTRIBUTES:
            levelStr = "INCLUDING ATTRIBUTES";
            break;
        case ALL_ATTRIBUTES:
            levelStr = "INCLUDING ALL ATTRIBUTES";
            break;
        default:
            break;
        }

        switch (depth) {
        case TYPE:
            depthStr = "WITH TYPE";
            break;
        case LOGICAL:
            depthStr = "WITHOUT IMPLEMENTATION";
            break;
        case PHYSICAL:
            depthStr = "WITH IMPLEMENTATION";
            break;
        default:
            throw new UnsupportedOperationException();
        }

        sql = "EXPLAIN PLAN " + levelStr + " " + depthStr + "  for " + QueryTestUtil.normalizeQuery(sql);

        return getPlanInString(sql, OPTIQ_FORMAT);
    }

    /*
     * This will submit an "EXPLAIN" statement, and return the column value which
     * contains the plan's string.
     */
    protected static String getPlanInString(String sql, String columnName) throws Exception {
        final List<QueryDataBatch> results = testSqlWithResults(sql);
        final RecordBatchLoader loader = new RecordBatchLoader(getDrillbitContext().getAllocator());
        final StringBuilder builder = new StringBuilder();

        for (final QueryDataBatch b : results) {
            if (!b.hasData()) {
                continue;
            }

            loader.load(b.getHeader().getDef(), b.getData());

            final VectorWrapper<?> vw;
            try {
                vw = loader.getValueAccessorById(NullableVarCharVector.class,
                        loader.getValueVectorId(SchemaPath.getSimplePath(columnName)).getFieldIds());
            } catch (Throwable t) {
                throw new Exception(
                        "Looks like you did not provide an explain plan query, please add EXPLAIN PLAN FOR to the beginning of your query.");
            }

            System.out.println(vw.getValueVector().getField().toExpr());
            final ValueVector vv = vw.getValueVector();
            for (int i = 0; i < vv.getAccessor().getValueCount(); i++) {
                final Object o = vv.getAccessor().getObject(i);
                builder.append(o);
                System.out.println(vv.getAccessor().getObject(i));
            }
            loader.clear();
            b.release();
        }

        return builder.toString();
    }

    private static String getLogicalPrefixJoinOrderFromPlan(String plan) {
        return getPrefixJoinOrderFromPlan(plan, "DrillJoinRel", "DrillScanRel");
    }

    private static String getPhysicalPrefixJoinOrderFromPlan(String plan) {
        return getPrefixJoinOrderFromPlan(plan, "JoinPrel", "ScanPrel");
    }

    private static String getPrefixJoinOrderFromPlan(String plan, String joinKeyWord, String scanKeyWord) {
        final StringBuilder builder = new StringBuilder();
        final String[] planLines = plan.split("\n");
        int cnt = 0;
        final Stack<Integer> s = new Stack<>();

        for (final String line : planLines) {
            if (line.trim().isEmpty()) {
                continue;
            }
            if (line.contains(joinKeyWord)) {
                builder.append(Strings.repeat(" ", 2 * s.size()));
                builder.append(joinKeyWord + "\n");
                cnt++;
                s.push(cnt);
                cnt = 0;
                continue;
            }

            if (line.contains(scanKeyWord)) {
                cnt++;
                builder.append(Strings.repeat(" ", 2 * s.size()));
                builder.append(line.trim() + "\n");

                if (cnt == 2) {
                    cnt = s.pop();
                }
            }
        }

        return builder.toString();
    }
}