org.verdictdb.coordinator.ExecutionContext.java Source code

Java tutorial

Introduction

Here is the source code for org.verdictdb.coordinator.ExecutionContext.java

Source

/*
 *    Copyright 2018 University of Michigan
 *
 *    Licensed 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.verdictdb.coordinator;

import static org.verdictdb.coordinator.VerdictSingleResultFromListData.createWithSingleColumn;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.verdictdb.VerdictResultStream;
import org.verdictdb.VerdictSingleResult;
import org.verdictdb.commons.DataTypeConverter;
import org.verdictdb.commons.VerdictDBLogger;
import org.verdictdb.commons.VerdictOption;
import org.verdictdb.connection.CachedDbmsConnection;
import org.verdictdb.connection.DbmsConnection;
import org.verdictdb.connection.MetaDataProvider;
import org.verdictdb.connection.StaticMetaData;
import org.verdictdb.core.resulthandler.ExecutionResultReader;
import org.verdictdb.core.scrambling.FastConvergeScramblingMethod;
import org.verdictdb.core.scrambling.HashScramblingMethod;
import org.verdictdb.core.scrambling.ScrambleMeta;
import org.verdictdb.core.scrambling.ScrambleMetaSet;
import org.verdictdb.core.scrambling.ScramblingMethod;
import org.verdictdb.core.scrambling.UniformScramblingMethod;
import org.verdictdb.core.sqlobject.AbstractRelation;
import org.verdictdb.core.sqlobject.BaseTable;
import org.verdictdb.core.sqlobject.ColumnOp;
import org.verdictdb.core.sqlobject.CreateScrambleQuery;
import org.verdictdb.core.sqlobject.JoinTable;
import org.verdictdb.core.sqlobject.SelectQuery;
import org.verdictdb.core.sqlobject.SubqueryColumn;
import org.verdictdb.core.sqlobject.UnnamedColumn;
import org.verdictdb.exception.VerdictDBDbmsException;
import org.verdictdb.exception.VerdictDBException;
import org.verdictdb.exception.VerdictDBTypeException;
import org.verdictdb.metastore.CachedScrambleMetaStore;
import org.verdictdb.metastore.ScrambleMetaStore;
import org.verdictdb.metastore.VerdictMetaStore;
import org.verdictdb.parser.VerdictSQLParser;
import org.verdictdb.parser.VerdictSQLParser.IdContext;
import org.verdictdb.parser.VerdictSQLParserBaseVisitor;
import org.verdictdb.sqlreader.CondGen;
import org.verdictdb.sqlreader.NonValidatingSQLParser;
import org.verdictdb.sqlreader.RelationGen;
import org.verdictdb.sqlreader.RelationStandardizer;
import org.verdictdb.sqlsyntax.SqlSyntax;

/**
 * Stores the context for a single query execution. Includes both scrambling query and select query.
 *
 * @author Yongjoo Park
 */
public class ExecutionContext {

    private DbmsConnection conn;

    private VerdictMetaStore metaStore;

    private QueryContext queryContext;

    private Coordinator runningCoordinator = null;

    private final long serialNumber;

    private final VerdictDBLogger log = VerdictDBLogger.getLogger(getClass());

    private VerdictOption options;

    public enum QueryType {
        select, scrambling, insert_scramble, drop_scramble, drop_all_scrambles, set_default_schema, unknown, show_databases, show_tables, show_scrambles, describe_table
    }

    /**
     * @param conn DbmsConnection
     * @param contextId parent's context id
     * @param serialNumber serial number of this ExecutionContext
     * @param options
     */
    public ExecutionContext(DbmsConnection conn, VerdictMetaStore metaStore, String contextId, long serialNumber,
            VerdictOption options) {
        this.conn = conn;
        this.metaStore = metaStore;
        this.serialNumber = serialNumber;
        this.queryContext = new QueryContext(contextId, serialNumber);
        this.options = options;
    }

    public long getExecutionContextSerialNumber() {
        return serialNumber;
    }

