com.tesora.dve.sql.parser.InvokeParser.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.sql.parser.InvokeParser.java

Source

package com.tesora.dve.sql.parser;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * 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 this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.TokenRewriteStream;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.tesora.dve.common.PECharsetUtils;
import com.tesora.dve.exceptions.PECodingException;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.ParserException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.PlannerStatisticType;
import com.tesora.dve.sql.PlannerStatistics;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.cache.CandidateCachedPlan;
import com.tesora.dve.sql.schema.cache.PlanCacheUtils;
import com.tesora.dve.sql.schema.cache.PlanCacheUtils.PlanCacheCallback;
import com.tesora.dve.sql.schema.types.Type;
import com.tesora.dve.sql.statement.CacheableStatement;
import com.tesora.dve.sql.statement.EmptyStatement;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.StatementType;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.session.TransactionStatement;
import com.tesora.dve.sql.transform.execution.ConnectionValuesMap;
import com.tesora.dve.sql.transform.execution.ExecutionPlan;
import com.tesora.dve.sql.transform.execution.RootExecutionPlan;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.variables.KnownVariables;

public class InvokeParser {

    public static final Logger sqlLogger = Logger.getLogger("sql.logger");
    public static final Logger logger = Logger.getLogger(InvokeParser.class);

    public static final long defaultLargeInsertThreshold = 1024 * 1024;

    public static String parseOneLine(InputState line, ParserOptions opts) throws Exception {
        TranslatorUtils utils = new TranslatorUtils(opts, null, line);
        PE parser = buildParser(line, utils);
        Object firstPass = parser.sql_statements().getTree();
        Exception any = utils.buildError();
        if (any != null)
            throw any;
        return utils.displayTree(null, firstPass);
    }

    public static sql2003Lexer buildLexer(InputState inputStatement, Utils utils) {
        ANTLRStringStream input = inputStatement.buildNewStream();
        sql2003Lexer lexer = new sql2003Lexer(input);
        lexer.setUtils(utils);
        return lexer;
    }

    private static PE buildParser(sql2003Lexer lexer, TranslatorUtils utils) {
        // CommonTokenStream tokens = new CommonTokenStream(lexer);
        TokenRewriteStream tokens = new TokenRewriteStream(lexer);
        PE parser = new PE(tokens);
        parser.setUtils(utils);
        return parser;
    }

    private static PE buildParser(InputState line, TranslatorUtils utils) {
        return buildParser(buildLexer(line, utils), utils);
    }

    public static InputState buildInputState(String icmd, SchemaContext pc) {
        long maxLen = KnownVariables.LARGE_INSERT_CUTOFF
                .getValue(pc == null ? null : pc.getConnection().getVariableSource()).longValue();
        if (icmd.length() > maxLen)
            return new ContinuationInputState(icmd, maxLen);
        return new InitialInputState(icmd);
    }

    public static ParseResult parse(InputState icmd, ParserOptions opts, SchemaContext pc) throws ParserException {
        return parse(icmd, DEFAULT_PARSER_RULE, opts, pc, TranslatorInitCallback.INSTANCE);
    }

    public static ParseResult parse(InputState icmd, ParserEntryPoint entryPoint, ParserOptions opts,
            SchemaContext pc, TranslatorInitCallback ticb) throws ParserException {
        preparse(pc);
        return parse(icmd, entryPoint, opts, pc, Collections.emptyList(), ticb);
    }

    private static void preparse(SchemaContext pc) throws ParserException {
        if (pc != null) {
            // before we do anything, figure out if the current user is a
            // tenant, and if so, if they have been suspended
            pc.getPolicyContext().checkEnabled();
        }
    }

