Stmt.java

// 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.
// This file is copied from
// https://github.com/apache/hive/blob/master/hplsql/src/main/java/org/apache/hive/hplsql/Stmt.java
// and modified by Doris

package org.apache.doris.plsql;

import org.apache.doris.common.AnalysisException;
import org.apache.doris.nereids.PLParser.Allocate_cursor_stmtContext;
import org.apache.doris.nereids.PLParser.Assignment_stmt_select_itemContext;
import org.apache.doris.nereids.PLParser.Associate_locator_stmtContext;
import org.apache.doris.nereids.PLParser.Break_stmtContext;
import org.apache.doris.nereids.PLParser.Close_stmtContext;
import org.apache.doris.nereids.PLParser.Declare_cursor_itemContext;
import org.apache.doris.nereids.PLParser.Doris_statementContext;
import org.apache.doris.nereids.PLParser.Exec_stmtContext;
import org.apache.doris.nereids.PLParser.Exit_stmtContext;
import org.apache.doris.nereids.PLParser.Fetch_stmtContext;
import org.apache.doris.nereids.PLParser.For_cursor_stmtContext;
import org.apache.doris.nereids.PLParser.For_range_stmtContext;
import org.apache.doris.nereids.PLParser.Get_diag_stmt_exception_itemContext;
import org.apache.doris.nereids.PLParser.Get_diag_stmt_rowcount_itemContext;
import org.apache.doris.nereids.PLParser.If_bteq_stmtContext;
import org.apache.doris.nereids.PLParser.If_plsql_stmtContext;
import org.apache.doris.nereids.PLParser.If_tsql_stmtContext;
import org.apache.doris.nereids.PLParser.Include_stmtContext;
import org.apache.doris.nereids.PLParser.IntoClauseContext;
import org.apache.doris.nereids.PLParser.Leave_stmtContext;
import org.apache.doris.nereids.PLParser.Open_stmtContext;
import org.apache.doris.nereids.PLParser.Print_stmtContext;
import org.apache.doris.nereids.PLParser.QueryPrimaryDefaultContext;
import org.apache.doris.nereids.PLParser.Quit_stmtContext;
import org.apache.doris.nereids.PLParser.RegularQuerySpecificationContext;
import org.apache.doris.nereids.PLParser.Resignal_stmtContext;
import org.apache.doris.nereids.PLParser.Return_stmtContext;
import org.apache.doris.nereids.PLParser.Set_current_schema_optionContext;
import org.apache.doris.nereids.PLParser.Signal_stmtContext;
import org.apache.doris.nereids.PLParser.StatementDefaultContext;
import org.apache.doris.nereids.PLParser.TableRowContext;
import org.apache.doris.nereids.PLParser.Unconditional_loop_stmtContext;
import org.apache.doris.nereids.PLParser.Values_into_stmtContext;
import org.apache.doris.nereids.PLParser.While_stmtContext;
import org.apache.doris.nereids.trees.plans.commands.info.FuncNameInfo;
import org.apache.doris.plsql.Var.Type;
import org.apache.doris.plsql.exception.QueryException;
import org.apache.doris.plsql.exception.UndefinedIdentException;
import org.apache.doris.plsql.executor.Metadata;
import org.apache.doris.plsql.executor.PlsqlResult;
import org.apache.doris.plsql.executor.QueryExecutor;
import org.apache.doris.plsql.executor.QueryResult;
import org.apache.doris.plsql.executor.ResultListener;
import org.apache.doris.plsql.objects.Table;

import org.antlr.v4.runtime.ParserRuleContext;

import java.sql.SQLException;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * PL/SQL statements execution
 */
public class Stmt {
    Exec exec = null;
    Stack<Var> stack = null;
    Conf conf;
    Meta meta;
    Console console;

    boolean trace = false;
    ResultListener resultListener = ResultListener.NONE;
    private final QueryExecutor queryExecutor;

    Stmt(Exec e, QueryExecutor queryExecutor) {
        exec = e;
        stack = exec.getStack();
        conf = exec.getConf();
        meta = exec.getMeta();
        trace = exec.getTrace();
        console = exec.console;
        this.queryExecutor = queryExecutor;
    }

    public void setResultListener(ResultListener resultListener) {
        this.resultListener = resultListener;
    }

