/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.persist;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

import org.hsqldb.Database;
import org.hsqldb.DatabaseType;
import org.hsqldb.HsqlNameManager;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.NumberSequence;
import org.hsqldb.Row;
import org.hsqldb.Session;
import org.hsqldb.SessionInterface;
import org.hsqldb.SqlInvariants;
import org.hsqldb.Statement;
import org.hsqldb.Table;
import org.hsqldb.TableBase;
import org.hsqldb.Tokens;
import org.hsqldb.TransactionManager;
import org.hsqldb.TransactionManagerMV2PL;
import org.hsqldb.TransactionManagerMVCC;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.index.Index;
import org.hsqldb.index.IndexAVL;
import org.hsqldb.index.IndexAVLMemory;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.FileAccess;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.FrameworkLogger;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.InputStreamInterface;
import org.hsqldb.lib.InputStreamWrapper;
import org.hsqldb.lib.SimpleLog;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.lib.tar.DbBackup;
import org.hsqldb.lib.tar.TarMalformatException;
import org.hsqldb.result.Result;
import org.hsqldb.scriptio.ScriptWriterBase;
import org.hsqldb.scriptio.ScriptWriterText;
import org.hsqldb.types.RowType;
import org.hsqldb.types.Type;

// campbell-burnet@users 20030510 - patch 1.7.2 - added cooperative file locking

/**
 *  The public interface of persistence and logging classes.<p>
 *
 *  Implements a storage manager wrapper that provides a consistent,
 *  always available interface to storage management for the Database
 *  class, despite the fact not all Database objects actually use file
 *  storage.<p>
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.3.5
 * @since 1.7.0
 */
public class Logger implements EventLogInterface {

    public SimpleLog appLog;
    public SimpleLog sqlLog;

    //
    FrameworkLogger fwLogger;
    FrameworkLogger sqlLogger;

    //
    private Database database;
    private boolean  logsStatements;    // false indicates Log is being opened
    private boolean  loggingEnabled;
    private boolean  syncFile = false;

    //
    private boolean propIsFileDatabase;
    boolean         propIncrementBackup;
    boolean         propNioDataFile;
    long            propNioMaxSize    = 256 * 1024 * 1024L;
    int             propMaxFreeBlocks = 512;
    int             propMinReuse      = 0;
    private int     propCacheMaxRows;
    private long    propCacheMaxSize;
    int             propCacheDefragLimit;
    private int     propDataFileScale;
    String          propTextSourceDefault = "";
    boolean         propTextAllowFullPath;
    private int     propWriteDelay;
    private int     propLogSize;
    private boolean propLogData = true;
    private int     propEventLogLevel;
    int             propSqlLogLevel;
    int             propGC;
    int             propTxMode       = TransactionManager.LOCKS;
    boolean         propRefIntegrity = true;
    int             propLobBlockSize = 32 * 1024;
    boolean         propCompressLobs;
    int             propScriptFormat = 0;
    boolean         propLargeData;
    int             propFileSpaceValue;
    long            propFileTimestamp;

    //
    Log               log;
    private LockFile  lockFile;
    private Crypto    crypto;
    boolean           cryptLobs;
    public FileAccess fileAccess;
    public boolean    isStoredFileAccess;
    public boolean    isNewStoredFileAccess;
    String            tempDirectoryPath;

    //
    public TextTableStorageManager textTableManager =
        new TextTableStorageManager();

    //
    public boolean isNewDatabase;

    //
    public boolean isSingleFile;

    //
    AtomicInteger backupState     = new AtomicInteger();
    AtomicInteger checkpointState = new AtomicInteger();

    //
    static final int largeDataFactor = 128;

    //
    static final int stateNormal     = 0;
    static final int stateBackup     = 1;
    static final int stateCheckpoint = 2;

    //
    static final int stateCheckpointNormal   = 0;
    static final int stateCheckpointRequired = 1;
    static final int stateCheckpointDue      = 2;

    //
    public static final String oldFileExtension        = ".old";
    public static final String newFileExtension        = ".new";
    public static final String appLogFileExtension     = ".app.log";
    public static final String sqlLogFileExtension     = ".sql.log";
    public static final String logFileExtension        = ".log";
    public static final String scriptFileExtension     = ".script";
    public static final String propertiesFileExtension = ".properties";
    public static final String dataFileExtension       = ".data";
    public static final String backupFileExtension     = ".backup";
    public static final String lobsFileExtension       = ".lobs";
    public static final String lockFileExtension       = ".lck";

    public Logger(Database database) {
        this.database = database;
    }

    /**
     *  Opens the specified Database object's database files and starts up
     *  the logging process. <p>
     *
     *  If the specified Database object is a new database, its database
     *  files are first created.
     *
     * @throws  HsqlException if there is a problem, such as the case when
     *      the specified files are in use by another process
     */
    public void open() {

        // oj@openoffice.org - changed to file access api
        String fileaccess_class_name =
            (String) database.getURLProperties().getProperty(
                HsqlDatabaseProperties.url_fileaccess_class_name);
        String storage_class_name =
            (String) database.getURLProperties().getProperty(
                HsqlDatabaseProperties.url_storage_class_name);
        boolean hasFileProps = false;
        boolean hasScript    = false;

        if (fileaccess_class_name != null) {
            String storagekey = database.getURLProperties().getProperty(
                HsqlDatabaseProperties.url_storage_key);

            try {
                Class fileAccessClass = null;
                Class storageClass    = null;

                try {
                    ClassLoader classLoader =
                        Thread.currentThread().getContextClassLoader();

                    fileAccessClass =
                        classLoader.loadClass(fileaccess_class_name);
                    storageClass = classLoader.loadClass(storage_class_name);
                } catch (ClassNotFoundException e) {
                    fileAccessClass = Class.forName(fileaccess_class_name);
                    storageClass    = Class.forName(storage_class_name);
                }

                if (storageClass.isAssignableFrom(
                        RandomAccessInterface.class)) {
                    isNewStoredFileAccess = true;
                }

                Constructor constructor =
                    fileAccessClass.getConstructor(new Class[]{
                        Object.class });

                fileAccess =
                    (FileAccess) constructor.newInstance(new Object[]{
                        storagekey });
                isStoredFileAccess = true;
            } catch (ClassNotFoundException e) {
                System.out.println("ClassNotFoundException");
            } catch (InstantiationException e) {
                System.out.println("InstantiationException");
            } catch (IllegalAccessException e) {
                System.out.println("IllegalAccessException");
            } catch (Exception e) {
                System.out.println("Exception");
            }
        } else {
            fileAccess = FileUtil.getFileAccess(database.isFilesInJar());
        }

        propIsFileDatabase          = database.getType().isFileBased();
        database.databaseProperties = new HsqlDatabaseProperties(database);
        propTextAllowFullPath = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.textdb_allow_full_path);

        if (propIsFileDatabase) {
            hasFileProps = database.databaseProperties.load();
            hasScript = fileAccess.isStreamElement(database.getPath()
                                                   + scriptFileExtension);

            boolean exists;

            if (database.databaseProperties.isVersion18()) {
                exists = hasFileProps;

                database.databaseProperties.setProperty(
                    HsqlDatabaseProperties.hsqldb_inc_backup, false);
            } else {
                exists = hasScript;

                if (!exists) {
                    exists =
                        fileAccess.isStreamElement(database.getPath()
                                                   + scriptFileExtension
                                                   + Logger.newFileExtension);

                    if (exists) {
                        database.databaseProperties.setDBModified(
                            HsqlDatabaseProperties.FILES_MODIFIED_NEW);
                    }
                }
            }

            isNewDatabase = !exists;
        } else {
            isNewDatabase = true;
        }