    /**
     * Check whether given sql contains 'bypass' keyword at the beginning
     *
     * @param sql original sql
     * @return without 'bypass' keyword if the original sql begins with it. null otherwise.
     */
    private String checkBypass(String sql) {
        if (sql.trim().toLowerCase().startsWith("bypass")) {
            return sql.trim().substring(6);
        }
        return null;
    }

    private VerdictSingleResult executeAsIs(String sql) throws VerdictDBDbmsException {
        return new VerdictSingleResultFromDbmsQueryResult(conn.execute(sql));
    }

    public VerdictSingleResult sql(String query) throws VerdictDBException {
        return this.sql(query, true);
    }

    public VerdictSingleResult sql(String query, boolean getResult) throws VerdictDBException {
        String bypassSql = checkBypass(query);
        if (bypassSql != null) {
            return executeAsIs(bypassSql);
        }

        QueryType queryType = identifyQueryType(query);

        if ((queryType != QueryType.select && queryType != QueryType.show_databases
                && queryType != QueryType.show_tables && queryType != QueryType.describe_table
                && queryType != QueryType.show_scrambles) && getResult) {
            throw new VerdictDBException("Can not issue data manipulation statements with executeQuery().");
        }

        if (queryType.equals(QueryType.select)) {
            log.debug("Query type: select");
            return sqlSelectQuery(query, getResult);
        }

        // for other types of queries, we invalidate cached metadata for expected data
        // manipulations
        if (conn instanceof CachedDbmsConnection) {
            ((CachedDbmsConnection) conn).clearCache();
        }

        if (queryType.equals(QueryType.scrambling)) {
            log.debug("Query type: scrambling");

            CreateScrambleQuery scrambleQuery = generateScrambleQuery(query);

            List<String> existingPartitionColumns = conn.getPartitionColumns(scrambleQuery.getOriginalSchema(),
                    scrambleQuery.getOriginalTable());

            // add existing partition columns
            scrambleQuery.setExistingPartitionColumns(existingPartitionColumns);

            // checks the validity; throws an exception if not.
            scrambleQuery.checkIfSupported(conn.getSyntax());

            ScramblingCoordinator scrambler = new ScramblingCoordinator(conn, scrambleQuery.getNewSchema(),
                    options.getVerdictTempSchemaName(), scrambleQuery.getBlockSize(),
                    scrambleQuery.getExistingPartitionColumns());

            // store this metadata to our own metadata db.
            ScrambleMeta meta = scrambler.scramble(scrambleQuery);

            // Add metadata to metastore
            ScrambleMetaStore metaStore = new ScrambleMetaStore(conn, options);
            metaStore.addToStore(meta);
            refreshScrambleMetaStore();
            return null;

        } else if (queryType.equals(QueryType.insert_scramble)) {
            log.debug("Query type: insert_scramble");

            ScrambleMetaStore metaStore = new ScrambleMetaStore(conn, options);
            CreateScrambleQuery scrambleQuery = generateInsertScrambleQuery(query, metaStore);

            ScrambleMeta existingScrambleMeta = metaStore.retrieveExistingScramble(scrambleQuery.getNewSchema(),
                    scrambleQuery.getNewTable());

            if (existingScrambleMeta == null) {
                throw new VerdictDBException(String.format("A scramble '%s.%s' does not exist",
                        scrambleQuery.getNewSchema(), scrambleQuery.getNewTable()));
            }

            ScramblingMethod scrambleMethod = null;
            if (existingScrambleMeta.getScramblingMethod() == null) {
                log.warn(String.format("Scrambling method information on the scramble '%s.%s' does not exist.",
                        scrambleQuery.getNewSchema(), scrambleQuery.getNewTable()));
                log.warn(String.format(
                        "Using other existing metadata on the scramble '%s.%s' to " + "perform scramble appending.",
                        scrambleQuery.getNewSchema(), scrambleQuery.getNewTable()));

                String methodName = existingScrambleMeta.getMethod();
                if (methodName.equalsIgnoreCase("uniform")) {
                    scrambleMethod = new UniformScramblingMethod(
                            existingScrambleMeta.getCumulativeDistributionForTier());
                } else if (methodName.equalsIgnoreCase("hash")) {
                    scrambleMethod = new HashScramblingMethod(
                            existingScrambleMeta.getCumulativeDistributionForTier(),
                            existingScrambleMeta.getHashColumn());
                } else if (methodName.equalsIgnoreCase("fastconverge")) {
                    scrambleMethod = new FastConvergeScramblingMethod(
                            existingScrambleMeta.getCumulativeDistributionForTier(),
                            existingScrambleMeta.getHashColumn());
                }
            } else {
                scrambleMethod = existingScrambleMeta.getScramblingMethod();
            }

            if (scrambleMethod == null) {
                throw new VerdictDBException(String.format("Could not determine scrambling method for '%s.%s'.",
                        scrambleQuery.getNewSchema(), scrambleQuery.getNewTable()));
            }

            // set scrambleQuery for appending data
            scrambleQuery.setOriginalSchema(existingScrambleMeta.getOriginalSchemaName());
            scrambleQuery.setOriginalTable(existingScrambleMeta.getOriginalTableName());
            scrambleQuery.setMethod(existingScrambleMeta.getMethod());
            scrambleQuery.setHashColumnName(existingScrambleMeta.getHashColumn());
            scrambleQuery.setScramblingMethod(scrambleMethod);

            ScramblingCoordinator scrambler = new ScramblingCoordinator(conn, scrambleQuery.getNewSchema(),
                    options.getVerdictTempSchemaName());

            // append new scramble
            scrambler.appendScramble(scrambleQuery);
            return null;
        } else if (queryType.equals(QueryType.drop_scramble)) {
            log.debug("Query type: drop_scramble");

            ScrambleMetaStore metaStore = new ScrambleMetaStore(conn, options);
            Pair<BaseTable, BaseTable> tablePair = getTablePairForDropScramble(query);
            metaStore.dropScrambleTable(tablePair.getLeft(), tablePair.getRight());
            return null;

        } else if (queryType.equals(QueryType.drop_all_scrambles)) {
            log.debug("Query type: drop_all_scrambles");

            ScrambleMetaStore metaStore = new ScrambleMetaStore(conn, options);
            BaseTable table = getTableForDropAllScramble(query);
            metaStore.dropAllScrambleTable(table);
            return null;

        } else if (queryType.equals(QueryType.show_scrambles)) {
            log.debug("Query type: show_scrambles");

            ScrambleMetaStore metaStore = new ScrambleMetaStore(conn, options);
            return metaStore.showScrambles();

        } else if (queryType.equals(QueryType.set_default_schema)) {
            log.debug("Query type: set_default_schema");
            updateDefaultSchemaFromQuery(query);
            return null;

        } else if (queryType.equals(QueryType.show_databases)) {
            log.debug("Query type: show_databases");
            return generateShowSchemaResultFromQuery();

        } else if (queryType.equals(QueryType.show_tables)) {
            log.debug("Query type: show_tables");
            return generateShowTablesResultFromQuery(query);

        } else if (queryType.equals(QueryType.describe_table)) {
            log.debug("Query type: describe_table");
            return generateDescribeTableResultFromQuery(query);

        } else {
            throw new VerdictDBTypeException("Unexpected type of query: " + query);
        }
    }