    /**
     * Executing Statement statement
     */
    public Integer statement(ParserRuleContext ctx) {
        trace(ctx, "SELECT");
        if (exec.getOffline()) {
            trace(ctx, "Not executed - offline mode set");
            return 0;
        }

        QueryResult query = queryExecutor.executeQuery(exec.logicalPlanBuilder.getOriginSql(ctx), ctx);
        resultListener.setProcessor(query.processor());

        if (query.error()) {
            exec.signal(query);
            return 1;
        }
        trace(ctx, "statement completed successfully");
        exec.setSqlSuccess();
        try {
            int intoCount = getIntoCount(ctx);
            if (intoCount > 0) {
                // TODO SELECT BULK COLLECT INTO statement executed
                trace(ctx, "SELECT INTO statement executed");
                if (query.next()) {
                    for (int i = 0; i < intoCount; i++) {
                        populateVariable(ctx, query, i);
                    }
                    exec.incRowCount();
                    exec.setSqlSuccess();
                    if (query.next()) {
                        exec.setSqlCode(SqlCodes.TOO_MANY_ROWS);
                        exec.signal(Signal.Type.TOO_MANY_ROWS, "too many rows into variables");
                    }
                } else {
                    exec.setSqlCode(SqlCodes.NO_DATA_FOUND);
                    exec.signal(Signal.Type.NOTFOUND, "no rows into variables");
                }
            } else if (ctx instanceof Doris_statementContext) { // only from visitStatement
                // Print all results for standalone Statement.
                if (query.metadata() != null && !query.isHandleQueryInFe()) {
                    resultListener.onMetadata(query.metadata());
                    int cols = query.columnCount();
                    if (trace) {
                        trace(ctx, "Standalone statement executed: " + cols + " columns in the result set");
                    }
                    while (query.next()) {
                        if (resultListener instanceof PlsqlResult) { // if running from mysql clent
                            resultListener.onMysqlRow(query.mysqlRow());
                        } else { // if running from plsql.sh
                            Object[] row = new Object[cols]; // TODO if there is a large amount of data?
                            for (int i = 0; i < cols; i++) {
                                row[i] = query.column(i, Object.class);
                                if (i > 0) {
                                    console.print("\t");
                                }
                                console.print(String.valueOf(row[i]));
                            }
                            console.printLine("");
                            exec.incRowCount();

                            resultListener.onRow(row);
                        }
                    }
                    resultListener.onEof();
                }
            } else { // Scalar subquery, such as visitExpr
                trace(ctx, "Scalar subquery executed, first row and first column fetched only");
                if (query.next()) {
                    exec.stackPush(new Var().setValue(query, 1));
                    exec.setSqlSuccess();
                } else {
                    evalNull();
                    exec.setSqlCode(SqlCodes.NO_DATA_FOUND);
                }
            }
        } catch (QueryException | AnalysisException e) {
            if (query.error()) {
                exec.signal(query);
            } else {
                exec.signal(e);
            }
            query.close();
            return 1;
        }
        query.close();
        return 0;
    }

    /**
     * Get INTO clause
     */
    IntoClauseContext getIntoClause(ParserRuleContext ctx) {
        if (ctx.getChild(0) instanceof StatementDefaultContext) {
            ParserRuleContext queryTermDefaultCtx = ((StatementDefaultContext) ctx.getChild(0)).query().queryTerm();
            if (queryTermDefaultCtx.getChild(0) instanceof QueryPrimaryDefaultContext) {
                ParserRuleContext queryPrimaryDefaultContext
                        = ((QueryPrimaryDefaultContext) queryTermDefaultCtx.getChild(0));
                if (queryPrimaryDefaultContext.getChild(0) instanceof RegularQuerySpecificationContext) {
                    return ((RegularQuerySpecificationContext) queryPrimaryDefaultContext.getChild(0)).intoClause();
                }
            }
        }
        return null;
    }

    /**
     * Get number of elements in INTO or var=col assignment clause
     */
    int getIntoCount(ParserRuleContext ctx) {
        IntoClauseContext into = getIntoClause(ctx);
        if (into != null) {
            return into.identifier().size() + into.tableRow().size();
        }
        // TODO support var=col assignment clause
        return 0;
    }

    /**
     * Get variable name assigned in INTO or var=col clause by index
     */
    String getIntoVariable(ParserRuleContext ctx, int idx) {
        IntoClauseContext into = getIntoClause(ctx);
        if (into != null) {
            return into.tableRow(idx) != null ? into.tableRow(idx).identifier().getText()
                    : into.identifier(idx).getText();
        }
        // TODO support var=col assignment clause
        return null;
    }

