org.apache.hive.hplsql.Exec.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.hplsql.Exec.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hive.hplsql;

import java.math.BigDecimal;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.Iterator;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.io.FileUtils;
import org.apache.hive.hplsql.Var.Type;
import org.apache.hive.hplsql.functions.*;

/**
 * HPL/SQL script executor
 *
 */
public class Exec extends HplsqlBaseVisitor<Integer> {

    public static final String VERSION = "HPL/SQL 0.3.31";
    public static final String ERRORCODE = "ERRORCODE";
    public static final String SQLCODE = "SQLCODE";
    public static final String SQLSTATE = "SQLSTATE";
    public static final String HOSTCODE = "HOSTCODE";

    Exec exec = null;
    ParseTree tree = null;

    public enum OnError {
        EXCEPTION, SETERROR, STOP
    };

    // Scopes of execution (code blocks) with own local variables, parameters and exception handlers
    Stack<Scope> scopes = new Stack<Scope>();
    Scope globalScope;
    Scope currentScope;

    Stack<Var> stack = new Stack<Var>();
    Stack<String> labels = new Stack<String>();
    Stack<String> callStack = new Stack<String>();

    Stack<Signal> signals = new Stack<Signal>();
    Signal currentSignal;
    Scope currentHandlerScope;
    boolean resignal = false;

    HashMap<String, String> managedTables = new HashMap<String, String>();
    HashMap<String, String> objectMap = new HashMap<String, String>();
    HashMap<String, String> objectConnMap = new HashMap<String, String>();
    HashMap<String, ArrayList<Var>> returnCursors = new HashMap<String, ArrayList<Var>>();
    HashMap<String, Package> packages = new HashMap<String, Package>();

    Package currentPackageDecl = null;

    public ArrayList<String> stmtConnList = new ArrayList<String>();

    Arguments arguments = new Arguments();
    public Conf conf;
    Expression expr;
    Function function;
    Converter converter;
    Meta meta;
    Select select;
    Stmt stmt;
    Conn conn;

    int rowCount = 0;

    String execString;
    String execFile;
    String execMain;
    StringBuilder localUdf = new StringBuilder();
    boolean initRoutines = false;
    public boolean buildSql = false;
    public boolean inCallStmt = false;
    boolean udfRegistered = false;
    boolean udfRun = false;

    boolean dotHplsqlrcExists = false;
    boolean hplsqlrcExists = false;

    boolean trace = false;
    boolean info = true;
    boolean offline = false;

    Exec() {
        exec = this;
    }

    Exec(Exec exec) {
        this.exec = exec;
    }

    /** 
     * Set a variable using a value from the parameter or the stack 
     */
    public Var setVariable(String name, Var value) {
        if (value == null || value == Var.Empty) {
            if (exec.stack.empty()) {
                return Var.Empty;
            }
            value = exec.stack.pop();
        }
        if (name.startsWith("hplsql.")) {
            exec.conf.setOption(name, value.toString());
            return Var.Empty;
        }
        Var var = findVariable(name);
        if (var != null) {
            var.cast(value);
        } else {
            var = new Var(value);
            var.setName(name);
            if (exec.currentScope != null) {
                exec.currentScope.addVariable(var);
            }
        }
        return var;
    }

    public Var setVariable(String name) {
        return setVariable(name, Var.Empty);
    }

    public Var setVariable(String name, String value) {
        return setVariable(name, new Var(value));
    }

    public Var setVariable(String name, int value) {
        return setVariable(name, new Var(new Long(value)));
    }

    /** 
     * Set variable to NULL 
     */
    public Var setVariableToNull(String name) {
        Var var = findVariable(name);
        if (var != null) {
            var.removeValue();
        } else {
            var = new Var();
            var.setName(name);
            if (exec.currentScope != null) {
                exec.currentScope.addVariable(var);
            }
        }
        return var;
    }

    /**
     * Add a local variable to the current scope
     */
    public void addVariable(Var var) {
        if (currentPackageDecl != null) {
            currentPackageDecl.addVariable(var);
        } else if (exec.currentScope != null) {
            exec.currentScope.addVariable(var);
        }
    }

    /**
     * Add a condition handler to the current scope
     */
    public void addHandler(Handler handler) {
        if (exec.currentScope != null) {
            exec.currentScope.addHandler(handler);
        }
    }

    /**
     * Add a return cursor visible to procedure callers and clients
     */
    public void addReturnCursor(Var var) {
        String routine = callStackPeek();
        ArrayList<Var> cursors = returnCursors.get(routine);
        if (cursors == null) {
            cursors = new ArrayList<Var>();
            returnCursors.put(routine, cursors);
        }
        cursors.add(var);
    }

    /**
     * Get the return cursor defined in the specified procedure
     */
    public Var consumeReturnCursor(String routine) {
        ArrayList<Var> cursors = returnCursors.get(routine.toUpperCase());
        if (cursors == null) {
            return null;
        }
        Var var = cursors.get(0);
        cursors.remove(0);
        return var;
    }

    /**
     * Push a value to the stack
     */
    public void stackPush(Var var) {
        exec.stack.push(var);
    }

    /**
     * Push a string value to the stack
     */
    public void stackPush(String val) {
        exec.stack.push(new Var(val));
    }

    public void stackPush(StringBuilder val) {
        stackPush(val.toString());
    }

    /**
     * Push a boolean value to the stack
     */
    public void stackPush(Boolean val) {
        exec.stack.push(new Var(val));
    }

    /**
     * Select a value from the stack, but not remove
     */
    public Var stackPeek() {
        return exec.stack.peek();
    }

    /**
     * Pop a value from the stack
     */
    public Var stackPop() {
        if (!exec.stack.isEmpty()) {
            return exec.stack.pop();
        }
        return Var.Empty;
    }

    /**
     * Push a value to the call stack
     */
    public void callStackPush(String val) {
        exec.callStack.push(val.toUpperCase());
    }

    /**
     * Select a value from the call stack, but not remove
     */
    public String callStackPeek() {
        if (!exec.callStack.isEmpty()) {
            return exec.callStack.peek();
        }
        return null;
    }

    /**
     * Pop a value from the call stack
     */
    public String callStackPop() {
        if (!exec.callStack.isEmpty()) {
            return exec.callStack.pop();
        }
        return null;
    }

    /** 
     * Find an existing variable by name 
     */
    public Var findVariable(String name) {
        Var var = null;
        String name1 = name;
        String name1a = null;
        String name2 = null;
        Scope cur = exec.currentScope;
        Package pack = null;
        Package packCallContext = exec.getPackageCallContext();
        ArrayList<String> qualified = exec.meta.splitIdentifier(name);
        if (qualified != null) {
            name1 = qualified.get(0);
            name2 = qualified.get(1);
            pack = findPackage(name1);
            if (pack != null) {
                var = pack.findVariable(name2);
                if (var != null) {
                    return var;
                }
            }
        }
        if (name1.startsWith(":")) {
            name1a = name1.substring(1);
        }
        while (cur != null) {
            var = findVariable(cur.vars, name1);
            if (var == null && name1a != null) {
                var = findVariable(cur.vars, name1a);
            }
            if (var == null && packCallContext != null) {
                var = packCallContext.findVariable(name1);
            }
            if (var != null) {
                if (qualified != null) {
                    if (var.type == Var.Type.ROW && var.value != null) {
                        Row row = (Row) var.value;
                        var = row.getValue(name2);
                    }
                }
                return var;
            }
            if (cur.type == Scope.Type.ROUTINE) {
                cur = exec.globalScope;
            } else {
                cur = cur.parent;
            }
        }
        return null;
    }

    public Var findVariable(Var name) {
        return findVariable(name.getName());
    }

    Var findVariable(ArrayList<Var> vars, String name) {
        for (Var var : vars) {
            if (name.equalsIgnoreCase(var.getName())) {
                return var;
            }
        }
        return null;
    }

    /**
     * Find a cursor variable by name
     */
    public Var findCursor(String name) {
        Var cursor = exec.findVariable(name);
        if (cursor != null && cursor.type == Type.CURSOR) {
            return cursor;
        }
        return null;
    }

    /**
     * Find the package by name
     */
    Package findPackage(String name) {
        return packages.get(name.toUpperCase());
    }

    /**
     * Enter a new scope
     */
    public void enterScope(Scope scope) {
        exec.scopes.push(scope);
    }

    public void enterScope(Scope.Type type) {
        enterScope(type, null);
    }

