/* Copyright (c) 2001-2024, 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;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.FilteredIterator;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.Iterator;
import org.hsqldb.lib.List;
import org.hsqldb.lib.MultiValueHashMap;
import org.hsqldb.lib.OrderedHashMap;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.WrapperIterator;
import org.hsqldb.navigator.RowIterator;
import org.hsqldb.rights.Grantee;
import org.hsqldb.types.Charset;
import org.hsqldb.types.Collation;
import org.hsqldb.types.Type;

/**
 * Manages all SCHEMA related database objects
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.7.3
 * @since 1.8.0
 */
public class SchemaManager {

    Database   database;
    HsqlName   defaultSchemaHsqlName;
    int        defaultTableType = TableBase.MEMORY_TABLE;
    HsqlName[] catalogNameArray;
    long       schemaChangeTimestamp;
    Table      dualTable;

    //
    OrderedHashMap<String, Schema> schemaMap        = new OrderedHashMap<>();
    MultiValueHashMap<HsqlName, HsqlName> referenceMap =
        new MultiValueHashMap<>();
    UserSchemaFilter               userSchemaFilter = new UserSchemaFilter();

    //
    ReadWriteLock lock      = new ReentrantReadWriteLock();
    Lock          readLock  = lock.readLock();
    Lock          writeLock = lock.writeLock();

    //
    public SchemaManager(Database database) {

        this.database         = database;
        defaultSchemaHsqlName = SqlInvariants.INFORMATION_SCHEMA_HSQLNAME;
        catalogNameArray      = new HsqlName[]{ database.getCatalogName() };

        Schema schema = new Schema(
            SqlInvariants.INFORMATION_SCHEMA_HSQLNAME,
            SqlInvariants.INFORMATION_SCHEMA_HSQLNAME.owner);

        schemaMap.put(schema.getName().name, schema);

        try {
            schema.charsetLookup.add(Charset.SQL_TEXT, false);
            schema.charsetLookup.add(Charset.SQL_IDENTIFIER_CHARSET, false);
            schema.charsetLookup.add(Charset.SQL_CHARACTER, false);
            schema.collationLookup.add(Collation.getDefaultInstance(), false);
            schema.collationLookup.add(
                Collation.getDefaultIgnoreCaseInstance(),
                false);
            schema.typeLookup.add(TypeInvariants.CARDINAL_NUMBER, false);
            schema.typeLookup.add(TypeInvariants.YES_OR_NO, false);
            schema.typeLookup.add(TypeInvariants.CHARACTER_DATA, false);
            schema.typeLookup.add(TypeInvariants.SQL_IDENTIFIER, false);
            schema.typeLookup.add(TypeInvariants.TIME_STAMP, false);
            schema.typeLookup.add(TypeInvariants.NCNAME, false);
            schema.typeLookup.add(TypeInvariants.URI, false);
        } catch (HsqlException e) {}
    }

    public void setSchemaChangeTimestamp() {
        schemaChangeTimestamp = database.txManager.getSystemChangeNumber();
    }

    public long getSchemaChangeTimestamp() {
        return schemaChangeTimestamp;
    }

    // pre-defined
    public HsqlName getSQLJSchemaHsqlName() {
        return SqlInvariants.SQLJ_SCHEMA_HSQLNAME;
    }

    // SCHEMA management
    public void createPublicSchema() {

        writeLock.lock();

        try {
            HsqlName name = database.nameManager.newHsqlName(
                null,
                SqlInvariants.PUBLIC_SCHEMA,
                SchemaObject.SCHEMA);
            Schema schema = new Schema(
                name,
                database.getGranteeManager().getDBARole());

            defaultSchemaHsqlName = schema.getName();

            schemaMap.put(schema.getName().name, schema);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Creates a schema belonging to the given grantee.
     */
    public void createSchema(HsqlName name, Grantee owner) {

        writeLock.lock();

        try {
            SqlInvariants.checkSchemaNameNotSystem(name.name);

            Schema schema = new Schema(name, owner);

            schemaMap.add(name.name, schema);
        } finally {
            writeLock.unlock();
        }
    }

    public void dropSchema(Session session, String name, boolean cascade) {

        writeLock.lock();

        try {
            Schema schema = schemaMap.get(name);

            if (schema == null) {
                throw Error.error(ErrorCode.X_42501, name);
            }

            if (SqlInvariants.isLobsSchemaName(name)) {
                throw Error.error(ErrorCode.X_42503, name);
            }

            if (!cascade && !schema.isEmpty()) {
                throw Error.error(ErrorCode.X_2B000);
            }

            OrderedHashSet<HsqlName> externalReferences =
                new OrderedHashSet<>();

            getCascadingReferencesToSchema(
                schema.getName(),
                externalReferences);
            removeSchemaObjects(externalReferences);

            Iterator<SchemaObject> tableIterator = schema.schemaObjectIterator(
                SchemaObject.TABLE);

            while (tableIterator.hasNext()) {
                Table        table = ((Table) tableIterator.next());
                Constraint[] list  = table.getFKConstraints();

                for (int i = 0; i < list.length; i++) {
                    Constraint constraint = list[i];

                    if (constraint.getMain().getSchemaName()
                            != schema.getName()) {
                        constraint.getMain()
                                  .removeConstraint(
                                      constraint.getMainName().name);
                        removeReferencesFrom(constraint);
                    }
                }

                removeTable(session, table);
            }

            Iterator<SchemaObject> sequenceIterator =
                schema.schemaObjectIterator(
                    SchemaObject.SEQUENCE);

            while (sequenceIterator.hasNext()) {
                NumberSequence sequence =
                    ((NumberSequence) sequenceIterator.next());

                database.getGranteeManager().removeDbObject(sequence.getName());
            }

            schema.release();
            schemaMap.remove(name);

            if (defaultSchemaHsqlName.name.equals(name)) {
                schema = new Schema(
                    defaultSchemaHsqlName,
                    database.getGranteeManager().getDBARole());
                defaultSchemaHsqlName = schema.getName();

                schemaMap.put(schema.getName().name, schema);
            } else {
                HsqlName schemaName = schema.getName();

                // these are called last and in this particular order
                database.getUserManager().removeSchemaReference(schemaName);
                database.getSessionManager().removeSchemaReference(schemaName);
            }
        } finally {
            writeLock.unlock();
        }
    }

    public void renameSchema(HsqlName name, HsqlName newName) {

        writeLock.lock();

        try {
            Schema schema = schemaMap.get(name.name);
            Schema exists = schemaMap.get(newName.name);

            if (schema == null) {
                throw Error.error(ErrorCode.X_42501, name.name);
            }

            if (exists != null) {
                throw Error.error(ErrorCode.X_42504, newName.name);
            }

            SqlInvariants.checkSchemaNameNotSystem(name.name);
            SqlInvariants.checkSchemaNameNotSystem(newName.name);

            int index = schemaMap.getIndex(name.name);

            schema.getName().rename(newName);
            schemaMap.set(index, newName.name, schema);
        } finally {
            writeLock.unlock();
        }
    }

    public void release() {

        writeLock.lock();

        try {
            Iterator<Schema> it = schemaMap.values().iterator();

            while (it.hasNext()) {
                Schema schema = it.next();

                schema.release();
            }
        } finally {
            writeLock.unlock();
        }
    }

    public String[] getSchemaNamesArray() {

        readLock.lock();

        try {
            String[] array = new String[schemaMap.size()];

            schemaMap.keysToArray(array);

            return array;
        } finally {
            readLock.unlock();
        }
    }

    public Schema[] getAllSchemas() {

        readLock.lock();

        try {
            Schema[] objects = new Schema[schemaMap.size()];

            schemaMap.valuesToArray(objects);

            return objects;
        } finally {
            readLock.unlock();
        }
    }

    public Iterator<Schema> getUserSchemaIterator() {
        return new FilteredIterator<>(
            schemaMap.values().iterator(),
            userSchemaFilter);
    }

    public HsqlName getUserSchemaHsqlName(String name) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(name);

            if (schema == null) {
                throw Error.error(ErrorCode.X_3F000, name);
            }

            if (schema.getName() == SqlInvariants.INFORMATION_SCHEMA_HSQLNAME) {
                throw Error.error(ErrorCode.X_3F000, name);
            }

            return schema.getName();
        } finally {
            readLock.unlock();
        }
    }