    private VerdictSingleResult sqlSelectQuery(String query, boolean getResult) throws VerdictDBException {
        SelectQuery selectQuery = standardizeQuery(query);
        VerdictResultStream stream = streamSelectQuery(selectQuery);

        if (stream == null) {
            return null;
        }
        QueryResultAccuracyEstimator accEst = new QueryResultAccuracyEstimatorFromDifference(selectQuery);

        try {
            while (stream.hasNext()) {
                VerdictSingleResult rs = stream.next();
                accEst.add(rs);
                if (accEst.isLastResultAccurate()) {
                    return rs;
                }
            }
            // return the last result otherwise
            return accEst.getAnswers().get(accEst.getAnswerCount() - 1);
        } catch (RuntimeException e) {
            throw e;
        } finally {
            stream.close();
            abort();
        }

        //    SelectQuery selectQuery = standardizeQuery(query);
        //    return streamSelectQuery(selectQuery);

    }

    // not used for now due to instability (i.e., often fails to achieve the goal).
    private void abortInParallel(VerdictResultStream stream) {
        Thread task = new Thread(new ConcurrentAborter(stream));
        task.start();
    }

    class ConcurrentAborter implements Runnable {

        VerdictResultStream stream;

        public ConcurrentAborter(VerdictResultStream stream) {
            this.stream = stream;
        }