    public void enterScope(Scope.Type type, Package pack) {
        exec.currentScope = new Scope(exec.currentScope, type, pack);
        enterScope(exec.currentScope);
    }

    void enterGlobalScope() {
        globalScope = new Scope(Scope.Type.GLOBAL);
        currentScope = globalScope;
        enterScope(globalScope);
    }

    /**
     * Leave the current scope
     */
    public void leaveScope() {
        if (!exec.signals.empty()) {
            Scope scope = exec.scopes.peek();
            Signal signal = exec.signals.peek();
            if (exec.conf.onError != OnError.SETERROR) {
                runExitHandler();
            }
            if (signal.type == Signal.Type.LEAVE_ROUTINE && scope.type == Scope.Type.ROUTINE) {
                exec.signals.pop();
            }
        }
        exec.currentScope = exec.scopes.pop().getParent();
    }

    /**
     * Send a signal
     */
    public void signal(Signal signal) {
        exec.signals.push(signal);
    }

    public void signal(Signal.Type type, String value, Exception exception) {
        signal(new Signal(type, value, exception));
    }

    public void signal(Signal.Type type, String value) {
        setSqlCode(-1);
        signal(type, value, null);
    }

    public void signal(Signal.Type type) {
        setSqlCode(-1);
        signal(type, null, null);
    }

    public void signal(Query query) {
        setSqlCode(query.getException());
        signal(Signal.Type.SQLEXCEPTION, query.errorText(), query.getException());
    }

    public void signal(Exception exception) {
        setSqlCode(exception);
        signal(Signal.Type.SQLEXCEPTION, exception.getMessage(), exception);
    }

    /**
     * Resignal the condition
     */
    public void resignal() {
        resignal(exec.currentSignal);
    }

    public void resignal(Signal signal) {
        if (signal != null) {
            exec.resignal = true;
            signal(signal);
        }
    }

    /**
     * Run CONTINUE handlers 
     */
    boolean runContinueHandler() {
        Scope cur = exec.currentScope;
        exec.currentSignal = exec.signals.pop();
        while (cur != null) {
            for (Handler h : cur.handlers) {
                if (h.execType != Handler.ExecType.CONTINUE) {
                    continue;
                }
                if ((h.type != Signal.Type.USERDEFINED && h.type == exec.currentSignal.type)
                        || (h.type == Signal.Type.USERDEFINED && h.type == exec.currentSignal.type
                                && h.value.equalsIgnoreCase(exec.currentSignal.value))) {
                    trace(h.ctx, "CONTINUE HANDLER");
                    enterScope(Scope.Type.HANDLER);
                    exec.currentHandlerScope = h.scope;
                    visit(h.ctx.single_block_stmt());
                    leaveScope();
                    exec.currentSignal = null;
                    return true;
                }
            }
            cur = cur.parent;
        }
        exec.signals.push(exec.currentSignal);
        exec.currentSignal = null;
        return false;
    }

    /**
     * Run EXIT handler defined for the current scope 
     */
    boolean runExitHandler() {
        exec.currentSignal = exec.signals.pop();
        for (Handler h : currentScope.handlers) {
            if (h.execType != Handler.ExecType.EXIT) {
                continue;
            }
            if ((h.type != Signal.Type.USERDEFINED && h.type == exec.currentSignal.type)
                    || (h.type == Signal.Type.USERDEFINED && h.type == exec.currentSignal.type
                            && h.value.equalsIgnoreCase(currentSignal.value))) {
                trace(h.ctx, "EXIT HANDLER");
                enterScope(Scope.Type.HANDLER);
                exec.currentHandlerScope = h.scope;
                visit(h.ctx.single_block_stmt());
                leaveScope();
                exec.currentSignal = null;
                return true;
            }
        }
        exec.signals.push(exec.currentSignal);
        exec.currentSignal = null;
        return false;
    }

    /**
     * Pop the last signal
     */
    public Signal signalPop() {
        if (!exec.signals.empty()) {
            return exec.signals.pop();
        }
        return null;
    }

    /**
     * Peek the last signal
     */
    public Signal signalPeek() {
        if (!exec.signals.empty()) {
            return exec.signals.peek();
        }
        return null;
    }

    /**
     * Pop the current label
     */
    public String labelPop() {
        if (!exec.labels.empty()) {
            return exec.labels.pop();
        }
        return "";
    }

    /**
     * Execute a SQL query (SELECT)
     */
    public Query executeQuery(ParserRuleContext ctx, Query query, String connProfile) {
        if (!exec.offline) {
            exec.rowCount = 0;
            exec.conn.executeQuery(query, connProfile);
            return query;
        }
        setSqlNoData();
        info(ctx, "Not executed - offline mode set");
        return query;
    }

    public Query executeQuery(ParserRuleContext ctx, String sql, String connProfile) {
        return executeQuery(ctx, new Query(sql), connProfile);
    }

    /**
     * Prepare a SQL query (SELECT)
     */
    public Query prepareQuery(ParserRuleContext ctx, Query query, String connProfile) {
        if (!exec.offline) {
            exec.rowCount = 0;
            exec.conn.prepareQuery(query, connProfile);
            return query;
        }
        setSqlNoData();
        info(ctx, "Not executed - offline mode set");
        return query;
    }

    public Query prepareQuery(ParserRuleContext ctx, String sql, String connProfile) {
        return prepareQuery(ctx, new Query(sql), connProfile);
    }

    /**
     * Execute a SQL statement 
     */
    public Query executeSql(ParserRuleContext ctx, String sql, String connProfile) {
        if (!exec.offline) {
            exec.rowCount = 0;
            Query query = conn.executeSql(sql, connProfile);
            exec.rowCount = query.getRowCount();
            return query;
        }
        info(ctx, "Not executed - offline mode set");
        return new Query("");
    }

    /**
     * Close the query object
     */
    public void closeQuery(Query query, String conn) {
        if (!exec.offline) {
            exec.conn.closeQuery(query, conn);
        }
    }

    /**
     * Register JARs, FILEs and CREATE TEMPORARY FUNCTION for UDF call
     */
    public void registerUdf() {
        if (udfRegistered) {
            return;
        }
        ArrayList<String> sql = new ArrayList<String>();
        String dir = Utils.getExecDir();
        String hplsqlJarName = "hplsql.jar";
        for (String jarName : new java.io.File(dir).list()) {
            if (jarName.startsWith("hive-hplsql") && jarName.endsWith(".jar")) {
                hplsqlJarName = jarName;
                break;
            }
        }
        sql.add("ADD JAR " + dir + hplsqlJarName);
        sql.add("ADD JAR " + dir + "antlr4-runtime-4.5.jar");
        if (!conf.getLocation().equals("")) {
            sql.add("ADD FILE " + conf.getLocation());
        } else {
            sql.add("ADD FILE " + dir + Conf.SITE_XML);
        }
        if (dotHplsqlrcExists) {
            sql.add("ADD FILE " + dir + Conf.DOT_HPLSQLRC);
        }
        if (hplsqlrcExists) {
            sql.add("ADD FILE " + dir + Conf.HPLSQLRC);
        }
        String lu = createLocalUdf();
        if (lu != null) {
            sql.add("ADD FILE " + lu);
        }
        sql.add("CREATE TEMPORARY FUNCTION hplsql AS 'org.apache.hive.hplsql.Udf'");
        exec.conn.addPreSql(exec.conf.defaultConnection, sql);
        udfRegistered = true;
    }

    /**
     * Initialize options
     */
    void initOptions() {
        Iterator<Map.Entry<String, String>> i = exec.conf.iterator();
        while (i.hasNext()) {
            Entry<String, String> item = (Entry<String, String>) i.next();
            String key = (String) item.getKey();
            String value = (String) item.getValue();
            if (key == null || value == null || !key.startsWith("hplsql.")) {
                continue;
            } else if (key.compareToIgnoreCase(Conf.CONN_DEFAULT) == 0) {
                exec.conf.defaultConnection = value;
            } else if (key.startsWith("hplsql.conn.init.")) {
                exec.conn.addConnectionInit(key.substring(17), value);
            } else if (key.startsWith(Conf.CONN_CONVERT)) {
                exec.conf.setConnectionConvert(key.substring(20), value);
            } else if (key.startsWith("hplsql.conn.")) {
                exec.conn.addConnection(key.substring(12), value);
            } else if (key.startsWith("hplsql.")) {
                exec.conf.setOption(key, value);
            }
        }
    }

