/**
 * 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.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Stack;
import java.util.UUID;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.apache.hive.hplsql.Var.Type;

/**
 * HPL/SQL statements execution
 */
public class Stmt {

  Exec exec = null;
  Stack<Var> stack = null;
  Conf conf;
  Meta meta;
  
  boolean trace = false; 
  
  Stmt(Exec e) {
    exec = e;  
    stack = exec.getStack();
    conf = exec.getConf();
    meta = exec.getMeta();
    trace = exec.getTrace();
  }
  
  /**
   * ALLOCATE CURSOR statement
   */
  public Integer allocateCursor(HplsqlParser.Allocate_cursor_stmtContext ctx) { 
    trace(ctx, "ALLOCATE CURSOR");
    String name = ctx.ident(0).getText();
    Var cur = null;
    if (ctx.T_PROCEDURE() != null) {
      cur = exec.consumeReturnCursor(ctx.ident(1).getText());
    }
    else if (ctx.T_RESULT() != null) {
      cur = exec.findVariable(ctx.ident(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(HplsqlParser.Associate_locator_stmtContext ctx) { 
    trace(ctx, "ASSOCIATE LOCATOR");
    int cnt = ctx.ident().size();
    if (cnt < 2) {
      return -1;
    }
    String procedure = ctx.ident(cnt - 1).getText();
    for (int i = 0; i < cnt - 1; i++) {
      Var cur = exec.consumeReturnCursor(procedure);
      if (cur != null) {
        String name = ctx.ident(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(HplsqlParser.Declare_cursor_itemContext ctx) { 
    String name = ctx.ident().getText();
    if (trace) {
      trace(ctx, "DECLARE CURSOR " + name);
    }
    Query query = new Query();
    if (ctx.expr() != null) {
      query.setExprCtx(ctx.expr());
    }
    else if (ctx.select_stmt() != null) {
      query.setSelectCtx(ctx.select_stmt());
    }
    if (ctx.cursor_with_return() != null) {
      query.setWithReturn(true);
    }
    Var var = new Var(name, Type.CURSOR, query);
    exec.addVariable(var);
    return 0; 
  }
  
  /**
   * CREATE TABLE statement
   */
  public Integer createTable(HplsqlParser.Create_table_stmtContext ctx) { 
    trace(ctx, "CREATE TABLE");
    StringBuilder sql = new StringBuilder();
    exec.append(sql, ctx.T_CREATE(), ctx.T_TABLE());
    exec.append(sql, evalPop(ctx.table_name()).toString(), ctx.T_TABLE().getSymbol(), ctx.table_name().getStart());
    Token last = ctx.table_name().getStop();
    if (ctx.create_table_preoptions() != null) {
      String preopt = evalPop(ctx.create_table_preoptions()).toString();
      if (preopt != null) {
        sql.append(" " + preopt);
      }
      last = ctx.create_table_preoptions().stop;
    }
    sql.append(createTableDefinition(ctx.create_table_definition(), last));
    trace(ctx, sql.toString());
    Query query = exec.executeSql(ctx, sql.toString(), exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0; 
  }  

  /**
   * Get CREATE TABLE definition (columns or query)
   */
  String createTableDefinition(HplsqlParser.Create_table_definitionContext ctx, Token last) { 
    StringBuilder sql = new StringBuilder();
    HplsqlParser.Create_table_columnsContext colCtx = ctx.create_table_columns();
    if (colCtx != null) {
      int cnt = colCtx.create_table_columns_item().size();
      for (int i = 0; i < cnt; i++) {
        HplsqlParser.Create_table_columns_itemContext col = colCtx.create_table_columns_item(i);
        if (col.create_table_column_cons() != null) {
          last = col.getStop();
          continue;
        }
        exec.append(sql, evalPop(col.column_name()).toString(), last, col.column_name().getStop());
        exec.append(sql, exec.evalPop(col.dtype(), col.dtype_len()), col.column_name().getStop(), col.dtype().getStart());
        last = col.getStop();
      }
      exec.append(sql, ctx.T_CLOSE_P().getText(), last, ctx.T_CLOSE_P().getSymbol());
    }
    // CREATE TABLE AS SELECT statement
    else {
      exec.append(sql, evalPop(ctx.select_stmt()).toString(), last, ctx.select_stmt().getStart());
      if (ctx.T_CLOSE_P() != null) {
        exec.append(sql, ctx.T_CLOSE_P().getText(), ctx.select_stmt().stop, ctx.T_CLOSE_P().getSymbol());
      }
    }
    HplsqlParser.Create_table_optionsContext options = ctx.create_table_options();
    if (options != null) {
      String opt = evalPop(options).toString();
      if (opt != null) {
        sql.append(" " + opt);
      }
    }
    return sql.toString(); 
  }  

  /**
   * CREATE TABLE options for Hive
   */
  public Integer createTableHiveOptions(HplsqlParser.Create_table_options_hive_itemContext ctx) {
    if (ctx.create_table_hive_row_format() != null) {
      createTableHiveRowFormat(ctx.create_table_hive_row_format());
    }
    else if (ctx.T_STORED() != null) {
      evalString(exec.getText(ctx));
    }
    return 0; 
  }
  
  public Integer createTableHiveRowFormat(HplsqlParser.Create_table_hive_row_formatContext ctx) {
    StringBuilder sql = new StringBuilder();
    sql.append("ROW FORMAT DELIMITED");
    int cnt = ctx.create_table_hive_row_format_fields().size();
    for (int i = 0; i < cnt; i++) {
      HplsqlParser.Create_table_hive_row_format_fieldsContext c = ctx.create_table_hive_row_format_fields(i);
      if (c.T_FIELDS() != null) {
        sql.append(" FIELDS TERMINATED BY " + evalPop(c.expr(0)).toSqlString());
      }
      else if (c.T_LINES() != null) {
        sql.append(" LINES TERMINATED BY " + evalPop(c.expr(0)).toSqlString());
      } 
    }
    evalString(sql);
    return 0; 
  }
  
  /**
   * CREATE TABLE options for MySQL
   */
  public Integer createTableMysqlOptions(HplsqlParser.Create_table_options_mysql_itemContext ctx) {
    if (ctx.T_COMMENT() != null) {
      evalString(ctx.T_COMMENT().getText() + " " + evalPop(ctx.expr()).toSqlString());
    }
    return 0; 
  }
    
  /**
   * DECLARE TEMPORARY TABLE statement 
   */
  public Integer declareTemporaryTable(HplsqlParser.Declare_temporary_table_itemContext ctx) { 
    trace(ctx, "DECLARE TEMPORARY TABLE");
    return createTemporaryTable(ctx.ident(), ctx.create_table_definition(), ctx.create_table_preoptions());
  }
  
  /**
   * CREATE DATABASE | SCHEMA statement
   */
  public Integer createDatabase(HplsqlParser.Create_database_stmtContext ctx) {
    trace(ctx, "CREATE DATABASE");
    StringBuilder sql = new StringBuilder();
    sql.append(ctx.T_CREATE().getText() + " ");
    if (ctx.T_DATABASE() != null) {
      sql.append(ctx.T_DATABASE().getText() + " "); 
    }
    else {
      sql.append(ctx.T_SCHEMA().getText() + " "); 
    }
    if (ctx.T_IF() != null) {
      sql.append(exec.getText(ctx, ctx.T_IF().getSymbol(), ctx.T_EXISTS().getSymbol()) + " "); 
    }    
    sql.append(evalPop(ctx.expr()).toString());
    int cnt = ctx.create_database_option().size();
    for (int i = 0; i < cnt; i++) {
      HplsqlParser.Create_database_optionContext option = ctx.create_database_option(i);
      if (option.T_COMMENT() != null) {
        sql.append(" " + option.T_COMMENT().getText() + " " + evalPop(option.expr()).toSqlString()); 
      }
      else if (option.T_LOCATION() != null) {
        sql.append(" " + option.T_LOCATION().getText() + " " + evalPop(option.expr()).toSqlString()); 
      }
    }
    trace(ctx, sql.toString());
    Query query = exec.executeSql(ctx, sql.toString(), exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0;
  }
  
  /**
   * CREATE LOCAL TEMPORARY | VOLATILE TABLE statement 
   */
  public Integer createLocalTemporaryTable(HplsqlParser.Create_local_temp_table_stmtContext ctx) { 
    trace(ctx, "CREATE LOCAL TEMPORARY TABLE");
    return createTemporaryTable(ctx.ident(), ctx.create_table_definition(), ctx.create_table_preoptions());
   }
  
  /**
   * Create a temporary table statement 
   */
  public Integer createTemporaryTable(HplsqlParser.IdentContext identCtx, HplsqlParser.Create_table_definitionContext defCtx,
                                      HplsqlParser.Create_table_preoptionsContext optCtx) { 
    StringBuilder sql = new StringBuilder();
    String name = identCtx.getText();
    String managedName = null;
    Token last = identCtx.getStop();
    if (optCtx != null) {
      last = optCtx.stop;
    }
    if (conf.tempTables == Conf.TempTables.NATIVE) {
      sql.append("CREATE TEMPORARY TABLE " + name);
      sql.append(createTableDefinition(defCtx, last));
    } 
    else if (conf.tempTables == Conf.TempTables.MANAGED) {
      managedName = name + "_" + UUID.randomUUID().toString().replace("-","");
      if (!conf.tempTablesSchema.isEmpty()) {
        managedName = conf.tempTablesSchema + "." + managedName;
      }      
      sql.append("CREATE TABLE " + managedName);
      sql.append(createTableDefinition(defCtx, last));
      if (!conf.tempTablesLocation.isEmpty()) {
        sql.append("\nLOCATION '" + conf.tempTablesLocation + "/" + managedName + "'");
      }
      if (trace) {
        trace(null, "Managed table name: " + managedName);
      }
    }  
    if (trace) {
      trace(null, sql.toString()); 
    }
    if (sql != null) {
      Query query = exec.executeSql(null, sql.toString(), exec.conf.defaultConnection);
      if (query.error()) {
        exec.signal(query);
        return 1;
      }
      if (managedName != null) {
        exec.addManagedTable(name, managedName);
      }
      exec.setSqlSuccess();
      exec.closeQuery(query, exec.conf.defaultConnection);
    }    
    return 0; 
  }
  
  /**
   * DESCRIBE statement
   */
  public Integer describe(HplsqlParser.Describe_stmtContext ctx) {
    trace(ctx, "DESCRIBE");
    String sql = "DESCRIBE " + evalPop(ctx.table_name()).toString();   
    trace(ctx, sql);
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    try {
      ResultSet rs = query.getResultSet();
      ResultSetMetaData rm = null;
      if (rs != null) {
        rm = rs.getMetaData();
        int cols = rm.getColumnCount();
        while (rs.next()) {
          for (int i = 1; i <= cols; i++) {
            if (i > 1) {
              System.out.print("\t");
            }
            System.out.print(rs.getString(i));
          }
          System.out.println("");
        }
      }
    }    
    catch (SQLException e) {
      exec.signal(query);
      exec.closeQuery(query, exec.conf.defaultConnection);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0;
  }
  
  /**
   * DROP statement
   */
  public Integer drop(HplsqlParser.Drop_stmtContext ctx) { 
    trace(ctx, "DROP");
    String sql = null;    
    if (ctx.T_TABLE() != null) {
      sql = "DROP TABLE ";
      if (ctx.T_EXISTS() != null) {
        sql += "IF EXISTS ";
      }
      sql += evalPop(ctx.table_name()).toString();
    }
    else if (ctx.T_DATABASE() != null || ctx.T_SCHEMA() != null) {
      sql = "DROP DATABASE ";
      if (ctx.T_EXISTS() != null) {
        sql += "IF EXISTS ";
      }
      sql += evalPop(ctx.expr()).toString();
    }
    if (sql != null) {
      trace(ctx, sql);
      Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
      if (query.error()) {
        exec.signal(query);
        return 1;
      }
      exec.setSqlSuccess();
      exec.closeQuery(query, exec.conf.defaultConnection);
    }
    return 0; 
  }
  
  /**
   * TRUNCATE statement
   */
  public Integer truncate(HplsqlParser.Truncate_stmtContext ctx) { 
    trace(ctx, "TRUNCATE");
    String sql = "TRUNCATE TABLE " + evalPop(ctx.table_name()).toString();    
    trace(ctx, sql);
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0; 
  }
  
  /**
   * OPEN cursor statement
   */
  public Integer open(HplsqlParser.Open_stmtContext ctx) { 
    trace(ctx, "OPEN");
    Query query = null;
    Var var = null;
    String cursor = ctx.L_ID().toString();   
    String sql = null;
    if (ctx.T_FOR() != null) {                             // SELECT statement or dynamic SQL
      if (ctx.expr() != null) {
        sql = evalPop(ctx.expr()).toString();
      }
      else {
        sql = evalPop(ctx.select_stmt()).toString();
      }
      query = new Query(sql);
      var = exec.findCursor(cursor);                      // Can be a ref cursor variable
      if (var == null) {
        var = new Var(cursor, Type.CURSOR, query);
        exec.addVariable(var);
      }
      else {
        var.setValue(query);
      }
    }
    else {                                                 // Declared cursor
      var = exec.findVariable(cursor);      
      if (var != null && var.type == Type.CURSOR) {
        query = (Query)var.value;
        if (query.sqlExpr != null) {
          sql = evalPop(query.sqlExpr).toString();
          query.setSql(sql);
        }
        else if (query.sqlSelect != null) {
          sql = evalPop(query.sqlSelect).toString();
          query.setSql(sql);
        }
      }
    }
    if (query != null) {
      if (trace) {
        trace(ctx, cursor + ": " + sql);
      } 
      exec.executeQuery(ctx, query, exec.conf.defaultConnection);
      if (query.error()) {
        exec.signal(query);
        return 1;
      }
      else if (!exec.getOffline()) {
        exec.setSqlCode(0);
      }
      if (query.getWithReturn()) {
        exec.addReturnCursor(var);
      }
    }
    else {
      trace(ctx, "Cursor not found: " + cursor);
      exec.setSqlCode(-1);
      exec.signal(Signal.Type.SQLEXCEPTION);
      return 1;
    }
    return 0; 
  }
  
  /**
   * FETCH cursor statement
   */
  public Integer fetch(HplsqlParser.Fetch_stmtContext ctx) { 
    trace(ctx, "FETCH");
    String name = ctx.L_ID(0).toString();
    Var cursor = exec.findCursor(name);
    if (cursor == null) {
      trace(ctx, "Cursor not found: " + name);
      exec.setSqlCode(-1);
      exec.signal(Signal.Type.SQLEXCEPTION);
      return 1;
    }  
    else if (cursor.value == null) {
      trace(ctx, "Cursor not open: " + name);
      exec.setSqlCode(-1);
      exec.signal(Signal.Type.SQLEXCEPTION);
      return 1;
    }  
    else if (exec.getOffline()) {
      exec.setSqlCode(100);
      exec.signal(Signal.Type.NOTFOUND);
      return 0;
    }
    // Assign values from the row to local variables
    try {
      Query query = (Query)cursor.value;
      ResultSet rs = query.getResultSet();
      ResultSetMetaData rsm = null;
      if(rs != null) {
        rsm = rs.getMetaData();
      }
      if(rs != null && rsm != null) {
        int cols = ctx.L_ID().size() - 1;
        if(rs.next()) {
          query.setFetch(true);
          for(int i=1; i <= cols; i++) {
            Var var = exec.findVariable(ctx.L_ID(i).getText());
            if(var != null) {
              if (var.type != Var.Type.ROW) {
                var.setValue(rs, rsm, i);
              }
              else {
                var.setValues(rs, rsm);
              }
              if (trace) {
                trace(ctx, var, rs, rsm, i);
              }
            } 
            else if(trace) {
              trace(ctx, "Variable not found: " + ctx.L_ID(i).getText());
            }
          }
          exec.incRowCount();
          exec.setSqlSuccess();
        }
        else {
          query.setFetch(false);
          exec.setSqlCode(100);
        }
      }
      else {
        exec.setSqlCode(-1);
      }
    } 
    catch (SQLException e) {
      exec.setSqlCode(e);
      exec.signal(Signal.Type.SQLEXCEPTION, e.getMessage(), e);
    } 
    return 0; 
  }  
  
  /**
   * CLOSE cursor statement
   */
  public Integer close(HplsqlParser.Close_stmtContext ctx) { 
    trace(ctx, "CLOSE");
    String name = ctx.L_ID().toString();
    Var var = exec.findVariable(name);
    if(var != null && var.type == Type.CURSOR) {
      exec.closeQuery((Query)var.value, exec.conf.defaultConnection);
      exec.setSqlCode(0);
    }
    else if(trace) {
      trace(ctx, "Cursor not found: " + name);
    }
    return 0; 
  }
  
  /**
   * INCLUDE statement
   */
  public Integer include(HplsqlParser.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(HplsqlParser.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(HplsqlParser.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.T_ELSE() != null) {
      trace(ctx, "ELSE executed");
      visit(ctx.single_block_stmt(1));
    }
    return 0; 
  }
  
  /**
   * IF statement (BTEQ syntax)
   */
  public Integer ifBteq(HplsqlParser.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(HplsqlParser.Assignment_stmt_select_itemContext ctx) { 
    String sql = evalPop(ctx.select_stmt()).toString();
    if (trace) {
      trace(ctx, sql.toString());
    }
    String conn = exec.getStatementConnection();
    Query query = exec.executeQuery(ctx, sql.toString(), conn);
    if (query.error()) { 
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    try {
      ResultSet rs = query.getResultSet();
      ResultSetMetaData rm = null;
      if (rs != null) {
        rm = rs.getMetaData();
        int cnt = ctx.ident().size();
        if (rs.next()) {
          for (int i = 1; i <= cnt; i++) {
            Var var = exec.findVariable(ctx.ident(i-1).getText());
            if (var != null) {
              var.setValue(rs, rm, i);
              if (trace) {
                trace(ctx, "COLUMN: " + rm.getColumnName(i) + ", " + rm.getColumnTypeName(i));
                trace(ctx, "SET " + var.getName() + " = " + var.toString());
              }             
            } 
            else if(trace) {
              trace(ctx, "Variable not found: " + ctx.ident(i-1).getText());
            }
          }
          exec.incRowCount();
          exec.setSqlSuccess();
        }
        else {
          exec.setSqlCode(100);
          exec.signal(Signal.Type.NOTFOUND);
        }
      }
    }
    catch (SQLException e) {
      exec.signal(query);
      return 1;
    }
    finally {
      exec.closeQuery(query, conn);
    }
    return 0; 
  }
  
  /**
   * SQL INSERT statement
   */
  public Integer insert(HplsqlParser.Insert_stmtContext ctx) {
    exec.stmtConnList.clear();
    if (ctx.select_stmt() != null) {
      return insertSelect(ctx);
    }
    return insertValues(ctx); 
  }
  
  /**
   * SQL INSERT SELECT statement
   */
  public Integer insertSelect(HplsqlParser.Insert_stmtContext ctx) { 
    trace(ctx, "INSERT SELECT");
    StringBuilder sql = new StringBuilder();
    sql.append(ctx.T_INSERT().getText() + " ");
    if (ctx.T_OVERWRITE() != null) {
      sql.append(ctx.T_OVERWRITE().getText() + " " + ctx.T_TABLE().getText() + " ");
    }
    else {
      sql.append(ctx.T_INTO().getText() + " ");
      if (ctx.T_TABLE() != null) {
        sql.append(ctx.T_TABLE().getText() + " ");
      }
    }
    sql.append(evalPop(ctx.table_name()).toString() + " ");
    sql.append(evalPop(ctx.select_stmt()).toString());
    trace(ctx, sql.toString());
    Query query = exec.executeSql(ctx, sql.toString(), exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0; 
  }
  
  /**
   * SQL INSERT VALUES statement
   */
  public Integer insertValues(HplsqlParser.Insert_stmtContext ctx) { 
    trace(ctx, "INSERT VALUES");
    String table = evalPop(ctx.table_name()).toString();
    String conn = exec.getObjectConnection(ctx.table_name().getText());
    Conn.Type type = exec.getConnectionType(conn); 
    StringBuilder sql = new StringBuilder();
    if (type == Conn.Type.HIVE) {
      sql.append("INSERT INTO TABLE " + table + " ");
      if (conf.insertValues == Conf.InsertValues.NATIVE) {
        sql.append("VALUES\n("); 
      }
    }
    else {
      sql.append("INSERT INTO " + table);
      if (ctx.insert_stmt_cols() != null) {
        sql.append(" " + exec.getFormattedText(ctx.insert_stmt_cols()));
      }
      sql.append(" VALUES\n("); 
    }
    int rows = ctx.insert_stmt_rows().insert_stmt_row().size();
    for (int i = 0; i < rows; i++) {
      HplsqlParser.Insert_stmt_rowContext row =ctx.insert_stmt_rows().insert_stmt_row(i);
      int cols = row.expr().size();
      for (int j = 0; j < cols; j++) {         
        String value = evalPop(row.expr(j)).toSqlString();
        if (j == 0 && type == Conn.Type.HIVE && conf.insertValues == Conf.InsertValues.SELECT ) {
          sql.append("SELECT ");
        }
        sql.append(value);
        if (j + 1 != cols) {
          sql.append(", ");
        }       
      }
      if (type != Conn.Type.HIVE || conf.insertValues == Conf.InsertValues.NATIVE) {
        if (i + 1 == rows) {
          sql.append(")");
        } else {
          sql.append("),\n(");
        } 
      }
      else if (type == Conn.Type.HIVE && conf.insertValues == Conf.InsertValues.SELECT) {
        if (conf.dualTable != null) {
          sql.append(" FROM " + conf.dualTable);
        }
        if (i + 1 < rows) {
          sql.append("\nUNION ALL\n");
        }
      }      
    }    
    if (trace) {
      trace(ctx, sql.toString());
    }
    Query query = exec.executeSql(ctx, sql.toString(), conn);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, conn);
    return 0; 
  }
  
  /**
   * INSERT DIRECTORY statement
   */
  public Integer insertDirectory(HplsqlParser.Insert_directory_stmtContext ctx) { 
    trace(ctx, "INSERT DIRECTORY");
    StringBuilder sql = new StringBuilder();
    sql.append(ctx.T_INSERT().getText() + " " + ctx.T_OVERWRITE().getText() + " ");
    if (ctx.T_LOCAL() != null) {
      sql.append(ctx.T_LOCAL().getText() + " ");
    }
    sql.append(ctx.T_DIRECTORY().getText() + " " + evalPop(ctx.expr_file()).toSqlString() + " ");
    sql.append(evalPop(ctx.expr_select()).toString());
    String conn = exec.getStatementConnection();
    trace(ctx, sql.toString());
    Query query = exec.executeSql(ctx, sql.toString(), conn);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, conn);
    return 0; 
  }
  
  /**
   * GET DIAGNOSTICS EXCEPTION statement
   */
  public Integer getDiagnosticsException(HplsqlParser.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.ident().getText(), signal.getValue());
    }
    return 0; 
  }
  
  /**
   * GET DIAGNOSTICS ROW_COUNT statement
   */
  public Integer getDiagnosticsRowCount(HplsqlParser.Get_diag_stmt_rowcount_itemContext ctx) {
    trace(ctx, "GET DIAGNOSTICS ROW_COUNT");
    exec.setVariable(ctx.ident().getText(), exec.getRowCount());
    return 0;  
  }
  
  /**
   * USE statement
   */
  public Integer use(HplsqlParser.Use_stmtContext ctx) {
    trace(ctx, "USE");
    return use(ctx, ctx.T_USE().toString() + " " + meta.normalizeIdentifierPart(evalPop(ctx.expr()).toString()));
  }
  
  public Integer use(ParserRuleContext ctx, String sql) {
    if(trace) {
      trace(ctx, "SQL statement: " + sql);
    }    
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if(query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlCode(0);
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0; 
  }
  
  /** 
   * VALUES statement
   */
  public Integer values(HplsqlParser.Values_into_stmtContext ctx) { 
    trace(ctx, "VALUES statement");    
    int cnt = ctx.ident().size();        // Number of variables and assignment expressions
    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 = exec.setVariable(name);        
        if (trace) {
          trace(ctx, "SET " + name + " = " + var.toString());      
        } 
      }      
    }    
    return 0; 
  } 
  
  /**
   * WHILE statement
   */
  public Integer while_(HplsqlParser.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(HplsqlParser.For_cursor_stmtContext ctx) { 
    trace(ctx, "FOR CURSOR - ENTERED");
    exec.enterScope(Scope.Type.LOOP);
    String cursor = ctx.L_ID().getText();
    String sql = evalPop(ctx.select_stmt()).toString();   
    trace(ctx, sql);
    Query query = exec.executeQuery(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) { 
      exec.signal(query);
      return 1;
    }
    trace(ctx, "SELECT completed successfully");
    exec.setSqlSuccess();
    try {
      ResultSet rs = query.getResultSet();
      if (rs != null) {
        ResultSetMetaData rm = rs.getMetaData();
        int cols = rm.getColumnCount();
        Row row = new Row();
        for (int i = 1; i <= cols; i++) {
          row.addColumn(rm.getColumnName(i), rm.getColumnTypeName(i));
        }
        Var var = new Var(cursor, row);
        exec.addVariable(var);
        while (rs.next()) {
          var.setValues(rs, rm);
          if (trace) {
            trace(ctx, var, rs, rm, 0);
          }
          visit(ctx.block());
          exec.incRowCount();
        }
      }
    }
    catch (SQLException e) {
      exec.signal(e);
      exec.closeQuery(query, exec.conf.defaultConnection);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    exec.leaveScope();
    trace(ctx, "FOR CURSOR - LEFT");
    return 0; 
  }
  
  /**
   * FOR (integer range) statement
   */
  public Integer forRange(HplsqlParser.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; 
  }

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

    if (ctx.T_REVERSE() == null)
      return new Var(ctx.L_ID().getText(), new Long(start));
    else
      return new Var(ctx.L_ID().getText(), new Long(end));
  }

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

    if (ctx.T_REVERSE() == null)
      index.increment(new Long(step));
    else
      index.decrement(new Long(step));
  }

  /**
   * EXEC, EXECUTE and EXECUTE IMMEDIATE statement to execute dynamic SQL or stored procedure
   */
  public Integer exec(HplsqlParser.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);
    }
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    ResultSet rs = query.getResultSet();
    if (rs != null) {
      try {
        ResultSetMetaData rm = rs.getMetaData();
        if (ctx.T_INTO() != null) {
          int cols = ctx.L_ID().size();
          if (rs.next()) {
            for (int i = 0; i < cols; i++) {
              Var var = exec.findVariable(ctx.L_ID(i).getText());
              if (var != null) {
                if (var.type != Var.Type.ROW) {
                  var.setValue(rs, rm, i + 1);
                }
                else {
                  var.setValues(rs, rm);
                }
                if (trace) {
                  trace(ctx, var, rs, rm, i + 1);
                }
              } 
              else if (trace) {
                trace(ctx, "Variable not found: " + ctx.L_ID(i).getText());
              }
            }
            exec.setSqlCode(0);
          }
        }
        // Print the results
        else {
          int cols = rm.getColumnCount();
          while(rs.next()) {
            for(int i = 1; i <= cols; i++) {
              if(i > 1) {
                System.out.print("\t");
              }
              System.out.print(rs.getString(i));
            }
            System.out.println("");
          }
        }
      } 
      catch(SQLException e) {
        exec.setSqlCode(e);
      } 
    }   
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0; 
  }
  
  /**
   * EXEC to execute a stored procedure
   */
  public Boolean execProc(HplsqlParser.Exec_stmtContext ctx) { 
    String name = evalPop(ctx.expr()).toString();
    if (exec.function.isProc(name)) {
      if (exec.function.execProc(name, ctx.expr_func_params(), ctx)) {
        return true;
      }
    }
    return false;
  }
      
  /**
   * EXIT statement (leave the specified loop with a condition)
   */
  public Integer exit(HplsqlParser.Exit_stmtContext ctx) { 
    trace(ctx, "EXIT");
    String label = "";
    if (ctx.L_ID() != null) {
      label = ctx.L_ID().toString();
    }
    if (ctx.T_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_(HplsqlParser.Break_stmtContext ctx) { 
    trace(ctx, "BREAK");
    leaveLoop("");
    return 0;
  }
  
  /**
   * LEAVE statement (leave the specified loop unconditionally)
   */
  public Integer leave(HplsqlParser.Leave_stmtContext ctx) { 
    trace(ctx, "LEAVE");
    String label = "";
    if (ctx.L_ID() != null) {
      label = ctx.L_ID().toString();
    }
    leaveLoop(label);    
    return 0;
  }
  
  /**
   * Leave the specified or innermost loop unconditionally
   */
  public void leaveLoop(String value) { 
    exec.signal(Signal.Type.LEAVE_LOOP, value);
  }
  
  /**
   * UPDATE statement
   */
  public Integer update(HplsqlParser.Update_stmtContext ctx) {
    trace(ctx, "UPDATE");
    String sql = exec.getFormattedText(ctx);
    trace(ctx, sql);
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0;
  }
  
  /**
   * DELETE statement
   */
  public Integer delete(HplsqlParser.Delete_stmtContext ctx) {
    trace(ctx, "DELETE");
    String table = evalPop(ctx.table_name()).toString();
    StringBuilder sql = new StringBuilder();
    if (ctx.T_ALL() == null) {
      sql.append("DELETE FROM " + table);
      if (ctx.where_clause() != null) {
        boolean oldBuildSql = exec.buildSql; 
        exec.buildSql = true;
        sql.append(" " + evalPop(ctx.where_clause()).toString());
        exec.buildSql = oldBuildSql;
      }
    }
    else {
      sql.append("TRUNCATE TABLE " + table);
    }
    trace(ctx, sql.toString());
    Query query = exec.executeSql(ctx, sql.toString(), exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0;
  }
  
  /**
   * MERGE statement
   */
  public Integer merge(HplsqlParser.Merge_stmtContext ctx) {
    trace(ctx, "MERGE");
    String sql = exec.getFormattedText(ctx);
    trace(ctx, sql);
    Query query = exec.executeSql(ctx, sql, exec.conf.defaultConnection);
    if (query.error()) {
      exec.signal(query);
      return 1;
    }
    exec.setSqlSuccess();
    exec.closeQuery(query, exec.conf.defaultConnection);
    return 0;
  }
  
  /**
   * PRINT Statement 
   */
  public Integer print(HplsqlParser.Print_stmtContext ctx) { 
    trace(ctx, "PRINT");
    if (ctx.expr() != null) {
      System.out.println(evalPop(ctx.expr()).toString());
    }
	  return 0; 
  }
  
  /**
   * QUIT Statement 
   */
  public Integer quit(HplsqlParser.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(HplsqlParser.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(HplsqlParser.Signal_stmtContext ctx) {
    trace(ctx, "SIGNAL");
    Signal signal = new Signal(Signal.Type.USERDEFINED, ctx.ident().getText());
    exec.signal(signal);
    return 0; 
  }  
  
  /**
   * RESIGNAL statement
   */
  public Integer resignal(HplsqlParser.Resignal_stmtContext ctx) { 
    trace(ctx, "RESIGNAL");
    if (ctx.T_SQLSTATE() != null) {
      String sqlstate = evalPop(ctx.expr(0)).toString();
      String text = "";
      if (ctx.T_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_(HplsqlParser.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();
      }
      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 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, ResultSet rs, ResultSetMetaData rm, int idx) throws SQLException {
    exec.trace(ctx, var, rs, rm, idx);
  }
}
