/**
   * 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);
  }
  /**
   * Retrieves the result of executing the prepared statement whose csid and parameter values/types
   * are encapsulated by the cmd argument.
   *
   * @return the result of executing the statement
   */
  private Result sqlExecute(Result cmd) {

    int csid;
    Object[] pvals;
    CompiledStatement cs;
    Expression[] parameters;

    csid = cmd.getStatementID();
    pvals = cmd.getParameterData();
    cs = compiledStatementManager.getStatement(csid);

    if (cs == null) {
      String msg = "Statement not prepared for csid: " + csid;

      return new Result(msg, "22019", Trace.INVALID_IDENTIFIER);
    }

    if (!compiledStatementManager.isValid(csid, iId)) {
      Result r = sqlPrepare(cs.sql, cs.type);

      if (r.iMode == ResultConstants.ERROR) {

        // TODO:
        // maybe compiledStatementManager.freeStatement(csid,iId);?
        return r;
      }
    }

    parameters = cs.parameters;

    // Don't bother with array length or type checks...trust the client
    // to send pvals with length at least as long
    // as parameters array and with each pval already converted to the
    // correct internal representation corresponding to the type
    try {
      for (int i = 0; i < parameters.length; i++) {
        parameters[i].bind(pvals[i]);
      }
    } catch (Throwable t) {
      return new Result(t, cs.sql);
    }

    return compiledStatementExecutor.execute(cs);
  }
  private Result sqlExecuteBatch(Result cmd) {

    int csid;
    Object[] pvals;
    Record record;
    Result in;
    Result out;
    Result err;
    CompiledStatement cs;
    Expression[] parameters;
    int[] updateCounts;
    int count;

    csid = cmd.getStatementID();
    cs = compiledStatementManager.getStatement(csid);

    if (cs == null) {
      String msg = "Statement not prepared for csid: " + csid;

      return new Result(msg, "22019", Trace.INVALID_IDENTIFIER);
    }

    if (!compiledStatementManager.isValid(csid, iId)) {
      out = sqlPrepare(cs.sql, cs.type);

      if (out.iMode == ResultConstants.ERROR) {
        return out;
      }
    }

    parameters = cs.parameters;
    count = 0;
    updateCounts = new int[cmd.getSize()];
    record = cmd.rRoot;
    out = new Result(ResultConstants.SQLEXECUTE, updateCounts, 0);
    err = new Result(ResultConstants.ERROR);

    while (record != null) {
      pvals = record.data;
      in = err;

      try {
        for (int i = 0; i < parameters.length; i++) {
          parameters[i].bind(pvals[i]);
        }

        in = compiledStatementExecutor.execute(cs);
      } catch (Throwable t) {

        // t.printStackTrace();
        // System.out.println(t.toString());
        // if (t instanceof OutOfMemoryError) {
        // System.gc();
        // }
        // "in" alread equals "err"
        // maybe test for OOME and do a gc() ?
        // t.printStackTrace();
      }

      // On the client side, iterate over the vals and throw
      // a BatchUpdateException if a batch status value of
      // esultConstants.EXECUTE_FAILED is encountered in the result
      switch (in.iMode) {
        case ResultConstants.UPDATECOUNT:
          {
            updateCounts[count++] = in.iUpdateCount;

            break;
          }
        case ResultConstants.DATA:
          {

            // FIXME:  we don't have what it takes yet
            // to differentiate between things like
            // stored procedure calls to methods with
            // void return type and select statements with
            // a single row/column containg null
            updateCounts[count++] = ResultConstants.SUCCESS_NO_INFO;

            break;
          }
        case ResultConstants.ERROR:
        default:
          {
            updateCounts[count++] = ResultConstants.EXECUTE_FAILED;

            break;
          }
      }

      record = record.next;
    }

    return out;
  }