        @Override
        public void run() {
            stream.close();
            abort();
        }
    }

    public VerdictResultStream streamsql(String query) throws VerdictDBException {
        // determines the type of the given query and forward it to an appropriate coordinator.
        QueryType queryType = identifyQueryType(query);

        if (!queryType.equals(QueryType.select)) {
            throw new VerdictDBTypeException("Only a select query can be issued to streamsql().");
        }

        SelectQuery selectQuery = standardizeQuery(query);
        return streamSelectQuery(selectQuery);
    }

    /**
     * Returns a stream of answers for the select query .
     *
     * @param selectQuery Already standardized select query.
     * @return
     * @throws VerdictDBException
     */
    private VerdictResultStream streamSelectQuery(SelectQuery selectQuery) throws VerdictDBException {
        //    selectQuery = standardizeSelectQuery(selectQuery, conn);

        ScrambleMetaSet metaset = metaStore.retrieve();
        SelectQueryCoordinator coordinator = new SelectQueryCoordinator(conn, metaset, options);
        runningCoordinator = null;

        ExecutionResultReader reader = coordinator.process(selectQuery, queryContext);
        if (coordinator.getLastQuery() != null) {
            // this means there are scrambles for the query so that
            // we need to abort the coordinator at the end.
            runningCoordinator = coordinator;
        }
        VerdictResultStream stream = new VerdictResultStreamFromExecutionResultReader(reader);
        return stream;
    }

    /**
     * Standardizes a query string into a select query object.
     *
     * @param query
     * @return
     * @throws VerdictDBException
     */
    private SelectQuery standardizeQuery(String query) throws VerdictDBException {
        return standardizeQuery(query, conn);
    }

    static SelectQuery standardizeQuery(String query, DbmsConnection conn) throws VerdictDBException {
        SelectQuery selectQuery = NonValidatingSQLParser.toSelectQuery(query);
        SelectQuery standardized = standardizeSelectQuery(selectQuery, conn);
        return standardized;
    }

    /**
     * Standardizes a parsed query.
     *
     * @param selectQuery
     * @return
     * @throws VerdictDBException
     */
    static SelectQuery standardizeSelectQuery(SelectQuery selectQuery, DbmsConnection conn)
            throws VerdictDBException {
        //    if (selectQuery.isStandardized()) {
        //      throw new VerdictDBValueException("The query has already been standardized.");
        //    }

        RelationStandardizer.resetItemID();
        SqlSyntax syntax = conn.getSyntax();
        MetaDataProvider metaData = createMetaDataFor(selectQuery, conn);
        selectQuery = RelationStandardizer.standardizeSelectQuery(selectQuery, metaData, syntax);
        return selectQuery;
    }

    private void refreshScrambleMetaStore() {
        // no type check was added to make it fail if non-cached metastore is used.
        ((CachedScrambleMetaStore) this.metaStore).refreshCache();
    }

    private Pair<BaseTable, BaseTable> getTablePairForDropScramble(String query) {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        VerdictSQLParserBaseVisitor<Pair<BaseTable, BaseTable>> visitor = new VerdictSQLParserBaseVisitor<Pair<BaseTable, BaseTable>>() {
            @Override
            public Pair<BaseTable, BaseTable> visitDrop_scramble_statement(
                    VerdictSQLParser.Drop_scramble_statementContext ctx) {
                RelationGen g = new RelationGen();
                BaseTable originalTable = (ctx.original_table != null) ? (BaseTable) g.visit(ctx.original_table)
                        : null;
                BaseTable scrambleTable = (BaseTable) g.visit(ctx.scrambled_table);
                return ImmutablePair.of(originalTable, scrambleTable);
            }
        };
        return visitor.visit(parser.drop_scramble_statement());
    }

