/**
   * 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);
  }
  /**
   * Opens this database. The database should be opened after construction. or reopened by the
   * close(int closemode) method during a "shutdown compact". Closes the log if there is an error.
   */
  void reopen() throws HsqlException {

    boolean isNew;

    setState(DATABASE_OPENING);

    try {
      databaseProperties = new HsqlDatabaseProperties(this);
      isNew = !DatabaseURL.isFileBasedDatabaseType(sType) || !databaseProperties.checkFileExists();

      if (isNew && urlProperties.isPropertyTrue("ifexists")) {
        throw Trace.error(Trace.DATABASE_NOT_EXISTS, sName);
      }

      databaseProperties.load();
      databaseProperties.setURLProperties(urlProperties);
      compiledStatementManager.reset();

      nameManager = new HsqlNameManager();
      granteeManager = new GranteeManager(this);
      userManager = new UserManager(this);
      hAlias = Library.getAliasMap();
      schemaManager = new SchemaManager(this);
      bReferentialIntegrity = true;
      sessionManager = new SessionManager(this);
      txManager = new TransactionManager(this);
      collation = new Collation();
      dbInfo = DatabaseInformation.newDatabaseInformation(this);

      databaseProperties.setDatabaseVariables();

      if (DatabaseURL.isFileBasedDatabaseType(sType)) {
        logger.openLog(this);
      }

      if (isNew) {
        sessionManager
            .getSysSession()
            .sqlExecuteDirectNoPreChecks("CREATE USER SA PASSWORD \"\" ADMIN");
        logger.synchLogForce();
      }

      dbInfo.setWithContent(true);
    } catch (Throwable e) {
      logger.closeLog(Database.CLOSEMODE_IMMEDIATELY);
      logger.releaseLock();
      setState(DATABASE_SHUTDOWN);
      clearStructures();
      DatabaseManager.removeDatabase(this);

      if (!(e instanceof HsqlException)) {
        e = Trace.error(Trace.GENERAL_ERROR, e.toString());
      }

      throw (HsqlException) e;
    }

    setState(DATABASE_ONLINE);
  }
  /**
   * 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);
  }
  /**
   * Ensures system table producer's table cache, if it exists, is set dirty. After this call
   * up-to-date versions are generated in response to system table requests.
   *
   * <p>Also resets all prepared statements if a change to database structure can possibly affect
   * any existing prepared statement's validity.
   *
   * <p>The argument is false if the change to the database structure does not affect the prepared
   * statement, such as when a new table is added.
   *
   * <p>The argument is typically true when a database object is dropped, altered or a permission
   * was revoked.
   *
   * @param resetPrepared If true, reset all prepared statements.
   */
  public void setMetaDirty(boolean resetPrepared) {

    if (dbInfo != null) {
      dbInfo.setDirty();
    }

    if (resetPrepared) {
      compiledStatementManager.resetStatements();
    }
  }
  /**
   * Opens this database. The database should be opened after construction. or reopened by the
   * close(int closemode) method during a "shutdown compact". Closes the log if there is an error.
   */
  void reopen() throws HsqlException {

    setState(DATABASE_OPENING);

    try {
      User sysUser;

      isNew =
          (sType == DatabaseManager.S_MEM
              || !HsqlProperties.checkFileExists(sPath, isFilesInJar(), getClass()));
      databaseProperties = new HsqlDatabaseProperties(this);

      databaseProperties.load();
      databaseProperties.setURLProperties(urlProperties);
      compiledStatementManager.reset();

      tTable = new HsqlArrayList();
      userManager = new UserManager();
      hAlias = Library.getAliasMap();
      nameManager = new HsqlNameManager();
      triggerNameList = new DatabaseObjectNames();
      indexNameList = new DatabaseObjectNames();
      constraintNameList = new DatabaseObjectNames();
      sequenceManager = new SequenceManager();
      bReferentialIntegrity = true;
      sysUser = userManager.createSysUser(this);
      sessionManager = new SessionManager(this, sysUser);
      dInfo = DatabaseInformation.newDatabaseInformation(this);

      if (sType != DatabaseManager.S_MEM) {
        logger.openLog(this);
      }

      if (isNew) {
        sessionManager
            .getSysSession()
            .sqlExecuteDirectNoPreChecks("CREATE USER SA PASSWORD \"\" ADMIN");
      }

      dInfo.setWithContent(true);
    } catch (Throwable e) {
      logger.closeLog(Database.CLOSEMODE_IMMEDIATELY);
      logger.releaseLock();
      setState(DATABASE_SHUTDOWN);
      clearStructures();

      if (!(e instanceof HsqlException)) {
        e = Trace.error(Trace.GENERAL_ERROR, e.toString());
      }

      throw (HsqlException) e;
    }

    setState(DATABASE_ONLINE);
  }
  /**
   * Retrieves the result of freeing the statement with the given id.
   *
   * @param csid the numeric identifier of the statement
   * @return the result of freeing the indicated statement
   */
  private Result sqlFreeStatement(int csid) {

    boolean existed;
    Result result;

    existed = compiledStatementManager.freeStatement(csid, iId);
    result = new Result(ResultConstants.UPDATECOUNT);

    if (existed) {
      result.iUpdateCount = 1;
    }

    return result;
  }
  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;
  }