    private int getIntoTableIndex(ParserRuleContext ctx, int idx) {
        IntoClauseContext into = getIntoClause(ctx);
        TableRowContext row = into.tableRow(idx);
        if (row == null) {
            throw new RuntimeException("Missing into table index");
        }
        return Integer.parseInt(row.INTEGER_VALUE().getText());
    }

    private void populateVariable(ParserRuleContext ctx, QueryResult query, int columnIndex) throws AnalysisException {
        String intoName = getIntoVariable(ctx, columnIndex);
        Var var = exec.findVariable(intoName);
        if (var != null) {
            if (var.type == Var.Type.PL_OBJECT && var.value instanceof Table) {
                Table table = (Table) var.value;
                table.populate(query, getIntoTableIndex(ctx, columnIndex), columnIndex);
            } else if (var.type == Var.Type.ROW) {
                var.setRowValues(query);
            } else {
                var.setValue(query, columnIndex);
            }
            exec.trace(ctx, var, query.metadata(), columnIndex);
        } else {
            throw new UndefinedIdentException(ctx, intoName);
        }
    }

    /**
     * ALLOCATE CURSOR statement
     */
    public Integer allocateCursor(Allocate_cursor_stmtContext ctx) {
        trace(ctx, "ALLOCATE CURSOR");
        String name = ctx.ident_pl(0).getText();
        Var cur = null;
        if (ctx.PROCEDURE() != null) {
            cur = exec.consumeReturnCursor(ctx.ident_pl(1).getText());
        } else if (ctx.RESULT() != null) {
            cur = exec.findVariable(ctx.ident_pl(1).getText());
            if (cur != null && cur.type != Type.RS_LOCATOR) {
                cur = null;
            }
        }
        if (cur == null) {
            trace(ctx, "Cursor for procedure not found: " + name);
            exec.signal(Signal.Type.SQLEXCEPTION);
            return -1;
        }
        exec.addVariable(new Var(name, Type.CURSOR, cur.value));
        return 0;
    }

    /**
     * ASSOCIATE LOCATOR statement
     */
    public Integer associateLocator(Associate_locator_stmtContext ctx) {
        trace(ctx, "ASSOCIATE LOCATOR");
        int cnt = ctx.ident_pl().size();
        if (cnt < 2) {
            return -1;
        }
        String procedure = ctx.ident_pl(cnt - 1).getText();
        for (int i = 0; i < cnt - 1; i++) {
            Var cur = exec.consumeReturnCursor(procedure);
            if (cur != null) {
                String name = ctx.ident_pl(i).getText();
                Var loc = exec.findVariable(name);
                if (loc == null) {
                    loc = new Var(name, Type.RS_LOCATOR, cur.value);
                    exec.addVariable(loc);
                } else {
                    loc.setValue(cur.value);
                }
            }
        }
        return 0;
    }

    /**
     * DECLARE cursor statement
     */
    public Integer declareCursor(Declare_cursor_itemContext ctx) {
        String name = ctx.ident_pl().getText();
        if (trace) {
            trace(ctx, "DECLARE CURSOR " + name);
        }
        Cursor cursor = new Cursor(null);
        if (ctx.expr() != null) {
            cursor.setExprCtx(ctx.expr());
        } else if (ctx.query() != null) {
            cursor.setSelectCtx(ctx.query());
        }
        if (ctx.cursor_with_return() != null) {
            cursor.setWithReturn(true);
        }
        Var var = new Var(name, Type.CURSOR, cursor);
        exec.addVariable(var);
        return 0;
    }