    private static ParseResult parse(InputState input, ParserEntryPoint entry, ParserOptions opts, SchemaContext pc,
            List<Object> parameters, TranslatorInitCallback cb) throws ParserException {
        // debug log is set only for non tests
        if (pc != null) {
            pc.setOptions(opts);
            pc.setParameters(parameters);
            SchemaContext.threadContext.set(pc);
        }
        Pair<TranslatorUtils, List<Statement>> result = null;
        if (pc != null && pc.getIntraStmtState().isUnderLockTable()) {
            result = parseFastInsert(pc, opts, input);
        }
        if (result == null)
            result = parse(pc, entry, opts, input, cb);
        List<Statement> stmts = result.getSecond();
        TranslatorUtils utils = result.getFirst();
        if (stmts.isEmpty())
            stmts.add(new EmptyStatement(""));
        try {
            if (!opts.isTSchema())
                utils.assignPositions();
        } catch (Throwable t) {
            throw new ParserException(Pass.SECOND, "Unable to complete parsing for '" + input.describe() + "'", t);
        }
        for (Statement s : stmts) {
            if (logger.isDebugEnabled() && opts.isDebugLog()) {
                if (pc != null)
                    logger.debug("  Statement (" + pc.describeContext() + ") => " + s.getSQL(pc, true, true));
                else
                    logger.debug("  Statement => " + s.getSQL(pc, true, true));
            }
        }
        // break a self referential chain
        utils.setContext(null);
        return new ParseResult(stmts, utils.getInputState(input));
    }

    @SuppressWarnings("unchecked")
    private static Pair<TranslatorUtils, List<Statement>> parseFastInsert(SchemaContext pc, ParserOptions opts,
            InputState icmd) {
        // first of all, make sure it's an actual insert statement
        if (!icmd.isInsert())
            return null;
        TranslatorUtils utils = new TranslatorUtils(opts, pc, icmd);
        PE parser = buildParser(icmd, utils);
        if (pc != null)
            pc.setTokenStream(parser.getTokenStream(), icmd.getCommand());
        List<Statement> stmts = null;
        List<List<ExpressionNode>> continuedInsert = null;
        try {
            if (icmd.getCurrentPosition() == 0) {
                stmts = Collections.singletonList(parser.fast_insert_statement().s);
            } else {
                continuedInsert = parser.fast_continuation_insert_value_list().l;
                utils.popScope();
            }
        } catch (EnoughException ee) {
            return new Pair<TranslatorUtils, List<Statement>>(utils,
                    Collections.singletonList(((Statement) ee.getInsertStatement())));
        } catch (Throwable t) {
            // basically, just return null and try again
            return null;
        } finally {
            if (pc != null)
                pc.clearOrigStmt();
        }
        ParserException any = utils.buildError();
        if (any != null)
            return null;
        if (stmts == null) {
            stmts = new ArrayList<Statement>();
            if (continuedInsert == null || continuedInsert.isEmpty()) {
                stmts.add(new TransactionStatement(TransactionStatement.Kind.COMMIT));
            } else {
                stmts.add(utils.buildInsertStatement(continuedInsert, false, TransactionStatement.Kind.COMMIT, null,
                        false));
            }
        }
        return new Pair<TranslatorUtils, List<Statement>>(utils, stmts);
    }

    public static Type parseType(final SchemaContext pc, final ParserOptions opts, final String typeDescriptor) {
        final InputState icmd = buildInputState(typeDescriptor, pc);
        final TranslatorUtils utils = new TranslatorUtils(opts, pc, icmd);
        final PE parser = buildParser(icmd, utils);
        try {
            return parser.type_description().type;
        } catch (final RecognitionException e) {
            throw new PECodingException("Could not parse the type descriptor: '" + typeDescriptor + "'", e);
        }
    }

    public static ExpressionNode parseExpression(SchemaContext pc, String input) {
        InputState icmd = buildInputState(input, pc);
        ParserOptions opts = ParserOptions.NONE.setDebugLog(true).setResolve().setFailEarly().setActualLiterals();
        TranslatorUtils utils = new TranslatorUtils(opts, pc, icmd);
        PE parser = buildParser(icmd, utils);
        try {
            return parser.value_expression().expr;
        } catch (Throwable t) {
            throw new SchemaException(Pass.PLANNER, "Unable to parser expression '" + input + "'", t);
        }
    }