    /**
     * Set SQLCODE
     */
    public void setSqlCode(int sqlcode) {
        Long code = new Long(sqlcode);
        Var var = findVariable(SQLCODE);
        if (var != null) {
            var.setValue(code);
        }
        var = findVariable(ERRORCODE);
        if (var != null) {
            var.setValue(code);
        }
    }

    public void setSqlCode(Exception exception) {
        if (exception instanceof SQLException) {
            setSqlCode(((SQLException) exception).getErrorCode());
            setSqlState(((SQLException) exception).getSQLState());
        } else {
            setSqlCode(-1);
            setSqlState("02000");
        }
    }

    /**
     * Set SQLSTATE
     */
    public void setSqlState(String sqlstate) {
        Var var = findVariable(SQLSTATE);
        if (var != null) {
            var.setValue(sqlstate);
        }
    }

    /**
     * Set HOSTCODE
     */
    public void setHostCode(int code) {
        Var var = findVariable(HOSTCODE);
        if (var != null) {
            var.setValue(new Long(code));
        }
    }

    /**
     * Set successful execution for SQL
     */
    public void setSqlSuccess() {
        setSqlCode(0);
        setSqlState("00000");
    }

    /**
     * Set SQL_NO_DATA as the result of SQL execution
     */
    public void setSqlNoData() {
        setSqlCode(100);
        setSqlState("01000");
    }

    /**
     * Compile and run HPL/SQL script 
     */
    public Integer run(String[] args) throws Exception {
        enterGlobalScope();
        if (init(args) != 0) {
            return 1;
        }
        Var result = run();
        if (result != null) {
            System.out.println(result.toString());
        }
        leaveScope();
        cleanup();
        printExceptions();
        return getProgramReturnCode();
    }

    /**
     * Run already compiled HPL/SQL script (also used from Hive UDF)
     */
    public Var run() {
        if (tree == null) {
            return null;
        }
        if (execMain != null) {
            initRoutines = true;
            visit(tree);
            initRoutines = false;
            exec.function.execProc(execMain);
        } else {
            visit(tree);
        }
        if (!exec.stack.isEmpty()) {
            return exec.stackPop();
        }
        return null;
    }

    /**
     * Initialize PL/HQL
     */
    Integer init(String[] args) throws Exception {
        if (!parseArguments(args)) {
            return 1;
        }
        // specify the default log4j2 properties file.
        System.setProperty("log4j.configurationFile", "hive-log4j2.properties");
        conf = new Conf();
        conf.init();
        conn = new Conn(this);
        meta = new Meta(this);
        initOptions();

        expr = new Expression(this);
        select = new Select(this);
        stmt = new Stmt(this);
        converter = new Converter(this);

        function = new Function(this);
        new FunctionDatetime(this).register(function);
        new FunctionMisc(this).register(function);
        new FunctionString(this).register(function);
        new FunctionOra(this).register(function);
        addVariable(new Var(ERRORCODE, Var.Type.BIGINT, 0L));
        addVariable(new Var(SQLCODE, Var.Type.BIGINT, 0L));
        addVariable(new Var(SQLSTATE, Var.Type.STRING, "00000"));
        addVariable(new Var(HOSTCODE, Var.Type.BIGINT, 0L));
        for (Map.Entry<String, String> v : arguments.getVars().entrySet()) {
            addVariable(new Var(v.getKey(), Var.Type.STRING, v.getValue()));
        }
        InputStream input = null;
        if (execString != null) {
            input = new ByteArrayInputStream(execString.getBytes("UTF-8"));
        } else {
            input = new FileInputStream(execFile);
        }
        HplsqlLexer lexer = new HplsqlLexer(new ANTLRInputStream(input));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        HplsqlParser parser = new HplsqlParser(tokens);
        tree = parser.program();
        if (trace) {
            System.err.println("Configuration file: " + conf.getLocation());
            System.err.println("Parser tree: " + tree.toStringTree(parser));
        }
        includeRcFile();
        return 0;
    }

    /**
     * Parse command line arguments
     */
    boolean parseArguments(String[] args) {
        boolean parsed = arguments.parse(args);
        if (parsed && arguments.hasVersionOption()) {
            System.err.println(VERSION);
            return false;
        }
        if (!parsed || arguments.hasHelpOption()
                || (arguments.getExecString() == null && arguments.getFileName() == null)) {
            arguments.printHelp();
            return false;
        }
        execString = arguments.getExecString();
        execFile = arguments.getFileName();
        execMain = arguments.getMain();
        if (arguments.hasTraceOption()) {
            trace = true;
        }
        if (arguments.hasOfflineOption()) {
            offline = true;
        }
        if (execString != null && execFile != null) {
            System.err.println("The '-e' and '-f' options cannot be specified simultaneously.");
            return false;
        }
        return true;
    }

    /**
     * Include statements from .hplsqlrc and hplsql rc files
     */
    void includeRcFile() {
        if (includeFile(Conf.DOT_HPLSQLRC, false)) {
            dotHplsqlrcExists = true;
        } else {
            if (includeFile(Conf.HPLSQLRC, false)) {
                hplsqlrcExists = true;
            }
        }
        if (udfRun) {
            includeFile(Conf.HPLSQL_LOCALS_SQL, true);
        }
    }

    /**
     * Include statements from a file
     */
    boolean includeFile(String file, boolean showError) {
        try {
            String content = FileUtils.readFileToString(new java.io.File(file), "UTF-8");
            if (content != null && !content.isEmpty()) {
                if (trace) {
                    trace(null, "INCLUDE CONTENT " + file + " (non-empty)");
                }
                new Exec(this).include(content);
                return true;
            }
        } catch (Exception e) {
            if (showError) {
                error(null, "INCLUDE file error: " + e.getMessage());
            }
        }
        return false;
    }

    /**
     * Execute statements from an include file
     */
    void include(String content) throws Exception {
        InputStream input = new ByteArrayInputStream(content.getBytes("UTF-8"));
        HplsqlLexer lexer = new HplsqlLexer(new ANTLRInputStream(input));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        HplsqlParser parser = new HplsqlParser(tokens);
        ParseTree tree = parser.program();
        visit(tree);
    }

    /**
     * Start executing HPL/SQL script
     */
    @Override
    public Integer visitProgram(HplsqlParser.ProgramContext ctx) {
        Integer rc = visitChildren(ctx);
        return rc;
    }

    /**
     * Enter BEGIN-END block
     */
    @Override
    public Integer visitBegin_end_block(HplsqlParser.Begin_end_blockContext ctx) {
        enterScope(Scope.Type.BEGIN_END);
        Integer rc = visitChildren(ctx);
        leaveScope();
        return rc;
    }

    /**
     * Free resources before exit
     */
    void cleanup() {
        for (Map.Entry<String, String> i : managedTables.entrySet()) {
            String sql = "DROP TABLE IF EXISTS " + i.getValue();
            Query query = executeSql(null, sql, exec.conf.defaultConnection);
            closeQuery(query, exec.conf.defaultConnection);
            if (trace) {
                trace(null, sql);
            }
        }
    }

    /**
     * Output information about unhandled exceptions
     */
    void printExceptions() {
        while (!signals.empty()) {
            Signal sig = signals.pop();
            if (sig.type == Signal.Type.SQLEXCEPTION) {
                System.err.println("Unhandled exception in HPL/SQL");
            }
            if (sig.exception != null) {
                sig.exception.printStackTrace();
            } else if (sig.value != null) {
                System.err.println(sig.value);
            }
        }
    }

    /**
     * Get the program return code
     */
    Integer getProgramReturnCode() {
        Integer rc = 0;
        if (!signals.empty()) {
            Signal sig = signals.pop();
            if ((sig.type == Signal.Type.LEAVE_PROGRAM || sig.type == Signal.Type.LEAVE_ROUTINE)
                    && sig.value != null) {
                try {
                    rc = Integer.parseInt(sig.value);
                } catch (NumberFormatException e) {
                    rc = 1;
                }
            }
        }
        return rc;
    }

    /**
     * Executing a statement
     */
    @Override
    public Integer visitStmt(HplsqlParser.StmtContext ctx) {
        if (ctx.semicolon_stmt() != null) {
            return 0;
        }
        if (initRoutines && ctx.create_procedure_stmt() == null && ctx.create_function_stmt() == null) {
            return 0;
        }
        if (exec.resignal) {
            if (exec.currentScope != exec.currentHandlerScope.parent) {
                return 0;
            }
            exec.resignal = false;
        }
        if (!exec.signals.empty() && exec.conf.onError != OnError.SETERROR) {
            if (!runContinueHandler()) {
                return 0;
            }
        }
        Var prev = stackPop();
        if (prev != null && prev.value != null) {
            System.out.println(prev.toString());
        }
        return visitChildren(ctx);
    }