    /**
     * OPEN cursor statement
     */
    public Integer open(Open_stmtContext ctx) {
        trace(ctx, "OPEN");
        Cursor cursor = null;
        Var var = null;
        String cursorName = ctx.ident_pl().getText();
        String sql = null;
        if (ctx.FOR() != null) {                             // SELECT statement or dynamic SQL
            sql = ctx.expr() != null ? exec.logicalPlanBuilder.getOriginSql(ctx.expr())
                    : exec.logicalPlanBuilder.getOriginSql(ctx.query());
            cursor = new Cursor(sql);
            var = exec.findCursor(cursorName);                      // Can be a ref cursor variable
            if (var == null) {
                var = new Var(cursorName, Type.CURSOR, cursor);
                exec.addVariable(var);
            } else {
                var.setValue(cursor);
            }
        } else {                                                 // Declared cursor
            var = exec.findVariable(cursorName);
            if (var != null && var.type == Type.CURSOR) {
                cursor = (Cursor) var.value;
                if (cursor.getSqlExpr() != null) {
                    cursor.setSql(exec.logicalPlanBuilder.getOriginSql(cursor.getSqlExpr()));
                } else if (cursor.getSqlSelect() != null) {
                    cursor.setSql(exec.logicalPlanBuilder.getOriginSql(cursor.getSqlSelect()));
                }
            }
        }
        if (cursor != null) {
            if (trace) {
                trace(ctx, cursorName + ": " + sql);
            }
            cursor.open(queryExecutor, ctx);
            QueryResult queryResult = cursor.getQueryResult();
            if (queryResult.error()) {
                exec.signal(queryResult);
                return 1;
            } else if (!exec.getOffline()) {
                exec.setSqlCode(SqlCodes.SUCCESS);
            }
            if (cursor.isWithReturn()) {
                exec.addReturnCursor(var);
            }
        } else {
            trace(ctx, "Cursor not found: " + cursorName);
            exec.setSqlCode(SqlCodes.ERROR);
            exec.signal(Signal.Type.SQLEXCEPTION);
            return 1;
        }
        return 0;
    }

    /**
     * FETCH cursor statement
     */
    public Integer fetch(Fetch_stmtContext ctx) {
        trace(ctx, "FETCH");
        String name = ctx.ident_pl(0).getText();
        Var varCursor = exec.findCursor(name);
        if (varCursor == null) {
            trace(ctx, "Cursor not found: " + name);
            exec.setSqlCode(SqlCodes.ERROR);
            exec.signal(Signal.Type.SQLEXCEPTION);
            return 1;
        } else if (varCursor.value == null) {
            trace(ctx, "Cursor not open: " + name);
            exec.setSqlCode(SqlCodes.ERROR);
            exec.signal(Signal.Type.SQLEXCEPTION);
            return 1;
        } else if (exec.getOffline()) {
            exec.setSqlCode(SqlCodes.NO_DATA_FOUND);
            exec.signal(Signal.Type.NOTFOUND, "fetch not found data");
            return 0;
        }
        // Assign values from the row to local variables
        try {
            Cursor cursor = (Cursor) varCursor.value;
            int cols = ctx.ident_pl().size() - 1;
            QueryResult queryResult = cursor.getQueryResult();

            if (ctx.bulkCollectClause() != null) {
                long limit = ctx.fetch_limit() != null ? evalPop(ctx.fetch_limit().expr()).longValue() : -1;
                long rowIndex = 1;
                List<Table> tables = exec.intoTables(ctx, intoVariableNames(ctx, cols));
                tables.forEach(Table::removeAll);
                while (queryResult.next()) {
                    cursor.setFetch(true);
                    for (int i = 0; i < cols; i++) {
                        Table table = tables.get(i);
                        table.populate(queryResult, rowIndex, i);
                    }
                    rowIndex++;
                    if (limit != -1 && rowIndex - 1 >= limit) {
                        break;
                    }
                }
            } else {
                if (queryResult.next()) {
                    cursor.setFetch(true);
                    for (int i = 0; i < cols; i++) {
                        Var var = exec.findVariable(ctx.ident_pl(i + 1).getText());
                        if (var != null) { // Variables must be defined in advance by DECLARE etc.
                            if (var.type != Var.Type.ROW) {
                                var.setValue(queryResult, i); // Set value of each column into variable
                            } else {
                                var.setRowValues(queryResult);
                            }
                            if (trace) {
                                trace(ctx, var, queryResult.metadata(), i);
                            }
                        } else if (trace) {
                            trace(ctx, "Variable not found: " + ctx.ident_pl(i + 1).getText());
                        }
                    }
                    exec.incRowCount();
                    exec.setSqlSuccess();
                } else {
                    cursor.setFetch(false);
                    exec.setSqlCode(SqlCodes.NO_DATA_FOUND); // Check when exiting cursor
                }
            }
        } catch (QueryException | AnalysisException e) {
            exec.setSqlCode(e);
            exec.signal(Signal.Type.SQLEXCEPTION, e.getMessage(), e);
        }
        return 0;
    }