        if (isNewDatabase) {
            String name = newUniqueName();

            database.setDatabaseName(name);

            boolean checkExists = database.isFilesInJar();

            checkExists |=
                (database.urlProperties
                    .isPropertyTrue(HsqlDatabaseProperties
                        .url_ifexists) || !database.urlProperties
                            .isPropertyTrue(HsqlDatabaseProperties
                                .url_create, true));

            if (checkExists) {
                throw Error.error(ErrorCode.DATABASE_NOT_EXISTS,
                                  database.getPath());
            }

            database.databaseProperties.setURLProperties(
                database.urlProperties);
        } else {
            if (!hasFileProps) {
                database.databaseProperties.setDBModified(
                    HsqlDatabaseProperties.FILES_MODIFIED);
            }

            // properties that also apply to existing database only if they exist
            if (database.urlProperties.isPropertyTrue(
                    HsqlDatabaseProperties.hsqldb_files_readonly)) {
                database.databaseProperties.setProperty(
                    HsqlDatabaseProperties.hsqldb_files_readonly, true);
            }

            if (database.urlProperties.isPropertyTrue(
                    HsqlDatabaseProperties.hsqldb_readonly)) {
                database.databaseProperties.setProperty(
                    HsqlDatabaseProperties.hsqldb_readonly, true);
            }

            // hsqldb.lock_file=false is applied
            if (!database.urlProperties.isPropertyTrue(
                    HsqlDatabaseProperties.hsqldb_lock_file, true)) {
                database.databaseProperties.setProperty(
                    HsqlDatabaseProperties.hsqldb_lock_file, false);
            }
        }

        setVariables();

        String appLogPath = null;
        String sqlLogPath = null;

        if (propIsFileDatabase && !database.isFilesReadOnly()) {
            appLogPath = database.getPath() + appLogFileExtension;
            sqlLogPath = database.getPath() + sqlLogFileExtension;
        }

        appLog = new SimpleLog(appLogPath, propEventLogLevel, false);
        sqlLog = new SimpleLog(sqlLogPath, propSqlLogLevel, true);

        database.setReferentialIntegrity(propRefIntegrity);

        if (!isFileDatabase()) {
            return;
        }

        checkpointState.set(stateCheckpointNormal);

        logsStatements = false;

        boolean useLock = database.getProperties().isPropertyTrue(
            HsqlDatabaseProperties.hsqldb_lock_file);

        if (useLock && !database.isFilesReadOnly()) {
            acquireLock(database.getPath());
        }

        boolean version18 = database.databaseProperties.isVersion18();

        if (version18) {
            database.setDatabaseName(newUniqueName());
            database.schemaManager.createPublicSchema();

            HsqlName name = database.schemaManager.findSchemaHsqlName(
                SqlInvariants.PUBLIC_SCHEMA);

            database.schemaManager.setDefaultSchemaHsqlName(name);
        }

        log = new Log(database);

        log.open();

        logsStatements = true;
        loggingEnabled = propLogData && !database.isFilesReadOnly();

        if (version18) {
            checkpoint(null, false, false);
        }

        if (database.getNameString().length() == 0) {
            database.setDatabaseName(newUniqueName());
        }

        // URL database properties that can override .script file settings
        int level = database.urlProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_applog, -1);

        if (level >= 0) {
            setEventLogLevel(level, false);
        }

        level = database.urlProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_sqllog, -1);

        if (level >= 0) {
            setEventLogLevel(level, true);
        }
    }

    private void setVariables() {

        String cryptKey = database.urlProperties.getProperty(
            HsqlDatabaseProperties.url_crypt_key);

        if (cryptKey != null) {
            String cryptType = database.urlProperties.getProperty(
                HsqlDatabaseProperties.url_crypt_type);
            String cryptProvider = database.urlProperties.getProperty(
                HsqlDatabaseProperties.url_crypt_provider);
            String cryptIv = database.urlProperties.getProperty(
                HsqlDatabaseProperties.url_crypt_iv);

            crypto = new Crypto(cryptKey, cryptIv, cryptType, cryptProvider);
            cryptLobs = database.urlProperties.isPropertyTrue(
                HsqlDatabaseProperties.url_crypt_lobs, true);
        }

        if (database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_readonly)) {
            database.setReadOnly();
        }

        if (database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_files_readonly)) {
            database.setFilesReadOnly();
        }

        // handle invalid paths as well as access issues
        if (!database.isFilesReadOnly()) {
            if (database.getType() == DatabaseType.DB_MEM
                    || isStoredFileAccess) {
                tempDirectoryPath = database.getProperties().getStringProperty(
                    HsqlDatabaseProperties.hsqldb_temp_directory);
            } else {
                tempDirectoryPath = database.getPath() + ".tmp";
            }

            if (tempDirectoryPath != null) {
                tempDirectoryPath =
                    FileUtil.makeDirectories(tempDirectoryPath);
            }
        }

        propScriptFormat = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_script_format);

        boolean version18 = database.databaseProperties.isVersion18();

        propMaxFreeBlocks = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_cache_free_count);
        propMaxFreeBlocks = ArrayUtil.getTwoPowerFloor(propMaxFreeBlocks);

        if (database.urlProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_large_data, false)) {
            propLargeData = true;
        }

        if (!database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.sql_pad_space, true)) {
            database.collation.setPadding(false);
        }

        if (version18 && isStoredFileAccess) {
            database.collation.setPadding(false);
        }

        String temp = database.getProperties().getStringProperty(
            HsqlDatabaseProperties.hsqldb_digest);

        database.granteeManager.setDigestAlgo(temp);

        if (!isNewDatabase && !version18) {
            return;
        }

        temp = database.databaseProperties.getStringProperty(
            HsqlDatabaseProperties.hsqldb_digest);

        database.granteeManager.setDigestAlgo(temp);

        if (tempDirectoryPath != null) {
            int rows = database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_result_max_memory_rows);

            database.setResultMaxMemoryRows(rows);
        }

        String tableType = database.databaseProperties.getStringProperty(
            HsqlDatabaseProperties.hsqldb_default_table_type);

        if ("CACHED".equalsIgnoreCase(tableType)) {
            database.schemaManager.setDefaultTableType(TableBase.CACHED_TABLE);
        }

        String txMode = database.databaseProperties.getStringProperty(
            HsqlDatabaseProperties.hsqldb_tx);

        if (Tokens.T_MVCC.equalsIgnoreCase(txMode)) {
            propTxMode = TransactionManager.MVCC;
        } else if (Tokens.T_MVLOCKS.equalsIgnoreCase(txMode)) {
            propTxMode = TransactionManager.MVLOCKS;
        } else if (Tokens.T_LOCKS.equalsIgnoreCase(txMode)) {
            propTxMode = TransactionManager.LOCKS;
        }

        switch (propTxMode) {

            case TransactionManager.LOCKS :
                break;

            case TransactionManager.MVLOCKS :
                database.txManager = new TransactionManagerMV2PL(database);
                break;

            case TransactionManager.MVCC :
                database.txManager = new TransactionManagerMVCC(database);
                break;
        }

        String txLevel = database.databaseProperties.getStringProperty(
            HsqlDatabaseProperties.hsqldb_tx_level);

        if (Tokens.T_SERIALIZABLE.equalsIgnoreCase(txLevel)) {
            database.defaultIsolationLevel = SessionInterface.TX_SERIALIZABLE;
        } else {
            database.defaultIsolationLevel =
                SessionInterface.TX_READ_COMMITTED;
        }

        database.txConflictRollback =
            database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_tx_conflict_rollback);
        database.sqlRestrictExec = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_restrict_exec);
        database.sqlEnforceNames = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_names);
        database.sqlRegularNames = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_regular_names);
        database.sqlEnforceRefs = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_refs);
        database.sqlEnforceSize = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_size);
        database.sqlEnforceTypes = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_types);
        database.sqlEnforceTDCD = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_tdcd);
        database.sqlEnforceTDCU = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_enforce_tdcu);
        database.sqlTranslateTTI = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.jdbc_translate_tti_types);
        database.sqlLiveObject = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_live_object);
        database.sqlCharLiteral = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_char_literal);
        database.sqlConcatNulls = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_concat_nulls);
        database.sqlNullsFirst = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_nulls_first);
        database.sqlNullsOrder = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_nulls_order);
        database.sqlUniqueNulls = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_unique_nulls);
        database.sqlConvertTruncate =
            database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.sql_convert_trunc);
        database.sqlAvgScale = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.sql_avg_scale);
        database.sqlDoubleNaN = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_double_nan);
        database.sqlLongvarIsLob = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_longvar_is_lob);
        database.sqlIgnoreCase = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_ignore_case);
        database.sqlSyntaxDb2 = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_syntax_db2);
        database.sqlSyntaxMss = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_syntax_mss);
        database.sqlSyntaxMys = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_syntax_mys);
        database.sqlSyntaxOra = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_syntax_ora);
        database.sqlSyntaxPgs = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_syntax_pgs);
        database.sqlSysIndexNames = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_sys_index_names);

        if (database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.sql_compare_in_locale)) {
            database.collation.setCollationAsLocale();
        }

        propEventLogLevel = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_applog);
        propSqlLogLevel = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_sqllog);

        if (database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_files_readonly)) {
            database.setFilesReadOnly();
        }

        if (database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_readonly)) {
            database.setReadOnly();
        }

        propIncrementBackup = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.hsqldb_inc_backup);
        propNioDataFile = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.hsqldb_nio_data_file);
        propNioMaxSize =
            database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_nio_max_size) * 1024L * 1024L;
        propCacheMaxRows = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_cache_rows);
        propCacheMaxSize =
            database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_cache_size) * 1024L;

        setLobFileScaleNoCheck(
            database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_lob_file_scale));
        this.setLobFileCompressedNoCheck(
            database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_lob_file_compressed));
        setDataFileScaleNoCheck(
            database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_cache_file_scale));

        // linked with FILES SCALE
        int fileSpace = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_files_space, 0);

        if (fileSpace != 0) {
            setDataFileSpaces(fileSpace);
        }

        propCacheDefragLimit = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_defrag_limit);
        propWriteDelay = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_write_delay_millis);

        if (!database.databaseProperties.isPropertyTrue(
                HsqlDatabaseProperties.hsqldb_write_delay)) {
            propWriteDelay = 0;
        }

        propLogSize = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.hsqldb_log_size);
        propLogData = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.hsqldb_log_data);
        propGC = database.databaseProperties.getIntegerProperty(
            HsqlDatabaseProperties.runtime_gc_interval);
        propRefIntegrity = database.databaseProperties.isPropertyTrue(
            HsqlDatabaseProperties.sql_ref_integrity);

        setCacheMinReuseSize(
            database.databaseProperties.getIntegerProperty(
                HsqlDatabaseProperties.hsqldb_min_reuse));
    }