    /**
     * Executing or building SELECT statement
     */
    @Override
    public Integer visitSelect_stmt(HplsqlParser.Select_stmtContext ctx) {
        return exec.select.select(ctx);
    }

    @Override
    public Integer visitCte_select_stmt(HplsqlParser.Cte_select_stmtContext ctx) {
        return exec.select.cte(ctx);
    }

    @Override
    public Integer visitFullselect_stmt(HplsqlParser.Fullselect_stmtContext ctx) {
        return exec.select.fullselect(ctx);
    }

    @Override
    public Integer visitSubselect_stmt(HplsqlParser.Subselect_stmtContext ctx) {
        return exec.select.subselect(ctx);
    }

    @Override
    public Integer visitSelect_list(HplsqlParser.Select_listContext ctx) {
        return exec.select.selectList(ctx);
    }

    @Override
    public Integer visitFrom_clause(HplsqlParser.From_clauseContext ctx) {
        return exec.select.from(ctx);
    }

    @Override
    public Integer visitFrom_table_name_clause(HplsqlParser.From_table_name_clauseContext ctx) {
        return exec.select.fromTable(ctx);
    }

    @Override
    public Integer visitFrom_subselect_clause(HplsqlParser.From_subselect_clauseContext ctx) {
        return exec.select.fromSubselect(ctx);
    }

    @Override
    public Integer visitFrom_join_clause(HplsqlParser.From_join_clauseContext ctx) {
        return exec.select.fromJoin(ctx);
    }

    @Override
    public Integer visitFrom_table_values_clause(HplsqlParser.From_table_values_clauseContext ctx) {
        return exec.select.fromTableValues(ctx);
    }

    @Override
    public Integer visitWhere_clause(HplsqlParser.Where_clauseContext ctx) {
        return exec.select.where(ctx);
    }

    @Override
    public Integer visitSelect_options_item(HplsqlParser.Select_options_itemContext ctx) {
        return exec.select.option(ctx);
    }

    /**
     * Column name
     */
    @Override
    public Integer visitColumn_name(HplsqlParser.Column_nameContext ctx) {
        stackPush(meta.normalizeIdentifierPart(ctx.getText()));
        return 0;
    }

    /**
     * Table name
     */
    @Override
    public Integer visitTable_name(HplsqlParser.Table_nameContext ctx) {
        String name = ctx.getText();
        String nameUp = name.toUpperCase();
        String nameNorm = meta.normalizeObjectIdentifier(name);
        String actualName = exec.managedTables.get(nameUp);
        String conn = exec.objectConnMap.get(nameUp);
        if (conn == null) {
            conn = conf.defaultConnection;
        }
        stmtConnList.add(conn);
        if (actualName != null) {
            stackPush(actualName);
            return 0;
        }
        actualName = exec.objectMap.get(nameUp);
        if (actualName != null) {
            stackPush(actualName);
            return 0;
        }
        stackPush(nameNorm);
        return 0;
    }

    /**
     * SQL INSERT statement
     */
    @Override
    public Integer visitInsert_stmt(HplsqlParser.Insert_stmtContext ctx) {
        return exec.stmt.insert(ctx);
    }

    /**
     * INSERT DIRECTORY statement
     */
    @Override
    public Integer visitInsert_directory_stmt(HplsqlParser.Insert_directory_stmtContext ctx) {
        return exec.stmt.insertDirectory(ctx);
    }

    /**
     * EXCEPTION block
     */
    @Override
    public Integer visitException_block_item(HplsqlParser.Exception_block_itemContext ctx) {
        if (exec.signals.empty()) {
            return 0;
        }
        if (exec.conf.onError == OnError.SETERROR || exec.conf.onError == OnError.STOP) {
            exec.signals.pop();
            return 0;
        }
        if (ctx.L_ID().toString().equalsIgnoreCase("OTHERS")) {
            trace(ctx, "EXCEPTION HANDLER");
            exec.signals.pop();
            enterScope(Scope.Type.HANDLER);
            visit(ctx.block());
            leaveScope();
        }
        return 0;
    }

    /**
     * DECLARE variable statement
     */
    @Override
    public Integer visitDeclare_var_item(HplsqlParser.Declare_var_itemContext ctx) {
        String type = null;
        Row row = null;
        String len = null;
        String scale = null;
        Var default_ = null;
        if (ctx.dtype().T_ROWTYPE() != null) {
            row = meta.getRowDataType(ctx, exec.conf.defaultConnection, ctx.dtype().ident().getText());
            if (row == null) {
                type = Var.DERIVED_ROWTYPE;
            }
        } else {
            type = getDataType(ctx);
            if (ctx.dtype_len() != null) {
                len = ctx.dtype_len().L_INT(0).getText();
                if (ctx.dtype_len().L_INT(1) != null) {
                    scale = ctx.dtype_len().L_INT(1).getText();
                }
            }
            if (ctx.dtype_default() != null) {
                default_ = evalPop(ctx.dtype_default());
            }
        }
        int cnt = ctx.ident().size(); // Number of variables declared with the same data type and default
        for (int i = 0; i < cnt; i++) {
            String name = ctx.ident(i).getText();
            if (row == null) {
                Var var = new Var(name, type, len, scale, default_);
                exec.addVariable(var);
                if (ctx.T_CONSTANT() != null) {
                    var.setConstant(true);
                }
                if (trace) {
                    if (default_ != null) {
                        trace(ctx, "DECLARE " + name + " " + type + " = " + var.toSqlString());
                    } else {
                        trace(ctx, "DECLARE " + name + " " + type);
                    }
                }
            } else {
                exec.addVariable(new Var(name, row));
                if (trace) {
                    trace(ctx, "DECLARE " + name + " " + ctx.dtype().getText());
                }
            }
        }
        return 0;
    }

    /**
     * Get the variable data type
     */
    String getDataType(HplsqlParser.Declare_var_itemContext ctx) {
        String type = null;
        if (ctx.dtype().T_TYPE() != null) {
            type = meta.getDataType(ctx, exec.conf.defaultConnection, ctx.dtype().ident().getText());
            if (type == null) {
                type = Var.DERIVED_TYPE;
            }
        } else {
            type = getFormattedText(ctx.dtype());
        }
        return type;
    }

    /**
     * ALLOCATE CURSOR statement
     */
    @Override
    public Integer visitAllocate_cursor_stmt(HplsqlParser.Allocate_cursor_stmtContext ctx) {
        return exec.stmt.allocateCursor(ctx);
    }

    /**
     * ASSOCIATE LOCATOR statement
     */
    @Override
    public Integer visitAssociate_locator_stmt(HplsqlParser.Associate_locator_stmtContext ctx) {
        return exec.stmt.associateLocator(ctx);
    }

    /**
     * DECLARE cursor statement
     */
    @Override
    public Integer visitDeclare_cursor_item(HplsqlParser.Declare_cursor_itemContext ctx) {
        return exec.stmt.declareCursor(ctx);
    }

    /**
     * DESCRIBE statement
     */
    @Override
    public Integer visitDescribe_stmt(HplsqlParser.Describe_stmtContext ctx) {
        return exec.stmt.describe(ctx);
    }

    /**
     * DROP statement
     */
    @Override
    public Integer visitDrop_stmt(HplsqlParser.Drop_stmtContext ctx) {
        return exec.stmt.drop(ctx);
    }

    /**
     * OPEN cursor statement
     */
    @Override
    public Integer visitOpen_stmt(HplsqlParser.Open_stmtContext ctx) {
        return exec.stmt.open(ctx);
    }

    /**
     * FETCH cursor statement
     */
    @Override
    public Integer visitFetch_stmt(HplsqlParser.Fetch_stmtContext ctx) {
        return exec.stmt.fetch(ctx);
    }

    /**
     * CLOSE cursor statement
     */
    @Override
    public Integer visitClose_stmt(HplsqlParser.Close_stmtContext ctx) {
        return exec.stmt.close(ctx);
    }

    /**
     * CMP statement
     */
    @Override
    public Integer visitCmp_stmt(HplsqlParser.Cmp_stmtContext ctx) {
        return new Cmp(exec).run(ctx);
    }

    /**
     * COPY statement
     */
    @Override
    public Integer visitCopy_stmt(HplsqlParser.Copy_stmtContext ctx) {
        return new Copy(exec).run(ctx);
    }