    @SuppressWarnings("unchecked")
    private static Pair<TranslatorUtils, List<Statement>> parse(SchemaContext pc, ParserEntryPoint entry,
            ParserOptions opts, InputState input, TranslatorInitCallback cb) {
        TranslatorUtils utils = new TranslatorUtils(opts, pc, input);
        cb.onInit(utils);
        PE parser = buildParser(input, utils);
        if (pc != null)
            pc.setTokenStream(parser.getTokenStream(), input.getCommand());
        List<Statement> stmts = null;
        List<List<ExpressionNode>> continuedInsert = null;
        try {
            if (input.getCurrentPosition() == 0) {
                stmts = entry.invoke(parser);
                //            stmts = parser.sql_statements().stmts;
            } else {
                continuedInsert = parser.continuation_insert_value_list().l;
                utils.popScope();
            }
        } catch (EnoughException ee) {
            return new Pair<TranslatorUtils, List<Statement>>(utils,
                    Collections.singletonList(((Statement) ee.getInsertStatement())));
        } catch (ParserException pe) {
            throw pe;
        } catch (Throwable t) {
            throw new ParserException(Pass.SECOND, "Unable to parse '" + input.describe() + "'", t);
        } finally {
            // clear the input string
            if (pc != null)
                pc.clearOrigStmt();
        }
        ParserException any = utils.buildError();
        if (any != null)
            throw any;
        if (stmts == null) {
            stmts = new ArrayList<Statement>();
            if (continuedInsert == null || continuedInsert.isEmpty()) {
                stmts.add(new TransactionStatement(TransactionStatement.Kind.COMMIT));
            } else {
                stmts.add(utils.buildInsertStatement(continuedInsert, false, TransactionStatement.Kind.COMMIT, null,
                        false));
            }
        }
        return new Pair<TranslatorUtils, List<Statement>>(utils, stmts);
    }

    public static List<Statement> parse(String line, SchemaContext pc) throws ParserException {
        return parse(line, pc, Collections.emptyList());
    }

    public static List<Statement> parse(String line, SchemaContext pc, List<Object> params, ParserOptions options)
            throws ParserException {
        return parse(line, pc, params, options, TranslatorInitCallback.INSTANCE);
    }

    public static List<Statement> parseTriggerBody(String body, SchemaContext pc, ParserOptions options,
            TranslatorInitCallback ticb) throws ParserException {
        return parse(body, pc, Collections.emptyList(), TRIGGER_PARSER_RULE, options, ticb);
    }

    public static List<Statement> parse(String line, SchemaContext pc, List<Object> params, ParserOptions options,
            TranslatorInitCallback ticb) throws ParserException {
        return parse(line, pc, params, DEFAULT_PARSER_RULE, options, ticb);
    }

    public static List<Statement> parse(String line, SchemaContext pc, List<Object> params, ParserEntryPoint entry,
            ParserOptions opts, TranslatorInitCallback ticb) throws ParserException {
        return parse(buildInputState(line, pc), entry, opts, pc, params, ticb).getStatements();
    }

    public static List<Statement> parse(String line, SchemaContext pc, List<Object> params) throws ParserException {
        // the tests were all written to use latin1
        ParserOptions options = ParserOptions.NONE.setDebugLog(true).setResolve().setFailEarly();
        return parse(line, pc, params, options);
    }

    public static ParseResult parse(byte[] line, SchemaContext pc, Charset cs) throws ParserException {
        preparse(pc);
        ParserOptions options = ParserOptions.NONE.setDebugLog(true).setResolve().setFailEarly();

        String lineStr = PECharsetUtils.getString(line, cs, true);
        if (lineStr != null) {
            lineStr = StringUtils.strip(lineStr, new String(Character.toString(Character.MIN_VALUE)));
            return parse(buildInputState(lineStr, pc), options, pc);
        }
        return parameterizeAndParse(pc, options, line, cs);
    }