// fredt@users 20020130 - patch 495484 by campbell-burnet@users

    /**
     *  Shuts down the logging process using the specified mode. <p>
     *
     * @param  closemode The mode in which to shut down the logging
     *      process
     *      <OL>
     *        <LI> CLOSEMODE_IMMEDIATELY performs SHUTDOWN IMMEDIATELY, equivalent
     *        to  a poweroff or crash.
     *        <LI> CLOSEMODE_NORMAL performs a normal SHUTDOWN that
     *        checkpoints the database normally.
     *        <LI> CLOSEMODE_COMPACT 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.
     *        <LI> CLOSEMODE_SCRIPT performs a SHUTDOWN SCRIPT.
     *      </OL>
     *
     * @return  true if closed with no problems or false if a problem was
     *        encountered.
     */
    public boolean close(int closemode) {

        boolean result = true;

        if (log == null) {
            textTableManager.closeAllTextCaches(false);

            return true;
        }

        log.synchLog();
        database.lobManager.synch();

        try {
            switch (closemode) {

                case Database.CLOSEMODE_IMMEDIATELY :
                    log.shutdown();
                    break;

                case Database.CLOSEMODE_NORMAL :
                    log.close(false);
                    break;

                case Database.CLOSEMODE_COMPACT :
                case Database.CLOSEMODE_SCRIPT :
                    log.close(true);
                    break;
            }

            database.persistentStoreCollection.release();
        } catch (Throwable e) {
            database.logger.logSevereEvent("error closing log", e);

            result = false;
        }

        logInfoEvent("Database closed");

        log = null;

        appLog.close();
        sqlLog.close();

        logsStatements = false;
        loggingEnabled = false;

        return result;
    }

    String newUniqueName() {

        String name = StringUtil.toPaddedString(
            Long.toHexString(System.currentTimeMillis()), 16, '0', false);

        name = "HSQLDB" + name.substring(6).toUpperCase(Locale.ENGLISH);

        return name;
    }

    /*
     * Must return correct mode prior to initialisation
     * @return  true if this object encapsulates a non-null Log instance,
     *      else false
     */
    public boolean isLogged() {
        return propIsFileDatabase && !database.isFilesReadOnly();
    }

    public boolean isCurrentlyLogged() {
        return loggingEnabled;
    }

    public boolean isAllowedFullPath() {
        return this.propTextAllowFullPath;
    }

    /**
     * All usage of FrameworkLogger should call this method before using an
     * instance.
     *
     * It ensures and requires that no logging should take place before a new
     * database unique name has been created for a new database or read from the
     * .script file for an old database.<p>
     *
     * An instance is returned when:
     * - database unique name has been created
     * - FrameworkLogger would use log4j
     *
     * Otherwise null is returned.
     *
     * This tactic avoids usage of file-based jdk logging for the time being.
     *
     */
    private void getEventLogger() {

        if (fwLogger != null) {
            return;
        }

        String name = database.getNameString();

        if (name.length() == 0) {

            // The database unique name is set up at different times
            // depending on upgraded / exiting / new databases.
            // Therefore FrameworkLogger is not used until the unique
            // name is known.
            return;
        }

        fwLogger = FrameworkLogger.getLog(SimpleLog.logTypeNameEngine,
                                          "hsqldb.db."
                                          + database.getNameString());
        /*
        sqlLogger = FrameworkLogger.getLog(SimpleLog.logTypeNameEngine,
                                           "hsqldb.sql."
                                           + database.getUniqueName());
        */
    }

    public void setEventLogLevel(int level, boolean logSql) {

        if (level < SimpleLog.LOG_NONE || level > SimpleLog.LOG_RESULT) {
            throw Error.error(ErrorCode.X_42556);
        }

        if (logSql) {
            propSqlLogLevel = level;

            sqlLog.setLevel(level);
        } else {
            if (level > SimpleLog.LOG_DETAIL) {
                level = SimpleLog.LOG_DETAIL;
            }

            propEventLogLevel = level;

            appLog.setLevel(level);
        }
    }

    public void logSevereEvent(String message, Throwable t) {

        getEventLogger();

        if (fwLogger != null) {
            fwLogger.severe(message, t);
        }

        if (appLog != null) {
            if (t == null) {
                appLog.logContext(SimpleLog.LOG_ERROR, message);
            } else {
                appLog.logContext(t, message, SimpleLog.LOG_ERROR);
            }
        }
    }

    public void logWarningEvent(String message, Throwable t) {

        getEventLogger();

        if (fwLogger != null) {
            fwLogger.warning(message, t);
        }

        appLog.logContext(t, message, SimpleLog.LOG_ERROR);
    }

    public void logInfoEvent(String message) {

        getEventLogger();

        if (fwLogger != null) {
            fwLogger.info(message);
        }

        appLog.logContext(SimpleLog.LOG_NORMAL, message);
    }

    public void logDetailEvent(String message) {

        getEventLogger();

        if (fwLogger != null) {
            fwLogger.finest(message);
        }

        if (appLog != null) {
            appLog.logContext(SimpleLog.LOG_DETAIL, message);
        }
    }

    public void logStatementEvent(Session session, Statement statement,
                                  Object[] paramValues, Result result,
                                  int level) {

        if (sqlLog != null && level <= propSqlLogLevel) {
            String sessionId   = Long.toString(session.getId());
            String sql         = statement.getSQL();
            String values      = "";
            int    paramLength = 0;

            if (propSqlLogLevel < SimpleLog.LOG_DETAIL) {
                if (sql.length() > 256) {
                    sql = sql.substring(0, 256);
                }

                paramLength = 32;
            }

            if (paramValues != null && paramValues.length > 0) {
                values = RowType.convertToSQLString(
                    paramValues,
                    statement.getParametersMetaData().getParameterTypes(),
                    paramLength);
            }

            if (propSqlLogLevel == SimpleLog.LOG_RESULT) {
                StringBuffer sb = new StringBuffer(values);

                sb.append(' ').append('[');

                if (result.isError()) {
                    sb.append(result.getErrorCode());
                } else if (result.isData()) {
                    sb.append(result.getNavigator().getSize());
                } else if (result.isUpdateCount()) {
                    sb.append(result.getUpdateCount());
                }

                sb.append(']');

                values = sb.toString();
            }

            sqlLog.logContext(level, sessionId, sql, values);
        }
    }

    public int getSqlEventLogLevel() {
        return propSqlLogLevel;
    }

    /**
     * Returns the Cache object or null if one doesn't exist.
     */
    public DataFileCache getCache() {

        if (log == null) {
            return null;
        } else {
            return log.getCache();
        }
    }

    /**
     * Returns true if Cache object exists.
     */
    public boolean hasCache() {

        if (log == null) {
            return false;
        } else {
            return log.hasCache();
        }
    }

    /**
     * Records a Log entry for the specified SQL statement, on behalf of
     * the specified Session object.
     */
    public synchronized void writeOtherStatement(Session session,
            String statement) {

        if (loggingEnabled) {
            log.writeOtherStatement(session, statement);
        }
    }

    /**
     * Used exclusively by PersistentStore objects
     */
    public synchronized void writeInsertStatement(Session session, Row row,
            Table table) {

        if (loggingEnabled) {
            log.writeInsertStatement(session, row, table);
        }
    }

    /**
     * Used exclusively by PersistentStore objects
     */
    public synchronized void writeDeleteStatement(Session session, Table t,
            Object[] row) {

        if (loggingEnabled) {
            log.writeDeleteStatement(session, t, row);
        }
    }

    /**
     * Used at transaction commit
     */
    public synchronized void writeSequenceStatement(Session session,
            NumberSequence s) {

        if (loggingEnabled) {
            log.writeSequenceStatement(session, s);
        }
    }

    /**
     * Used at transaction commit
     */
    public synchronized void writeCommitStatement(Session session) {

        if (loggingEnabled) {
            log.writeCommitStatement(session);
        }
    }

    public synchronized void synchLog() {

        if (loggingEnabled) {
            log.synchLog();
        }
    }

    /**
     *  Checkpoints the database. <p>
     *
     *  The most important effect of calling this method is to cause the
     *  log file to be rewritten in the most efficient form to
     *  reflect the current state of the database, i.e. only the DDL and
     *  insert DML required to recreate the database in its present state.
     *  Other house-keeping duties are performed w.r.t. other database
     *  files, in order to ensure as much as possible the ACID properties
     *  of the database.
     *
     * @throws  HsqlException if there is a problem checkpointing the
     *      database
     */
    public void checkpoint(Session session, boolean defrag, boolean lobs) {

        if (!backupState.compareAndSet(stateNormal, stateCheckpoint)) {
            throw Error.error(ErrorCode.ACCESS_IS_DENIED);
        }

        database.lobManager.lock();

        try {
            synchronized (this) {
                checkpointInternal(session, defrag);

                if (lobs) {
                    database.lobManager.deleteUnusedLobs();
                }
            }
        } finally {
            backupState.set(stateNormal);
            checkpointState.set(stateCheckpointNormal);
            database.lobManager.unlock();
        }
    }

    private void checkpointInternal(Session session, boolean defrag) {

        if (logsStatements) {
            logInfoEvent("Checkpoint start");
            log.checkpoint(session, defrag);
            logInfoEvent("Checkpoint end - txts: "
                         + database.txManager.getGlobalChangeTimestamp());
        }
    }

    /**
     *  Sets the maximum size to which the log file can grow
     *  before being automatically checkpointed.
     *
     * @param  megas size in MB
     */
    public synchronized void setLogSize(int megas) {

        propLogSize = megas;

        if (log != null) {
            log.setLogSize(propLogSize);
        }
    }

    /**
     *  Sets logging on or off.
     */
    public synchronized void setLogData(boolean mode) {

        propLogData    = mode;
        loggingEnabled = propLogData && !database.isFilesReadOnly();
        loggingEnabled &= logsStatements;
    }

    /**
     *  Sets the type of script file, currently 0 for text (default)
     *  3 for compressed
     *
     * @param  format The type
     */
    public synchronized void setScriptType(int format) {

        if (format == propScriptFormat) {
            return;
        }

        propScriptFormat = format;

        checkpointState.compareAndSet(stateCheckpointNormal,
                                      stateCheckpointRequired);
    }

    /**
     *  Sets the log write delay mode to number of seconds. By default
     *  executed commands written to the log are committed fully at most
     *  0.5 second after they are executed. This improves performance for
     *  applications that execute a large number
     *  of short running statements in a short period of time, but risks
     *  failing to log some possibly large number of statements in the
     *  event of a crash. A small value improves recovery.
     *  A value of 0 will severly slow down logging when autocommit is on,
     *  or many short transactions are committed.
     *
     * @param delay in milliseconds
     */
    public synchronized void setWriteDelay(int delay) {

        propWriteDelay = delay;

        if (log != null) {
            syncFile = (delay == 0);

            log.setWriteDelay(delay);
        }
    }

    public Crypto getCrypto() {
        return crypto;
    }

    public int getWriteDelay() {
        return propWriteDelay;
    }

    public int getLogSize() {
        return propLogSize;
    }

    public int getLobBlockSize() {
        return propLobBlockSize;
    }

    public synchronized void setIncrementBackup(boolean val) {

        if (val == propIncrementBackup) {
            return;
        }

        if (log != null) {
            log.setIncrementBackup(val);

            if (log.hasCache()) {
                checkpointState.compareAndSet(stateCheckpointNormal,
                                              stateCheckpointRequired);
            }
        }

        propIncrementBackup = val;
    }

    public void setCacheMaxRows(int value) {
        propCacheMaxRows = value;
    }

    public int getCacheMaxRows() {
        return propCacheMaxRows;
    }

    public void setCacheSize(int value) {
        propCacheMaxSize = value * 1024L;
    }

    public long getCacheSize() {
        return propCacheMaxSize;
    }

    public void setCacheMinReuseSize(int value) {
        this.propMinReuse = ArrayUtil.getTwoPowerFloor(value);
    }

    public void setDataFileScale(int value) {

        if (propDataFileScale == value) {
            return;
        }

        checkPower(value, 10);

        if (value < 8 && value != 1) {
            throw Error.error(ErrorCode.X_42556);
        }

        if (hasCache()) {
            throw Error.error(ErrorCode.DATA_FILE_IN_USE);
        }

        propDataFileScale = value;
    }

    public void setDataFileScaleNoCheck(int value) {

        checkPower(value, 10);

        if (value < 8 && value != 1) {
            throw Error.error(ErrorCode.X_42556);
        }

        propDataFileScale = value;
    }

    public int getDataFileScale() {
        return propDataFileScale;
    }

    public int getDataFileFactor() {
        return propLargeData ? largeDataFactor
                             : 1;
    }

    public void setDataFileSpaces(boolean value) {

        if (value) {
            setDataFileSpaces(propDataFileScale / 16);
        } else {
            setDataFileSpaces(0);
        }
    }

    public void setDataFileSpaces(int value) {

        if (propFileSpaceValue == value) {
            return;
        }

        if (value != 0) {
            checkPower(value, 6);
        }

        if (value > propDataFileScale / 16) {
            value = propDataFileScale / 16;
        }

        propFileSpaceValue = value;

        if (hasCache()) {
            DataFileCache dataCache = getCache();
            boolean       result    = dataCache.setDataSpaceManager();

            if (!result) {
                return;
            }

            database.persistentStoreCollection.setNewTableSpaces();
        }
    }

    public int getDataFileSpaces() {
        return propFileSpaceValue;
    }

    public long getFilesTimestamp() {
        return propFileTimestamp;
    }

    public void setFilesTimestamp(long value) {
        propFileTimestamp = value;
    }

    public void setLobFileScale(int value) {

        if (propLobBlockSize == value * 1024) {
            return;
        }

        checkPower(value, 5);

        if (database.lobManager.getLobCount() > 0) {
            throw Error.error(ErrorCode.DATA_FILE_IN_USE);
        }

        propLobBlockSize = value * 1024;

        database.lobManager.close();
        database.lobManager.open();
    }

    public void setLobFileScaleNoCheck(int value) {

        checkPower(value, 5);

        propLobBlockSize = value * 1024;
    }

    public int getLobFileScale() {
        return propLobBlockSize / 1024;
    }

    public void setLobFileCompressed(boolean value) {

        if (propCompressLobs == value) {
            return;
        }

        if (database.lobManager.getLobCount() > 0) {
            throw Error.error(ErrorCode.DATA_FILE_IN_USE);
        }

        propCompressLobs = value;

        database.lobManager.close();
        database.lobManager.open();
    }

    public void setLobFileCompressedNoCheck(boolean value) {
        propCompressLobs = value;
    }

    public void setDefagLimit(int value) {
        propCacheDefragLimit = value;
    }

    public int getDefragLimit() {
        return propCacheDefragLimit;
    }

    public void setDefaultTextTableProperties(String source,
            HsqlProperties props) {

        props.setProperty(HsqlDatabaseProperties.url_check_props, true);
        database.getProperties().setURLProperties(props);

        this.propTextSourceDefault = source;
    }

    public void setNioDataFile(boolean value) {
        propNioDataFile = value;
    }

    public void setNioMaxSize(int value) {

        if (value < 8) {
            throw Error.error(ErrorCode.X_42556);
        }

        if (!ArrayUtil.isTwoPower(value, 10)) {
            if (value < 1024 || value % 512 != 0) {
                throw Error.error(ErrorCode.X_42556);
            }
        }

        propNioMaxSize = value * 1024L * 1024L;
    }

    public FileAccess getFileAccess() {
        return fileAccess;
    }

    public boolean isStoredFileAccess() {
        return isStoredFileAccess;
    }

    public boolean isNewStoredFileAccess() {
        return isNewStoredFileAccess;
    }

    public boolean isFileDatabase() {
        return propIsFileDatabase;
    }

    public String getTempDirectoryPath() {
        return tempDirectoryPath;
    }

    static void checkPower(int n, int max) {

        if (!ArrayUtil.isTwoPower(n, max)) {
            throw Error.error(ErrorCode.X_42556);
        }
    }

    public void setCheckpointRequired() {
        checkpointState.compareAndSet(stateCheckpointNormal,
                                      stateCheckpointRequired);
    }

    public boolean needsCheckpointReset() {
        return checkpointState.compareAndSet(stateCheckpointRequired,
                                             stateCheckpointDue);
    }

    public boolean hasLockFile() {
        return lockFile != null;
    }

    public void acquireLock(String path) {

        if (lockFile != null) {
            return;
        }

        lockFile = LockFile.newLockFileLock(path);
    }

    public void releaseLock() {

        try {
            if (lockFile != null) {
                lockFile.tryRelease();
            }
        } catch (Exception e) {}

        lockFile = null;
    }

    public PersistentStore newStore(Session session,
                                    PersistentStoreCollection collection,
                                    TableBase table) {

        switch (table.getTableType()) {

            case TableBase.CACHED_TABLE :
                DataFileCache cache = getCache();

                if (cache == null) {
                    break;
                }

                return new RowStoreAVLDisk(cache, (Table) table);

            case TableBase.MEMORY_TABLE :
            case TableBase.SYSTEM_TABLE :
                return new RowStoreAVLMemory((Table) table);

            case TableBase.TEXT_TABLE :
                return new RowStoreAVLDiskData((Table) table);

            case TableBase.INFO_SCHEMA_TABLE :
                return new RowStoreAVLHybridExtended(session, table, false);

            case TableBase.TEMP_TABLE :
                return new RowStoreAVLHybridExtended(session, table, true);

            case TableBase.CHANGE_SET_TABLE :
                return new RowStoreDataChange(session, table);

            case TableBase.FUNCTION_TABLE :
            case TableBase.RESULT_TABLE :
            case TableBase.SYSTEM_SUBQUERY :
            case TableBase.VIEW_TABLE :
            case TableBase.TRANSITION_TABLE :
                if (session == null) {
                    return null;
                }

                return new RowStoreAVLHybrid(session, table, true);
        }

        throw Error.runtimeError(ErrorCode.U_S0500, "Logger");
    }

    public Index newIndex(HsqlName name, long id, TableBase table,
                          int[] columns, boolean[] descending,
                          boolean[] nullsLast, Type[] colTypes, boolean pk,
                          boolean unique, boolean constraint,
                          boolean forward) {

        switch (table.getTableType()) {

            case TableBase.INFO_SCHEMA_TABLE :
            case TableBase.SYSTEM_TABLE :
            case TableBase.MEMORY_TABLE :
                return new IndexAVLMemory(name, id, table, columns,
                                          descending, nullsLast, colTypes, pk,
                                          unique, constraint, forward);

            case TableBase.CACHED_TABLE :
            case TableBase.CHANGE_SET_TABLE :
            case TableBase.FUNCTION_TABLE :
            case TableBase.TEXT_TABLE :
            case TableBase.TEMP_TABLE :
            case TableBase.RESULT_TABLE :
            case TableBase.SYSTEM_SUBQUERY :
            case TableBase.VIEW_TABLE :
            case TableBase.TRANSITION_TABLE :
                return new IndexAVL(name, id, table, columns, descending,
                                    nullsLast, colTypes, pk, unique,
                                    constraint, forward);
        }

        throw Error.runtimeError(ErrorCode.U_S0500, "Logger");
    }

    public String getValueStringForProperty(String name) {

        String value = "";

        if (HsqlDatabaseProperties.hsqldb_tx.equals(name)) {
            switch (database.txManager.getTransactionControl()) {

                case TransactionManager.MVCC :
                    value = Tokens.T_MVCC.toLowerCase();
                    break;

                case TransactionManager.MVLOCKS :
                    value = Tokens.T_MVLOCKS.toLowerCase();
                    break;

                case TransactionManager.LOCKS :
                    value = Tokens.T_LOCKS.toLowerCase();
                    break;
            }

            return value;
        }

        if (HsqlDatabaseProperties.hsqldb_tx_level.equals(name)) {
            switch (database.defaultIsolationLevel) {

                case SessionInterface.TX_READ_COMMITTED :
                    value = new StringBuffer(Tokens.T_READ).append(' ').append(
                        Tokens.T_COMMITTED).toString().toLowerCase();
                    break;

                case SessionInterface.TX_SERIALIZABLE :
                    value = Tokens.T_SERIALIZABLE.toLowerCase();
                    break;
            }

            return value;
        }

        if (HsqlDatabaseProperties.hsqldb_applog.equals(name)) {
            return String.valueOf(appLog.getLevel());
        }

        if (HsqlDatabaseProperties.hsqldb_sqllog.equals(name)) {
            return String.valueOf(sqlLog.getLevel());
        }

        if (HsqlDatabaseProperties.hsqldb_lob_file_scale.equals(name)) {
            return String.valueOf(propLobBlockSize / 1024);
        }

        if (HsqlDatabaseProperties.hsqldb_lob_file_compressed.equals(name)) {
            return String.valueOf(propCompressLobs);
        }

        if (HsqlDatabaseProperties.hsqldb_cache_file_scale.equals(name)) {
            return String.valueOf(propDataFileScale);
        }

        if (HsqlDatabaseProperties.hsqldb_cache_free_count.equals(name)) {
            return String.valueOf(propMaxFreeBlocks);
        }

        if (HsqlDatabaseProperties.hsqldb_cache_rows.equals(name)) {
            return String.valueOf(propCacheMaxRows);
        }

        if (HsqlDatabaseProperties.hsqldb_cache_size.equals(name)) {
            return String.valueOf(propCacheMaxSize / 1024);
        }

        if (HsqlDatabaseProperties.hsqldb_default_table_type.equals(name)) {
            return database.schemaManager.getDefaultTableType()
                   == TableBase.CACHED_TABLE ? Tokens.T_CACHED
                                             : Tokens.T_MEMORY;
        }

        if (HsqlDatabaseProperties.hsqldb_defrag_limit.equals(name)) {
            return String.valueOf(propCacheDefragLimit);
        }

        if (HsqlDatabaseProperties.hsqldb_files_space.equals(name)) {
            return String.valueOf(propFileSpaceValue);
        }

        if (HsqlDatabaseProperties.hsqldb_files_readonly.equals(name)) {
            return database.databaseProperties.getPropertyString(
                HsqlDatabaseProperties.hsqldb_files_readonly);
        }

        if (HsqlDatabaseProperties.hsqldb_inc_backup.equals(name)) {
            return String.valueOf(propIncrementBackup);
        }

        if (HsqlDatabaseProperties.hsqldb_large_data.equals(name)) {
            return String.valueOf(propLargeData);
        }

        if (HsqlDatabaseProperties.hsqldb_large_data.equals(name)) {
            return String.valueOf(propLargeData);
        }

        if (HsqlDatabaseProperties.hsqldb_lock_file.equals(name)) {
            return database.databaseProperties.getPropertyString(
                HsqlDatabaseProperties.hsqldb_lock_file);
        }

        if (HsqlDatabaseProperties.hsqldb_log_data.equals(name)) {
            return String.valueOf(propLogData);
        }

        if (HsqlDatabaseProperties.hsqldb_log_size.equals(name)) {
            return String.valueOf(propLogSize);
        }

        if (HsqlDatabaseProperties.hsqldb_nio_data_file.equals(name)) {
            return String.valueOf(propNioDataFile);
        }

        if (HsqlDatabaseProperties.hsqldb_nio_max_size.equals(name)) {
            return String.valueOf(propNioMaxSize / (1024 * 1024));
        }

        if (HsqlDatabaseProperties.hsqldb_script_format.equals(name)) {
            return ScriptWriterBase.LIST_SCRIPT_FORMATS[propScriptFormat]
                .toLowerCase();
        }

        if (HsqlDatabaseProperties.hsqldb_temp_directory.equals(name)) {
            return tempDirectoryPath;
        }

        if (HsqlDatabaseProperties.hsqldb_tx_conflict_rollback.equals(name)) {
            return String.valueOf(database.txConflictRollback);
        }

        if (HsqlDatabaseProperties.hsqldb_result_max_memory_rows.equals(
                name)) {
            return String.valueOf(database.getResultMaxMemoryRows());
        }

        if (HsqlDatabaseProperties.hsqldb_write_delay.equals(name)) {
            return String.valueOf(propWriteDelay != 0);
        }

        if (HsqlDatabaseProperties.hsqldb_write_delay_millis.equals(name)) {
            return String.valueOf(propWriteDelay);
        }

        if (HsqlDatabaseProperties.hsqldb_digest.equals(name)) {
            return database.granteeManager.getDigestAlgo();
        }

        if (HsqlDatabaseProperties.sql_restrict_exec.equals(name)) {
            return String.valueOf(database.sqlRestrictExec);
        }

        if (HsqlDatabaseProperties.sql_avg_scale.equals(name)) {
            return String.valueOf(database.sqlAvgScale);
        }

        if (HsqlDatabaseProperties.sql_char_literal.equals(name)) {
            return String.valueOf(database.sqlCharLiteral);
        }

        if (HsqlDatabaseProperties.sql_concat_nulls.equals(name)) {
            return String.valueOf(database.sqlConcatNulls);
        }

        if (HsqlDatabaseProperties.sql_convert_trunc.equals(name)) {
            return String.valueOf(database.sqlConvertTruncate);
        }

        if (HsqlDatabaseProperties.sql_double_nan.equals(name)) {
            return String.valueOf(database.sqlDoubleNaN);
        }

        if (HsqlDatabaseProperties.sql_enforce_names.equals(name)) {
            return String.valueOf(database.sqlEnforceNames);
        }

        if (HsqlDatabaseProperties.sql_enforce_refs.equals(name)) {
            return String.valueOf(database.sqlEnforceRefs);
        }

        if (HsqlDatabaseProperties.sql_enforce_size.equals(name)) {
            return String.valueOf(database.sqlEnforceSize);
        }

        if (HsqlDatabaseProperties.sql_enforce_tdcd.equals(name)) {
            return String.valueOf(database.sqlEnforceTDCD);
        }

        if (HsqlDatabaseProperties.sql_enforce_tdcu.equals(name)) {
            return String.valueOf(database.sqlEnforceTDCU);
        }

        if (HsqlDatabaseProperties.sql_enforce_types.equals(name)) {
            return String.valueOf(database.sqlEnforceTypes);
        }

        if (HsqlDatabaseProperties.sql_ignore_case.equals(name)) {
            return String.valueOf(database.sqlIgnoreCase);
        }

        if (HsqlDatabaseProperties.sql_longvar_is_lob.equals(name)) {
            return String.valueOf(database.sqlLongvarIsLob);
        }

        if (HsqlDatabaseProperties.sql_nulls_first.equals(name)) {
            return String.valueOf(database.sqlNullsFirst);
        }

        if (HsqlDatabaseProperties.sql_nulls_order.equals(name)) {
            return String.valueOf(database.sqlNullsOrder);
        }

        if (HsqlDatabaseProperties.sql_syntax_db2.equals(name)) {
            return String.valueOf(database.sqlSyntaxDb2);
        }

        if (HsqlDatabaseProperties.sql_syntax_mss.equals(name)) {
            return String.valueOf(database.sqlSyntaxMss);
        }

        if (HsqlDatabaseProperties.sql_syntax_mys.equals(name)) {
            return String.valueOf(database.sqlSyntaxMys);
        }

        if (HsqlDatabaseProperties.sql_syntax_ora.equals(name)) {
            return String.valueOf(database.sqlSyntaxOra);
        }

        if (HsqlDatabaseProperties.sql_syntax_pgs.equals(name)) {
            return String.valueOf(database.sqlSyntaxPgs);
        }

        if (HsqlDatabaseProperties.sql_ref_integrity.equals(name)) {
            return String.valueOf(database.isReferentialIntegrity());
        }

        if (HsqlDatabaseProperties.sql_regular_names.equals(name)) {
            return String.valueOf(database.sqlRegularNames);
        }

        if (HsqlDatabaseProperties.sql_unique_nulls.equals(name)) {
            return String.valueOf(database.sqlUniqueNulls);
        }

        if (HsqlDatabaseProperties.sql_live_object.equals(name)) {
            return String.valueOf(database.sqlLiveObject);
        }

        if (HsqlDatabaseProperties.jdbc_translate_tti_types.equals(name)) {
            return String.valueOf(database.sqlTranslateTTI);
        }

        if (HsqlDatabaseProperties.hsqldb_min_reuse.equals(name)) {
            return String.valueOf(this.propMinReuse);
        }

        if (HsqlDatabaseProperties.sql_sys_index_names.equals(name)) {
            return String.valueOf(database.sqlSysIndexNames);
        }

/*
        if (HsqlDatabaseProperties.textdb_all_quoted.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_allow_full_path.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_encoding.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_ignore_first.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_quoted.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_fs.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_vs.equals(name)) {
            return null;
        }

        if (HsqlDatabaseProperties.textdb_lvs.equals(name)) {
            return null;
        }
*/
        return null;
    }

    public String[] getPropertiesSQL(boolean indexRoots) {

        HsqlArrayList list = new HsqlArrayList();
        StringBuffer  sb   = new StringBuffer();

        sb.append("SET DATABASE ").append(Tokens.T_UNIQUE).append(' ');
        sb.append(Tokens.T_NAME).append(' ').append(database.getNameString());
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_GC).append(' ');
        sb.append(propGC);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_DEFAULT).append(' ');
        sb.append(Tokens.T_RESULT).append(' ').append(Tokens.T_MEMORY);
        sb.append(' ').append(Tokens.T_ROWS).append(' ');
        sb.append(database.getResultMaxMemoryRows());
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_EVENT).append(' ');
        sb.append(Tokens.T_LOG).append(' ').append(Tokens.T_LEVEL);
        sb.append(' ').append(propEventLogLevel);
        list.add(sb.toString());
        sb.setLength(0);

        if (propSqlLogLevel != SimpleLog.LOG_NONE) {
            sb.append("SET DATABASE ").append(Tokens.T_EVENT).append(' ');
            sb.append(Tokens.T_LOG).append(' ').append(Tokens.T_SQL);
            sb.append(' ').append(Tokens.T_LEVEL);
            sb.append(' ').append(propEventLogLevel);
            list.add(sb.toString());
            sb.setLength(0);
        }

        sb.append("SET DATABASE ").append(Tokens.T_TRANSACTION);
        sb.append(' ').append(Tokens.T_CONTROL).append(' ');

        switch (database.txManager.getTransactionControl()) {

            case TransactionManager.MVCC :
                sb.append(Tokens.T_MVCC);
                break;

            case TransactionManager.MVLOCKS :
                sb.append(Tokens.T_MVLOCKS);
                break;

            case TransactionManager.LOCKS :
                sb.append(Tokens.T_LOCKS);
                break;
        }

        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_DEFAULT).append(' ');
        sb.append(Tokens.T_ISOLATION).append(' ').append(Tokens.T_LEVEL);
        sb.append(' ');

        switch (database.defaultIsolationLevel) {

            case SessionInterface.TX_READ_COMMITTED :
                sb.append(Tokens.T_READ).append(' ').append(
                    Tokens.T_COMMITTED);
                break;

            case SessionInterface.TX_SERIALIZABLE :
                sb.append(Tokens.T_SERIALIZABLE);
                break;
        }

        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_TRANSACTION);
        sb.append(' ').append(Tokens.T_ROLLBACK).append(' ');
        sb.append(Tokens.T_ON).append(' ');
        sb.append(Tokens.T_CONFLICT).append(' ');
        sb.append(database.txConflictRollback ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_TEXT).append(' ');
        sb.append(Tokens.T_TABLE).append(' ').append(Tokens.T_DEFAULTS);
        sb.append(' ').append('\'');
        sb.append(propTextSourceDefault).append('\'');
        list.add(sb.toString());
        sb.setLength(0);

        String temp = database.getProperties().getStringPropertyDefault(
            HsqlDatabaseProperties.hsqldb_digest);

        if (!temp.equals(database.granteeManager.getDigestAlgo())) {
            sb.append("SET DATABASE ").append(' ').append(Tokens.T_PASSWORD);
            sb.append(' ').append(Tokens.T_DIGEST).append(' ').append('\'');
            sb.append(database.granteeManager.getDigestAlgo()).append('\'');
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.schemaManager.getDefaultTableType()
                == TableBase.CACHED_TABLE) {
            sb.append("SET DATABASE ").append(Tokens.T_DEFAULT).append(' ');
            sb.append(Tokens.T_TABLE).append(' ');
            sb.append(Tokens.T_TYPE).append(' ');
            sb.append(Tokens.T_CACHED);
            list.add(sb.toString());
            sb.setLength(0);
        }

        //
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_NAMES).append(' ');
        sb.append(database.sqlEnforceNames ? Tokens.T_TRUE
                                           : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);

        if (database.sqlRestrictExec) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_RESTRICT).append(' ');
            sb.append(Tokens.T_EXEC).append(' ');
            sb.append(database.sqlRestrictExec ? Tokens.T_TRUE
                                               : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (!database.sqlRegularNames) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_REGULAR).append(' ');
            sb.append(Tokens.T_NAMES).append(' ');
            sb.append(database.sqlRegularNames ? Tokens.T_TRUE
                                               : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_REFERENCES).append(' ');
        sb.append(database.sqlEnforceRefs ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_SIZE).append(' ');
        sb.append(database.sqlEnforceSize ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_TYPES).append(' ');
        sb.append(database.sqlEnforceTypes ? Tokens.T_TRUE
                                           : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_TDC).append(' ');
        sb.append(Tokens.T_DELETE).append(' ');
        sb.append(database.sqlEnforceTDCD ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_TDC).append(' ');
        sb.append(Tokens.T_UPDATE).append(' ');
        sb.append(database.sqlEnforceTDCU ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());

        if (!database.sqlTranslateTTI) {
            sb.setLength(0);
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_TRANSLATE).append(' ').append(Tokens.T_TTI);
            sb.append(' ').append(Tokens.T_TYPES).append(' ');
            sb.append(database.sqlTranslateTTI ? Tokens.T_TRUE
                                               : Tokens.T_FALSE);
            list.add(sb.toString());
        }

        if (database.sqlSysIndexNames) {
            sb.setLength(0);
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYS).append(' ').append(Tokens.T_INDEX);
            sb.append(' ').append(Tokens.T_NAMES).append(' ');
            sb.append(database.sqlSysIndexNames ? Tokens.T_TRUE
                                                : Tokens.T_FALSE);
            list.add(sb.toString());
        }

        if (!database.sqlCharLiteral) {
            sb.setLength(0);
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_CHARACTER).append(' ');
            sb.append(Tokens.T_LITERAL).append(' ');
            sb.append(database.sqlCharLiteral ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
            list.add(sb.toString());
        }

        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_CONCAT_WORD).append(' ');
        sb.append(Tokens.T_NULLS).append(' ');
        sb.append(database.sqlConcatNulls ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());

        if (!database.sqlNullsFirst) {
            sb.setLength(0);
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_NULLS).append(' ');
            sb.append(Tokens.T_FIRST).append(' ');
            sb.append(database.sqlNullsFirst ? Tokens.T_TRUE
                                             : Tokens.T_FALSE);
            list.add(sb.toString());
        }

        if (!database.sqlNullsOrder) {
            sb.setLength(0);
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_NULLS).append(' ');
            sb.append(Tokens.T_ORDER).append(' ');
            sb.append(database.sqlNullsOrder ? Tokens.T_TRUE
                                             : Tokens.T_FALSE);
            list.add(sb.toString());
        }

        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_UNIQUE).append(' ');
        sb.append(Tokens.T_NULLS).append(' ');
        sb.append(database.sqlUniqueNulls ? Tokens.T_TRUE
                                          : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_CONVERT).append(' ');
        sb.append(Tokens.T_TRUNCATE).append(' ');
        sb.append(database.sqlConvertTruncate ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_AVG).append(' ');
        sb.append(Tokens.T_SCALE).append(' ');
        sb.append(database.sqlAvgScale);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
        sb.append(Tokens.T_DOUBLE).append(' ');
        sb.append(Tokens.T_NAN).append(' ');
        sb.append(database.sqlDoubleNaN ? Tokens.T_TRUE
                                        : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);

        if (database.sqlLongvarIsLob) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_LONGVAR).append(' ');
            sb.append(Tokens.T_IS).append(' ');
            sb.append(Tokens.T_LOB).append(' ');
            sb.append(database.sqlLongvarIsLob ? Tokens.T_TRUE
                                               : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlIgnoreCase) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_IGNORECASE).append(' ');
            sb.append(database.sqlIgnoreCase ? Tokens.T_TRUE
                                             : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlSyntaxDb2) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYNTAX).append(' ');
            sb.append(Tokens.T_DB2).append(' ');
            sb.append(database.sqlSyntaxDb2 ? Tokens.T_TRUE
                                            : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlSyntaxMss) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYNTAX).append(' ');
            sb.append(Tokens.T_MSS).append(' ');
            sb.append(database.sqlSyntaxMss ? Tokens.T_TRUE
                                            : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlSyntaxMys) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYNTAX).append(' ');
            sb.append(Tokens.T_MYS).append(' ');
            sb.append(database.sqlSyntaxMys ? Tokens.T_TRUE
                                            : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlSyntaxOra) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYNTAX).append(' ');
            sb.append(Tokens.T_ORA).append(' ');
            sb.append(database.sqlSyntaxOra ? Tokens.T_TRUE
                                            : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (database.sqlSyntaxPgs) {
            sb.append("SET DATABASE ").append(Tokens.T_SQL).append(' ');
            sb.append(Tokens.T_SYNTAX).append(' ');
            sb.append(Tokens.T_PGS).append(' ');
            sb.append(database.sqlSyntaxPgs ? Tokens.T_TRUE
                                            : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        //
        int     delay  = propWriteDelay;
        boolean millis = delay > 0 && delay < 1000;

        if (millis) {
            if (delay < 20) {
                delay = 20;
            }
        } else {
            delay /= 1000;
        }

        sb.append("SET FILES ").append(Tokens.T_WRITE).append(' ');
        sb.append(Tokens.T_DELAY).append(' ').append(delay);

        if (millis) {
            sb.append(' ').append(Tokens.T_MILLIS);
        }

        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_BACKUP);
        sb.append(' ').append(Tokens.T_INCREMENT).append(' ');
        sb.append(propIncrementBackup ? Tokens.T_TRUE
                                      : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_CACHE);
        sb.append(' ').append(Tokens.T_SIZE).append(' ');
        sb.append(propCacheMaxSize / 1024);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_CACHE);
        sb.append(' ').append(Tokens.T_ROWS).append(' ');
        sb.append(propCacheMaxRows);
        list.add(sb.toString());

        {
            int fileScale = propDataFileScale;

            if (!indexRoots && fileScale < 32) {
                fileScale = 32;
            }

            sb.setLength(0);
            sb.append("SET FILES ").append(Tokens.T_SCALE);
            sb.append(' ').append(fileScale);
            list.add(sb.toString());
            sb.setLength(0);
        }

        sb.append("SET FILES ").append(Tokens.T_LOB).append(' ').append(
            Tokens.T_SCALE);
        sb.append(' ').append(getLobFileScale());
        list.add(sb.toString());
        sb.setLength(0);

        if (propCompressLobs) {
            sb.append("SET FILES ").append(Tokens.T_LOB).append(' ').append(
                Tokens.T_COMPRESSED);
            sb.append(' ').append(propCompressLobs ? Tokens.T_TRUE
                                                   : Tokens.T_FALSE);
            list.add(sb.toString());
            sb.setLength(0);
        }

        sb.append("SET FILES ").append(Tokens.T_DEFRAG);
        sb.append(' ').append(propCacheDefragLimit);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_NIO);
        sb.append(' ').append(propNioDataFile ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_NIO).append(' ').append(
            Tokens.T_SIZE);
        sb.append(' ').append(propNioMaxSize / (1024 * 1024));
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_LOG).append(' ');
        sb.append(propLogData ? Tokens.T_TRUE
                              : Tokens.T_FALSE);
        list.add(sb.toString());
        sb.setLength(0);
        sb.append("SET FILES ").append(Tokens.T_LOG).append(' ');
        sb.append(Tokens.T_SIZE).append(' ').append(propLogSize);
        list.add(sb.toString());
        sb.setLength(0);

        if (propFileTimestamp != 0) {
            sb.append("SET FILES ").append(Tokens.T_CHECK).append(' ');
            sb.append(propFileTimestamp);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (propFileSpaceValue != 0) {
            sb.append("SET FILES ").append(Tokens.T_SPACE).append(' ');
            sb.append(propFileSpaceValue);
            list.add(sb.toString());
            sb.setLength(0);
        }

        String[] array = new String[list.size()];

        list.toArray(array);

        return array;
    }

    public void backup(String destPath, boolean script, boolean blocking,
                       boolean compressed, boolean files) {

        if (!backupState.compareAndSet(stateNormal, stateBackup)) {
            throw Error.error(ErrorCode.BACKUP_ERROR, "backup in progress");
        }

        if (blocking) {
            database.lobManager.lock();

            try {
                synchronized (this) {
                    backupInternal(destPath, script, blocking, compressed,
                                   files);
                }
            } finally {
                backupState.set(stateNormal);
                database.lobManager.unlock();
            }
        } else {
            try {
                backupInternal(destPath, script, blocking, compressed, files);
            } finally {
                backupState.set(stateNormal);
            }
        }
    }

    public SimpleDateFormat fileDateFormat =
        new SimpleDateFormat("yyyyMMdd'T'HHmmss");
    private static char runtimeFileDelim =
        System.getProperty("file.separator").charAt(0);
    DbBackup backup;

    void backupInternal(String destPath, boolean script, boolean blocking,
                        boolean compressed, boolean asFiles) {

        String scriptName = null;
        String dbPath     = database.getPath();
        /* If want to add db Id also, will need to pass either Database
         * instead of dbPath, or pass dbPath + Id from StatementCommand.
         */
        String instanceName = new File(dbPath).getName();
        char   lastChar     = destPath.charAt(destPath.length() - 1);
        boolean generateName = (lastChar == '/'
                                || lastChar == runtimeFileDelim);
        File archiveFile;

        if (asFiles) {
            if (!generateName) {
                throw Error.error(null, ErrorCode.UNSUPPORTED_FILENAME_SUFFIX,
                                  0, new String[] {
                    "", "/"
                });
            }

            destPath = getSecurePath(destPath, true, false);

            if (destPath == null) {
                throw Error.error(ErrorCode.BACKUP_ERROR,
                                  "access to directory denied");
            }

            archiveFile = new File(destPath);

            archiveFile.mkdirs();

            File[] files = FileUtil.getDatabaseMainFileList(destPath
                + instanceName);

            if (files == null || files.length != 0) {
                throw Error.error(ErrorCode.BACKUP_ERROR,
                                  "files exist in directory");
            }
        } else {
            String defaultSuffix = compressed ? ".tar.gz"
                                              : ".tar";

            if (generateName) {
                archiveFile =
                    (new File(destPath.substring(0, destPath.length() - 1),
                              instanceName + '-'
                              + fileDateFormat.format(new java.util.Date())
                              + defaultSuffix));
            } else {
                archiveFile = new File(destPath);
            }

            boolean nameImpliesCompress =
                archiveFile.getName().endsWith(".tar.gz")
                || archiveFile.getName().endsWith(".tgz");

            if ((!nameImpliesCompress)
                    && !archiveFile.getName().endsWith(".tar")) {
                throw Error.error(null, ErrorCode.UNSUPPORTED_FILENAME_SUFFIX,
                                  0, new String[] {
                    archiveFile.getName(), ".tar, .tar.gz, .tgz"
                });
            }

            if (compressed != nameImpliesCompress) {
                throw Error.error(null, ErrorCode.COMPRESSION_SUFFIX_MISMATCH,
                                  0, new Object[] {
                    Boolean.valueOf(compressed), archiveFile.getName()
                });
            }

            if (archiveFile.exists()) {
                throw Error.error(ErrorCode.BACKUP_ERROR,
                                  "file exists :" + archiveFile.getName());
            }
        }

        if (blocking) {
            log.checkpointClose();
        }

        try {
            logInfoEvent("Initiating backup of instance '" + instanceName
                         + "'");

            // By default, DbBackup will throw if archiveFile (or
            // corresponding work file) already exist.  That's just what we
            // want here.
            if (script) {
                String path = getTempDirectoryPath();

                if (path == null) {
                    return;
                }

                path = path + "/" + new File(database.getPath()).getName();
                scriptName = path + scriptFileExtension;

                ScriptWriterText dsw = new ScriptWriterText(database,
                    scriptName, true, true, true);

                dsw.writeAll();
                dsw.close();

                backup = new DbBackup(archiveFile, path, true);

                backup.write();
            } else {
                backup = new DbBackup(archiveFile, dbPath);

                backup.setAbortUponModify(false);

                if (!blocking) {
                    InputStreamWrapper isw;
                    File               file = null;

                    if (hasCache()) {
                        DataFileCache dataFileCache = getCache();
                        RAShadowFile shadowFile =
                            dataFileCache.getShadowFile();

                        if (shadowFile == null) {

                            // non-incremental backup - ignore .data file
                            backup.setFileIgnore(dataFileExtension);
                        } else {
                            file = new File(dataFileCache.dataFileName);
                            isw = new InputStreamWrapper(
                                new FileInputStream(file));

                            isw.setSizeLimit(
                                dataFileCache.fileStartFreePosition);
                            backup.setStream(dataFileExtension, isw);

                            InputStreamInterface isi =
                                shadowFile.getInputStream();

                            backup.setStream(backupFileExtension, isi);
                        }
                    }

                    // log
                    file = new File(log.getLogFileName());

                    long fileLength = file.length();

                    if (fileLength == 0) {
                        backup.setFileIgnore(logFileExtension);
                    } else {
                        isw = new InputStreamWrapper(
                            new FileInputStream(file));

                        isw.setSizeLimit(fileLength);
                        backup.setStream(logFileExtension, isw);
                    }
                }

                if (asFiles) {
                    backup.writeAsFiles();
                } else {
                    backup.write();
                }
            }

            logInfoEvent("Successfully backed up instance '" + instanceName
                         + "' to '" + destPath + "'");
        } catch (IOException ioe) {
            throw Error.error(ErrorCode.FILE_IO_ERROR, ioe.toString());
        } catch (TarMalformatException tme) {
            throw Error.error(ErrorCode.FILE_IO_ERROR, tme.toString());
        } finally {
            if (scriptName != null) {
                FileUtil.getFileUtil().delete(scriptName);
            }

            if (blocking) {
                log.checkpointReopen();
            }
        }
    }

    /**
     *  Returns a secure path or null for a user-defined path when
     *  hsqldb.allow_full_path is false. Returns the path otherwise.
     *
     */
    public String getSecurePath(String path, boolean allowFull,
                                boolean includeRes) {

        if (database.getType() == DatabaseType.DB_RES) {
            if (includeRes) {
                return path;
            } else {
                return null;
            }
        }

        if (database.getType() == DatabaseType.DB_MEM) {
            if (propTextAllowFullPath) {
                return path;
            } else {
                return null;
            }
        }

        // absolute paths
        if (path.startsWith("/") || path.startsWith("\\")
                || path.indexOf(":") > -1) {
            if (allowFull || propTextAllowFullPath) {
                return path;
            } else {
                return null;
            }
        }

        if (path.indexOf("..") > -1) {
            if (allowFull || propTextAllowFullPath) {

                // allow
            } else {
                return null;
            }
        }

        String fullPath =
            new File(new File(database.getPath()
                              + ".properties").getAbsolutePath()).getParent();

        if (fullPath != null) {
            path = fullPath + File.separator + path;
        }

        return path;
    }

    public boolean isNewDatabase() {
        return isNewDatabase;
    }
}