    /**
     * COPY FROM FTP statement
     */
    @Override
    public Integer visitCopy_from_ftp_stmt(HplsqlParser.Copy_from_ftp_stmtContext ctx) {
        return new Ftp(exec).run(ctx);
    }

    /**
     * COPY FROM LOCAL statement
     */
    @Override
    public Integer visitCopy_from_local_stmt(HplsqlParser.Copy_from_local_stmtContext ctx) {
        return new Copy(exec).runFromLocal(ctx);
    }

    /**
     * DECLARE HANDLER statement
     */
    @Override
    public Integer visitDeclare_handler_item(HplsqlParser.Declare_handler_itemContext ctx) {
        trace(ctx, "DECLARE HANDLER");
        Handler.ExecType execType = Handler.ExecType.EXIT;
        Signal.Type type = Signal.Type.SQLEXCEPTION;
        String value = null;
        if (ctx.T_CONTINUE() != null) {
            execType = Handler.ExecType.CONTINUE;
        }
        if (ctx.ident() != null) {
            type = Signal.Type.USERDEFINED;
            value = ctx.ident().getText();
        } else if (ctx.T_NOT() != null && ctx.T_FOUND() != null) {
            type = Signal.Type.NOTFOUND;
        }
        addHandler(new Handler(execType, type, value, exec.currentScope, ctx));
        return 0;
    }

    /**
     * DECLARE CONDITION
     */
    @Override
    public Integer visitDeclare_condition_item(HplsqlParser.Declare_condition_itemContext ctx) {
        return 0;
    }

    /**
     * DECLARE TEMPORARY TABLE statement 
     */
    @Override
    public Integer visitDeclare_temporary_table_item(HplsqlParser.Declare_temporary_table_itemContext ctx) {
        return exec.stmt.declareTemporaryTable(ctx);
    }

    /**
     * CREATE TABLE statement
     */
    @Override
    public Integer visitCreate_table_stmt(HplsqlParser.Create_table_stmtContext ctx) {
        return exec.stmt.createTable(ctx);
    }

    @Override
    public Integer visitCreate_table_options_hive_item(HplsqlParser.Create_table_options_hive_itemContext ctx) {
        return exec.stmt.createTableHiveOptions(ctx);
    }

    @Override
    public Integer visitCreate_table_options_ora_item(HplsqlParser.Create_table_options_ora_itemContext ctx) {
        return 0;
    }

    @Override
    public Integer visitCreate_table_options_td_item(HplsqlParser.Create_table_options_td_itemContext ctx) {
        return 0;
    }

    @Override
    public Integer visitCreate_table_options_mssql_item(HplsqlParser.Create_table_options_mssql_itemContext ctx) {
        return 0;
    }

    @Override
    public Integer visitCreate_table_options_db2_item(HplsqlParser.Create_table_options_db2_itemContext ctx) {
        return 0;
    }

    @Override
    public Integer visitCreate_table_options_mysql_item(HplsqlParser.Create_table_options_mysql_itemContext ctx) {
        return exec.stmt.createTableMysqlOptions(ctx);
    }

    /**
     * CREATE LOCAL TEMPORARY | VOLATILE TABLE statement 
     */
    @Override
    public Integer visitCreate_local_temp_table_stmt(HplsqlParser.Create_local_temp_table_stmtContext ctx) {
        return exec.stmt.createLocalTemporaryTable(ctx);
    }

    /**
     * ALTER TABLE statement
     */
    @Override
    public Integer visitAlter_table_stmt(HplsqlParser.Alter_table_stmtContext ctx) {
        return 0;
    }

    /**
     * CREATE DATABASE | SCHEMA statement
     */
    @Override
    public Integer visitCreate_database_stmt(HplsqlParser.Create_database_stmtContext ctx) {
        return exec.stmt.createDatabase(ctx);
    }

    /**
     * CREATE FUNCTION statement
     */
    @Override
    public Integer visitCreate_function_stmt(HplsqlParser.Create_function_stmtContext ctx) {
        exec.function.addUserFunction(ctx);
        addLocalUdf(ctx);
        return 0;
    }

    /**
     * CREATE PACKAGE specification statement
     */
    @Override
    public Integer visitCreate_package_stmt(HplsqlParser.Create_package_stmtContext ctx) {
        String name = ctx.ident(0).getText().toUpperCase();
        exec.currentPackageDecl = new Package(name, exec);
        exec.packages.put(name, exec.currentPackageDecl);
        trace(ctx, "CREATE PACKAGE");
        exec.currentPackageDecl.createSpecification(ctx);
        exec.currentPackageDecl = null;
        return 0;
    }

    /**
     * CREATE PACKAGE body statement
     */
    @Override
    public Integer visitCreate_package_body_stmt(HplsqlParser.Create_package_body_stmtContext ctx) {
        String name = ctx.ident(0).getText().toUpperCase();
        exec.currentPackageDecl = exec.packages.get(name);
        if (exec.currentPackageDecl == null) {
            exec.currentPackageDecl = new Package(name, exec);
            exec.currentPackageDecl.setAllMembersPublic(true);
            exec.packages.put(name, exec.currentPackageDecl);
        }
        trace(ctx, "CREATE PACKAGE BODY");
        exec.currentPackageDecl.createBody(ctx);
        exec.currentPackageDecl = null;
        return 0;
    }

    /**
     * CREATE PROCEDURE statement
     */
    @Override
    public Integer visitCreate_procedure_stmt(HplsqlParser.Create_procedure_stmtContext ctx) {
        exec.function.addUserProcedure(ctx);
        addLocalUdf(ctx); // Add procedures as they can be invoked by functions
        return 0;
    }

    /**
     * CREATE INDEX statement
     */
    @Override
    public Integer visitCreate_index_stmt(HplsqlParser.Create_index_stmtContext ctx) {
        return 0;
    }

    /**
     * Add functions and procedures defined in the current script
     */
    void addLocalUdf(ParserRuleContext ctx) {
        if (exec == this) {
            localUdf.append(exec.getFormattedText(ctx));
            localUdf.append("\n");
        }
    }