    public static ListOfPairs<Statement, List<Object>> buildParameterizedCommands(String in, Charset cs,
            SchemaContext pc) {
        // note that we are _not_ setting resolve here
        ParserOptions options = ParserOptions.NONE.setDebugLog(false);
        ParseResult results = parse(buildInputState(in, pc), options, pc);
        if (results.hasMore())
            throw new ParserException(Pass.FIRST, "Unable to parameterize for invalid chars - input too large");
        List<Statement> stmts = parse(buildInputState(in, pc), options, pc).getStatements();
        ListOfPairs<Statement, List<Object>> out = new ListOfPairs<Statement, List<Object>>();
        for (Statement s : stmts)
            try {
                out.add(s, s.extractParameters(pc, PECharsetUtils.latin1, cs));
            } catch (PEException pe) {
                throw new ParserException(Pass.FIRST, "Unable to extract parameters for input stmt '" + in
                        + "' in order to handle character set " + cs.name(), pe);
            }
        return out;
    }

    private static ParseResult parameterizeAndParse(SchemaContext pc, ParserOptions options, byte[] line,
            Charset cs) throws ParserException {
        String singleByteEncoded = PECharsetUtils.getString(line, PECharsetUtils.latin1, false);

        logParse(pc, singleByteEncoded, null);

        ListOfPairs<Statement, List<Object>> parameterized = buildParameterizedCommands(singleByteEncoded, cs, pc);
        ArrayList<Statement> out = new ArrayList<Statement>();
        for (Pair<Statement, List<Object>> p : parameterized) {
            DMLStatement dmls = (DMLStatement) p.getFirst();
            String msql = dmls.getSQL(pc, false, true);
            List<Object> params = p.getSecond();
            byte[] modstmt = msql.getBytes(PECharsetUtils.latin1);
            String orig = PECharsetUtils.getString(modstmt, cs, true);
            if (orig == null)
                throw new ParserException(Pass.FIRST,
                        "Unable to parameterize SQL statement to handle characters invalid for character set "
                                + cs.name());
            orig = StringUtils.strip(orig, new String(Character.toString(Character.MIN_VALUE)));
            out.addAll(parse(buildInputState(orig, pc), DEFAULT_PARSER_RULE, options, pc, params,
                    TranslatorInitCallback.INSTANCE).getStatements());
        }
        return new ParseResult(out, null);
    }

    public static PlanningResult preparePlan(SchemaContext pc, InputState input, ParserOptions options,
            String pstmtID) throws PEException {
        ParseResult pr = parse(input, options, pc);
        if (pr.getStatements().size() > 1)
            throw new PEException("Cannot prepare more than one statement at a time");
        Statement toPrepare = pr.getStatements().get(0);
        return Statement.prepare(pc, toPrepare, pc.getBehaviorConfiguration(), pstmtID, input.getCommand());
    }

    public static PlanningResult buildPlan(SchemaContext pc, InputState input, ParserOptions options,
            PlanCacheCallback ipcb) throws PEException {
        PlanCacheCallback pcb = (ipcb == null ? logCacheCallback : ipcb);
        List<ExecutionPlan> plans = null;
        ConnectionValuesMap values = null;
        boolean tryCache = pc.getSource().canCachePlans(pc) && !pc.getIntraStmtState().isUnderLockTable();
        CandidateCachedPlan ccp = null;
        if (!pc.getSource().isPlanCacheEmpty() && input.getCommand() != null) {
            ccp = PlanCacheUtils.getCachedPlan(pc, input.getCommand(), pcb);
            if (ccp.getPlan() != null) {
                plans = new ArrayList<ExecutionPlan>();
                plans.add(ccp.getPlan());
                values = ccp.getValues();
            } else if (!ccp.tryCaching()) {
                tryCache = false;
            }
        }
        if (plans == null) {
            ParseResult pr = parse(input, options, pc);
            plans = new ArrayList<ExecutionPlan>();
            Statement first = null;
            boolean explain = false;
            values = new ConnectionValuesMap();
            for (Statement s : pr.getStatements()) {
                if (first == null)
                    first = s;
                PlanningResult builtPlan = null;
                if (s.isExplain()) {
                    explain = true;
                    builtPlan = buildExplainPlan(pc, s, input.getCommand(), input);
                } else {
                    builtPlan = Statement.getExecutionPlan(pc, s, pc.getBehaviorConfiguration(), input.getCommand(),
                            input);
                }
                values.take(builtPlan.getValues());
                plans.addAll(builtPlan.getPlans());
            }
            if (pcb != null && input.getCommand() != null && !explain)
                pcb.onMiss(input.getCommand());
            if (!explain && input.getCommand() != null && plans.size() == 1 && tryCache
                    && first instanceof CacheableStatement) {
                PlanCacheUtils.maybeCachePlan(pc, pc.getSource(), (CacheableStatement) first, plans.get(0),
                        input.getCommand(), ccp == null ? null : ccp.getShrunk());
            }
            if (ccp != null && ccp.getShrunk() == null && pc.getCurrentDatabase(false) != null) {
                if (first instanceof DMLStatement) {
                    // we have to count this late because the candidate parser can only handle dml statements.
                    PlannerStatistics.increment(PlannerStatisticType.UNCACHEABLE_CANDIDATE_PARSE);
                }
            }
            return new PlanningResult(plans, values, pr.getInputState(), input.getCommand());
        }
        // clear the warnings
        pc.getConnection().getMessageManager().clear();
        return new PlanningResult(plans, values, null, input.getCommand());
    }