    private List<String> intoVariableNames(Fetch_stmtContext ctx, int count) {
        return IntStream.range(0, count).mapToObj(i -> ctx.ident_pl(i + 1).getText()).collect(Collectors.toList());
    }


    /**
     * CLOSE cursor statement
     */
    public Integer close(Close_stmtContext ctx) {
        trace(ctx, "CLOSE");
        String name = ctx.IDENTIFIER().toString();
        Var var = exec.findVariable(name);
        if (var != null && var.type == Type.CURSOR) {
            ((Cursor) var.value).close();
            exec.setSqlCode(SqlCodes.SUCCESS);
        } else if (trace) {
            trace(ctx, "Cursor not found: " + name);
        }
        return 0;
    }

    /**
     * INCLUDE statement
     */
    public Integer include(Include_stmtContext ctx) {
        String file;
        if (ctx.file_name() != null) {
            file = ctx.file_name().getText();
        } else {
            file = evalPop(ctx.expr()).toString();
        }
        trace(ctx, "INCLUDE " + file);
        exec.includeFile(file, true);
        return 0;
    }

    /**
     * IF statement (PL/SQL syntax)
     */
    public Integer ifPlsql(If_plsql_stmtContext ctx) {
        boolean trueExecuted = false;
        trace(ctx, "IF");
        if (evalPop(ctx.bool_expr()).isTrue()) {
            trace(ctx, "IF TRUE executed");
            visit(ctx.block());
            trueExecuted = true;
        } else if (ctx.elseif_block() != null) {
            int cnt = ctx.elseif_block().size();
            for (int i = 0; i < cnt; i++) {
                if (evalPop(ctx.elseif_block(i).bool_expr()).isTrue()) {
                    trace(ctx, "ELSE IF executed");
                    visit(ctx.elseif_block(i).block());
                    trueExecuted = true;
                    break;
                }
            }
        }
        if (!trueExecuted && ctx.else_block() != null) {
            trace(ctx, "ELSE executed");
            visit(ctx.else_block());
        }
        return 0;
    }

    /**
     * IF statement (Transact-SQL syntax)
     */
    public Integer ifTsql(If_tsql_stmtContext ctx) {
        trace(ctx, "IF");
        visit(ctx.bool_expr());
        if (exec.stackPop().isTrue()) {
            trace(ctx, "IF TRUE executed");
            visit(ctx.single_block_stmt(0));
        } else if (ctx.ELSE() != null) {
            trace(ctx, "ELSE executed");
            visit(ctx.single_block_stmt(1));
        }
        return 0;
    }

    /**
     * IF statement (BTEQ syntax)
     */
    public Integer ifBteq(If_bteq_stmtContext ctx) {
        trace(ctx, "IF");
        visit(ctx.bool_expr());
        if (exec.stackPop().isTrue()) {
            trace(ctx, "IF TRUE executed");
            visit(ctx.single_block_stmt());
        }
        return 0;
    }

    /**
     * Assignment from SELECT statement
     */
    public Integer assignFromSelect(Assignment_stmt_select_itemContext ctx) {
        String sql = evalPop(ctx.query()).toString();
        if (trace) {
            trace(ctx, sql);
        }
        QueryResult query = queryExecutor.executeQuery(sql, ctx);
        if (query.error()) {
            exec.signal(query);
            return 1;
        }
        exec.setSqlSuccess();
        try {
            int cnt = ctx.ident_pl().size();
            if (query.next()) {
                for (int i = 0; i < cnt; i++) {
                    Var var = exec.findVariable(ctx.ident_pl(i).getText());
                    if (var != null) {
                        var.setValue(query, i);
                        if (trace) {
                            trace(ctx, "COLUMN: " + query.metadata().columnName(i) + ", " + query.metadata()
                                    .columnTypeName(i));
                            trace(ctx, "SET " + var.getName() + " = " + var);
                        }
                    } else if (trace) {
                        trace(ctx, "Variable not found: " + ctx.ident_pl(i).getText());
                    }
                }
                exec.incRowCount();
                exec.setSqlSuccess();
            } else {
                exec.setSqlCode(SqlCodes.NO_DATA_FOUND);
                exec.signal(Signal.Type.NOTFOUND, "assign from select not found data");
            }
        } catch (QueryException | AnalysisException e) {
            exec.signal(query);
            return 1;
        } finally {
            query.close();
        }
        return 0;
    }