    /**
     * Save local functions and procedures to a file (will be added to the distributed cache) 
     */
    String createLocalUdf() {
        if (localUdf.length() == 0) {
            return null;
        }
        try {
            String file = System.getProperty("user.dir") + "/" + Conf.HPLSQL_LOCALS_SQL;
            PrintWriter writer = new PrintWriter(file, "UTF-8");
            writer.print(localUdf);
            writer.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Assignment statement for single value
     */
    @Override
    public Integer visitAssignment_stmt_single_item(HplsqlParser.Assignment_stmt_single_itemContext ctx) {
        String name = ctx.ident().getText();
        visit(ctx.expr());
        Var var = setVariable(name);
        if (trace) {
            trace(ctx, "SET " + name + " = " + var.toSqlString());
        }
        return 0;
    }

    /**
     * Assignment statement for multiple values
     */
    @Override
    public Integer visitAssignment_stmt_multiple_item(HplsqlParser.Assignment_stmt_multiple_itemContext ctx) {
        int cnt = ctx.ident().size();
        int ecnt = ctx.expr().size();
        for (int i = 0; i < cnt; i++) {
            String name = ctx.ident(i).getText();
            if (i < ecnt) {
                visit(ctx.expr(i));
                Var var = setVariable(name);
                if (trace) {
                    trace(ctx, "SET " + name + " = " + var.toString());
                }
            }
        }
        return 0;
    }

    /**
     * Assignment from SELECT statement 
     */
    @Override
    public Integer visitAssignment_stmt_select_item(HplsqlParser.Assignment_stmt_select_itemContext ctx) {
        return stmt.assignFromSelect(ctx);
    }

    /**
     * Evaluate an expression
     */
    @Override
    public Integer visitExpr(HplsqlParser.ExprContext ctx) {
        if (exec.buildSql) {
            exec.expr.execSql(ctx);
        } else {
            exec.expr.exec(ctx);
        }
        return 0;
    }

    /**
     * Evaluate a boolean expression
     */
    @Override
    public Integer visitBool_expr(HplsqlParser.Bool_exprContext ctx) {
        if (exec.buildSql) {
            exec.expr.execBoolSql(ctx);
        } else {
            exec.expr.execBool(ctx);
        }
        return 0;
    }

    @Override
    public Integer visitBool_expr_binary(HplsqlParser.Bool_expr_binaryContext ctx) {
        if (exec.buildSql) {
            exec.expr.execBoolBinarySql(ctx);
        } else {
            exec.expr.execBoolBinary(ctx);
        }
        return 0;
    }

    @Override
    public Integer visitBool_expr_unary(HplsqlParser.Bool_expr_unaryContext ctx) {
        if (exec.buildSql) {
            exec.expr.execBoolUnarySql(ctx);
        } else {
            exec.expr.execBoolUnary(ctx);
        }
        return 0;
    }

    /**
     * Static SELECT statement (i.e. unquoted) or expression
     */
    @Override
    public Integer visitExpr_select(HplsqlParser.Expr_selectContext ctx) {
        if (ctx.select_stmt() != null) {
            stackPush(new Var(evalPop(ctx.select_stmt())));
        } else {
            visit(ctx.expr());
        }
        return 0;
    }

    /**
     * File path (unquoted) or expression
     */
    @Override
    public Integer visitExpr_file(HplsqlParser.Expr_fileContext ctx) {
        if (ctx.file_name() != null) {
            stackPush(new Var(ctx.file_name().getText()));
        } else {
            visit(ctx.expr());
        }
        return 0;
    }

    /**
     * Cursor attribute %ISOPEN, %FOUND and %NOTFOUND
     */
    @Override
    public Integer visitExpr_cursor_attribute(HplsqlParser.Expr_cursor_attributeContext ctx) {
        exec.expr.execCursorAttribute(ctx);
        return 0;
    }

    /**
     * Function call
     */
    @Override
    public Integer visitExpr_func(HplsqlParser.Expr_funcContext ctx) {
        String name = ctx.ident().getText();
        if (exec.buildSql) {
            exec.function.execSql(name, ctx.expr_func_params());
        } else {
            Package packCallContext = exec.getPackageCallContext();
            ArrayList<String> qualified = exec.meta.splitIdentifier(name);
            boolean executed = false;
            if (qualified != null) {
                Package pack = findPackage(qualified.get(0));
                if (pack != null) {
                    executed = pack.execFunc(qualified.get(1), ctx.expr_func_params());
                }
            }
            if (!executed && packCallContext != null) {
                executed = packCallContext.execFunc(name, ctx.expr_func_params());
            }
            if (!executed) {
                exec.function.exec(name, ctx.expr_func_params());
            }
        }
        return 0;
    }

    /**
     * Aggregate or window function call
     */
    @Override
    public Integer visitExpr_agg_window_func(HplsqlParser.Expr_agg_window_funcContext ctx) {
        exec.function.execAggWindowSql(ctx);
        return 0;
    }

    /**
     * Function with specific syntax
     */
    @Override
    public Integer visitExpr_spec_func(HplsqlParser.Expr_spec_funcContext ctx) {
        if (exec.buildSql) {
            exec.function.specExecSql(ctx);
        } else {
            exec.function.specExec(ctx);
        }
        return 0;
    }

    /**
     * INCLUDE statement
     */
    @Override
    public Integer visitInclude_stmt(@NotNull HplsqlParser.Include_stmtContext ctx) {
        return exec.stmt.include(ctx);
    }

    /**
     * IF statement (PL/SQL syntax)
     */
    @Override
    public Integer visitIf_plsql_stmt(HplsqlParser.If_plsql_stmtContext ctx) {
        return exec.stmt.ifPlsql(ctx);
    }

    /**
     * IF statement (Transact-SQL syntax)
     */
    @Override
    public Integer visitIf_tsql_stmt(HplsqlParser.If_tsql_stmtContext ctx) {
        return exec.stmt.ifTsql(ctx);
    }

    /**
     * IF statement (BTEQ syntax)
     */
    @Override
    public Integer visitIf_bteq_stmt(HplsqlParser.If_bteq_stmtContext ctx) {
        return exec.stmt.ifBteq(ctx);
    }

    /**
     * USE statement
     */
    @Override
    public Integer visitUse_stmt(HplsqlParser.Use_stmtContext ctx) {
        return exec.stmt.use(ctx);
    }

    /** 
     * VALUES statement
     */
    @Override
    public Integer visitValues_into_stmt(HplsqlParser.Values_into_stmtContext ctx) {
        return exec.stmt.values(ctx);
    }

    /**
     * WHILE statement
     */
    @Override
    public Integer visitWhile_stmt(HplsqlParser.While_stmtContext ctx) {
        return exec.stmt.while_(ctx);
    }

    /**
     * FOR cursor statement
     */
    @Override
    public Integer visitFor_cursor_stmt(HplsqlParser.For_cursor_stmtContext ctx) {
        return exec.stmt.forCursor(ctx);
    }

    /**
     * FOR (integer range) statement
     */
    @Override
    public Integer visitFor_range_stmt(HplsqlParser.For_range_stmtContext ctx) {
        return exec.stmt.forRange(ctx);
    }

    /**
     * EXEC, EXECUTE and EXECUTE IMMEDIATE statement to execute dynamic SQL
     */
    @Override
    public Integer visitExec_stmt(HplsqlParser.Exec_stmtContext ctx) {
        exec.inCallStmt = true;
        Integer rc = exec.stmt.exec(ctx);
        exec.inCallStmt = false;
        return rc;
    }

    /**
     * CALL statement
     */
    @Override
    public Integer visitCall_stmt(HplsqlParser.Call_stmtContext ctx) {
        String name = ctx.ident().getText();
        Package packCallContext = exec.getPackageCallContext();
        ArrayList<String> qualified = exec.meta.splitIdentifier(name);
        exec.inCallStmt = true;
        boolean executed = false;
        if (qualified != null) {
            Package pack = findPackage(qualified.get(0));
            if (pack != null) {
                executed = pack.execProc(qualified.get(1), ctx.expr_func_params(),
                        true /*trace error if not exists*/);
            }
        }
        if (!executed && packCallContext != null) {
            executed = packCallContext.execProc(name, ctx.expr_func_params(), false /*trace error if not exists*/);
        }
        if (!executed) {
            exec.function.execProc(name, ctx.expr_func_params(), ctx);
        }
        exec.inCallStmt = false;
        return 0;
    }

    /**
     * EXIT statement (leave the specified loop with a condition)
     */
    @Override
    public Integer visitExit_stmt(HplsqlParser.Exit_stmtContext ctx) {
        return exec.stmt.exit(ctx);
    }

    /**
     * BREAK statement (leave the innermost loop unconditionally)
     */
    @Override
    public Integer visitBreak_stmt(HplsqlParser.Break_stmtContext ctx) {
        return exec.stmt.break_(ctx);
    }

    /**
     * LEAVE statement (leave the specified loop unconditionally)
     */
    @Override
    public Integer visitLeave_stmt(HplsqlParser.Leave_stmtContext ctx) {
        return exec.stmt.leave(ctx);
    }

    /** 
     * PRINT statement 
     */
    @Override
    public Integer visitPrint_stmt(HplsqlParser.Print_stmtContext ctx) {
        return exec.stmt.print(ctx);
    }

    /** 
     * QUIT statement 
     */
    @Override
    public Integer visitQuit_stmt(HplsqlParser.Quit_stmtContext ctx) {
        return exec.stmt.quit(ctx);
    }

    /**
     * SIGNAL statement
     */
    @Override
    public Integer visitSignal_stmt(HplsqlParser.Signal_stmtContext ctx) {
        return exec.stmt.signal(ctx);
    }

    /**
     * RESIGNAL statement
     */
    @Override
    public Integer visitResignal_stmt(HplsqlParser.Resignal_stmtContext ctx) {
        return exec.stmt.resignal(ctx);
    }

    /**
     * RETURN statement
     */
    @Override
    public Integer visitReturn_stmt(HplsqlParser.Return_stmtContext ctx) {
        return exec.stmt.return_(ctx);
    }

    /** 
     * SET session options
     */
    @Override
    public Integer visitSet_current_schema_option(HplsqlParser.Set_current_schema_optionContext ctx) {
        return exec.stmt.setCurrentSchema(ctx);
    }

    /**
     * TRUNCATE statement
     */
    @Override
    public Integer visitTruncate_stmt(HplsqlParser.Truncate_stmtContext ctx) {
        return exec.stmt.truncate(ctx);
    }

    /**
     * MAP OBJECT statement
     */
    @Override
    public Integer visitMap_object_stmt(HplsqlParser.Map_object_stmtContext ctx) {
        String source = evalPop(ctx.expr(0)).toString();
        String target = null;
        String conn = null;
        if (ctx.T_TO() != null) {
            target = evalPop(ctx.expr(1)).toString();
            exec.objectMap.put(source.toUpperCase(), target);
        }
        if (ctx.T_AT() != null) {
            if (ctx.T_TO() == null) {
                conn = evalPop(ctx.expr(1)).toString();
            } else {
                conn = evalPop(ctx.expr(2)).toString();
            }
            exec.objectConnMap.put(source.toUpperCase(), conn);
        }
        if (trace) {
            String log = "MAP OBJECT " + source;
            if (target != null) {
                log += " AS " + target;
            }
            if (conn != null) {
                log += " AT " + conn;
            }
            trace(ctx, log);
        }
        return 0;
    }

    /**
     * UPDATE statement
     */
    @Override
    public Integer visitUpdate_stmt(HplsqlParser.Update_stmtContext ctx) {
        return stmt.update(ctx);
    }

    /**
     * DELETE statement
     */
    @Override
    public Integer visitDelete_stmt(HplsqlParser.Delete_stmtContext ctx) {
        return stmt.delete(ctx);
    }

    /**
     * MERGE statement
     */
    @Override
    public Integer visitMerge_stmt(HplsqlParser.Merge_stmtContext ctx) {
        return stmt.merge(ctx);
    }

    /**
     * Run a Hive command line
     */
    @Override
    public Integer visitHive(@NotNull HplsqlParser.HiveContext ctx) {
        trace(ctx, "HIVE");
        ArrayList<String> cmd = new ArrayList<String>();
        cmd.add("hive");
        Var params = new Var(Var.Type.STRINGLIST, cmd);
        stackPush(params);
        visitChildren(ctx);
        stackPop();
        try {
            String[] cmdarr = new String[cmd.size()];
            cmd.toArray(cmdarr);
            if (trace) {
                trace(ctx, "HIVE Parameters: " + Utils.toString(cmdarr, ' '));
            }
            if (!offline) {
                Process p = Runtime.getRuntime().exec(cmdarr);
                new StreamGobbler(p.getInputStream()).start();
                new StreamGobbler(p.getErrorStream()).start();
                int rc = p.waitFor();
                if (trace) {
                    trace(ctx, "HIVE Process exit code: " + rc);
                }
            }
        } catch (Exception e) {
            setSqlCode(-1);
            signal(Signal.Type.SQLEXCEPTION, e.getMessage(), e);
            return -1;
        }
        return 0;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Integer visitHive_item(HplsqlParser.Hive_itemContext ctx) {
        Var params = stackPeek();
        ArrayList<String> a = (ArrayList<String>) params.value;
        String param = ctx.getChild(1).getText();
        if (param.equals("e")) {
            a.add("-e");
            a.add(evalPop(ctx.expr()).toString());
        } else if (param.equals("f")) {
            a.add("-f");
            a.add(evalPop(ctx.expr()).toString());
        } else if (param.equals("hiveconf")) {
            a.add("-hiveconf");
            a.add(ctx.L_ID().toString() + "=" + evalPop(ctx.expr()).toString());
        }
        return 0;
    }

    /**
     * Executing OS command
     */
    @Override
    public Integer visitHost_cmd(HplsqlParser.Host_cmdContext ctx) {
        trace(ctx, "HOST");
        execHost(ctx, ctx.start.getInputStream().getText(
                new org.antlr.v4.runtime.misc.Interval(ctx.start.getStartIndex(), ctx.stop.getStopIndex())));
        return 0;
    }

    @Override
    public Integer visitHost_stmt(HplsqlParser.Host_stmtContext ctx) {
        trace(ctx, "HOST");
        execHost(ctx, evalPop(ctx.expr()).toString());
        return 0;
    }

    public void execHost(ParserRuleContext ctx, String cmd) {
        try {
            if (trace) {
                trace(ctx, "HOST Command: " + cmd);
            }
            Process p = Runtime.getRuntime().exec(cmd);
            new StreamGobbler(p.getInputStream()).start();
            new StreamGobbler(p.getErrorStream()).start();
            int rc = p.waitFor();
            if (trace) {
                trace(ctx, "HOST Process exit code: " + rc);
            }
            setHostCode(rc);
        } catch (Exception e) {
            setHostCode(1);
            signal(Signal.Type.SQLEXCEPTION);
        }
    }

    /**
     * Standalone expression (as a statement)
     */
    @Override
    public Integer visitExpr_stmt(HplsqlParser.Expr_stmtContext ctx) {
        visitChildren(ctx);
        return 0;
    }

    /**
     * String concatenation operator
     */
    @Override
    public Integer visitExpr_concat(HplsqlParser.Expr_concatContext ctx) {
        if (exec.buildSql) {
            exec.expr.operatorConcatSql(ctx);
        } else {
            exec.expr.operatorConcat(ctx);
        }
        return 0;
    }

    /**
     * Simple CASE expression
     */
    @Override
    public Integer visitExpr_case_simple(HplsqlParser.Expr_case_simpleContext ctx) {
        if (exec.buildSql) {
            exec.expr.execSimpleCaseSql(ctx);
        } else {
            exec.expr.execSimpleCase(ctx);
        }
        return 0;
    }

    /**
     * Searched CASE expression
     */
    @Override
    public Integer visitExpr_case_searched(HplsqlParser.Expr_case_searchedContext ctx) {
        if (exec.buildSql) {
            exec.expr.execSearchedCaseSql(ctx);
        } else {
            exec.expr.execSearchedCase(ctx);
        }
        return 0;
    }

    /**
     * GET DIAGNOSTICS EXCEPTION statement
     */
    @Override
    public Integer visitGet_diag_stmt_exception_item(HplsqlParser.Get_diag_stmt_exception_itemContext ctx) {
        return exec.stmt.getDiagnosticsException(ctx);
    }

    /**
     * GET DIAGNOSTICS ROW_COUNT statement
     */
    @Override
    public Integer visitGet_diag_stmt_rowcount_item(HplsqlParser.Get_diag_stmt_rowcount_itemContext ctx) {
        return exec.stmt.getDiagnosticsRowCount(ctx);
    }

    /**
     * GRANT statement
     */
    @Override
    public Integer visitGrant_stmt(HplsqlParser.Grant_stmtContext ctx) {
        trace(ctx, "GRANT");
        return 0;
    }

    /**
     * Label
     */
    @Override
    public Integer visitLabel(HplsqlParser.LabelContext ctx) {
        if (ctx.L_ID() != null) {
            exec.labels.push(ctx.L_ID().toString());
        } else {
            String label = ctx.L_LABEL().getText();
            if (label.endsWith(":")) {
                label = label.substring(0, label.length() - 1);
            }
            exec.labels.push(label);
        }
        return 0;
    }

    /**
     * Identifier
     */
    @Override
    public Integer visitIdent(HplsqlParser.IdentContext ctx) {
        String ident = ctx.getText();
        Var var = findVariable(ident);
        if (var != null) {
            if (!exec.buildSql) {
                exec.stackPush(var);
            } else {
                exec.stackPush(new Var(ident, Var.Type.STRING, var.toSqlString()));
            }
        } else {
            if (!exec.buildSql && !exec.inCallStmt && exec.function.isProc(ident)
                    && exec.function.execProc(ident, null, ctx)) {
                return 0;
            } else {
                exec.stackPush(new Var(Var.Type.IDENT, ident));
            }
        }
        return 0;
    }

    /** 
     * Single quoted string literal 
     */
    @Override
    public Integer visitSingle_quotedString(HplsqlParser.Single_quotedStringContext ctx) {
        if (exec.buildSql) {
            exec.stackPush(ctx.getText());
        } else {
            exec.stackPush(Utils.unquoteString(ctx.getText()));
        }
        return 0;
    }

    /**
     * Integer literal, signed or unsigned
     */
    @Override
    public Integer visitInt_number(HplsqlParser.Int_numberContext ctx) {
        exec.stack.push(new Var(new Long(ctx.getText())));
        return 0;
    }

    /**
     * Interval expression (INTERVAL '1' DAY i.e)
     */
    @Override
    public Integer visitExpr_interval(HplsqlParser.Expr_intervalContext ctx) {
        int num = evalPop(ctx.expr()).intValue();
        Interval interval = new Interval().set(num, ctx.interval_item().getText());
        stackPush(new Var(interval));
        return 0;
    }

    /**
     * Decimal literal, signed or unsigned
     */
    @Override
    public Integer visitDec_number(HplsqlParser.Dec_numberContext ctx) {
        stackPush(new Var(new BigDecimal(ctx.getText())));
        return 0;
    }

    /**
     * Boolean literal
     */
    @Override
    public Integer visitBool_literal(HplsqlParser.Bool_literalContext ctx) {
        boolean val = true;
        if (ctx.T_FALSE() != null) {
            val = false;
        }
        stackPush(new Var(new Boolean(val)));
        return 0;
    }

    /**
     * NULL constant
     */
    @Override
    public Integer visitNull_const(HplsqlParser.Null_constContext ctx) {
        stackPush(new Var());
        return 0;
    }

    /**
     * DATE 'YYYY-MM-DD' literal
     */
    @Override
    public Integer visitDate_literal(HplsqlParser.Date_literalContext ctx) {
        if (!exec.buildSql) {
            String str = evalPop(ctx.string()).toString();
            stackPush(new Var(Var.Type.DATE, Utils.toDate(str)));
        } else {
            stackPush(getFormattedText(ctx));
        }
        return 0;
    }

    /**
     * TIMESTAMP 'YYYY-MM-DD HH:MI:SS.FFF' literal
     */
    @Override
    public Integer visitTimestamp_literal(HplsqlParser.Timestamp_literalContext ctx) {
        if (!exec.buildSql) {
            String str = evalPop(ctx.string()).toString();
            int len = str.length();
            int precision = 0;
            if (len > 19 && len <= 29) {
                precision = len - 20;
                if (precision > 3) {
                    precision = 3;
                }
            }
            stackPush(new Var(Utils.toTimestamp(str), precision));
        } else {
            stackPush(getFormattedText(ctx));
        }
        return 0;
    }

    /**
     * Get the package context within which the current routine is executed
     */
    Package getPackageCallContext() {
        Scope cur = exec.currentScope;
        while (cur != null) {
            if (cur.type == Scope.Type.ROUTINE) {
                return cur.pack;
            }
            cur = cur.parent;
        }
        return null;
    }

    /**
     * Define the connection profile to execute the current statement
     */
    String getStatementConnection() {
        if (exec.stmtConnList.contains(exec.conf.defaultConnection)) {
            return exec.conf.defaultConnection;
        } else if (!exec.stmtConnList.isEmpty()) {
            return exec.stmtConnList.get(0);
        }
        return exec.conf.defaultConnection;
    }

    /**
     * Define the connection profile for the specified object
     * @return
     */
    String getObjectConnection(String name) {
        String conn = exec.objectConnMap.get(name.toUpperCase());
        if (conn != null) {
            return conn;
        }
        return exec.conf.defaultConnection;
    }

    /**
     * Get the connection (open the new connection if not available)
     * @throws Exception 
     */
    Connection getConnection(String conn) throws Exception {
        if (conn == null || conn.equalsIgnoreCase("default")) {
            conn = exec.conf.defaultConnection;
        }
        return exec.conn.getConnection(conn);
    }

    /**
     * Return the connection to the pool
     */
    void returnConnection(String name, Connection conn) {
        exec.conn.returnConnection(name, conn);
    }

    /**
     * Define the database type by profile name
     */
    Conn.Type getConnectionType(String conn) {
        return exec.conn.getTypeByProfile(conn);
    }

    /**
     * Get the current database type
     */
    public Conn.Type getConnectionType() {
        return getConnectionType(exec.conf.defaultConnection);
    }

    /** 
     * Add managed temporary table
     */
    public void addManagedTable(String name, String managedName) {
        exec.managedTables.put(name, managedName);
    }

    /**
     * Get node text including spaces
     */
    String getText(ParserRuleContext ctx) {
        return ctx.start.getInputStream().getText(
                new org.antlr.v4.runtime.misc.Interval(ctx.start.getStartIndex(), ctx.stop.getStopIndex()));
    }

    String getText(ParserRuleContext ctx, Token start, Token stop) {
        return ctx.start.getInputStream()
                .getText(new org.antlr.v4.runtime.misc.Interval(start.getStartIndex(), stop.getStopIndex()));
    }

    /**
     * Append the text preserving the formatting (space symbols) between tokens
     */
    void append(StringBuilder str, String appendStr, Token start, Token stop) {
        String spaces = start.getInputStream()
                .getText(new org.antlr.v4.runtime.misc.Interval(start.getStartIndex(), stop.getStopIndex()));
        spaces = spaces.substring(start.getText().length(), spaces.length() - stop.getText().length());
        str.append(spaces);
        str.append(appendStr);
    }

    void append(StringBuilder str, TerminalNode start, TerminalNode stop) {
        String text = start.getSymbol().getInputStream().getText(new org.antlr.v4.runtime.misc.Interval(
                start.getSymbol().getStartIndex(), stop.getSymbol().getStopIndex()));
        str.append(text);
    }

    /**
     * Get the first non-null node
     */
    TerminalNode nvl(TerminalNode t1, TerminalNode t2) {
        if (t1 != null) {
            return t1;
        }
        return t2;
    }

    /**
     * Evaluate the expression and pop value from the stack
     */
    Var evalPop(ParserRuleContext ctx) {
        visit(ctx);
        if (!exec.stack.isEmpty()) {
            return exec.stackPop();
        }
        return Var.Empty;
    }

    Var evalPop(ParserRuleContext ctx, long def) {
        visit(ctx);
        if (!exec.stack.isEmpty()) {
            return stackPop();
        }
        return new Var(def);
    }

    /**
     * Evaluate the data type and length 
     * 
     */
    String evalPop(HplsqlParser.DtypeContext type, HplsqlParser.Dtype_lenContext len) {
        if (isConvert(exec.conf.defaultConnection)) {
            return exec.converter.dataType(type, len);
        }
        return getText(type, type.getStart(), len.getStop());
    }

    /**
     * Evaluate the expression to NULL
     */
    void evalNull() {
        stackPush(Var.Null);
    }

    /**
     * Get formatted text between 2 tokens
     */
    public String getFormattedText(ParserRuleContext ctx) {
        return ctx.start.getInputStream().getText(
                new org.antlr.v4.runtime.misc.Interval(ctx.start.getStartIndex(), ctx.stop.getStopIndex()));
    }

    /**
     * Flag whether executed from UDF or not
     */
    void setUdfRun(boolean udfRun) {
        this.udfRun = udfRun;
    }

    /**
     * Whether on-the-fly SQL conversion is required for the connection 
     */
    boolean isConvert(String connName) {
        return exec.conf.getConnectionConvert(connName);
    }

    /**
     * Increment the row count
     */
    public int incRowCount() {
        return exec.rowCount++;
    }

    /**
     * Set the row count
     */
    public void setRowCount(int rowCount) {
        exec.rowCount = rowCount;
    }

    /**
     * Trace information
     */
    public void trace(ParserRuleContext ctx, String message) {
        if (!trace) {
            return;
        }
        if (ctx != null) {
            System.out.println("Ln:" + ctx.getStart().getLine() + " " + message);
        } else {
            System.out.println(message);
        }
    }

    /**
     * Trace values retrived from the database
     */
    public void trace(ParserRuleContext ctx, Var var, ResultSet rs, ResultSetMetaData rm, int idx)
            throws SQLException {
        if (var.type != Var.Type.ROW) {
            trace(ctx, "COLUMN: " + rm.getColumnName(idx) + ", " + rm.getColumnTypeName(idx));
            trace(ctx, "SET " + var.getName() + " = " + var.toString());
        } else {
            Row row = (Row) var.value;
            int cnt = row.size();
            for (int j = 1; j <= cnt; j++) {
                Var v = row.getValue(j - 1);
                trace(ctx, "COLUMN: " + rm.getColumnName(j) + ", " + rm.getColumnTypeName(j));
                trace(ctx, "SET " + v.getName() + " = " + v.toString());
            }
        }
    }

    /**
     * Informational messages
     */
    public void info(ParserRuleContext ctx, String message) {
        if (!info) {
            return;
        }
        if (ctx != null) {
            System.err.println("Ln:" + ctx.getStart().getLine() + " " + message);
        } else {
            System.err.println(message);
        }
    }

    /**
     * Error message
     */
    public void error(ParserRuleContext ctx, String message) {
        if (ctx != null) {
            System.err.println("Ln:" + ctx.getStart().getLine() + " " + message);
        } else {
            System.err.println(message);
        }
    }

    public Stack<Var> getStack() {
        return exec.stack;
    }

    public int getRowCount() {
        return exec.rowCount;
    }

    public Conf getConf() {
        return exec.conf;
    }

    public Meta getMeta() {
        return exec.meta;
    }

    public boolean getTrace() {
        return exec.trace;
    }

    public boolean getInfo() {
        return exec.info;
    }

    public boolean getOffline() {
        return exec.offline;
    }
}