/**
   * Executes a generic CompiledStatement. Execution includes first building any subquery result
   * dependencies and clearing them after the main result is built.
   *
   * @return the result of executing the statement
   * @param cs any valid CompiledStatement
   */
  Result execute(CompiledStatement cs) {

    Result result = null;

    DatabaseManager.gc();

    try {
      cs.materializeSubQueries(session);

      result = executeImpl(cs);
    } catch (Throwable t) {

      // t.printStackTrace();
      result = new Result(t, cs.sql);
    }

    // clear redundant data
    cs.dematerializeSubQueries();

    if (result == null) {
      result = emptyResult;
    }

    return result;
  }
  /**
   * Retrieves a MULTI Result describing three aspects of the CompiledStatement prepared from the
   * SQL argument for execution in this session context:
   *
   * <p>
   *
   * <ol>
   *   <li>An PREPARE_ACK mode Result describing id of the statement prepared by this request. This
   *       is used by the JDBC implementation to later identify to the engine which prepared
   *       statement to execute.
   *   <li>A DATA mode result describing the statement's result set metadata. This is used to
   *       generate the JDBC ResultSetMetaData object returned by PreparedStatement.getMetaData and
   *       CallableStatement.getMetaData.
   *   <li>A DATA mode result describing the statement's parameter metdata. This is used to by the
   *       JDBC implementation to determine how to send parameters back to the engine when executing
   *       the statement. It is also used to construct the JDBC ParameterMetaData object for
   *       PreparedStatements and CallableStatements.
   *
   * @param sql a string describing the desired statement object
   * @throws HsqlException is a database access error occurs
   * @return a MULTI Result describing the compiled statement.
   */
  private Result sqlPrepare(String sql, int type) {

    CompiledStatement cs = null;
    int csid = compiledStatementManager.getStatementID(sql);
    Result rsmd;
    Result pmd;

    // ...check valid...
    if (csid > 0 && compiledStatementManager.isValid(csid, iId)) {
      cs = compiledStatementManager.getStatement(csid);
      rsmd = cs.describeResultSet();
      pmd = cs.describeParameters();

      return Result.newPrepareResponse(csid, rsmd, pmd);
    }

    // ...compile or (re)validate
    try {
      cs = sqlCompileStatement(sql, type);
    } catch (Throwable t) {
      return new Result(t, sql);
    }

    // boucherb@users
    // TODO:  It is still unclear to me as to whether, in the case of revalidation
    //        v.s. first compilation, the newly created CompiledStatement
    //        object should replace the old one in the CompiledStatementManager
    //        repository.  If, for instance, a table column has been dropped and
    //        then a column with the same name is added with different data type,
    //        constraints, etc., the existing CompiledStatement object is not
    //        equivalent in its effect and perhaps runs the risk of corrupting
    //        the database.  For instance, a CompiledStatement contains
    //        fixed mappings from positions in a column value expression array
    //        to column positions in the target table.  Thus, an alteration to a
    //        target table may leave an existing CompiledStatement's SQL
    //        character sequence valid, but not its execution plan.
    //        OTOH, simply replacing the old execution plan with a new one
    //        may also be undesirable, as the intended and actual effects
    //        may become divergent. Once again, for example, if a column name
    //        comes to mean a different column, then by blindly replacing the
    //        old CompiledStatement with the new, inserting, updating
    //        or predicating happens upon an unintended column.
    //        The only DDL operations that raise such dangers are sequences
    //        involving dropping a columns and then adding an incompatible one
    //        of the same name at the same position or alterations that
    //        change the positions of columns.  All other alterations to
    //        database objects should, in theory, allow the original
    //        CompiledStatement to continue to operate as intended.
    if (csid <= 0) {
      csid = compiledStatementManager.registerStatement(cs);
    }

    compiledStatementManager.setValidated(csid, iId, dDatabase.getDDLSCN());

    rsmd = cs.describeResultSet();
    pmd = cs.describeParameters();

    return Result.newPrepareResponse(csid, rsmd, pmd);
  }
  /**
   * Binds the specified CompiledStatement object into this object's active compiled statement
   * registry. It is trusted completely that the caller is actually registering a previously
   * unregistered CompiledStatement object; no checks are done in the interest of performance.
   * Typically, the only caller should be a Session that is attempting to perform a prepare and has
   * discovered that this CompiledStatementManager has no such statement registered, as indicated by
   * a negative return value from {@link #getStatementID(String) getStatementID()}.
   *
   * @param cs The CompiledStatement to add
   * @return The compiled statement id assigned to the freshly bound CompiledStatement object
   */
  synchronized int registerStatement(CompiledStatement cs) {

    cs.id = nextID();
    cs.use = 0;

    sqlMap.put(cs.sql, cs);
    csidMap.put(cs.id, cs);

    return cs.id;
  }
  private CompiledStatement sqlCompileStatement(String sql, int type) throws HsqlException {

    Tokenizer tokenizer;
    String token;
    Parser parser;
    int cmd;
    CompiledStatement cs;
    boolean isCmdOk;

    tokenizer = new Tokenizer(sql);
    parser = new Parser(dDatabase, tokenizer, this);
    token = tokenizer.getString();
    cmd = Token.get(token);
    isCmdOk = true;

    switch (cmd) {
      case Token.SELECT:
        {
          cs = parser.compileSelectStatement(null);

          break;
        }
      case Token.INSERT:
        {
          cs = parser.compileInsertStatement(null);

          break;
        }
      case Token.UPDATE:
        {
          cs = parser.compileUpdateStatement(null);

          break;
        }
      case Token.DELETE:
        {
          cs = parser.compileDeleteStatement(null);

          break;
        }
      case Token.CALL:
        {
          if (type != CompiledStatement.CALL) {
            throw Trace.error(Trace.ASSERT_FAILED, "not a CALL statement");
          }

          cs = parser.compileCallStatement(null);

          break;
        }
      default:
        {
          isCmdOk = false;
          cs = null;

          break;
        }
    }

    // In addition to requiring that the compilation was successful,
    // we also require that the submitted sql represents a _single_
    // valid DML statement.
    if (!isCmdOk) {
      throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
    }

    // fredt - now accepts semicolon and whitespace at the end of statement
    // fredt - investigate if it should or not
    while (tokenizer.getPosition() < tokenizer.getLength()) {
      token = tokenizer.getString();

      Trace.check(
          token.length() == 0 || token.equals(Token.T_SEMICOLON), Trace.UNEXPECTED_TOKEN, token);
    }

    // - need to be able to key cs against its sql in statement pool
    // - also need to be able to revalidate its sql occasionally
    cs.sql = sql;

    return cs;
  }