    /**
     * GET DIAGNOSTICS EXCEPTION statement
     */
    public Integer getDiagnosticsException(Get_diag_stmt_exception_itemContext ctx) {
        trace(ctx, "GET DIAGNOSTICS EXCEPTION");
        Signal signal = exec.signalPeek();
        if (signal == null || (signal != null && signal.type != Signal.Type.SQLEXCEPTION)) {
            signal = exec.currentSignal;
        }
        if (signal != null) {
            exec.setVariable(ctx.qident().getText(), signal.getValue());
        }
        return 0;
    }

    /**
     * GET DIAGNOSTICS ROW_COUNT statement
     */
    public Integer getDiagnosticsRowCount(Get_diag_stmt_rowcount_itemContext ctx) {
        trace(ctx, "GET DIAGNOSTICS ROW_COUNT");
        exec.setVariable(ctx.qident().getText(), exec.getRowCount());
        return 0;
    }

    public Integer use(ParserRuleContext ctx, String sql) {
        if (trace) {
            trace(ctx, "SQL statement: " + sql);
        }
        QueryResult query = queryExecutor.executeQuery(sql, ctx);
        if (query.error()) {
            exec.signal(query);
            return 1;
        }
        exec.setSqlCode(SqlCodes.SUCCESS);
        query.close();
        return 0;
    }

    /**
     * VALUES statement
     */
    public Integer values(Values_into_stmtContext ctx) {
        trace(ctx, "VALUES statement");
        int cnt = ctx.ident_pl().size();        // Number of variables and assignment expressions
        int ecnt = ctx.expr().size();
        for (int i = 0; i < cnt; i++) {
            String name = ctx.ident_pl(i).getText();
            if (i < ecnt) {
                visit(ctx.expr(i));
                Var var = exec.setVariable(name);
                if (trace) {
                    trace(ctx, "SET " + name + " = " + var.toString());
                }
            }
        }
        return 0;
    }

    /**
     * WHILE statement
     */
    public Integer while_(While_stmtContext ctx) {
        trace(ctx, "WHILE - ENTERED");
        String label = exec.labelPop();
        while (true) {
            if (evalPop(ctx.bool_expr()).isTrue()) {
                exec.enterScope(Scope.Type.LOOP);
                visit(ctx.block());
                exec.leaveScope();
                if (canContinue(label)) {
                    continue;
                }
            }
            break;
        }
        trace(ctx, "WHILE - LEFT");
        return 0;
    }

    /**
     * FOR cursor statement
     */
    public Integer forCursor(For_cursor_stmtContext ctx) {
        trace(ctx, "FOR CURSOR - ENTERED");
        exec.enterScope(Scope.Type.LOOP);
        String cursor = ctx.IDENTIFIER().getText();
        String sql = exec.logicalPlanBuilder.getOriginSql(ctx.query());
        trace(ctx, sql);
        QueryResult query = exec.queryExecutor.executeQuery(sql, ctx);
        if (query.error()) {
            exec.signal(query);
            return 1;
        }
        trace(ctx, "SELECT completed successfully");
        exec.setSqlSuccess();
        try {
            int cols = query.columnCount();
            Row row = new Row();
            for (int i = 0; i < cols; i++) {
                row.addColumnDefinition(query.metadata().columnName(i), query.metadata().columnTypeName(i));
            }
            Var var = new Var(cursor, row);
            exec.addVariable(var);
            while (query.next()) {
                var.setRowValues(query);
                if (trace) {
                    trace(ctx, var, query.metadata(), 0);
                }
                visit(ctx.block());
                exec.incRowCount();
            }
        } catch (QueryException | AnalysisException e) {
            exec.signal(e);
            query.close();
            return 1;
        }
        exec.setSqlSuccess();
        query.close();
        exec.leaveScope();
        trace(ctx, "FOR CURSOR - LEFT");
        return 0;
    }