    private BaseTable getTableForDropAllScramble(String query) {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        VerdictSQLParserBaseVisitor<BaseTable> visitor = new VerdictSQLParserBaseVisitor<BaseTable>() {
            @Override
            public BaseTable visitDrop_all_scrambles_statement(
                    VerdictSQLParser.Drop_all_scrambles_statementContext ctx) {
                RelationGen g = new RelationGen();
                return (BaseTable) g.visit(ctx.original_table);
            }
        };
        return visitor.visit(parser.drop_all_scrambles_statement());
    }

    private String stripQuote(String expr) {
        if (expr == null) {
            return null;
        }
        return expr.replace("\"", "").replace("`", "").replace("'", "");
    }

    private CreateScrambleQuery generateInsertScrambleQuery(String query, ScrambleMetaStore store) {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);

        VerdictSQLParserBaseVisitor<CreateScrambleQuery> visitor = new VerdictSQLParserBaseVisitor<CreateScrambleQuery>() {
            @Override
            public CreateScrambleQuery visitInsert_scramble_statement(
                    VerdictSQLParser.Insert_scramble_statementContext ctx) {
                CreateScrambleQuery scrambleQuery = new CreateScrambleQuery();
                RelationGen g = new RelationGen();
                CondGen cond = new CondGen();
                BaseTable scrambleTable = (BaseTable) g.visit(ctx.scrambled_table);
                UnnamedColumn where = cond.visit(ctx.where);

                scrambleQuery.setNewSchema(scrambleTable.getSchemaName());
                scrambleQuery.setNewTable(scrambleTable.getTableName());
                scrambleQuery.setWhere(where);

                return scrambleQuery;
            }
        };