    // if we have an explain - we should try to match to the plan cache
    private static PlanningResult buildExplainPlan(SchemaContext sc, Statement s, String origSQL, InputState input)
            throws PEException {
        // if the explain is for raw statistics, or a regular explain - then we can try the plan cache
        // otherwise not so much
        if (s.getExplain().tryCache()) {
            String sql = origSQL.substring(s.getSourceLocation().getPositionInLine());
            CandidateCachedPlan ccp = null;
            if (!sc.getSource().isPlanCacheEmpty()) {
                ccp = PlanCacheUtils.getCachedPlan(sc, sql, null);
                if (ccp.getPlan() != null) {
                    RootExecutionPlan actual = ccp.getPlan();
                    RootExecutionPlan expep = new RootExecutionPlan(null, actual.getValueManager(),
                            StatementType.EXPLAIN);
                    expep.getSequence().append(actual.generateExplain(sc, ccp.getValues(), s, sql));
                    return new PlanningResult(expep, ccp.getValues(), input, origSQL);
                }
            }
        }
        // if we're still here - we have to build the old fashioned way
        return Statement.getExecutionPlan(sc, s, sc.getBehaviorConfiguration(), origSQL, input);
    }

    // continuation version
    public static PlanningResult buildPlan(SchemaContext pc, InputState stmt) throws PEException {
        if (pc != null)
            SchemaContext.threadContext.set(pc);
        preparse(pc);
        ParserOptions options = ParserOptions.NONE.setDebugLog(true).setResolve();
        PlanningResult result = buildPlan(pc, stmt, options, null);
        for (ExecutionPlan ep : result.getPlans())
            if (ep.isRoot()) {
                SqlStatistics.incrementCounter(((RootExecutionPlan) ep).getStatementType());
            }
        return result;
    }

    public static PlanningResult buildPlan(SchemaContext pc, byte[] line, Charset cs, String pstmtID)
            throws PEException {
        boolean isPrepare = (pstmtID != null);
        if (pc != null)
            SchemaContext.threadContext.set(pc);
        preparse(pc);
        ParserOptions options = ParserOptions.NONE.setDebugLog(true).setResolve().setFailEarly();
        if (isPrepare)
            options = options.setPrepare().setActualLiterals();

        PlanningResult result = null;

        String lineStr = PECharsetUtils.getString(line, cs, true);
        if (lineStr == null) {
            // bad characters - not a candidate for caching
            // if we get this for a prepared stmt, just give up for now
            if (isPrepare)
                throw new PEException("Invalid prepare request: bad characters");
            ParseResult pr = parameterizeAndParse(pc, options, line, cs);
            List<Statement> stmts = pr.getStatements();
            List<ExecutionPlan> plans = new ArrayList<ExecutionPlan>();
            ConnectionValuesMap cvm = new ConnectionValuesMap();
            for (Statement s : stmts) {
                PlanningResult epr = Statement.getExecutionPlan(pc, s, pc.getBehaviorConfiguration(), lineStr,
                        pr.getInputState());
                plans.addAll(epr.getPlans());
                cvm.take(epr.getValues());
            }
            result = new PlanningResult(plans, cvm, pr.getInputState(), lineStr);
        } else {
            lineStr = StringUtils.strip(lineStr, new String(Character.toString(Character.MIN_VALUE)));

            logParse(pc, lineStr, pstmtID);

            InputState input = buildInputState(lineStr, pc);

            if (isPrepare) {
                if (input.getCommand() == null)
                    throw new PEException(
                            "Prepare stmt is too long (greater than " + input.getThreshold() + " characters)");
                result = preparePlan(pc, input, options, pstmtID);
            } else
                result = buildPlan(pc, input, options, null);
        }
        for (ExecutionPlan ep : result.getPlans())
            if (ep.isRoot()) {
                SqlStatistics.incrementCounter(((RootExecutionPlan) ep).getStatementType());
            }
        return result;
    }