    /**
     * FOR (integer range) statement
     */
    public Integer forRange(For_range_stmtContext ctx) {
        trace(ctx, "FOR RANGE - ENTERED");
        int start = evalPop(ctx.expr(0)).intValue();
        int end = evalPop(ctx.expr(1)).intValue();
        int step = evalPop(ctx.expr(2), 1L).intValue();
        exec.enterScope(Scope.Type.LOOP);
        Var index = setIndex(start, end, ctx);
        exec.addVariable(index);
        for (int i = start; i <= end; i += step) {
            visit(ctx.block());
            updateIndex(step, index, ctx);
        }
        exec.leaveScope();
        trace(ctx, "FOR RANGE - LEFT");
        return 0;
    }

    public Integer unconditionalLoop(Unconditional_loop_stmtContext ctx) {
        trace(ctx, "UNCONDITIONAL LOOP - ENTERED");
        String label = exec.labelPop();
        do {
            exec.enterScope(Scope.Type.LOOP);
            visit(ctx.block());
            exec.leaveScope();
        } while (canContinue(label));
        trace(ctx, "UNCONDITIONAL LOOP - LEFT");
        return 0;
    }

    /**
     * To set the Value index for FOR Statement
     */
    private Var setIndex(int start, int end, For_range_stmtContext ctx) {

        if (ctx.REVERSE() == null) {
            return new Var(ctx.IDENTIFIER().getText(), Long.valueOf(start));
        } else {
            return new Var(ctx.IDENTIFIER().getText(), Long.valueOf(end));
        }
    }

    /**
     * To update the value of index for FOR Statement
     */
    private void updateIndex(int step, Var index, For_range_stmtContext ctx) {

        if (ctx.REVERSE() == null) {
            index.increment(step);
        } else {
            index.decrement(step);
        }
    }

    /**
     * EXEC, EXECUTE and EXECUTE IMMEDIATE statement to execute dynamic SQL or stored procedure
     */
    public Integer exec(Exec_stmtContext ctx) {
        if (execProc(ctx)) {
            return 0;
        }
        trace(ctx, "EXECUTE");
        Var vsql = evalPop(ctx.expr());
        String sql = vsql.toString();
        if (trace) {
            trace(ctx, "SQL statement: " + sql);
        }
        QueryResult query = queryExecutor.executeQuery(sql, ctx);
        if (query.error()) {
            exec.signal(query);
            return 1;
        }
        try {
            if (ctx.INTO() != null) {
                int cols = ctx.IDENTIFIER().size();
                if (query.next()) {
                    for (int i = 0; i < cols; i++) {
                        Var var = exec.findVariable(ctx.IDENTIFIER(i).getText());
                        if (var != null) {
                            if (var.type != Type.ROW) {
                                var.setValue(query, i);
                            } else {
                                var.setRowValues(query);
                            }
                            if (trace) {
                                trace(ctx, var, query.metadata(), i);
                            }
                        } else if (trace) {
                            trace(ctx, "Variable not found: " + ctx.IDENTIFIER(i).getText());
                        }
                    }
                    exec.setSqlCode(SqlCodes.SUCCESS);
                }
            } else { // Print the results
                int cols = query.columnCount();
                while (query.next()) {
                    for (int i = 0; i < cols; i++) {
                        if (i > 1) {
                            console.print("\t");
                        }
                        console.print(query.column(i, String.class));
                    }
                    console.printLine("");
                }
            }
        } catch (QueryException | AnalysisException e) {
            exec.signal(query);
            query.close();
            return 1;
        }
        query.close();
        return 0;
    }

    /**
     * EXEC to execute a stored procedure
     */
    public Boolean execProc(Exec_stmtContext ctx) {
        String name = evalPop(ctx.expr()).toString().toUpperCase();
        return exec.functions.exec(new FuncNameInfo(name), ctx.expr_func_params());
    }

    /**
     * EXIT statement (leave the specified loop with a condition)
     */
    public Integer exit(Exit_stmtContext ctx) {
        trace(ctx, "EXIT");
        String label = "";
        if (ctx.IDENTIFIER() != null) {
            label = ctx.IDENTIFIER().toString();
        }
        if (ctx.WHEN() != null) {
            if (evalPop(ctx.bool_expr()).isTrue()) {
                leaveLoop(label);
            }
        } else {
            leaveLoop(label);
        }
        return 0;
    }

    /**
     * BREAK statement (leave the innermost loop unconditionally)
     */
    public Integer break_(Break_stmtContext ctx) {
        trace(ctx, "BREAK");
        leaveLoop("");
        return 0;
    }