    public Grantee toSchemaOwner(String name) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(name);

            return schema == null
                   ? null
                   : schema.getOwner();
        } finally {
            readLock.unlock();
        }
    }

    public HsqlName getDefaultSchemaHsqlName() {
        return defaultSchemaHsqlName;
    }

    public void setDefaultSchemaHsqlName(HsqlName name) {
        defaultSchemaHsqlName = name;
    }

    public boolean schemaExists(String name) {

        readLock.lock();

        try {
            return schemaMap.containsKey(name);
        } finally {
            readLock.unlock();
        }
    }

    public HsqlName findSchemaHsqlName(String name) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(name);

            if (schema == null) {
                return null;
            }

            return schema.getName();
        } finally {
            readLock.unlock();
        }
    }

    /**
     * If schemaName is null, return the default schema name, else return
     * the HsqlName object for the schema. If schemaName does not exist,
     * throw.
     */
    public HsqlName getSchemaHsqlName(String name) {

        if (name == null) {
            return defaultSchemaHsqlName;
        }

        HsqlName schemaName = findSchemaHsqlName(name);

        if (schemaName == null) {
            throw Error.error(ErrorCode.X_3F000, name);
        }

        return schemaName;
    }

    /**
     * Same as above, but return string
     */
    public String getSchemaName(String name) {
        return getSchemaHsqlName(name).name;
    }

    public Schema findSchema(String name) {

        readLock.lock();

        try {
            return schemaMap.get(name);
        } finally {
            readLock.unlock();
        }
    }

    /**
     * drop all schemas with the given authorisation
     */
    public void dropSchemas(Session session, Grantee grantee, boolean cascade) {

        writeLock.lock();

        try {
            HsqlArrayList<Schema> list = getSchemas(grantee);
            Iterator<Schema>      it   = list.iterator();

            while (it.hasNext()) {
                Schema schema = it.next();

                dropSchema(session, schema.getName().name, cascade);
            }
        } finally {
            writeLock.unlock();
        }
    }

    public HsqlArrayList<Schema> getSchemas(Grantee grantee) {

        readLock.lock();

        try {
            HsqlArrayList<Schema> list = new HsqlArrayList<>();
            Iterator<Schema>      it   = schemaMap.values().iterator();

            while (it.hasNext()) {
                Schema schema = it.next();

                if (grantee.equals(schema.getOwner())) {
                    list.add(schema);
                }
            }

            return list;
        } finally {
            readLock.unlock();
        }
    }

    public boolean hasSchemas(Grantee grantee) {

        readLock.lock();

        try {
            Iterator<Schema> it = schemaMap.values().iterator();

            while (it.hasNext()) {
                Schema schema = it.next();

                if (grantee.equals(schema.getOwner())) {
                    return true;
                }
            }

            return false;
        } finally {
            readLock.unlock();
        }
    }

    /**
     *  Returns an HsqlArrayList containing references to all non-system
     *  tables and views of the given type.
     */
    public HsqlArrayList<Table> getAllTables(boolean withLobTables) {

        readLock.lock();

        try {
            HsqlArrayList<Table> allTables = new HsqlArrayList<>();
            String[]             schemas   = getSchemaNamesArray();

            for (int i = 0; i < schemas.length; i++) {
                String name = schemas[i];

                if (!withLobTables && SqlInvariants.isLobsSchemaName(name)) {
                    continue;
                }

                if (SqlInvariants.isSystemSchemaName(name)) {
                    continue;
                }

                OrderedHashMap<String, Table> current = getTables(name);

                allTables.addAll(current.values());
            }

            return allTables;
        } finally {
            readLock.unlock();
        }
    }

    public OrderedHashMap<String, Table> getTables(String schema) {

        readLock.lock();

        try {
            Schema temp = schemaMap.get(schema);

            return temp.tableList;
        } finally {
            readLock.unlock();
        }
    }

    public HsqlName[] getCatalogNameArray() {
        return catalogNameArray;
    }

    public HsqlName[] getCatalogAndBaseTableNames() {

        readLock.lock();

        try {
            OrderedHashSet<HsqlName> names  = new OrderedHashSet<>();
            HsqlArrayList<Table>     tables = getAllTables(false);

            for (int i = 0; i < tables.size(); i++) {
                Table table = tables.get(i);

                if (!table.isTemp()) {
                    names.add(table.getName());
                }
            }

            names.add(database.getCatalogName());

            HsqlName[] array = new HsqlName[names.size()];

            names.toArray(array);

            return array;
        } finally {
            readLock.unlock();
        }
    }

    public HsqlName[] getCatalogAndBaseTableNames(HsqlName name) {

        if (name == null) {
            return catalogNameArray;
        }

        readLock.lock();

        try {
            switch (name.type) {

                case SchemaObject.SCHEMA : {
                    if (findSchemaHsqlName(name.name) == null) {
                        return catalogNameArray;
                    }

                    OrderedHashSet<HsqlName> names = new OrderedHashSet<>();

                    names.add(database.getCatalogName());

                    OrderedHashMap<String, Table> list = getTables(name.name);

                    for (int i = 0; i < list.size(); i++) {
                        names.add(list.get(i).getName());
                    }

                    HsqlName[] array = new HsqlName[names.size()];

                    names.toArray(array);

                    return array;
                }

                case SchemaObject.GRANTEE : {
                    return catalogNameArray;
                }

                case SchemaObject.INDEX :
                case SchemaObject.CONSTRAINT :
                default :
            }

            SchemaObject object = findSchemaObject(
                name.name,
                name.schema.name,
                name.type);

            if (object == null) {
                return catalogNameArray;
            }

            HsqlName                 parent = object.getName().parent;
            OrderedHashSet<HsqlName> references = getReferencesTo(
                object.getName());
            OrderedHashSet<HsqlName> names  = new OrderedHashSet<>();

            names.add(database.getCatalogName());

            if (parent != null) {
                SchemaObject parentObject = findSchemaObject(
                    parent.name,
                    parent.schema.name,
                    parent.type);

                if (parentObject != null
                        && parentObject.getName().type == SchemaObject.TABLE) {
                    names.add(parentObject.getName());
                }
            }

            if (object.getName().type == SchemaObject.TABLE) {
                names.add(object.getName());
            }

            for (int i = 0; i < references.size(); i++) {
                HsqlName reference = references.get(i);

                if (reference.type == SchemaObject.TABLE) {
                    Table table = findUserTable(
                        reference.name,
                        reference.schema.name);

                    if (table != null && !table.isTemp()) {
                        names.add(reference);
                    }
                }
            }

            HsqlName[] array = new HsqlName[names.size()];

            names.toArray(array);

            return array;
        } finally {
            readLock.unlock();
        }
    }

    public void checkSchemaObjectNotExists(HsqlName name) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(name.schema.name);

            schema.checkObjectNotExists(name);
        } finally {
            readLock.unlock();
        }
    }

    public Table getUserTable(HsqlName name) {
        return getUserTable(name.name, name.schema.name);
    }

    /**
     *  Returns the specified user-defined table or view visible within the
     *  context of the specified Session.
     *  Throws if the table does not exist in the context.
     */
    public Table getUserTable(String name, String schema) {

        Table t = findUserTable(name, schema);

        if (t == null) {
            String longName = schema == null
                              ? name
                              : schema + '.' + name;

            throw Error.error(ErrorCode.X_42501, longName);
        }

        return t;
    }

    /**
     *  Returns the specified user-defined table or view visible within the
     *  context of the specified schema. It excludes system tables.
     *  Returns null if the table does not exist in the context.
     */
    public Table findUserTable(String name, String schemaName) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema == null) {
                return null;
            }

            int i = schema.tableList.getIndex(name);

            if (i == -1) {
                return null;
            }

            return schema.tableList.get(i);
        } finally {
            readLock.unlock();
        }
    }

    /**
     *  Returns the specified session context table.
     *  Returns null if the table does not exist in the context.
     */
    public Table findSessionTable(Session session, String name) {
        return session.sessionContext.findSessionTable(name);
    }

    /**
     * Drops the specified user-defined view or table from this Database object.
     *
     * <p> The process of dropping a table or view includes:
     * <OL>
     * <LI> checking that the specified Session's currently connected User has
     * the right to perform this operation and refusing to proceed if not by
     * throwing.
     * <LI> checking for referential constraints that conflict with this
     * operation and refusing to proceed if they exist by throwing.</LI>
     * <LI> removing the specified Table from this Database object.
     * <LI> removing any exported foreign keys Constraint objects held by any
     * tables referenced by the table to be dropped. This is especially
     * important so that the dropped Table ceases to be referenced, eventually
     * allowing its full garbage collection.
     * <LI>
     * </OL>
     *
     * <p>
     *
     * @param session the connected context in which to perform this operation
     * @param table if true and if the Table to drop does not exist, fail
     *   silently, else throw
     * @param cascade true if the name argument refers to a View
     */
    public void dropTableOrView(Session session, Table table, boolean cascade) {

        writeLock.lock();

        try {
            if (table.isView()) {
                dropView(table, cascade);
            } else {
                dropTable(session, table, cascade);
            }
        } finally {
            writeLock.unlock();
        }
    }

    private void dropView(Table table, boolean cascade) {

        Schema schema = schemaMap.get(table.getSchemaName().name);

        removeSchemaObject(table.getName(), cascade);
        removeTableDependentReferences(table);
        schema.triggerLookup.removeParent(table.getName());
    }

    private void dropTable(Session session, Table table, boolean cascade) {

        Schema schema = schemaMap.get(table.getSchemaName().name);
        OrderedHashSet<Constraint> externalConstraints =
            table.getDependentExternalConstraints();
        OrderedHashSet<HsqlName> externalReferences = new OrderedHashSet<>();

        getCascadingReferencesTo(table.getName(), externalReferences);

        if (!cascade) {
            for (int i = 0; i < externalConstraints.size(); i++) {
                Constraint c       = externalConstraints.get(i);
                HsqlName   refName = c.getRefName();

                if (c.getConstraintType()
                        == SchemaObject.ConstraintTypes.MAIN) {
                    throw Error.error(
                        ErrorCode.X_42533,
                        refName.getSchemaQualifiedStatementName());
                }
            }

            if (!externalReferences.isEmpty()) {
                int i = 0;

                for (; i < externalReferences.size(); i++) {
                    HsqlName name = externalReferences.get(i);

                    if (name.parent == table.getName()) {
                        continue;
                    }

                    throw Error.error(
                        ErrorCode.X_42502,
                        name.getSchemaQualifiedStatementName());
                }
            }
        }

        OrderedHashSet<Table>    tableSet          = new OrderedHashSet<>();
        OrderedHashSet<HsqlName> constraintNameSet = new OrderedHashSet<>();
        OrderedHashSet<HsqlName> indexNameSet      = new OrderedHashSet<>();

        for (int i = 0; i < externalConstraints.size(); i++) {
            Constraint c = externalConstraints.get(i);
            Table      t = c.getMain();

            if (t != table) {
                tableSet.add(t);
            }

            t = c.getRef();

            if (t != table) {
                tableSet.add(t);
            }

            constraintNameSet.add(c.getMainName());
            constraintNameSet.add(c.getRefName());
            indexNameSet.add(c.getRefIndex().getName());
        }

        OrderedHashSet<HsqlName> uniqueConstraintNames =
            table.getUniquePKConstraintNames();
        TableWorks tw = new TableWorks(session, table);

        tableSet = tw.dropConstraintsAndIndexes(
            tableSet,
            constraintNameSet,
            indexNameSet);

        tw.setNewTablesInSchema(tableSet);
        tw.updateConstraints(tableSet, constraintNameSet);
        removeSchemaObjects(externalReferences);
        removeTableDependentReferences(table);
        removeReferencesTo(uniqueConstraintNames);
        removeReferencesTo(table.getName());
        removeReferencesFrom(table);
        schema.tableList.remove(table.getName().name);
        schema.indexLookup.removeParent(table.getName());
        schema.constraintLookup.removeParent(table.getName());
        schema.triggerLookup.removeParent(table.getName());
        removeTable(session, table);
        recompileDependentObjects(tableSet);
    }

    private void removeTable(Session session, Table table) {

        database.getGranteeManager().removeDbObject(table.getName());
        table.releaseTriggers();

        if (!table.isView() && table.hasLobColumn()) {
            RowIterator it = table.rowIterator(session);

            while (it.next()) {
                Object[] data = it.getCurrent();

                session.sessionData.adjustLobUsageCount(table, data, -1);
            }
        }

        if (table.isTemp) {
            Session[] sessions = database.sessionManager.getAllSessions();

            for (int i = 0; i < sessions.length; i++) {
                sessions[i].sessionData.persistentStoreCollection.removeStore(
                    table);
            }
        } else {
            database.persistentStoreCollection.removeStore(table);
        }
    }

    public void setTable(int index, Table table) {

        writeLock.lock();

        try {
            Schema schema = schemaMap.get(table.getSchemaName().name);

            schema.tableList.set(index, table.getName().name, table);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     *  Returns index of a table or view in the HashMappedList that
     *  contains the table objects for this Database.
     *
     * @param  table the Table object
     * @return  the index of the specified table or view, or -1 if not found
     */
    public int getTableIndex(Table table) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(table.getSchemaName().name);

            if (schema == null) {
                return -1;
            }

            HsqlName name = table.getName();

            return schema.tableList.getIndex(name.name);
        } finally {
            readLock.unlock();
        }
    }

    public void recompileDependentObjects(OrderedHashSet<Table> tableSet) {

        writeLock.lock();

        try {
            OrderedHashSet<HsqlName> set = new OrderedHashSet<>();

            for (int i = 0; i < tableSet.size(); i++) {
                Table table = tableSet.get(i);

                set.addAll(getReferencesTo(table.getName()));
            }

            Session session = database.sessionManager.getSysSession();

            for (int i = 0; i < set.size(); i++) {
                HsqlName name = set.get(i);

                switch (name.type) {

                    case SchemaObject.ASSERTION :
                    case SchemaObject.CONSTRAINT :
                    case SchemaObject.FUNCTION :
                    case SchemaObject.PROCEDURE :
                    case SchemaObject.ROUTINE :
                    case SchemaObject.SPECIFIC_ROUTINE :
                    case SchemaObject.TRIGGER :
                    case SchemaObject.VIEW :
                        SchemaObject object = findSchemaObject(name);

                        object.compile(session, null);
                        break;

                    default :
                }
            }

            if (Error.TRACE) {
                HsqlArrayList<Table> list = getAllTables(false);

                for (int i = 0; i < list.size(); i++) {
                    Table t = list.get(i);

                    t.verifyConstraintsIntegrity();
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * After addition or removal of columns and indexes all views that
     * reference the table should be recompiled.
     */
    public void recompileDependentObjects(Table table) {

        writeLock.lock();

        try {
            OrderedHashSet<HsqlName> set = new OrderedHashSet<>();

            getCascadingReferencesTo(table.getName(), set);

            Session session = database.sessionManager.getSysSession();

            for (int i = 0; i < set.size(); i++) {
                HsqlName name = set.get(i);

                switch (name.type) {

                    case SchemaObject.ASSERTION :
                    case SchemaObject.CONSTRAINT :
                    case SchemaObject.FUNCTION :
                    case SchemaObject.PROCEDURE :
                    case SchemaObject.ROUTINE :
                    case SchemaObject.SPECIFIC_ROUTINE :
                    case SchemaObject.TRIGGER :
                    case SchemaObject.VIEW : {
                        SchemaObject object = findSchemaObject(name);

                        object.compile(session, null);
                        break;
                    }

                    default :
                }
            }

            if (Error.TRACE) {
                HsqlArrayList<Table> list = getAllTables(false);

                for (int i = 0; i < list.size(); i++) {
                    Table t = list.get(i);

                    t.verifyConstraintsIntegrity();
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public Collation getCollation(
            Session session,
            String name,
            String schemaName) {

        Collation collation = null;

        if (schemaName == null
                || SqlInvariants.INFORMATION_SCHEMA.equals(schemaName)) {
            try {
                collation = Collation.getCollation(name);
            } catch (HsqlException e) {}
        }

        if (collation == null) {
            schemaName = session.getSchemaName(schemaName);
            collation = (Collation) getSchemaObject(
                name,
                schemaName,
                SchemaObject.COLLATION);
        }

        return collation;
    }

    public NumberSequence findSequence(
            Session session,
            String name,
            String schemaName) {

        NumberSequence seq = getSequence(
            name,
            session.getSchemaName(schemaName),
            false);

        if (seq == null && schemaName == null) {
            schemaName = session.getSchemaName(null);

            ReferenceObject ref = findSynonym(
                name,
                schemaName,
                SchemaObject.SEQUENCE);

            if (ref != null) {
                seq = getSequence(
                    ref.target.name,
                    ref.target.schema.name,
                    false);
            }
        }

        return seq;
    }

    public NumberSequence getSequence(
            String name,
            String schemaName,
            boolean raise) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema != null) {
                NumberSequence object = schema.sequenceList.get(name);

                if (object != null) {
                    return object;
                }
            }

            if (raise) {
                throw Error.error(ErrorCode.X_42501, name);
            }

            return null;
        } finally {
            readLock.unlock();
        }
    }

    public Type getUserDefinedType(
            String name,
            String schemaName,
            boolean raise) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema != null) {
                SchemaObject object = schema.typeLookup.getObject(name);

                if (object != null) {
                    return (Type) object;
                }
            }

            if (raise) {
                throw Error.error(ErrorCode.X_42501, name);
            }

            return null;
        } finally {
            readLock.unlock();
        }
    }

    public Type findDomainOrUDT(
            Session session,
            String name,
            String prefix,
            String prePrefix,
            String prePrePrefix) {

        readLock.lock();

        try {
            Type type = (Type) findSchemaObject(
                session,
                name,
                prefix,
                prePrefix,
                SchemaObject.TYPE);

            return type;
        } finally {
            readLock.unlock();
        }
    }

    public Type getDomain(String name, String schemaName, boolean raise) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema != null) {
                SchemaObject object = schema.typeLookup.getObject(name);

                if (object != null && ((Type) object).isDomainType()) {
                    return (Type) object;
                }
            }

            if (raise) {
                throw Error.error(ErrorCode.X_42501, name);
            }

            return null;
        } finally {
            readLock.unlock();
        }
    }

    public Type getDistinctType(String name, String schemaName, boolean raise) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema != null) {
                SchemaObject object = schema.typeLookup.getObject(name);

                if (object != null && ((Type) object).isDistinctType()) {
                    return (Type) object;
                }
            }

            if (raise) {
                throw Error.error(ErrorCode.X_42501, name);
            }

            return null;
        } finally {
            readLock.unlock();
        }
    }

    public SchemaObject getSchemaObject(
            String name,
            String schemaName,
            int type) {

        readLock.lock();

        try {
            SchemaObject object = findSchemaObject(name, schemaName, type);

            if (object == null) {
                throw Error.error(SchemaObjectSet.getErrorCode, name);
            }

            return object;
        } finally {
            readLock.unlock();
        }
    }

    public SchemaObject getCharacterSet(
            Session session,
            String name,
            String schemaName) {

        if (schemaName == null
                || SqlInvariants.INFORMATION_SCHEMA.equals(schemaName)) {
            if (name.equals("SQL_IDENTIFIER")) {
                return Charset.SQL_IDENTIFIER_CHARSET;
            }

            if (name.equals("SQL_TEXT")) {
                return Charset.SQL_TEXT;
            }

            if (name.equals("LATIN1")) {
                return Charset.LATIN1;
            }

            if (name.equals("ASCII_GRAPHIC")) {
                return Charset.ASCII_GRAPHIC;
            }
        }

        if (schemaName == null) {
            schemaName = session.getSchemaName(null);
        }

        return getSchemaObject(name, schemaName, SchemaObject.CHARSET);
    }

    public Table findTable(
            Session session,
            String name,
            String prefix,
            String prePrefix) {

        return (Table) findSchemaObject(
            session,
            name,
            prefix,
            prePrefix,
            SchemaObject.TABLE);
    }

    public SchemaObject findSchemaObject(
            Session session,
            String name,
            String prefix,
            String prePrefix,
            int type) {

        // catalog resolution here
        if (prePrefix != null
                && !prePrefix.equals(database.getCatalogName().name)) {
            return null;
        }

        if (type == SchemaObject.TABLE) {
            if (prefix == null) {
                if (session.database.sqlSyntaxOra
                        || session.database.sqlSyntaxDb2
                        || session.isProcessingScript()) {
                    if (Tokens.T_DUAL.equals(name)) {
                        return dualTable;
                    }
                }

                // in future there will be a default module for
                // session tables and variables and anonymous
                // procedural sql blocks, which can eliminate this code
                Table t = findSessionTable(session, name);

                if (t != null) {
                    return t;
                }
            } else if (prePrefix == null) {

                // allow parsing in-routine table names in older .script files
                if (Tokens.T_SESSION.equals(prefix)
                        || Tokens.T_MODULE.equals(prefix)) {
                    Table t = findSessionTable(session, name);

                    if (t != null) {
                        return t;
                    }
                }
            }
        }

        if (prefix == null) {
            prefix = session.getSchemaName(null);
        }

        if (type == SchemaObject.TABLE
                && SqlInvariants.INFORMATION_SCHEMA.equals(prefix)
                && database.dbInfo != null) {
            Table t = database.dbInfo.getSystemTable(session, name);

            if (t != null) {
                return t;
            }
        }

        return findSchemaObject(name, prefix, type);
    }

    public ReferenceObject findSynonym(
            String name,
            String schemaName,
            int type) {

        Schema schema = schemaMap.get(schemaName);

        if (schema == null) {
            return null;
        }

        ReferenceObject reference = schema.findReference(name, type);

        return reference;
    }

    public SchemaObject findAnySchemaObjectForSynonym(
            String name,
            String schemaName) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema == null) {
                return null;
            }

            return schema.findAnySchemaObjectForSynonym(name);
        } finally {
            readLock.unlock();
        }
    }

    public SchemaObject findSchemaObject(
            String name,
            String schemaName,
            int type) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            if (schema == null) {
                return null;
            }

            if (type == SchemaObject.SCHEMA) {
                return schema;
            }

            return schema.findSchemaObject(name, type);
        } finally {
            readLock.unlock();
        }
    }

    // INDEX management

    /**
     * Returns the table that has an index with the given name and schema.
     */
    Table findUserTableForIndex(
            Session session,
            String name,
            String schemaName) {

        readLock.lock();

        try {
            Schema   schema    = schemaMap.get(schemaName);
            HsqlName indexName = schema.indexLookup.getName(name);

            if (indexName == null) {
                return null;
            }

            return findUserTable(indexName.parent.name, schemaName);
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Drops the index with the specified name.
     */
    void dropIndex(Session session, HsqlName name) {

        writeLock.lock();

        try {
            Table      t  = getUserTable(name.parent);
            TableWorks tw = new TableWorks(session, t);

            tw.dropIndex(name.name);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Drops the constraint with the specified name.
     */
    void dropConstraint(Session session, HsqlName name, boolean cascade) {

        writeLock.lock();

        try {
            Table      t  = getUserTable(name.parent);
            TableWorks tw = new TableWorks(session, t);

            tw.dropConstraint(name.name, cascade);
        } finally {
            writeLock.unlock();
        }
    }

    void removeDependentObjects(HsqlName name) {

        writeLock.lock();

        try {
            Schema schema = schemaMap.get(name.schema.name);

            schema.indexLookup.removeParent(name);
            schema.constraintLookup.removeParent(name);
            schema.triggerLookup.removeParent(name);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     *  Removes any foreign key Constraint objects (exported keys) held by any
     *  tables referenced by the specified table. <p>
     *
     *  This method is called as the last step of a successful call to
     *  dropTable() in order to ensure that the dropped Table ceases to be
     *  referenced when enforcing referential integrity.
     *
     * @param  toDrop The table to which other tables may be holding keys.
     *      This is a table that is in the process of being dropped.
     */
    void removeExportedKeys(Table toDrop) {

        writeLock.lock();

        try {

            // toDrop.schema may be null because it is not registered
            Schema schema = schemaMap.get(toDrop.getSchemaName().name);

            for (int i = 0; i < schema.tableList.size(); i++) {
                Table        table       = schema.tableList.get(i);
                Constraint[] constraints = table.getConstraints();

                for (int j = constraints.length - 1; j >= 0; j--) {
                    Table refTable = constraints[j].getRef();

                    if (toDrop == refTable) {
                        table.removeConstraint(j);
                    }
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public Iterator<SchemaObject> databaseObjectIterator(
            String schemaName,
            int type) {

        readLock.lock();

        try {
            Schema schema = schemaMap.get(schemaName);

            return schema.schemaObjectIterator(type);
        } finally {
            readLock.unlock();
        }
    }

    public Iterator<SchemaObject> databaseObjectIterator(int type) {

        readLock.lock();

        try {
            Iterator<Schema>       schemas   = schemaMap.values().iterator();
            Iterator<SchemaObject> dbObjects = new WrapperIterator<>();

            while (schemas.hasNext()) {
                Schema schema = schemas.next();
                Iterator<SchemaObject> iterator = schema.schemaObjectIterator(
                    type);

                if (iterator.hasNext()) {
                    dbObjects = new WrapperIterator<>(dbObjects, iterator);
                }
            }

            return dbObjects;
        } finally {
            readLock.unlock();
        }
    }

    public Iterator<Table> databaseTableIterator() {

        readLock.lock();

        try {
            Iterator<Schema> schemas = schemaMap.values().iterator();
            Iterator<Table>  tables  = new WrapperIterator<>();

            while (schemas.hasNext()) {
                Schema          schema   = schemas.next();
                Iterator<Table> iterator = schema.tableList.values().iterator();

                if (iterator.hasNext()) {
                    tables = new WrapperIterator<>(tables, iterator);
                }
            }

            return tables;
        } finally {
            readLock.unlock();
        }
    }

    public Iterator<Constraint> databaseCheckConstraintIterator() {

        return new Iterator<Constraint>() {

            Iterator<SchemaObject> constraints = databaseObjectIterator(
                SchemaObject.CONSTRAINT);
            Constraint current;
            boolean    b = filterToNext();
            public boolean hasNext() {
                return current != null;
            }
            public Constraint next() {

                Constraint value = current;

                filterToNext();

                return value;
            }
            private boolean filterToNext() {

                while (constraints.hasNext()) {
                    current = (Constraint) constraints.next();

                    if (current.constType
                            == SchemaObject.ConstraintTypes.CHECK) {
                        return true;
                    }
                }

                current = null;

                return false;
            }
        };
    }

    // references
    private void addReferencesFrom(SchemaObject object) {

        OrderedHashSet<HsqlName> set  = object.getReferences();
        HsqlName                 name = object.getName();

        if (object instanceof Routine) {
            name = ((Routine) object).getSpecificName();
        }

        if (set == null) {
            return;
        }

        for (int i = 0; i < set.size(); i++) {
            HsqlName referenced = set.get(i);

            referenceMap.put(referenced, name);
        }
    }

    private void removeReferencesTo(OrderedHashSet<HsqlName> set) {

        for (int i = 0; i < set.size(); i++) {
            HsqlName referenced = set.get(i);

            referenceMap.remove(referenced);
        }
    }

    private void removeReferencesTo(HsqlName referenced) {
        referenceMap.remove(referenced);
    }

    private void removeReferencesFrom(SchemaObject object) {

        HsqlName                 name = object.getName();
        OrderedHashSet<HsqlName> set  = object.getReferences();

        if (object instanceof Routine) {
            name = ((Routine) object).getSpecificName();
        }

        if (set == null) {
            return;
        }

        for (int i = 0; i < set.size(); i++) {
            HsqlName referenced = set.get(i);

            referenceMap.remove(referenced, name);
        }
    }

    private void removeTableDependentReferences(Table table) {

        OrderedHashSet<HsqlName> mainSet = table.getReferencesForDependents();

        for (int i = 0; i < mainSet.size(); i++) {
            HsqlName     name = mainSet.get(i);
            SchemaObject object;

            switch (name.type) {

                case SchemaObject.CONSTRAINT :
                    object = table.getConstraint(name.name);
                    break;

                case SchemaObject.TRIGGER :
                    object = table.getTrigger(name.name);
                    break;

                case SchemaObject.COLUMN :
                    object = table.getColumn(table.getColumnIndex(name.name));
                    break;

                default :
                    continue;
            }

            removeReferencesFrom(object);
        }
    }

    public OrderedHashSet<HsqlName> getReferencesTo(HsqlName object) {

        readLock.lock();

        try {
            OrderedHashSet<HsqlName> set = new OrderedHashSet<>();
            Iterator<HsqlName>       it = referenceMap.getValuesIterator(
                object);

            while (it.hasNext()) {
                HsqlName name = it.next();

                set.add(name);
            }

            return set;
        } finally {
            readLock.unlock();
        }
    }

    public OrderedHashSet<HsqlName> getReferencesTo(
            HsqlName table,
            HsqlName column) {

        readLock.lock();

        try {
            OrderedHashSet<HsqlName> set = new OrderedHashSet<>();
            Iterator<HsqlName>       it  = referenceMap.getValuesIterator(
                table);

            while (it.hasNext()) {
                HsqlName                 name       = it.next();
                SchemaObject             object     = findSchemaObject(name);
                OrderedHashSet<HsqlName> references = object.getReferences();

                if (references.contains(column)) {
                    set.add(name);
                }
            }

            it = referenceMap.getValuesIterator(column);

            while (it.hasNext()) {
                HsqlName name = it.next();

                set.add(name);
            }

            return set;
        } finally {
            readLock.unlock();
        }
    }

    private boolean isReferenced(HsqlName object) {

        writeLock.lock();

        try {
            return referenceMap.containsKey(object);
        } finally {
            writeLock.unlock();
        }
    }

    //
    public void getCascadingReferencesTo(
            HsqlName object,
            OrderedHashSet<HsqlName> set) {

        readLock.lock();

        try {
            OrderedHashSet<HsqlName> newSet = new OrderedHashSet<>();
            Iterator<HsqlName>       it = referenceMap.getValuesIterator(
                object);

            while (it.hasNext()) {
                HsqlName name  = it.next();
                boolean  added = set.add(name);

                if (added) {
                    newSet.add(name);
                }
            }

            for (int i = 0; i < newSet.size(); i++) {
                HsqlName name = newSet.get(i);

                getCascadingReferencesTo(name, set);
            }
        } finally {
            readLock.unlock();
        }
    }

    public void getCascadingReferencesToSchema(
            HsqlName schema,
            OrderedHashSet<HsqlName> set) {

        Iterator<HsqlName> mainIterator = referenceMap.keySet().iterator();

        while (mainIterator.hasNext()) {
            HsqlName name = mainIterator.next();

            if (name.schema != schema) {
                continue;
            }

            getCascadingReferencesTo(name, set);
        }

        for (int i = set.size() - 1; i >= 0; i--) {
            HsqlName name = set.get(i);

            if (name.schema == schema) {
                set.remove(i);
            }
        }
    }

    public MultiValueHashMap<HsqlName, HsqlName> getReferencesToSchema(
            String schemaName) {

        MultiValueHashMap<HsqlName, HsqlName> map = new MultiValueHashMap<>();
        Iterator<HsqlName> mainIterator = referenceMap.keySet().iterator();

        while (mainIterator.hasNext()) {
            HsqlName name = mainIterator.next();

            if (!name.schema.name.equals(schemaName)) {
                continue;
            }

            Iterator<HsqlName> it = referenceMap.getValuesIterator(name);

            while (it.hasNext()) {
                map.put(name, it.next());
            }
        }

        return map;
    }

    //
    public HsqlName getSchemaObjectName(
            HsqlName schemaName,
            String name,
            int type,
            boolean raise) {

        readLock.lock();

        try {
            SchemaObject object;

            if (type == SchemaObject.SCHEMA) {
                object = schemaMap.get(name);
            } else {
                object = findSchemaObject(name, schemaName.name, type);
            }

            if (object == null) {
                if (raise) {
                    throw Error.error(SchemaObjectSet.getErrorCode);
                } else {
                    return null;
                }
            }

            return object.getName();
        } finally {
            readLock.unlock();
        }
    }

    public SchemaObject findSchemaObject(HsqlName name) {

        readLock.lock();

        try {
            String nameString;
            String schemaString;

            if (name.type == SchemaObject.SCHEMA) {
                nameString   = null;
                schemaString = name.name;
            } else {
                nameString   = name.name;
                schemaString = name.schema.name;
            }

            return findSchemaObject(nameString, schemaString, name.type);
        } finally {
            readLock.unlock();
        }
    }

    public void checkColumnIsReferenced(HsqlName tableName, HsqlName name) {

        OrderedHashSet<HsqlName> set = getReferencesTo(tableName, name);

        if (!set.isEmpty()) {
            HsqlName objectName = set.get(0);

            throw Error.error(
                ErrorCode.X_42502,
                objectName.getSchemaQualifiedStatementName());
        }
    }

    public void checkObjectIsReferenced(HsqlName name) {

        OrderedHashSet<HsqlName> set     = getReferencesTo(name);
        HsqlName                 refName = null;

        for (int i = 0; i < set.size(); i++) {
            refName = set.get(i);

            // except columns of same table
            if (refName.parent != name) {
                break;
            }

            refName = null;
        }

        if (refName == null) {
            return;
        }

        if (name.type == SchemaObject.CONSTRAINT) {
            return;
        }

        int errorCode = ErrorCode.X_42502;

        if (refName.type == SchemaObject.ConstraintTypes.FOREIGN_KEY) {
            errorCode = ErrorCode.X_42533;
        }

        throw Error.error(errorCode, refName.getSchemaQualifiedStatementName());
    }

    public void checkSchemaNameCanChange(HsqlName name) {

        readLock.lock();

        try {
            Iterator<HsqlName> it       = referenceMap.values().iterator();
            HsqlName           refError = null;

            mainLoop:
            while (it.hasNext()) {
                HsqlName refName = it.next();

                switch (refName.type) {

                    case SchemaObject.VIEW :
                    case SchemaObject.ROUTINE :
                    case SchemaObject.FUNCTION :
                    case SchemaObject.PROCEDURE :
                    case SchemaObject.TRIGGER :
                    case SchemaObject.SPECIFIC_ROUTINE :
                        if (refName.schema == name) {
                            refError = refName;
                            break mainLoop;
                        }

                        break;

                    default :
                }
            }

            if (refError == null) {
                return;
            }

            throw Error.error(
                ErrorCode.X_42502,
                refError.getSchemaQualifiedStatementName());
        } finally {
            readLock.unlock();
        }
    }

    public void addSchemaObject(SchemaObject object) {

        writeLock.lock();

        try {
            HsqlName name   = object.getName();
            Schema   schema = schemaMap.get(name.schema.name);

            switch (name.type) {

                case SchemaObject.TABLE : {
                    OrderedHashSet<HsqlName> refs =
                        ((Table) object).getReferencesForDependents();

                    for (int i = 0; i < refs.size(); i++) {
                        HsqlName ref = refs.get(i);

                        switch (ref.type) {

                            case SchemaObject.COLUMN : {
                                int index = ((Table) object).findColumn(
                                    ref.name);
                                ColumnSchema column =
                                    ((Table) object).getColumn(
                                        index);

                                addSchemaObject(column);
                                break;
                            }
                        }
                    }

                    break;
                }

                case SchemaObject.COLUMN : {
                    OrderedHashSet<HsqlName> refs = object.getReferences();

                    if (refs == null || refs.isEmpty()) {
                        return;
                    }

                    addReferencesFrom(object);

                    return;
                }
            }

            schema.addSchemaObject(database.nameManager, object, false);
            addReferencesFrom(object);
        } finally {
            writeLock.unlock();
        }
    }

    public void removeSchemaObject(HsqlName name, boolean cascade) {

        writeLock.lock();

        try {
            OrderedHashSet<HsqlName> objectSet = new OrderedHashSet<>();

            switch (name.type) {

                case SchemaObject.ROUTINE :
                case SchemaObject.PROCEDURE :
                case SchemaObject.FUNCTION : {
                    RoutineSchema routine = (RoutineSchema) findSchemaObject(
                        name);

                    if (routine != null) {
                        Routine[] specifics = routine.getSpecificRoutines();

                        for (int i = 0; i < specifics.length; i++) {
                            getCascadingReferencesTo(
                                specifics[i].getSpecificName(),
                                objectSet);
                        }
                    }
                }

                break;

                case SchemaObject.SEQUENCE :
                case SchemaObject.TABLE :
                case SchemaObject.VIEW :
                case SchemaObject.TYPE :
                case SchemaObject.CHARSET :
                case SchemaObject.COLLATION :
                case SchemaObject.SPECIFIC_ROUTINE :
                    getCascadingReferencesTo(name, objectSet);
                    break;

                case SchemaObject.DOMAIN :
                    OrderedHashSet<HsqlName> set = getReferencesTo(name);
                    Iterator<HsqlName>       it  = set.iterator();

                    while (it.hasNext()) {
                        HsqlName ref = it.next();

                        if (ref.type != SchemaObject.COLUMN) {
                            throw Error.error(
                                ErrorCode.X_42502,
                                ref.getSchemaQualifiedStatementName());
                        }
                    }

                    break;
            }

            if (objectSet.isEmpty()) {
                removeSchemaObject(name);

                return;
            }

            if (!cascade) {
                HsqlName objectName = objectSet.get(0);

                throw Error.error(
                    ErrorCode.X_42502,
                    objectName.getSchemaQualifiedStatementName());
            }

            objectSet.add(name);
            removeSchemaObjects(objectSet);
        } finally {
            writeLock.unlock();
        }
    }

    public void removeSchemaObjects(OrderedHashSet<HsqlName> set) {

        writeLock.lock();

        try {
            for (int i = 0; i < set.size(); i++) {
                HsqlName name = set.get(i);

                if (name.parent != null) {
                    removeSchemaObject(name);
                }
            }

            for (int i = 0; i < set.size(); i++) {
                HsqlName name = set.get(i);

                if (name.parent == null) {
                    removeSchemaObject(name);
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public void removeSchemaObject(HsqlName name) {

        writeLock.lock();

        try {
            Schema          schema = schemaMap.get(name.schema.name);
            SchemaObject    object = null;
            SchemaObjectSet set    = null;

            switch (name.type) {

                case SchemaObject.SEQUENCE :
                    set    = schema.sequenceLookup;
                    object = set.getObject(name.name);
                    break;

                case SchemaObject.TABLE :
                case SchemaObject.VIEW : {
                    set    = schema.tableLookup;
                    object = set.getObject(name.name);
                    break;
                }

                case SchemaObject.COLUMN : {
                    Table table = (Table) findSchemaObject(name.parent);

                    if (table != null) {
                        object = table.getColumn(
                            table.getColumnIndex(name.name));
                    }

                    break;
                }

                case SchemaObject.CHARSET :
                    set    = schema.charsetLookup;
                    object = set.getObject(name.name);
                    break;

                case SchemaObject.COLLATION :
                    set    = schema.collationLookup;
                    object = set.getObject(name.name);
                    break;

                case SchemaObject.PROCEDURE : {
                    set = schema.procedureLookup;

                    RoutineSchema routine = (RoutineSchema) set.getObject(
                        name.name);

                    object = routine;

                    Routine[] specifics = routine.getSpecificRoutines();

                    for (int i = 0; i < specifics.length; i++) {
                        removeSchemaObject(specifics[i].getSpecificName());
                    }

                    break;
                }

                case SchemaObject.FUNCTION : {
                    set = schema.functionLookup;

                    RoutineSchema routine = (RoutineSchema) set.getObject(
                        name.name);

                    object = routine;

                    Routine[] specifics = routine.getSpecificRoutines();

                    for (int i = 0; i < specifics.length; i++) {
                        removeSchemaObject(specifics[i].getSpecificName());
                    }

                    break;
                }

                case SchemaObject.SPECIFIC_ROUTINE : {
                    set = schema.specificRLookup;

                    Routine routine = (Routine) set.getObject(name.name);

                    object = routine;

                    routine.routineSchema.removeSpecificRoutine(routine);

                    if (routine.routineSchema.getSpecificRoutines().length
                            == 0) {
                        removeSchemaObject(routine.getName());
                    }

                    break;
                }

                case SchemaObject.DOMAIN :
                case SchemaObject.TYPE :
                    set    = schema.typeLookup;
                    object = set.getObject(name.name);
                    break;

                case SchemaObject.INDEX :
                    set = schema.indexLookup;
                    break;

                case SchemaObject.CONSTRAINT : {
                    set = schema.constraintLookup;

                    if (name.parent.type == SchemaObject.TABLE) {
                        Table table = schema.tableList.get(name.parent.name);

                        object = table.getConstraint(name.name);

                        table.removeConstraint(name.name);
                    } else if (name.parent.type == SchemaObject.DOMAIN) {
                        Type type = (Type) schema.typeLookup.getObject(
                            name.parent.name);

                        object = type.userTypeModifier.getConstraint(name.name);

                        type.userTypeModifier.removeConstraint(name.name);
                    }

                    break;
                }

                case SchemaObject.TRIGGER : {
                    set = schema.triggerLookup;

                    Table table = schema.tableList.get(name.parent.name);

                    object = table.getTrigger(name.name);

                    if (object != null) {
                        table.removeTrigger((TriggerDef) object);
                    }

                    break;
                }

                case SchemaObject.REFERENCE : {
                    set    = schema.referenceLookup;
                    object = set.getObject(name.name);
                    break;
                }

                default :
                    throw Error.runtimeError(
                        ErrorCode.U_S0500,
                        "SchemaManager");
            }

            if (object != null) {
                database.getGranteeManager().removeDbObject(name);
                removeReferencesFrom(object);
            }

            if (set != null) {
                set.remove(name.name);
            }

            removeReferencesTo(name);
        } finally {
            writeLock.unlock();
        }
    }

    public void renameSchemaObject(HsqlName name, HsqlName newName) {

        writeLock.lock();

        try {
            if (name.schema != newName.schema) {
                throw Error.error(ErrorCode.X_42505, newName.schema.name);
            }

            checkObjectIsReferenced(name);

            Schema schema = schemaMap.get(name.schema.name);

            schema.renameObject(name, newName);
        } finally {
            writeLock.unlock();
        }
    }

    public void replaceReferences(
            SchemaObject oldObject,
            SchemaObject newObject) {

        writeLock.lock();

        try {
            removeReferencesFrom(oldObject);
            addReferencesFrom(newObject);
        } finally {
            writeLock.unlock();
        }
    }

    public List<String> getSQLArray() {

        readLock.lock();

        try {
            OrderedHashSet<HsqlName>     resolved   = new OrderedHashSet<>();
            OrderedHashSet<SchemaObject> unresolved = new OrderedHashSet<>();
            HsqlArrayList<String>        list       = new HsqlArrayList<>();
            Iterator<Schema>             schemas    = getUserSchemaIterator();

            while (schemas.hasNext()) {
                Schema schema = schemas.next();

                list.add(schema.getSQL());

                for (int round = 0; round < Schema.scriptSequenceOne.length;
                        round++) {
                    int objectType = Schema.scriptSequenceOne[round];

                    list.addAll(
                        schema.getSQLArray(objectType, resolved, unresolved));
                }

                while (true) {
                    Iterator<SchemaObject> it = unresolved.iterator();

                    if (!it.hasNext()) {
                        break;
                    }

                    OrderedHashSet<SchemaObject> newResolved =
                        new OrderedHashSet<>();

                    SchemaObjectSet.addAllSQL(
                        resolved,
                        unresolved,
                        list,
                        it,
                        newResolved);
                    unresolved.removeAll(newResolved);

                    if (newResolved.isEmpty()) {
                        break;
                    }
                }
            }

            // all NO SQL functions have been scripted, others are scripted at the end
            for (int round = 0; round < Schema.scriptSequenceTwo.length;
                    round++) {
                schemas = getUserSchemaIterator();

                while (schemas.hasNext()) {
                    Schema schema     = schemas.next();
                    int    objectType = Schema.scriptSequenceTwo[round];

                    list.addAll(
                        schema.getSQLArray(objectType, resolved, unresolved));
                }
            }

            while (true) {
                Iterator<SchemaObject> it = unresolved.iterator();

                if (!it.hasNext()) {
                    break;
                }

                OrderedHashSet<SchemaObject> newResolved =
                    new OrderedHashSet<>();

                SchemaObjectSet.addAllSQL(
                    resolved,
                    unresolved,
                    list,
                    it,
                    newResolved);
                unresolved.removeAll(newResolved);

                if (newResolved.isEmpty()) {
                    break;
                }
            }

            // forward reference routines
            Iterator<SchemaObject> it = unresolved.iterator();

            while (it.hasNext()) {
                SchemaObject object = it.next();

                if (object instanceof Routine) {
                    list.add(((Routine) object).getSQLDeclaration());
                }
            }

            it = unresolved.iterator();

            while (it.hasNext()) {
                SchemaObject object = it.next();

                if (object instanceof Routine) {
                    list.add(((Routine) object).getSQLAlter());
                } else {
                    list.add(object.getSQL());
                }
            }

            it = unresolved.iterator();

            while (it.hasNext()) {
                SchemaObject object = it.next();

                if (object instanceof ReferenceObject) {
                    list.add(object.getSQL());
                }
            }

            schemas = getUserSchemaIterator();

            while (schemas.hasNext()) {
                Schema                schema = schemas.next();
                HsqlArrayList<String> t      = schema.getTriggerSQLArray();

                if (t.size() > 0) {
                    list.add(schema.getSetSchemaSQL());
                    list.addAll(t);
                }
            }

            schemas = schemaMap.values().iterator();

            while (schemas.hasNext()) {
                Schema schema = schemas.next();

                list.addAll(schema.getSequenceRestartSQLArray());
            }

            if (defaultSchemaHsqlName != null) {
                StringBuilder sb = new StringBuilder(64);

                sb.append(Tokens.T_SET)
                  .append(' ')
                  .append(Tokens.T_DATABASE)
                  .append(' ')
                  .append(Tokens.T_DEFAULT)
                  .append(' ')
                  .append(Tokens.T_INITIAL)
                  .append(' ')
                  .append(Tokens.T_SCHEMA)
                  .append(' ')
                  .append(defaultSchemaHsqlName.statementName);
                list.add(sb.toString());
            }

            return list;
        } finally {
            readLock.unlock();
        }
    }

    public List<String> getTablePropsSQLArray(boolean withHeader) {

        readLock.lock();

        try {
            HsqlArrayList<Table>  tableList = getAllTables(false);
            HsqlArrayList<String> list      = new HsqlArrayList<>();

            for (int i = 0; i < tableList.size(); i++) {
                Table t = tableList.get(i);

                if (t.isText()) {
                    String[] ddl = t.getSQLForTextSource(withHeader);

                    list.addAll(ddl);
                }

                String ddl = t.getSQLForReadOnly();

                if (ddl != null) {
                    list.add(ddl);
                }

                if (t.isCached()) {
                    ddl = t.getSQLForClustered();

                    if (ddl != null) {
                        list.add(ddl);
                    }
                }
            }

            return list;
        } finally {
            readLock.unlock();
        }
    }

    public List<String> getTableSpaceSQLArray() {

        readLock.lock();

        try {
            HsqlArrayList<Table>  tableList = getAllTables(true);
            HsqlArrayList<String> list      = new HsqlArrayList<>();

            for (int i = 0; i < tableList.size(); i++) {
                Table t = tableList.get(i);

                if (t.isCached()) {
                    String ddl = t.getSQLForTableSpace();

                    if (ddl != null) {
                        list.add(ddl);
                    }
                }
            }

            return list;
        } finally {
            readLock.unlock();
        }
    }

    public List<String> getIndexRootsSQLArray() {

        readLock.lock();

        try {
            long[][]              rootsArray = getIndexRoots();
            HsqlArrayList<Table>  tableList  = getAllTables(true);
            HsqlArrayList<String> list       = new HsqlArrayList<>();

            for (int i = 0; i < rootsArray.length; i++) {
                Table table = tableList.get(i);

                if (rootsArray[i] != null
                        && rootsArray[i].length > 0
                        && rootsArray[i][0] != -1) {
                    String ddl = table.getIndexRootsSQL(rootsArray[i]);

                    list.add(ddl);
                }
            }

            return list;
        } finally {
            readLock.unlock();
        }
    }

    long[][] tempIndexRoots;

    public void setTempIndexRoots(long[][] roots) {
        tempIndexRoots = roots;
    }

    public long[][] getIndexRoots() {

        readLock.lock();

        try {
            if (tempIndexRoots != null) {
                long[][] roots = tempIndexRoots;

                tempIndexRoots = null;

                return roots;
            }

            HsqlArrayList<Table>  allTables = getAllTables(true);
            HsqlArrayList<long[]> list      = new HsqlArrayList<>();

            for (int i = 0, size = allTables.size(); i < size; i++) {
                Table t = allTables.get(i);

                if (t.getTableType() == TableBase.CACHED_TABLE) {
                    long[] roots = t.getIndexRootsArray();

                    list.add(roots);
                } else {
                    list.add(null);
                }
            }

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

            list.toArray(array);

            return array;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * called after the completion of defrag
     */
    public void setIndexRoots(long[][] roots) {

        readLock.lock();

        try {
            HsqlArrayList<Table> allTables =
                database.schemaManager.getAllTables(
                    true);

            for (int i = 0, size = allTables.size(); i < size; i++) {
                Table t = allTables.get(i);

                if (t.getTableType() == TableBase.CACHED_TABLE) {
                    long[] rootsArray = roots[i];

                    if (rootsArray != null) {
                        t.setIndexRoots(rootsArray);
                    }
                }
            }
        } finally {
            readLock.unlock();
        }
    }

    public void setDefaultTableType(int type) {
        defaultTableType = type;
    }

    public int getDefaultTableType() {
        return defaultTableType;
    }

    public void createSystemTables() {

        dualTable = TableUtil.newSingleColumnTable(
            database,
            SqlInvariants.DUAL_TABLE_HSQLNAME,
            TableBase.SYSTEM_TABLE,
            SqlInvariants.DUAL_COLUMN_HSQLNAME,
            Type.SQL_VARCHAR);

        dualTable.insertSys(
            database.sessionManager.getSysSession(),
            dualTable.getRowStore(null),
            new Object[]{ "X" });
        dualTable.setDataReadOnly(true);
    }

    static class UserSchemaFilter implements FilteredIterator.Filter<Schema> {

        public boolean test(Schema schema) {

            String name = schema.getName().name;

            return !SqlInvariants.isLobsSchemaName(name)
                   && !SqlInvariants.isSystemSchemaName(name);
        }
    }
}