        CreateScrambleQuery scrambleQuery = visitor.visit(parser.insert_scramble_statement());
        return scrambleQuery;
    }

    private CreateScrambleQuery generateScrambleQuery(String query) {

        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        VerdictSQLParserBaseVisitor<CreateScrambleQuery> visitor = new VerdictSQLParserBaseVisitor<CreateScrambleQuery>() {
            @Override
            public CreateScrambleQuery visitCreate_scramble_statement(
                    VerdictSQLParser.Create_scramble_statementContext ctx) {
                RelationGen g = new RelationGen();
                BaseTable originalTable = (BaseTable) g.visit(ctx.original_table);
                BaseTable scrambleTable = (BaseTable) g.visit(ctx.scrambled_table);
                String method = (ctx.method == null) ? "uniform" : stripQuote(ctx.method.getText());
                double percent = (ctx.percent == null) ? 1.0 : Double.parseDouble(ctx.percent.getText());
                long blocksize = (ctx.blocksize == null) ? (long) conn.getSyntax().getRecommendedblockSize()
                        : Long.parseLong(ctx.blocksize.getText());
                String hashColumnName = (ctx.hash_column == null) ? null : stripQuote(ctx.hash_column.getText());
                CondGen cond = new CondGen();
                UnnamedColumn where = (ctx.where == null ? null : cond.visit(ctx.where));

                CreateScrambleQuery query = new CreateScrambleQuery(scrambleTable.getSchemaName(),
                        scrambleTable.getTableName(), originalTable.getSchemaName(), originalTable.getTableName(),
                        method, percent, blocksize, hashColumnName, where);
                if (ctx.IF() != null)
                    query.setIfNotExists(true);
                return query;
            }
        };

        CreateScrambleQuery scrambleQuery = visitor.visit(parser.create_scramble_statement());
        return scrambleQuery;
    }

    private VerdictSingleResult generateShowSchemaResultFromQuery() throws VerdictDBException {
        List<String> header = Arrays.asList("schema");
        List<String> rows = conn.getSchemas();
        VerdictSingleResultFromListData result = createWithSingleColumn(header, (List) rows);
        return result;
    }

    private VerdictSingleResult generateShowTablesResultFromQuery(String query) throws VerdictDBException {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        IdContext schemaCtx = parser.show_tables_statement().schema;
        String schema = (schemaCtx == null) ? conn.getDefaultSchema() : schemaCtx.getText();
        List<String> header = Arrays.asList("table");
        List<String> rows = conn.getTables(schema);
        VerdictSingleResultFromListData result = createWithSingleColumn(header, (List) rows);
        return result;
    }

    private VerdictSingleResult generateDescribeTableResultFromQuery(String query) throws VerdictDBException {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        VerdictSQLParserBaseVisitor<Pair<String, String>> visitor = new VerdictSQLParserBaseVisitor<Pair<String, String>>() {
            @Override
            public Pair<String, String> visitDescribe_table_statement(
                    VerdictSQLParser.Describe_table_statementContext ctx) {
                IdContext schemaCtx = ctx.table.schema;
                String table = ctx.table.table.getText();
                String schema = (schemaCtx == null) ? conn.getDefaultSchema() : schemaCtx.getText();
                //        if (schema == null) {
                //          schema = conn.getDefaultSchema();
                //        }
                return new ImmutablePair<>(schema, table);
            }
        };
        Pair<String, String> t = visitor.visit(parser.verdict_statement());
        String table = t.getRight();
        String schema = t.getLeft();
        if (schema == null) {
            schema = conn.getDefaultSchema();
        }
        List<Pair<String, String>> columnInfo = conn.getColumns(schema, table);
        List<List<String>> newColumnInfo = new ArrayList<>();
        for (Pair<String, String> pair : columnInfo) {
            newColumnInfo.add(Arrays.asList(pair.getLeft(), pair.getRight()));
        }
        List<String> header = Arrays.asList("column name", "column type");
        VerdictSingleResultFromListData result = new VerdictSingleResultFromListData(header, (List) newColumnInfo);
        return result;
    }

    private void updateDefaultSchemaFromQuery(String query) throws VerdictDBDbmsException {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);
        String schema = parser.use_statement().database.getText();
        conn.setDefaultSchema(schema);
    }

    public void abort() {
        log.trace("Aborts an ExecutionContext: " + this);
        if (runningCoordinator != null) {
            Coordinator c = runningCoordinator;
            runningCoordinator = null;
            c.abort();
        }
    }

    /**
     * Terminates existing threads. The created database tables may still exist for successive uses.
     *
     * <p>This method also removes all temporary tables created by this ExecutionContext.
     */
    public void terminate() {
        abort();

        try {
            String schema = options.getVerdictTempSchemaName();
            String tempTablePrefix = String.format("%s_%s_%d", VerdictOption.getVerdictTempTablePrefix(),
                    queryContext.getVerdictContextId(), this.serialNumber);
            List<String> tempTableList = new ArrayList<>();

            List<String> allTempTables = null;
            if (conn instanceof CachedDbmsConnection) {
                allTempTables = ((CachedDbmsConnection) conn).getTablesWithoutCaching(schema);
            } else {
                allTempTables = conn.getTables(schema);
            }
            for (String tableName : allTempTables) {
                if (tableName.startsWith(tempTablePrefix)) {
                    tempTableList.add(tableName);
                }
            }

            for (String tempTable : tempTableList) {
                conn.execute(String.format("DROP TABLE IF EXISTS %s.%s", schema, tempTable));
            }
        } catch (VerdictDBDbmsException e) {
            e.printStackTrace();
        }
    }

    static MetaDataProvider createMetaDataFor(SelectQuery relation, DbmsConnection conn)
            throws VerdictDBDbmsException {
        StaticMetaData meta = new StaticMetaData();
        String defaultSchema = conn.getDefaultSchema();
        meta.setDefaultSchema(defaultSchema);

        // Extract all tables that appeared in the query
        HashSet<BaseTable> tables = new HashSet<>();
        List<SelectQuery> queries = new ArrayList<>();
        queries.add(relation);
        while (!queries.isEmpty()) {
            SelectQuery query = queries.get(0);
            queries.remove(0);
            for (AbstractRelation t : query.getFromList()) {
                if (t instanceof BaseTable)
                    tables.add((BaseTable) t);
                else if (t instanceof SelectQuery)
                    queries.add((SelectQuery) t);
                else if (t instanceof JoinTable) {
                    for (AbstractRelation join : ((JoinTable) t).getJoinList()) {
                        if (join instanceof BaseTable)
                            tables.add((BaseTable) join);
                        else if (join instanceof SelectQuery)
                            queries.add((SelectQuery) join);
                    }
                }
            }
            if (query.getFilter().isPresent()) {
                UnnamedColumn where = query.getFilter().get();
                List<UnnamedColumn> toCheck = new ArrayList<>();
                toCheck.add(where);
                while (!toCheck.isEmpty()) {
                    UnnamedColumn col = toCheck.get(0);
                    toCheck.remove(0);
                    if (col instanceof ColumnOp) {
                        toCheck.addAll(((ColumnOp) col).getOperands());
                    } else if (col instanceof SubqueryColumn) {
                        queries.add(((SubqueryColumn) col).getSubquery());
                    }
                }
            }
        }

        // Get table info from cached meta
        for (BaseTable t : tables) {
            List<Pair<String, String>> columns;
            StaticMetaData.TableInfo tableInfo;

            if (t.getSchemaName() == null) {
                columns = conn.getColumns(defaultSchema, t.getTableName());
                tableInfo = new StaticMetaData.TableInfo(defaultSchema, t.getTableName());
            } else {
                columns = conn.getColumns(t.getCatalogName(), t.getSchemaName(), t.getTableName());
                tableInfo = new StaticMetaData.TableInfo(t.getSchemaName(), t.getTableName());
            }
            List<Pair<String, Integer>> colInfo = new ArrayList<>();
            for (Pair<String, String> col : columns) {
                colInfo.add(new ImmutablePair<>(col.getLeft(),
                        DataTypeConverter.typeInt(col.getRight().toLowerCase())));
            }
            meta.addTableData(tableInfo, colInfo);
        }

        return meta;
    }

    MetaDataProvider createMetaDataFor(SelectQuery relation) throws VerdictDBException {
        return createMetaDataFor(relation, conn);
    }

    public static QueryType identifyQueryType(String query) {
        VerdictSQLParser parser = NonValidatingSQLParser.parserOf(query);

        VerdictSQLParserBaseVisitor<QueryType> visitor = new VerdictSQLParserBaseVisitor<QueryType>() {

            @Override
            public QueryType visitSelect_statement(VerdictSQLParser.Select_statementContext ctx) {
                return QueryType.select;
            }

            @Override
            public QueryType visitInsert_scramble_statement(VerdictSQLParser.Insert_scramble_statementContext ctx) {
                return QueryType.insert_scramble;
            }

            @Override
            public QueryType visitCreate_scramble_statement(VerdictSQLParser.Create_scramble_statementContext ctx) {
                return QueryType.scrambling;
            }

            @Override
            public QueryType visitDrop_scramble_statement(VerdictSQLParser.Drop_scramble_statementContext ctx) {
                return QueryType.drop_scramble;
            }

            @Override
            public QueryType visitDrop_all_scrambles_statement(
                    VerdictSQLParser.Drop_all_scrambles_statementContext ctx) {
                return QueryType.drop_all_scrambles;
            }

            @Override
            public QueryType visitShow_scrambles_statement(VerdictSQLParser.Show_scrambles_statementContext ctx) {
                return QueryType.show_scrambles;
            }

            @Override
            public QueryType visitUse_statement(VerdictSQLParser.Use_statementContext ctx) {
                return QueryType.set_default_schema;
            }

            @Override
            public QueryType visitShow_databases_statement(VerdictSQLParser.Show_databases_statementContext ctx) {
                return QueryType.show_databases;
            }

            @Override
            public QueryType visitShow_tables_statement(VerdictSQLParser.Show_tables_statementContext ctx) {
                return QueryType.show_tables;
            }

            @Override
            public QueryType visitDescribe_table_statement(VerdictSQLParser.Describe_table_statementContext ctx) {
                return QueryType.describe_table;
            }
        };

        QueryType type = visitor.visit(parser.verdict_statement());
        if (type == null) {
            type = QueryType.unknown;
        }
        return type;
    }
}