    public static PlanningResult bindPreparedStatement(SchemaContext sc, String stmtID, List<Object> params)
            throws PEException {
        // make sure we clear the options
        sc.setOptions(ParserOptions.NONE);
        Pair<RootExecutionPlan, ConnectionValuesMap> bound = PlanCacheUtils.bindPreparedStatement(sc, stmtID,
                params);
        SqlStatistics.incrementCounter(bound.getFirst().getStatementType());
        return new PlanningResult(Collections.singletonList((ExecutionPlan) bound.getFirst()), bound.getSecond(),
                null, null);
    }

    public static PreparePlanningResult reprepareStatement(SchemaContext sc, String rawSQL, String stmtID)
            throws PEException {
        // need to replan
        ParserOptions options = ParserOptions.NONE.setDebugLog(true).setResolve().setPrepare().setActualLiterals();
        InputState input = buildInputState(rawSQL, sc);
        return (PreparePlanningResult) preparePlan(sc, input, options, stmtID);
    }

    private static void logParse(SchemaContext pc, String inline, String pstmtID) {
        boolean isPrepare = (pstmtID != null);
        String line = inline;
        if (isPrepare)
            line = "PREPARE " + line;
        if (logger.isDebugEnabled()) {
            if (pc != null)
                logger.debug(
                        "Begin " + (isPrepare ? "" : pstmtID) + " parse:(" + pc.describeContext() + ") " + line);
            else
                logger.debug("Begin parse:" + line);
        }

        if (sqlLogger.isDebugEnabled())
            logSql(pc, line);
    }

    public static void enableSqlLogging(boolean enabled) {
        sqlLogger.setLevel(enabled ? Level.DEBUG : Level.OFF);

        logger.info("Set SQL Logging log level to " + sqlLogger.getLevel().toString());
    }

    public static boolean isSqlLoggingEnabled() {
        return sqlLogger.isDebugEnabled();
    }

    public static void logSql(SchemaContext pc, String line) {
        if (pc != null)
            sqlLogger.debug("(" + pc.describeContext() + ") " + line);
        else
            sqlLogger.debug(line);
    }

    private static final PlanCacheCallback logCacheCallback = new PlanCacheCallback() {

        private final Logger logger = Logger.getLogger(PlanCacheUtils.class);

        @Override
        public void onHit(String stmt) {
            if (logger.isDebugEnabled())
                logger.debug("PlanCache hit: " + stmt);
        }

        @Override
        public void onMiss(String stmt) {
            if (logger.isDebugEnabled())
                logger.debug("PlanCache miss: " + stmt);
        }

    };

    interface ParserEntryPoint {

        List<Statement> invoke(PE parser) throws Throwable;

    }

    private static final ParserEntryPoint DEFAULT_PARSER_RULE = new ParserEntryPoint() {

        @Override
        public List<Statement> invoke(PE parser) throws Throwable {
            return parser.sql_statements().stmts;
        }

    };

    private static final ParserEntryPoint TRIGGER_PARSER_RULE = new ParserEntryPoint() {

        @Override
        public List<Statement> invoke(PE parser) throws Throwable {
            return Collections.singletonList(parser.compound_or_single_statement().s);
        }

    };
}