    /**
     * LEAVE statement (leave the specified loop unconditionally)
     */
    public Integer leave(Leave_stmtContext ctx) {
        trace(ctx, "LEAVE");
        String label = "";
        if (ctx.IDENTIFIER() != null) {
            label = ctx.IDENTIFIER().toString();
        }
        leaveLoop(label);
        return 0;
    }

    /**
     * Leave the specified or innermost loop unconditionally
     */
    public void leaveLoop(String value) {
        exec.signal(Signal.Type.LEAVE_LOOP, value);
    }

    /**
     * PRINT Statement
     */
    public Integer print(Print_stmtContext ctx) {
        trace(ctx, "PRINT");
        if (ctx.expr() != null) {
            console.printLine(evalPop(ctx.expr()).toString());
        }
        return 0;
    }

    /**
     * QUIT Statement
     */
    public Integer quit(Quit_stmtContext ctx) {
        trace(ctx, "QUIT");
        String rc = null;
        if (ctx.expr() != null) {
            rc = evalPop(ctx.expr()).toString();
        }
        exec.signal(Signal.Type.LEAVE_PROGRAM, rc);
        return 0;
    }

    /**
     * SET current schema
     */
    public Integer setCurrentSchema(Set_current_schema_optionContext ctx) {
        trace(ctx, "SET CURRENT SCHEMA");
        return use(ctx, "USE " + meta.normalizeIdentifierPart(evalPop(ctx.expr()).toString()));
    }

    /**
     * SIGNAL statement
     */
    public Integer signal(Signal_stmtContext ctx) {
        trace(ctx, "SIGNAL");
        Signal signal = new Signal(Signal.Type.USERDEFINED, ctx.ident_pl().getText());
        exec.signal(signal);
        return 0;
    }

    /**
     * RESIGNAL statement
     */
    public Integer resignal(Resignal_stmtContext ctx) {
        trace(ctx, "RESIGNAL");
        if (ctx.SQLSTATE() != null) {
            String sqlstate = evalPop(ctx.expr(0)).toString();
            String text = "";
            if (ctx.MESSAGE_TEXT() != null) {
                text = evalPop(ctx.expr(1)).toString();
            }
            SQLException exception = new SQLException(text, sqlstate, -1);
            Signal signal = new Signal(Signal.Type.SQLEXCEPTION, text, exception);
            exec.setSqlCode(exception);
            exec.resignal(signal);
        } else {
            exec.resignal();
        }
        return 0;
    }

    /**
     * RETURN statement
     */
    public Integer return_(Return_stmtContext ctx) {
        trace(ctx, "RETURN");
        if (ctx.expr() != null) {
            eval(ctx.expr());
        }
        exec.signal(Signal.Type.LEAVE_ROUTINE);
        return 0;
    }

    /**
     * Check if an exception is raised or EXIT executed, and we should leave the block
     */
    boolean canContinue(String label) {
        Signal signal = exec.signalPeek();
        if (signal != null && signal.type == Signal.Type.SQLEXCEPTION) {
            return false;
        }
        signal = exec.signalPeek();
        if (signal != null && signal.type == Signal.Type.LEAVE_LOOP) {
            if (signal.value == null || signal.value.isEmpty() || (label != null && label.equalsIgnoreCase(
                    signal.value))) {
                exec.signalPop();
            } // why?
            return false;
        }
        return true;
    }

    /**
     * Evaluate the expression and push the value to the stack
     */
    void eval(ParserRuleContext ctx) {
        exec.visit(ctx);
    }

    /**
     * Evaluate the expression to specified String value
     */
    void evalString(String string) {
        exec.stackPush(new Var(string));
    }

    void evalString(StringBuilder string) {
        evalString(string.toString());
    }

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

    /**
     * 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) {
        if (ctx != null) {
            exec.visit(ctx);
            return exec.stackPop();
        }
        return new Var(def);
    }

    /**
     * Execute rules
     */
    Integer visit(ParserRuleContext ctx) {
        return exec.visit(ctx);
    }

    /**
     * Execute children rules
     */
    Integer visitChildren(ParserRuleContext ctx) {
        return exec.visitChildren(ctx);
    }

    /**
     * Trace information
     */
    void trace(ParserRuleContext ctx, String message) {
        exec.trace(ctx, message);
    }

    void trace(ParserRuleContext ctx, Var var, Metadata metadata, int idx) {
        exec.trace(ctx, var, metadata, idx);
    }
}