/**
   * 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() {

    boolean isNew = false;

    setState(DATABASE_OPENING);

    try {
      nameManager = new HsqlNameManager(this);
      granteeManager = new GranteeManager(this);
      userManager = new UserManager(this);
      schemaManager = new SchemaManager(this);
      persistentStoreCollection = new PersistentStoreCollectionDatabase(this);
      isReferentialIntegrity = true;
      sessionManager = new SessionManager(this);
      collation = collation.newDatabaseInstance();
      dbInfo = DatabaseInformation.newDatabaseInformation(this);
      txManager = new TransactionManager2PL(this);

      lobManager.createSchema();
      sessionManager.getSysLobSession().setSchema(SqlInvariants.LOBS_SCHEMA);
      schemaManager.setSchemaChangeTimestamp();
      schemaManager.createSystemTables();

      // completed metadata
      logger.open();

      isNew = logger.isNewDatabase;

      if (isNew) {
        String username = urlProperties.getProperty("user", "SA");
        String password = urlProperties.getProperty("password", "");

        userManager.createFirstUser(username, password);
        schemaManager.createPublicSchema();
        logger.checkpoint(false);
      }

      lobManager.open();
      dbInfo.setWithContent(true);

      checkpointRunner = new CheckpointRunner();
      timeoutRunner = new TimeoutRunner();
    } catch (Throwable e) {
      logger.close(Database.CLOSEMODE_IMMEDIATELY);
      logger.releaseLock();
      setState(DATABASE_SHUTDOWN);
      clearStructures();
      DatabaseManager.removeDatabase(this);

      if (!(e instanceof HsqlException)) {
        e = Error.error(ErrorCode.GENERAL_ERROR, e);
      }

      logger.logSevereEvent("could not reopen database", e);

      throw (HsqlException) e;
    }

    setState(DATABASE_ONLINE);
  }
  /**
   * 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);
  }
  /**
   * 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() {

    boolean isNew = false;

    setState(DATABASE_OPENING);

    try {
      nameManager = new HsqlNameManager(this);
      granteeManager = new GranteeManager(this);
      userManager = new UserManager(this);
      schemaManager = new SchemaManager(this);
      persistentStoreCollection = new PersistentStoreCollectionDatabase();
      isReferentialIntegrity = true;
      sessionManager = new SessionManager(this);
      collation = collation.getDefaultInstance();
      dbInfo = DatabaseInformation.newDatabaseInformation(this);
      txManager = new TransactionManager2PL(this);

      lobManager.createSchema();
      logger.openPersistence();

      isNew = logger.isNewDatabase;

      if (isNew) {
        userManager.createSAUser();
        schemaManager.createPublicSchema();
        lobManager.initialiseLobSpace();
        logger.checkpoint(false);
      }

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

      if (!(e instanceof HsqlException)) {
        e = Error.error(ErrorCode.GENERAL_ERROR, e);
      }

      logger.logSevereEvent("could not reopen database", e);

      throw (HsqlException) e;
    }

    setState(DATABASE_ONLINE);
  }
  /** Returns the schema and authorisation statements for the database. */
  public Result getScript(boolean indexRoots) {

    Result r = Result.newSingleColumnResult("COMMAND");

    // properties
    String[] list = logger.getPropertiesSQL(indexRoots);

    addRows(r, list);

    list = getSettingsSQL();

    addRows(r, list);

    list = getGranteeManager().getSQL();

    addRows(r, list);

    // schemas and schema objects such as tables, sequences, etc.
    list = schemaManager.getSQLArray();

    addRows(r, list);

    // optional comments on tables etc.
    list = schemaManager.getCommentsArray();

    addRows(r, list);

    list = schemaManager.getTableSpaceSQL();

    addRows(r, list);

    // index roots
    if (indexRoots) {
      list = schemaManager.getIndexRootsSQL();

      addRows(r, list);
    }

    // text headers - readonly - clustered
    list = schemaManager.getTablePropsSQL(!indexRoots);

    addRows(r, list);

    // password complexity
    list = getUserManager().getAuthenticationSQL();

    addRows(r, list);

    // user session start schema names
    list = getUserManager().getInitialSchemaSQL();

    addRows(r, list);

    // grantee rights
    list = getGranteeManager().getRightstSQL();

    addRows(r, list);

    return r;
  }
  /**
   * Closes this Database using the specified mode.
   *
   * <p>
   *
   * <ol>
   *   <LI>closemode -1 performs SHUTDOWN IMMEDIATELY, equivalent to a poweroff or crash.
   *   <LI>closemode 0 performs a normal SHUTDOWN that checkpoints the database normally.
   *   <LI>closemode 1 performs a shutdown compact that scripts out the contents of any CACHED
   *       tables to the log then deletes the existing *.data file that contains the data for all
   *       CACHED table before the normal checkpoint process which in turn creates a new, compact
   *       *.data file.
   * </ol>
   */
  public void close(int closemode) {

    HsqlException he = null;

    setState(DATABASE_CLOSING);
    sessionManager.closeAllSessions();
    sessionManager.clearAll();

    if (filesReadOnly) {
      closemode = CLOSEMODE_IMMEDIATELY;
    }

    /**
     * @todo fredt - impact of possible error conditions in closing the log should be investigated
     *     for the CLOSEMODE_COMPACT mode
     */
    logger.closePersistence(closemode);
    lobManager.close();

    try {
      if (closemode == CLOSEMODE_COMPACT) {
        clearStructures();
        reopen();
        setState(DATABASE_CLOSING);
        logger.closePersistence(CLOSEMODE_NORMAL);
      }
    } catch (Throwable t) {
      if (t instanceof HsqlException) {
        he = (HsqlException) t;
      } else {
        he = Error.error(ErrorCode.GENERAL_ERROR, t.toString());
      }
    }

    logger.releaseLock();
    setState(DATABASE_SHUTDOWN);
    clearStructures();

    // fredt - this could change to avoid removing a db from the
    // DatabaseManager repository if there are pending getDatabase()
    // calls
    DatabaseManager.removeDatabase(this);

    if (he != null) {
      throw he;
    }
  }
  /**
   * Constructs a new Session that operates within (is connected to) the context of this Database
   * object.
   *
   * <p>If successful, the new Session object initially operates on behalf of the user specified by
   * the supplied user name.
   *
   * <p>Throws if username or password is invalid.
   */
  synchronized Session connect(String username, String password) throws HsqlException {

    User user = userManager.getUser(username, password);
    Session session = sessionManager.newSession(this, user, databaseReadOnly, false);

    logger.logConnectUser(session);

    return session;
  }
  void closeIfLast() {

    if (sessionManager.isEmpty() && dbState == DATABASE_ONLINE) {
      if (shutdownOnNoConnection) {
        try {
          close(CLOSEMODE_NORMAL);
        } catch (HsqlException e) {
        }
      } else {
        logger.synchLog();
      }
    }
  }
  /** throws if database is not a normal read / write file database */
  public void checkDatabaseIsFiles() {

    if (!getType().equals(DatabaseURL.S_FILE)) {
      throw Error.error(ErrorCode.DATABASE_IS_NON_FILE);
    }

    if (isFilesReadOnly()) {
      throw Error.error(ErrorCode.DATABASE_IS_READONLY);
    }

    if (logger.isStoredFileAccess()) {
      throw Error.error(ErrorCode.DATABASE_IS_NON_FILE);
    }
  }
  /** Returns the schema and authorisation statements for the database. */
  public Result getScript(boolean indexRoots) {

    Result r = Result.newSingleColumnResult("COMMAND", Type.SQL_VARCHAR);

    // properties
    String[] list = logger.getPropertiesSQL();

    addRows(r, list);

    list = getSettingsSQL();

    addRows(r, list);

    list = getGranteeManager().getSQL();

    addRows(r, list);

    // schemas and schema objects such as tables, sequences, etc.
    list = schemaManager.getSQLArray();

    addRows(r, list);

    // user session start schema names
    list = getUserManager().getInitialSchemaSQL();

    addRows(r, list);

    // grantee rights
    list = getGranteeManager().getRightstSQL();

    addRows(r, list);

    // index roots
    if (indexRoots) {
      list = schemaManager.getIndexRootsSQL();

      addRows(r, list);
    }

    // text headers
    list = schemaManager.getTextTableSQL(!indexRoots);

    addRows(r, list);

    return r;
  }
  /**
   * Closes this Database using the specified mode.
   *
   * <p>
   *
   * <ol>
   *   <LI>closemode -1 performs SHUTDOWN IMMEDIATELY, equivalent to a poweroff or crash.
   *   <LI>closemode 0 performs a normal SHUTDOWN that checkpoints the database normally.
   *   <LI>closemode 1 performs a shutdown compact that scripts out the contents of any CACHED
   *       tables to the log then deletes the existing *.data file that contains the data for all
   *       CACHED table before the normal checkpoint process which in turn creates a new, compact
   *       *.data file.
   * </ol>
   */
  public void close(int closemode) {

    HsqlException he = null;

    // multiple simultaneous close
    synchronized (this) {
      if (getState() != DATABASE_ONLINE) {
        return;
      }

      setState(DATABASE_CLOSING);
    }

    sessionManager.closeAllSessions();

    if (filesReadOnly) {
      closemode = CLOSEMODE_IMMEDIATELY;
    }

    /** impact of possible error conditions in closing the log for the CLOSEMODE_COMPACT mode */
    boolean result = logger.close(closemode);

    lobManager.close();
    sessionManager.close();

    try {
      if (result && closemode == CLOSEMODE_COMPACT) {
        clearStructures();
        reopen();
        setState(DATABASE_CLOSING);
        sessionManager.closeAllSessions();
        logger.close(CLOSEMODE_NORMAL);
        lobManager.close();
        sessionManager.close();
      }
    } catch (Throwable t) {
      if (t instanceof HsqlException) {
        he = (HsqlException) t;
      } else {
        he = Error.error(ErrorCode.GENERAL_ERROR, t);
      }
    }

    lobManager = null;

    logger.releaseLock();
    setState(DATABASE_SHUTDOWN);
    clearStructures();

    // fredt - this could change to avoid removing a db from the
    // DatabaseManager repository if there are pending getDatabase()
    // calls
    DatabaseManager.removeDatabase(this);

    // todo - when hsqldb.sql. framework logging is supported, add another call
    FrameworkLogger.clearLoggers("hsqldb.db." + getUniqueName());

    if (he != null) {
      throw he;
    }
  }