Package org.voltdb.utils

Source Code of org.voltdb.utils.SQLCommand$Tables

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.utils;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jline.console.CursorBuffer;
import jline.console.KeyMap;
import jline.console.completer.Completer;
import jline.console.history.FileHistory;

import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcCallException;

import com.google_voltpatches.common.collect.ImmutableMap;

public class SQLCommand
{
    // SQL Parsing
    private static final Pattern EscapedSingleQuote = Pattern.compile("''", Pattern.MULTILINE);
    private static final Pattern SingleLineComments = Pattern.compile("^\\s*(\\/\\/|--).*$", Pattern.MULTILINE);
    private static final Pattern Extract = Pattern.compile("'[^']*'", Pattern.MULTILINE);
    private static final Pattern AutoSplit = Pattern.compile("(\\s|((\\(\\s*)+))(alter|create|drop|select|insert|update|upsert|delete|truncate|exec|execute|explain|explainproc)\\s", Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
    private static final Pattern SetOp = Pattern.compile("(\\s|\\))\\s*(union|except|intersect)(\\s\\s*all)?((\\s*\\({0,1}\\s*)*)select", Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
    private static final Pattern Subquery =
            Pattern.compile("(\\s*)(,|(?:\\s(?:from|in|exists|join)))((\\s*\\(\\s*)*)select",
                            Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);

    private static final String quotedIdPattern = "\"(?:[^\"]|\"\")+\""; // double-quoted ID, " escaped with ""
                                                                   // question: is 0-length name allowed?
    private static final String unquotedIdPattern = "[a-z][a-z0-9_]*";
    private static final String idPattern = "(?:" + unquotedIdPattern + "|" + quotedIdPattern + ")";

    // This pattern consumes no input itself but ensures that the next
    // character is either whitespace or a double quote. This is handy
    // when a keyword is followed by an identifier:
    //   INSERT INTO"Foo"SELECT ...
    // HSQL doesn't require whitespace between keywords and quoted
    // identifiers.
    private static String followedBySpaceOrQuote = "(?=\"|\\s)";

    // Ugh, these are all fragile.
    private static final Pattern CreateView =
        Pattern.compile(
                "(\\s*)" + // 0 or more spaces
                "(" + // start group 2
                "create\\s+view" + // 'create view'
                "\\s+" + // 1 or more spaces
                "((?!create\\s+(view|procedure)).)*" + // any string that doesn't contain 'create view'
                                                       // or 'create procedure' again
                "\\s+" + // 1 or more spaces
                "as" +
                "\\s+" + // 1 or more spaces
                ")" + // end group 2
                "select",
                Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
    private static final Pattern CreateProcedureSelect =
        Pattern.compile(
                "(\\s*)" + // 0 or more spaces
                "(" + // start group 2
                "create\\s+procedure" + // 'create procedure'
                "\\s+" + // 1 or more spaces
                "((?!create\\s+(view|procedure)).)*" + // any string that doesn't contain 'create view'
                                                       // or 'create procedure' again
                "\\s+" + // 1 or more spaces
                "as" +
                "\\s+" + // 1 or more spaces
                ")" + // end group 2
                "select",
                Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
    private static final Pattern CreateProcedureInsert =
        Pattern.compile(
                "(\\s*)" + // 0 or more spaces
                "(" + // start group 2
                "create\\s+procedure" + // 'create procedure'
                "\\s+" + // 1 or more spaces
                "((?!create\\s+(view|procedure)).)*" + // any string that doesn't contain 'create view'
                                                       // or 'create procedure' again
                "\\s+" + // 1 or more spaces
                "as" +
                "\\s+" + // 1 or more spaces
                ")" + // end group 2
                "insert",
                Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
    private static final Pattern CreateProcedureUpdate =
        Pattern.compile(
                "(\\s*)" + // 0 or more spaces
                "(" + // start group 2
                "create\\s+procedure" + // 'create procedure'
                "\\s+" + // 1 or more spaces
                "((?!create\\s+(view|procedure)).)*" + // any string that doesn't contain 'create view'
                                                       // or 'create procedure' again
                "\\s+" + // 1 or more spaces
                "as" +
                "\\s+" + // 1 or more spaces
                ")" + // end group 2
                "update",
                Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
    private static final Pattern CreateProcedureDelete =
        Pattern.compile(
                "(\\s*)" + // 0 or more spaces
                "(" + // start group 2
                "create\\s+procedure" + // 'create procedure'
                "\\s+" + // 1 or more spaces
                "((?!create\\s+(view|procedure)).)*" + // any string that doesn't contain 'create view'
                                                       // or 'create procedure' again
                "\\s+" + // 1 or more spaces
                "as" +
                "\\s+" + // 1 or more spaces
                ")" + // end group 2
                "delete",
                Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);

    // For HSQL's purposes, the optional column list that may appear
    // in an INSERT statement is recognized as:
    // - a left parenthesis
    // - 1 or more of the following items:
    //   - quoted identifiers (which may contain right parentheses)
    //   - characters which are not double quotes or right parentheses
    // - a right parenthesis
    // This should recognize whatever may appear inside the column
    // list, including quoted column names with embedded parentheses.
    private static final String optionalColumnList = "(?:\\((?:" + quotedIdPattern + "|[^\")])+\\))?";
    private static final Pattern InsertIntoSelect =
            Pattern.compile(
                    "(" +                  // start capturing group
                    "\\s*" +               // leading whitespace
                    "(?:insert|upsert)\\s+into" + followedBySpaceOrQuote + "\\s*" +
                    idPattern + "\\s*" +   // <tablename>
                    optionalColumnList +   // (column, "anotherColumn", ...)
                    "[(\\s]*" +            // 0 or more spaces or left parentheses
                    ")" +                  // end capturing group
                    "select",
                    Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);

    // the common prefix for both ALTER TABLE <table> DROP
    // and ALTER TABLE <table> ALTER
    private static String alterTableCommonPrefix =
            "\\s*alter\\s*table" + followedBySpaceOrQuote + "\\s*" +
            idPattern + "\\s*";
    private static final Pattern AlterTableAlter =
            Pattern.compile(
                    "(" + alterTableCommonPrefix + ")alter",
                    Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
    private static final Pattern AlterTableDrop =
            Pattern.compile(
                    "(" + alterTableCommonPrefix + ")drop",
                    Pattern.MULTILINE + Pattern.CASE_INSENSITIVE + Pattern.DOTALL);

    private static final Pattern AutoSplitParameters = Pattern.compile("[\\s,]+", Pattern.MULTILINE);
    /**
     * Matches a command followed by and SQL CRUD statement verb
     */
    private static final Pattern ParserStringKeywords = Pattern.compile(
            "\\s*" + // 0 or more spaces
            "(" + // start group 1
              "exec|execute|explain|explainproc" + // command
            ")" // end group 1
            "\\s+" + // one or more spaces
            "(" + // start group 2
              "select|insert|update|upsert|delete|truncate" + // SQL CRUD statement verb
            ")" + // end group 2
            "\\s+", // one or more spaces
            Pattern.MULTILINE|Pattern.CASE_INSENSITIVE
    );
    private static final String readme = "SQLCommandReadme.txt";

    public static String getReadme() {
        return readme;
    }

    public static Pattern getExecuteCall() {
        return ExecuteCall;
    }

    public static List<String> parseQuery(String query)
    {
        if (query == null) {
            return null;
        }

        /*
         * Mark any parser string keyword matches by interposing the #SQL_PARSER_STRING_KEYWORD#
         * tag. Which is later stripped at the end of this procedure. This tag is here to
         * aide the evaluation of SetOp and AutoSplit REGEXPs, meaning that an
         * 'explain select foo from bar will cause SetOp and AutoSplit match on the select as
         * is prefixed with the #SQL_PARSER_STRING_KEYWORD#
         *
         * For example
         *     'explain select foo from bar'
         *  becomes
         *     'explain #SQL_PARSER_STRING_KEYWORD#select foo from bar'
         */
        query = ParserStringKeywords.matcher(query).replaceAll(" $1 #SQL_PARSER_STRING_KEYWORD#$2 ");
        /*
         * strip out single line comments
         */
        query = SingleLineComments.matcher(query).replaceAll("");
        /*
         * replace all escaped single quotes with the #(SQL_PARSER_ESCAPE_SINGLE_QUOTE) tag
         */
        query = EscapedSingleQuote.matcher(query).replaceAll("#(SQL_PARSER_ESCAPE_SINGLE_QUOTE)");

        /*
         * move all single quoted strings into the string fragments list, and do in place
         * replacements with numbered instances of the #(SQL_PARSER_STRING_FRAGMENT#[n]) tag
         *
         */
        Matcher stringFragmentMatcher = Extract.matcher(query);
        ArrayList<String> stringFragments = new ArrayList<String>();
        int i = 0;
        while (stringFragmentMatcher.find()) {
            stringFragments.add(stringFragmentMatcher.group());
            query = stringFragmentMatcher.replaceFirst("#(SQL_PARSER_STRING_FRAGMENT#" + i + ")");
            stringFragmentMatcher = Extract.matcher(query);
            i++;
        }

        /*
         * Mark all SQL keywords that are part of another statement so they don't get auto-split
         */
        query = SetOp.matcher(query).replaceAll("$1$2$3$4SQL_PARSER_SAME_SELECT");
        query = Subquery.matcher(query).replaceAll("$1$2$3SQL_PARSER_SAME_SELECT");
        query = CreateView.matcher(query).replaceAll("$1$2SQL_PARSER_SAME_CREATEVIEW");
        query = CreateProcedureSelect.matcher(query).replaceAll("$1$2SQL_PARSER_SAME_CREATESELECT");
        query = CreateProcedureInsert.matcher(query).replaceAll("$1$2SQL_PARSER_SAME_CREATEINSERT");
        query = CreateProcedureUpdate.matcher(query).replaceAll("$1$2SQL_PARSER_SAME_CREATEUPDATE");
        query = CreateProcedureDelete.matcher(query).replaceAll("$1$2SQL_PARSER_SAME_CREATEDELETE");
        query = InsertIntoSelect.matcher(query).replaceAll("$1SQL_PARSER_SAME_INSERTINTOSELECT");
        query = AlterTableAlter.matcher(query).replaceAll("$1SQL_PARSER_SAME_ALTERTABLEALTER");
        query = AlterTableDrop.matcher(query).replaceAll("$1SQL_PARSER_SAME_ALTERTABLEDROP");
        query = AutoSplit.matcher(query).replaceAll(";$2$4 "); // there be dragons here
        query = query.replaceAll("SQL_PARSER_SAME_SELECT", "select");
        query = query.replaceAll("SQL_PARSER_SAME_CREATEVIEW", "select");
        query = query.replaceAll("SQL_PARSER_SAME_CREATESELECT", "select");
        query = query.replaceAll("SQL_PARSER_SAME_CREATEINSERT", "insert");
        query = query.replaceAll("SQL_PARSER_SAME_CREATEUPDATE", "update");
        query = query.replaceAll("SQL_PARSER_SAME_CREATEDELETE", "delete");
        query = query.replaceAll("SQL_PARSER_SAME_INSERTINTOSELECT", "select");
        query = query.replaceAll("SQL_PARSER_SAME_ALTERTABLEALTER", "alter");
        query = query.replaceAll("SQL_PARSER_SAME_ALTERTABLEDROP", "drop");
        String[] sqlFragments = query.split("\\s*;+\\s*");

        ArrayList<String> queries = new ArrayList<String>();
        for (String fragment : sqlFragments) {
            fragment = fragment.trim();
            if (fragment.isEmpty()) {
                continue;
            }
            if (fragment.indexOf("#(SQL_PARSER_STRING_FRAGMENT#") > -1) {
                int k = 0;
                for (String strFrag : stringFragments) {
                    fragment = fragment.replace("#(SQL_PARSER_STRING_FRAGMENT#" + k + ")", strFrag);
                    k++;
                }
            }
            fragment = fragment.replace("#(SQL_PARSER_ESCAPE_SINGLE_QUOTE)", "''");
            fragment = fragment.replace("#SQL_PARSER_STRING_KEYWORD#","");
            queries.add(fragment);
        }
        return queries;
    }

    public static List<String> parseQueryProcedureCallParameters(String query)
    {
        if (query == null) {
            return null;
        }

        query = SingleLineComments.matcher(query).replaceAll("");
        query = EscapedSingleQuote.matcher(query).replaceAll("#(SQL_PARSER_ESCAPE_SINGLE_QUOTE)");
        Matcher stringFragmentMatcher = Extract.matcher(query);
        ArrayList<String> stringFragments = new ArrayList<String>();
        int i = 0;
        while (stringFragmentMatcher.find()) {
            stringFragments.add(stringFragmentMatcher.group());
            query = stringFragmentMatcher.replaceFirst("#(SQL_PARSER_STRING_FRAGMENT#" + i + ")");
            stringFragmentMatcher = Extract.matcher(query);
            i++;
        }
        query = AutoSplitParameters.matcher(query).replaceAll(",");
        String[] sqlFragments = query.split("\\s*,+\\s*");
        ArrayList<String> queries = new ArrayList<String>();
        for(int j = 0;j<sqlFragments.length;j++)
        {
            sqlFragments[j] = sqlFragments[j].trim();
            if (sqlFragments[j].length() != 0)
            {
                if(sqlFragments[j].indexOf("#(SQL_PARSER_STRING_FRAGMENT#") > -1) {
                    for(int k = 0;k<stringFragments.size();k++) {
                        sqlFragments[j] = sqlFragments[j].replace("#(SQL_PARSER_STRING_FRAGMENT#" + k + ")", stringFragments.get(k));
                    }
                }
                sqlFragments[j] = sqlFragments[j].replace("#(SQL_PARSER_ESCAPE_SINGLE_QUOTE)", "''");
                sqlFragments[j] = sqlFragments[j].trim();
                queries.add(sqlFragments[j]);
            }
        }
        return queries;
    }

    // Command line interaction
    private static SQLConsoleReader lineInputReader = null;
    private static FileHistory historyFile = null;

    private static final Pattern HelpToken = Pattern.compile("^\\s*help;*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern GoToken = Pattern.compile("^\\s*go;*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern ExitToken = Pattern.compile("^\\s*(exit|quit);*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern ListProceduresToken = Pattern.compile("^\\s*((?:list|show) proc|(?:list|show) procedures);*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern ListTablesToken = Pattern.compile("^\\s*((?:list|show) tables);*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern ListClassesToken = Pattern.compile("^\\s*((?:list|show) classes);*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern SemicolonToken = Pattern.compile("^.*\\s*;+\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern RecallToken = Pattern.compile("^\\s*recall\\s*([^;]+)\\s*;*\\s*$", Pattern.CASE_INSENSITIVE);
    private static final Pattern FileToken = Pattern.compile("^\\s*file\\s*['\"]*([^;'\"]+)['\"]*\\s*;*\\s*", Pattern.CASE_INSENSITIVE);
    private static int LineIndex = 1;
    private static List<String> Lines = new ArrayList<String>();

    /**
     * The list of recognized basic tab-complete-able SQL command prefixes.
     * Comparisons are done in uppercase.
     */
    static final String[] m_commandPrefixes = new String[] {
        "DELETE",
        "EXEC",
        "EXIT",
        "EXPLAIN",
        "EXPLAINPROC",
        "FILE",
        "GO",
        "HELP",
        "INSERT",
        "LIST PROCEDURES",
        "LIST TABLES",
        "LIST CLASSES",
        "SHOW PROCEDURES",
        "SHOW TABLES",
        "SHOW CLASSES",
        "QUIT",
        "RECALL",
        "SELECT",
        "UPDATE",
    };

    public static List<String> getQuery(boolean interactive) throws Exception
    {
        StringBuilder query = new StringBuilder();
        boolean isRecall = false;
        String line = null;

        do
        {
            if (interactive)
            {
                if (isRecall)
                {
                    isRecall = false;
                    line = lineInputReader.readLine("");

                } else {
                    line = lineInputReader.readLine((LineIndex++) + "> ");
                }
            } else {
                line = lineInputReader.readLine();
            }

            if (line == null)
            {
                if (query == null) {
                    return null;
                } else {
                    return parseQuery(query.toString());
                }
            }

            // Process recall commands - ONLY in interactive mode
            if (interactive && RecallToken.matcher(line).matches())
            {
                    Matcher m = RecallToken.matcher(line);
                    if (m.find())
                    {
                        int recall = -1;
                        try { recall = Integer.parseInt(m.group(1))-1; } catch(Exception x){}
                        if (recall > -1 && recall < Lines.size())
                        {
                            line = Lines.get(recall);
                            lineInputReader.putString(line);
                            lineInputReader.flush();
                            isRecall = true;
                            continue;
                        } else {
                            System.out.printf("%s> Invalid RECALL reference: '" + m.group(1) + "'.\n", LineIndex-1);
                        }
                    } else {
                        System.out.printf("%s> Invalid RECALL command: '" + line + "'.\n", LineIndex-1);
                    }
            }

            // Strip out invalid recall commands
            if (RecallToken.matcher(line).matches()) {
                line = "";
            }

            // Queue up the line to the recall stack - ONLY in interactive mode
            if (interactive) {
                Lines.add(line);
            }

            // EXIT command - ONLY in interactive mode, exit immediately (without running any queued statements)
            if (ExitToken.matcher(line).matches())
            {
                if (interactive) {
                    return null;
                }
            }
            // LIST PROCEDURES command
            else if (ListProceduresToken.matcher(line).matches())
            {
                if (interactive)
                {
                    List<String> list = new LinkedList<String>(Procedures.keySet());
                    Collections.sort(list);
                    int padding = 0;
                    for(String procedure : list) {
                        if (padding < procedure.length()) {
                            padding = procedure.length();
                        }
                    }
                    padding++;
                    String format = "%1$-" + padding + "s";
                    for(int i = 0;i<2;i++)
                    {
                        int j = 0;
                        for(String procedure : list)
                        {
                            if (i == 0 && procedure.startsWith("@")) {
                                continue;
                            } else if (i == 1 && !procedure.startsWith("@")) {
                                continue;
                            }
                            if (j == 0)
                            {
                                if (i == 0) {
                                    System.out.println("\n--- User Procedures ----------------------------------------");
                                } else {
                                    System.out.println("\n--- System Procedures --------------------------------------");
                                }
                            }
                            for (List<String> parameterSet : Procedures.get(procedure).values()) {
                                System.out.printf(format, procedure);
                                System.out.print("\t");
                                int pidx = 0;
                                for(String paramType : parameterSet)
                                {
                                    if (pidx > 0) {
                                        System.out.print(", ");
                                    }
                                    System.out.print(paramType);
                                    pidx++;
                                }
                                System.out.print("\n");
                            }
                            j++;
                        }
                    }
                    System.out.print("\n");
                }
            }
            // LIST TABLES command
            else if (ListTablesToken.matcher(line).matches())
            {
                if (interactive)
                {
                    Tables tables = getTables();
                    printTables("User Tables", tables.tables);
                    printTables("User Views", tables.views);
                    printTables("User Export Streams", tables.exports);
                    System.out.print("\n");
                }
            }
            // SHOW CLASSES
            else if (ListClassesToken.matcher(line).matches()) {
                if (interactive) {
                    if (Classlist.isEmpty()) {
                        System.out.println("\n--- Empty Class List -----------------------\n");
                    }
                    List<String> list = new LinkedList<String>(Classlist.keySet());
                    Collections.sort(list);
                    int padding = 0;
                    for(String classname : list) {
                        if (padding < classname.length()) {
                            padding = classname.length();
                        }
                    }
                    padding++;
                    String format = "%1$-" + padding + "s";
                    for(int i = 0;i<3;i++)
                    {
                        int j = 0;
                        for(String classname : list)
                        {
                            List<Boolean> stuff = Classlist.get(classname);
                            // Print non-active procs first
                            if (i == 0 && !(stuff.get(0) && !stuff.get(1))) {
                                continue;
                            } else if (i == 1 && !(stuff.get(0) && stuff.get(1))) {
                                continue;
                            } else if (i == 2 && stuff.get(0)) {
                                continue;
                            }
                            if (j == 0)
                            {
                                if (i == 0) {
                                    System.out.println("\n--- Potential Procedure Classes ----------------------------");
                                } else if (i == 1) {
                                    System.out.println("\n--- Active Procedure Classes  ------------------------------");
                                } else {
                                    System.out.println("\n--- Non-Procedure Classes ----------------------------------");
                                }
                            }
                            System.out.printf(format, classname);
                            System.out.print("\n");
                            j++;
                        }
                    }
                    System.out.print("\n");
                }
            }
            // GO commands - ONLY in interactive mode, close batch and parse for execution
            else if (GoToken.matcher(line).matches())
            {
                if (interactive) {
                    return parseQuery(query.toString().trim());
                }
            }
            // HELP commands - ONLY in interactive mode, close batch and parse for execution
            else if (HelpToken.matcher(line).matches())
            {
                if (interactive) {
                    printHelp(System.out); // Print readme to the screen
                }
            }
            // FILE command - include the content of the file into the query
            else if (FileToken.matcher(line).matches())
            {
                boolean executeImmediate = false;
                if (interactive && SemicolonToken.matcher(line).matches()) {
                    executeImmediate = true;
                }
                Matcher m = FileToken.matcher(line);
                if (m.find())
                {
                    line = readScriptFile(m.group(1));
                    if (line == null)
                    {
                        if (!interactive) {
                            return null;
                        }
                    }
                    else
                    {
                        query.append(line);
                        query.append("\n");

                        if (executeImmediate) {
                            return parseQuery(query.toString().trim());
                        }
                    }
                }
                else
                {
                    System.err.print("Invalid FILE command: '" + line + "'.");
                    // In non-interactive mode, a failure aborts the entire batch
                    // In interactive mode, we'll just ignore that specific failed command.
                    if (!interactive) {
                        return null;
                    }
                }
            }
            // Regular SQL query - collect until the next semi-colon.
            else
            {
                query.append(line);
                query.append("\n");
                if (interactive && SemicolonToken.matcher(line).matches()) {
                    return parseQuery(query.toString().trim());
                }
            }
            line = null;
        }
        while(true);
    }

    private static void printTables(final String name, final Collection<String> tables)
    {
        System.out.printf("\n--- %s --------------------------------------------\n", name);
        for (String table : tables) {
            System.out.println(table);
        }
        System.out.print("\n");
    }

    public static String readScriptFile(String filePath)
    {
        BufferedReader script = null;
        try {
            StringBuilder query = new StringBuilder();
            script = new BufferedReader(new FileReader(filePath));
            String line;
            while ((line = script.readLine()) != null) {
                // Strip out RECALL, EXIT and GO commands
                if (RecallToken.matcher(line).matches() ||
                      ExitToken.matcher(line).matches() ||
                      GoToken.matcher(line).matches()) {
                    continue;
                }
                // Recursively process FILE commands, any failure will cause a recursive failure
                Matcher m = FileToken.matcher(line);
                if (m.matches()) {
                    line = readScriptFile(m.group(1));
                    if (line == null) {
                        return null;
                    }
                }
                query.append(line);
                query.append("\n");
            }
            return query.toString().trim();
        }
        catch (FileNotFoundException e) {
            System.err.println("Script file '" + filePath + "' could not be found.");
            return null;
        }
        catch (Exception x) {
            System.err.println(x.getMessage());
            return null;
        }
        finally {
            if (script != null) {
                try {
                    script.close();
                } catch (IOException e) { }
            }
        }
    }

    // Query Execution
    private static final Pattern ExecuteCall = Pattern.compile("^(exec|execute) ", Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
    // Match queries that start with "explain" (case insensitive).  We'll convert them to @Explain invocations.
    private static final Pattern ExplainCall = Pattern.compile("^explain ", Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
    // Match queries that start with "explainproc" (case insensitive).  We'll convert them to @ExplainProc invocations.
    private static final Pattern ExplainProcCall = Pattern.compile("^explainProc ", Pattern.MULTILINE + Pattern.CASE_INSENSITIVE);
    private static final Pattern StripCRLF = Pattern.compile("[\r\n]+", Pattern.MULTILINE);
    private static final Pattern IsNull = Pattern.compile("null", Pattern.CASE_INSENSITIVE);
    private static final SimpleDateFormat DateParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static final Pattern Unquote = Pattern.compile("^'|'$", Pattern.MULTILINE);

    private static long m_startTime;
    private static void executeQuery(String query) throws Exception
    {
        m_startTime = System.nanoTime();
        Matcher matcher = ExecuteCall.matcher(query);
        if (matcher.find()) {
            query = matcher.replaceFirst("");
            List<String> params = parseQueryProcedureCallParameters(query);
            String procedure = params.remove(0);
            Map<Integer, List<String>> signature = Procedures.get(procedure);
            if (signature == null) {
                throw new Exception("Undefined procedure: " + procedure);
            }

            List<String> paramTypes = signature.get(params.size());
            if (paramTypes == null || params.size() != paramTypes.size()) {
                String expectedSizes = "";
                for (Integer expectedSize : signature.keySet()) {
                    expectedSizes += expectedSize + ", ";
                }
                throw new Exception("Invalid parameter count for procedure: " + procedure + "(expected: " + expectedSizes + " received: " + params.size() + ")");
            }
            Object[] objectParams = new Object[params.size()];
            if (procedure.equals("@SnapshotDelete")) {
                objectParams[0] = new String[] { Unquote.matcher(params.get(0)).replaceAll("").replace("''","'") };
                objectParams[1] = new String[] { Unquote.matcher(params.get(1)).replaceAll("").replace("''","'") };
            }
            else {
                for (int i = 0; i < params.size(); i++) {
                    String paramType = paramTypes.get(i);
                    String param = params.get(i);
                    if (paramType.equals("bit")) {
                        if (param.equals("yes") || param.equals("true") || param.equals("1")) {
                            objectParams[i] = (byte)1;
                        } else {
                            objectParams[i] = (byte)0;
                        }
                    }
                    else if (paramType.equals("tinyint")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            try {
                                objectParams[i] = Byte.parseByte(param);
                            }
                            catch (NumberFormatException nfe) {
                                throw new Exception("Invalid parameter:  Expected a byte numeric value, got '" + param + "' (param " + (i+1) + ").");
                            }
                        }
                    }
                    else if (paramType.equals("smallint")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            try {
                                objectParams[i] = Short.parseShort(param);
                            }
                            catch (NumberFormatException nfe) {
                                throw new Exception("Invalid parameter:  Expected a short numeric value, got '" + param + "' (param " + (i+1) + ").");
                            }
                        }
                    }
                    else if (paramType.equals("int") || paramType.equals("integer")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            try {
                                objectParams[i] = Integer.parseInt(param);
                            }
                            catch (NumberFormatException nfe) {
                                throw new Exception("Invalid parameter:  Expected a numeric value, got '" + param + "' (param " + (i+1) + ").");
                            }
                        }
                    }
                    else if (paramType.equals("bigint")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            try {
                                objectParams[i] = Long.parseLong(param);
                            }
                            catch (NumberFormatException nfe) {
                                throw new Exception("Invalid parameter:  Expected a numeric value, got '" + param + "' (param " + (i+1) + ").");
                            }
                        }
                    }
                    else if (paramType.equals("float")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            try {
                                objectParams[i] = Double.parseDouble(param);
                            }
                            catch (NumberFormatException nfe) {
                                throw new Exception("Invalid parameter:  Expected a float value, got '" + param + "' (param " + (i+1) + ").");
                            }
                        }
                    }
                    else if (paramType.equals("varchar")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            objectParams[i] = Unquote.matcher(param).replaceAll("").replace("''","'");
                        }
                    }
                    else if (paramType.equals("decimal")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            objectParams[i] = new BigDecimal(param);
                        }
                    }
                    else if (paramType.equals("timestamp")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            // Remove any quotes around the timestamp value.  ENG-2623
                            objectParams[i] = DateParser.parse(param.replaceAll("^\"|\"$", "").replaceAll("^'|'$", ""));
                        }
                    }
                    else if (paramType.equals("statisticscomponent")) {
                        String p = preprocessParam(param);
                        objectParams[i] = p;
                    }
                    else if (paramType.equals("sysinfoselector")) {
                        String p = preprocessParam(param);
                        objectParams[i] = p;
                    }
                    else if (paramType.equals("metadataselector")) {
                        String p = preprocessParam(param);
                        objectParams[i] = p;
                    }
                    else if (paramType.equals("varbinary") || paramType.equals("tinyint_array")) {
                        if (IsNull.matcher(param).matches()) {
                            objectParams[i] = null;
                        } else {
                            // Make sure we have an even amount of characters, otherwise it is an invalid hex string
                            if (param.length() % 2 == 1) {
                                throw new Exception("Invalid varbinary value: input must have an even amount of characters to be a valid hex string.");
                            }
                            String val = Unquote.matcher(param).replaceAll("").replace("''","'");
                            objectParams[i] = Encoder.hexDecode(val);
                        }
                    } else {
                        throw new Exception("Unsupported Data Type: " + paramType);
                    }
                }
            }
            if (procedure.equals("@UpdateApplicationCatalog")) {
                File catfile = null;
                if (objectParams[0] != null) {
                    catfile = new File((String)objectParams[0]);
                }
                File depfile = null;
                if (objectParams[1] != null) {
                    depfile = new File((String)objectParams[1]);
                }
                printResponse(VoltDB.updateApplicationCatalog(catfile, depfile));

                // Need to update the stored procedures after a catalog change (could have added/removed SPs!).  ENG-3726
                Procedures.clear();
                loadSystemProcedures();
                loadStoredProcedures(Procedures, Classlist);
            }
            else if (procedure.equals("@UpdateClasses")) {
                File jarfile = null;
                if (objectParams[0] != null) {
                    jarfile = new File((String)objectParams[0]);
                }
                printResponse(VoltDB.updateClasses(jarfile, (String)objectParams[1]));
                // Need to reload the procedures and classes
                Procedures.clear();
                loadSystemProcedures();
                loadStoredProcedures(Procedures, Classlist);
            }
            else {
                printResponse(VoltDB.callProcedure(procedure, objectParams));
            }
        }
        else if (ExplainCall.matcher(query).find()) {
            // We've got a query that starts with "explain", send the query to
            // @Explain (after stripping "explain").
            // This all could probably be done more elegantly via a group extracted
            // from a more comprehensive regexp.
            query = query.substring("explain ".length());
            query = StripCRLF.matcher(query).replaceAll(" ");
            printResponse(VoltDB.callProcedure("@Explain", query));
        }
        else if (ExplainProcCall.matcher(query).find()) {
            // We've got a query that starts with "explainproc", send the proc name
            // to @ExplainPlan (after stripping "explainproc").
            // This all could probably be done more elegantly via a group extracted
            // from a more comprehensive regexp.
            query = query.substring("explainProc ".length());
            query = StripCRLF.matcher(query).replaceAll(" ");
            // Clean up any extra spaces from between explainproc and the proc name.
            query = query.trim();
            printResponse(VoltDB.callProcedure("@ExplainProc", query));
        }
        else { // All other commands get forwarded to @AdHoc
            query = StripCRLF.matcher(query).replaceAll(" ");
            printResponse(VoltDB.callProcedure("@AdHoc", query));
            // if the query was DDL, reload the stored procedures.
            if (SQLLexer.extractDDLToken(query) != null) {
                Procedures.clear();
                loadSystemProcedures();
                loadStoredProcedures(Procedures, Classlist);
            }
        }
        return;
    }

    // Uppercase param.
    // Remove any quotes.
    // Trim
    private static String preprocessParam(String param)
    {
        if ((param.charAt(0) == '\'' && param.charAt(param.length()-1) == '\'') ||
                (param.charAt(0) == '"' && param.charAt(param.length()-1) == '"')) {
            // The position of the closing quote, param.length()-1 is where to end the substring
            // to get a result with two fewer characters.
            param = param.substring(1, param.length()-1);
        }
        param = param.trim();
        param = param.toUpperCase();
        return param;
    }

    // Output generation
    private static SQLCommandOutputFormatter m_outputFormatter = new SQLCommandOutputFormatterDefault();
    private static boolean m_outputShowMetadata = true;

    private static boolean isUpdateResult(VoltTable table)
    {
        return ((table.getColumnName(0).isEmpty() || table.getColumnName(0).equals("modified_tuples")) &&
                 table.getRowCount() == 1 && table.getColumnCount() == 1 && table.getColumnType(0) == VoltType.BIGINT);
    }

    private static void printResponse(ClientResponse response) throws Exception
    {
        if (response.getStatus() != ClientResponse.SUCCESS) {
            throw new Exception("Execution Error: " + response.getStatusString());
        }

        long elapsedTime = System.nanoTime() - m_startTime;
        for (VoltTable t : response.getResults()) {
            long rowCount;
            if (!isUpdateResult(t)) {
                rowCount = t.getRowCount();
                // Run it through the output formatter.
                m_outputFormatter.printTable(System.out, t, m_outputShowMetadata);
            }
            else {
                rowCount = t.fetchRow(0).getLong(0);
            }
            if (m_outputShowMetadata) {
                System.out.printf("\n\n(Returned %d rows in %.2fs)\n",
                        rowCount, elapsedTime / 1000000000.0);
            }
        }
    }

    // VoltDB connection support
    private static Client VoltDB;
    private static Map<String,Map<Integer, List<String>>> Procedures =
            Collections.synchronizedMap(new HashMap<String,Map<Integer, List<String>>>());
    private static Map<String, List<Boolean>> Classlist =
        Collections.synchronizedMap(new HashMap<String, List<Boolean>>());
    private static void loadSystemProcedures()
    {
        Procedures.put("@Pause",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@Quiesce",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@Resume",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@Shutdown",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@StopNode",
                ImmutableMap.<Integer, List<String>>builder().put(1, Arrays.asList("int")).build());
        Procedures.put("@SnapshotDelete",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("varchar", "varchar")).build()
                );
        Procedures.put("@SnapshotRestore",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("varchar", "varchar"))
                                                             .put( 1, Arrays.asList("varchar")).build()
                );
        Procedures.put("@SnapshotSave",
                ImmutableMap.<Integer, List<String>>builder().put( 3, Arrays.asList("varchar", "varchar", "bit")).
                put( 1, Arrays.asList("varchar")).build()
                );
        Procedures.put("@SnapshotScan",
                ImmutableMap.<Integer, List<String>>builder().put( 1,
                Arrays.asList("varchar")).build());
        Procedures.put("@Statistics",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("statisticscomponent", "bit")).build());
        Procedures.put("@SystemCatalog",
                ImmutableMap.<Integer, List<String>>builder().put( 1,Arrays.asList("metadataselector")).build());
        Procedures.put("@SystemInformation",
                ImmutableMap.<Integer, List<String>>builder().put( 1, Arrays.asList("sysinfoselector")).build());
        Procedures.put("@UpdateApplicationCatalog",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("varchar", "varchar")).build());
        Procedures.put("@UpdateClasses",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("varchar", "varchar")).build());
        Procedures.put("@UpdateLogging",
                ImmutableMap.<Integer, List<String>>builder().put( 1, Arrays.asList("varchar")).build());
        Procedures.put("@Promote",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@SnapshotStatus",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@Explain",
                ImmutableMap.<Integer, List<String>>builder().put( 1, Arrays.asList("varchar")).build());
        Procedures.put("@ExplainProc",
                ImmutableMap.<Integer, List<String>>builder().put( 1, Arrays.asList("varchar")).build());
        Procedures.put("@ValidatePartitioning",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("int", "varbinary")).build());
        Procedures.put("@GetPartitionKeys",
                ImmutableMap.<Integer, List<String>>builder().put( 1, Arrays.asList("varchar")).build());
        Procedures.put("@GC",
                ImmutableMap.<Integer, List<String>>builder().put( 0, new ArrayList<String>()).build());
        Procedures.put("@ApplyBinaryLogSP",
                ImmutableMap.<Integer, List<String>>builder().put( 2, Arrays.asList("varbinary", "varbinary")).build());
    }

    public static Client getClient(ClientConfig config, String[] servers, int port) throws Exception
    {
        final Client client = ClientFactory.createClient(config);

        for (String server : servers) {
            client.createConnection(server.trim(), port);
        }
        return client;
    }

    // General application support
    private static void printUsage(String msg)
    {
        System.out.print(msg);
        System.out.println("\n");
        printUsage(-1);
    }
    private static void printUsage(int exitCode)
    {
        System.out.println(
        "Usage: sqlcmd --help\n"
        + "   or  sqlcmd [--servers=comma_separated_server_list]\n"
        + "              [--port=port_number]\n"
        + "              [--user=user]\n"
        + "              [--password=password]\n"
        + "              [--output-format=(fixed|csv|tab)]\n"
        + "              [--output-skip-metadata]\n"
        + "\n"
        + "[--servers=comma_separated_server_list]\n"
        + "  List of servers to connect to.\n"
        + "  Default: localhost.\n"
        + "\n"
        + "[--port=port_number]\n"
        + "  Client port to connect to on cluster nodes.\n"
        + "  Default: 21212.\n"
        + "\n"
        + "[--user=user]\n"
        + "  Name of the user for database login.\n"
        + "  Default: (not defined - connection made without credentials).\n"
        + "\n"
        + "[--password=password]\n"
        + "  Password of the user for database login.\n"
        + "  Default: (not defined - connection made without credentials).\n"
        + "\n"
        + "[--kerberos=jaas_login_configuration_entry_key]\n"
        + "  Enable kerberos authentication for user database login by specifying\n"
        + "  the JAAS login configuration file entry name"
        + "  Default: (not defined - connection made without credentials).\n"
        + "\n"
        + "[--query=query]\n"
        + "  Execute a non-interactive query. Multiple query options are allowed.\n"
        + "  Default: (runs the interactive shell when no query options are present).\n"
        + "\n"
        + "[--output-format=(fixed|csv|tab)]\n"
        + "  Format of returned resultset data (Fixed-width, CSV or Tab-delimited).\n"
        + "  Default: fixed.\n"
        + "\n"
        + "[--output-skip-metadata]\n"
        + "  Removes metadata information such as column headers and row count from\n"
        + "  produced output.\n"
        + "\n"
        + "[--debug]\n"
        + "  Causes the utility to print out stack traces for all exceptions.\n"
        );
        System.exit(exitCode);
    }

    // printHelp() can print readme either to a file or to the screen
    // depending on the argument passed in
    public static void printHelp(OutputStream prtStr)
    {
        try {
            InputStream is = SQLCommand.class.getResourceAsStream(readme);
            while (is.available() > 0) {
                byte[] bytes = new byte[is.available()]; // Fix for ENG-3440
                is.read(bytes, 0, bytes.length);
                prtStr.write(bytes); // For JUnit test
            }
        }
        catch (Exception x) {
            System.err.println(x.getMessage());
            System.exit(-1);
        }
    }

    private static class Tables
    {
        TreeSet<String> tables = new TreeSet<String>();
        TreeSet<String> exports = new TreeSet<String>();
        TreeSet<String> views = new TreeSet<String>();
    }

    private static Tables getTables() throws Exception
    {
        Tables tables = new Tables();
        VoltTable tableData = VoltDB.callProcedure("@SystemCatalog", "TABLES").getResults()[0];
        while (tableData.advanceRow()) {
            String tableName = tableData.getString("TABLE_NAME");
            String tableType = tableData.getString("TABLE_TYPE");
            if (tableType.equalsIgnoreCase("EXPORT")) {
                tables.exports.add(tableName);
            }
            else if (tableType.equalsIgnoreCase("VIEW")) {
                tables.views.add(tableName);
            }
            else {
                tables.tables.add(tableName);
            }
        }
        return tables;
    }

    private static void loadStoredProcedures(Map<String,Map<Integer, List<String>>> procedures,
            Map<String, List<Boolean>> classlist)
    {
        VoltTable procs = null;
        VoltTable params = null;
        VoltTable classes = null;
        try {
            procs = VoltDB.callProcedure("@SystemCatalog", "PROCEDURES").getResults()[0];
            params = VoltDB.callProcedure("@SystemCatalog", "PROCEDURECOLUMNS").getResults()[0];
            classes = VoltDB.callProcedure("@SystemCatalog", "CLASSES").getResults()[0];
        }
        catch (NoConnectionsException e) {
            e.printStackTrace();
            return;
        }
        catch (IOException e) {
            e.printStackTrace();
            return;
        }
        catch (ProcCallException e) {
            e.printStackTrace();
            return;
        }
        Map<String, Integer> proc_param_counts = Collections.synchronizedMap(new HashMap<String, Integer>());
        while (params.advanceRow()) {
            String this_proc = params.getString("PROCEDURE_NAME");
            Integer curr_val = proc_param_counts.get(this_proc);
            if (curr_val == null) {
                curr_val = 1;
            } else {
                ++curr_val;
            }
            proc_param_counts.put(this_proc, curr_val);
        }
        params.resetRowPosition();
        while (procs.advanceRow()) {
            String proc_name = procs.getString("PROCEDURE_NAME");
            Integer param_count = proc_param_counts.get(proc_name);
            ArrayList<String> this_params = new ArrayList<String>();
            // prepopulate it to make sure the size is right
            if (param_count != null) {
                for (int i = 0; i < param_count; i++) {
                    this_params.add(null);
                }
            }
            else {
                param_count = 0;
            }
            HashMap<Integer, List<String>> argLists = new HashMap<Integer, List<String>>();
            argLists.put(param_count, this_params);
            procedures.put(proc_name, argLists);
        }
        classlist.clear();
        while (classes.advanceRow()) {
            String classname = classes.getString("CLASS_NAME");
            boolean isProc = (classes.getLong("VOLT_PROCEDURE") == 1L);
            boolean isActive = (classes.getLong("ACTIVE_PROC") == 1L);
            if (!classlist.containsKey(classname)) {
                List<Boolean> stuff = Collections.synchronizedList(new ArrayList<Boolean>());
                stuff.add(isProc);
                stuff.add(isActive);
                classlist.put(classname, stuff);
            }
        }

        // Retrieve the parameter types.  Note we have to do some special checking
        // for array types.  ENG-3101
        params.resetRowPosition();
        while (params.advanceRow()) {
            Map<Integer, List<String>> argLists = procedures.get(params.getString("PROCEDURE_NAME"));
            assert(argLists.size() == 1);
            List<String> this_params = argLists.values().iterator().next();
            int idx = (int)params.getLong("ORDINAL_POSITION") - 1;
            String param_type = params.getString("TYPE_NAME").toLowerCase();
            // Detect if this parameter is supposed to be an array.  It's kind of clunky, we have to
            // look in the remarks column...
            String param_remarks = params.getString("REMARKS");
            if (null != param_remarks) {
                param_type += (param_remarks.equalsIgnoreCase("ARRAY_PARAMETER") ? "_array" : "");
            }
            this_params.set(idx, param_type);
        }
    }

    static public void mockVoltDBForTest(Client testVoltDB) { VoltDB = testVoltDB; }

    static public void mockLineReaderForTest(SQLConsoleReader reader) { lineInputReader = reader; }

    private static InputStream in = null;
    private static OutputStream out = null;
    // Application entry point
    public static void main(String args[])
    {
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+0"));
        boolean debug = false;
        try {
            // Initialize parameter defaults
            String serverList = "localhost";
            int port = 21212;
            String user = "";
            String password = "";
            String kerberos = "";
            List<String> queries = null;

            // Parse out parameters
            for (int i = 0; i < args.length; i++) {
                String arg = args[i];
                if (arg.startsWith("--servers=")) {
                    serverList = arg.split("=")[1];
                } else if (arg.startsWith("--port=")) {
                    port = Integer.valueOf(arg.split("=")[1]);
                } else if (arg.startsWith("--user=")) {
                    user = arg.split("=")[1];
                } else if (arg.startsWith("--password=")) {
                    password = arg.split("=")[1];
                } else if (arg.startsWith("--kerberos=")) {
                    kerberos = arg.split("=")[1];
                } else if (arg.startsWith("--kerberos")) {
                    kerberos = "VoltDBClient";
                } else if (arg.startsWith("--query=")) {
                    List<String> argQueries = parseQuery(arg.substring(8));
                    if (!argQueries.isEmpty()) {
                        if (queries == null) {
                            queries = argQueries;
                        }
                        else {
                            queries.addAll(argQueries);
                        }
                    }
                }
                else if (arg.startsWith("--output-format=")) {
                    String formatName = arg.split("=")[1].toLowerCase();
                    if (formatName.equals("fixed")) {
                        m_outputFormatter = new SQLCommandOutputFormatterDefault();
                    }
                    else if (formatName.equals("csv")) {
                        m_outputFormatter = new SQLCommandOutputFormatterCSV();
                    }
                    else if (formatName.equals("tab")) {
                        m_outputFormatter = new SQLCommandOutputFormatterTabDelimited();
                    }
                    else {
                        printUsage("Invalid value for --output-format");
                    }
                }
                else if (arg.equals("--output-skip-metadata")) {
                    m_outputShowMetadata = false;
                }
                else if (arg.equals("--debug")) {
                    debug = true;
                }
                else if (arg.equals("--help")) {
                    printHelp(System.out); // Print readme to the screen
                    System.out.println("\n\n");
                    printUsage(0);
                }
                else if ((arg.equals("--usage")) || (arg.equals("-?"))) {
                    printUsage(0);
                }
                else {
                    printUsage("Invalid Parameter: " + arg);
                }
            }

            // Split server list
            String[] servers = serverList.split(",");

            // Phone home to see if there is a newer version of VoltDB
            openURLAsync();

            // Load system procedures
            loadSystemProcedures();

            // Don't ask... Java is such a crippled language!
            DateParser.setLenient(true);

            // Create connection
            ClientConfig config = new ClientConfig(user, password);
            config.setProcedureCallTimeout(0)// Set procedure all to infinite timeout, see ENG-2670

            // if specified enable kerberos
            if (!kerberos.isEmpty()) {
                config.enableKerberosAuthentication(kerberos);
            }
            VoltDB = getClient(config, servers, port);

            // Load user stored procs
            loadStoredProcedures(Procedures, Classlist);

            in = new FileInputStream(FileDescriptor.in);
            out = System.out;
            lineInputReader = new SQLConsoleReader(in, out);

            lineInputReader.setBellEnabled(false);

            // Provide a custom completer.
            Completer completer = new SQLCompleter(m_commandPrefixes);
            lineInputReader.addCompleter(completer);

            // Maintain persistent history in ~/.sqlcmd_history.
            historyFile = new FileHistory(new File(System.getProperty("user.home"), ".sqlcmd_history"));
            lineInputReader.setHistory(historyFile);

            // Make Ctrl-D (EOF) exit if on an empty line, otherwise delete the next character.
            KeyMap keyMap = lineInputReader.getKeys();
            keyMap.bind(new Character(KeyMap.CTRL_D).toString(), new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    CursorBuffer cursorBuffer = lineInputReader.getCursorBuffer();
                    if (cursorBuffer.length() == 0) {
                        System.exit(0);
                    }
                    else {
                        try {
                            lineInputReader.delete();
                        }
                        catch (IOException e1) {}
                    }
                }
            });

            // Removed code to prevent Ctrl-C from exiting. The original code is visible
            // in Git history hash 837df236c059b5b4362ffca7e7a5426fba1b7f20.

            boolean interactive = true;
            if (queries != null && !queries.isEmpty()) {
                // If queries are provided via command line options run them in
                // non-interactive mode.
                //TODO: Someday we should honor batching.
                interactive = false;
                for (String query : queries) {
                    executeQuery(query);
                }
            }
            if (System.in.available() > 0) {
                // If Standard input comes loaded with data, run in non-interactive mode
                interactive = false;
                queries = getQuery(false);
                if (queries == null) {
                    System.exit(0);
                } else {
                    for (String query : queries) {
                        executeQuery(query);
                    }
                }
            }
            if (interactive) {
                // Print out welcome message
                System.out.printf("SQL Command :: %s%s:%d\n", (user == "" ? "" : user + "@"), serverList, port);

                while ((queries = getQuery(true)) != null) {
                    try {
                        for (String query : queries) {
                            executeQuery(query);
                        }
                    }
                    catch (Exception x) {
                        System.err.println(x.getMessage());
                        if (debug) {
                            x.printStackTrace(System.err);
                        }
                    }
                }
            }
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            if (debug) {
                e.printStackTrace(System.err);
            }
            System.exit(-1);
        }
        finally {
            try { VoltDB.close(); } catch (Exception x) { }
            // Flush input history to a file.
            if (historyFile != null) {
                try {
                    historyFile.flush();
                }
                catch (IOException e) {
                    System.err.printf("* Unable to write history to \"%s\" *\n",
                                      historyFile.getFile().getPath());
                    e.printStackTrace();
                }
            }
            // Clean up jline2 resources.
            if (lineInputReader != null) {
                lineInputReader.shutdown();
            }
        }
    }

    // The following two methods implement a "phone home" version check for VoltDB.
    // Asynchronously ping VoltDB to see what the current released version is.
    // If it is newer than the one running here, then notify the user in some manner TBD.
    // Note that this processing should not impact utility use in any way.  Ignore all
    // errors.
    private static void openURLAsync()
    {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                 openURL();
            }
        });

        // Set the daemon flag so that this won't hang the process if it runs into difficulty
        t.setDaemon(true);
        t.start();
    }

    private static void openURL()
    {
        URL url;

        try {
            // Read the response from VoltDB
            String a="http://community.voltdb.com/versioncheck?app=sqlcmd&ver=" + org.voltdb.VoltDB.instance().getVersionString();
            url = new URL(a);
            URLConnection conn = url.openConnection();

            // open the stream and put it into BufferedReader
            BufferedReader br = new BufferedReader(
                               new InputStreamReader(conn.getInputStream()));

            while (br.readLine() != null) {
                // At this time do nothing, just drain the stream.
                // In the future we'll notify the user that a new version of VoltDB is available.
            }
            br.close();
        } catch (Throwable e) {
            // ignore any error
        }
    }
}
TOP

Related Classes of org.voltdb.utils.SQLCommand$Tables

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.