org.hibernate.mapping.Table.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.mapping.Table.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.mapping;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.Exportable;
import org.hibernate.boot.model.relational.InitCommand;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.tool.hbm2ddl.ColumnMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
import org.hibernate.tool.schema.extract.spi.TableInformation;

import org.jboss.logging.Logger;

/**
 * A relational table
 *
 * @author Gavin King
 */
@SuppressWarnings("unchecked")
public class Table implements RelationalModel, Serializable, Exportable {
    private static final Logger log = Logger.getLogger(Table.class);

    private Identifier catalog;
    private Identifier schema;
    private Identifier name;

    /**
     * contains all columns, including the primary key
     */
    private Map columns = new LinkedHashMap();
    private KeyValue idValue;
    private PrimaryKey primaryKey;
    private Map<ForeignKeyKey, ForeignKey> foreignKeys = new LinkedHashMap<ForeignKeyKey, ForeignKey>();
    private Map<String, Index> indexes = new LinkedHashMap<String, Index>();
    private Map<String, UniqueKey> uniqueKeys = new LinkedHashMap<String, UniqueKey>();
    private int uniqueInteger;
    private List<String> checkConstraints = new ArrayList<String>();
    private String rowId;
    private String subselect;
    private boolean isAbstract;
    private boolean hasDenormalizedTables;
    private String comment;

    private List<InitCommand> initCommands;

    public Table() {
    }

    public Table(String name) {
        setName(name);
    }

    public Table(Namespace namespace, Identifier physicalTableName, boolean isAbstract) {
        this.catalog = namespace.getPhysicalName().getCatalog();
        this.schema = namespace.getPhysicalName().getSchema();
        this.name = physicalTableName;
        this.isAbstract = isAbstract;
    }

    public Table(Identifier catalog, Identifier schema, Identifier physicalTableName, boolean isAbstract) {
        this.catalog = catalog;
        this.schema = schema;
        this.name = physicalTableName;
        this.isAbstract = isAbstract;
    }

    public Table(Namespace namespace, Identifier physicalTableName, String subselect, boolean isAbstract) {
        this.catalog = namespace.getPhysicalName().getCatalog();
        this.schema = namespace.getPhysicalName().getSchema();
        this.name = physicalTableName;
        this.subselect = subselect;
        this.isAbstract = isAbstract;
    }

    public Table(Namespace namespace, String subselect, boolean isAbstract) {
        this.catalog = namespace.getPhysicalName().getCatalog();
        this.schema = namespace.getPhysicalName().getSchema();
        this.subselect = subselect;
        this.isAbstract = isAbstract;
    }

    /**
     * @deprecated Should use {@link QualifiedObjectNameFormatter#format} on QualifiedObjectNameFormatter
     * obtained from {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment}
     */
    @Deprecated
    public String getQualifiedName(Dialect dialect, String defaultCatalog, String defaultSchema) {
        if (subselect != null) {
            return "( " + subselect + " )";
        }
        String quotedName = getQuotedName(dialect);
        String usedSchema = schema == null ? defaultSchema : getQuotedSchema(dialect);
        String usedCatalog = catalog == null ? defaultCatalog : getQuotedCatalog(dialect);
        return qualify(usedCatalog, usedSchema, quotedName);
    }

    /**
     * @deprecated Should use {@link QualifiedObjectNameFormatter#format} on QualifiedObjectNameFormatter
     * obtained from {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment}
     */
    @Deprecated
    public static String qualify(String catalog, String schema, String table) {
        StringBuilder qualifiedName = new StringBuilder();
        if (catalog != null) {
            qualifiedName.append(catalog).append('.');
        }
        if (schema != null) {
            qualifiedName.append(schema).append('.');
        }
        return qualifiedName.append(table).toString();
    }

    public void setName(String name) {
        this.name = Identifier.toIdentifier(name);
    }

    public String getName() {
        return name == null ? null : name.getText();
    }

    public Identifier getNameIdentifier() {
        return name;
    }

    public String getQuotedName() {
        return name == null ? null : name.toString();
    }

    public String getQuotedName(Dialect dialect) {
        return name == null ? null : name.render(dialect);
    }

    public QualifiedTableName getQualifiedTableName() {
        return name == null ? null : new QualifiedTableName(catalog, schema, name);
    }

    public boolean isQuoted() {
        return name.isQuoted();
    }

    public void setQuoted(boolean quoted) {
        if (quoted == name.isQuoted()) {
            return;
        }
        this.name = new Identifier(name.getText(), quoted);
    }

    public void setSchema(String schema) {
        this.schema = Identifier.toIdentifier(schema);
    }

    public String getSchema() {
        return schema == null ? null : schema.getText();
    }

    public String getQuotedSchema() {
        return schema == null ? null : schema.toString();
    }

    public String getQuotedSchema(Dialect dialect) {
        return schema == null ? null : schema.render(dialect);
    }

    public boolean isSchemaQuoted() {
        return schema != null && schema.isQuoted();
    }

    public void setCatalog(String catalog) {
        this.catalog = Identifier.toIdentifier(catalog);
    }

    public String getCatalog() {
        return catalog == null ? null : catalog.getText();
    }

    public String getQuotedCatalog() {
        return catalog == null ? null : catalog.render();
    }

    public String getQuotedCatalog(Dialect dialect) {
        return catalog == null ? null : catalog.render(dialect);
    }

    public boolean isCatalogQuoted() {
        return catalog != null && catalog.isQuoted();
    }

    /**
     * Return the column which is identified by column provided as argument.
     *
     * @param column column with atleast a name.
     * @return the underlying column or null if not inside this table. Note: the instance *can* be different than the input parameter, but the name will be the same.
     */
    public Column getColumn(Column column) {
        if (column == null) {
            return null;
        }

        Column myColumn = (Column) columns.get(column.getCanonicalName());

        return column.equals(myColumn) ? myColumn : null;
    }

    public Column getColumn(Identifier name) {
        if (name == null) {
            return null;
        }

        return (Column) columns.get(name.getCanonicalName());
    }

    public Column getColumn(int n) {
        Iterator iter = columns.values().iterator();
        for (int i = 0; i < n - 1; i++) {
            iter.next();
        }
        return (Column) iter.next();
    }

    public void addColumn(Column column) {
        Column old = getColumn(column);
        if (old == null) {
            if (primaryKey != null) {
                for (Column c : primaryKey.getColumns()) {
                    if (c.getCanonicalName().equals(column.getCanonicalName())) {
                        column.setNullable(false);
                        log.debugf(
                                "Forcing column [%s] to be non-null as it is part of the primary key for table [%s]",
                                column.getCanonicalName(), getNameIdentifier().getCanonicalName());
                    }
                }
            }
            this.columns.put(column.getCanonicalName(), column);
            column.uniqueInteger = this.columns.size();
        } else {
            column.uniqueInteger = old.uniqueInteger;
        }
    }

    public int getColumnSpan() {
        return columns.size();
    }

    public Iterator getColumnIterator() {
        return columns.values().iterator();
    }

    public Iterator<Index> getIndexIterator() {
        return indexes.values().iterator();
    }

    public Iterator getForeignKeyIterator() {
        return foreignKeys.values().iterator();
    }

    public Map<ForeignKeyKey, ForeignKey> getForeignKeys() {
        return Collections.unmodifiableMap(foreignKeys);
    }

    public Iterator<UniqueKey> getUniqueKeyIterator() {
        return getUniqueKeys().values().iterator();
    }

    Map<String, UniqueKey> getUniqueKeys() {
        cleanseUniqueKeyMapIfNeeded();
        return uniqueKeys;
    }

    private int sizeOfUniqueKeyMapOnLastCleanse;

    private void cleanseUniqueKeyMapIfNeeded() {
        if (uniqueKeys.size() == sizeOfUniqueKeyMapOnLastCleanse) {
            // nothing to do
            return;
        }
        cleanseUniqueKeyMap();
        sizeOfUniqueKeyMapOnLastCleanse = uniqueKeys.size();
    }

    private void cleanseUniqueKeyMap() {
        // We need to account for a few conditions here...
        //    1) If there are multiple unique keys contained in the uniqueKeys Map, we need to deduplicate
        //       any sharing the same columns as other defined unique keys; this is needed for the annotation
        //       processor since it creates unique constraints automagically for the user
        //   2) Remove any unique keys that share the same columns as the primary key; again, this is
        //      needed for the annotation processor to handle @Id @OneToOne cases.  In such cases the
        //      unique key is unnecessary because a primary key is already unique by definition.  We handle
        //      this case specifically because some databases fail if you try to apply a unique key to
        //      the primary key columns which causes schema export to fail in these cases.
        if (uniqueKeys.isEmpty()) {
            // nothing to do
            return;
        } else if (uniqueKeys.size() == 1) {
            // we have to worry about condition 2 above, but not condition 1
            final Map.Entry<String, UniqueKey> uniqueKeyEntry = uniqueKeys.entrySet().iterator().next();
            if (isSameAsPrimaryKeyColumns(uniqueKeyEntry.getValue())) {
                uniqueKeys.remove(uniqueKeyEntry.getKey());
            }
        } else {
            // we have to check both conditions 1 and 2
            final Iterator<Map.Entry<String, UniqueKey>> uniqueKeyEntries = uniqueKeys.entrySet().iterator();
            while (uniqueKeyEntries.hasNext()) {
                final Map.Entry<String, UniqueKey> uniqueKeyEntry = uniqueKeyEntries.next();
                final UniqueKey uniqueKey = uniqueKeyEntry.getValue();
                boolean removeIt = false;

                // condition 1 : check against other unique keys
                for (UniqueKey otherUniqueKey : uniqueKeys.values()) {
                    // make sure its not the same unique key
                    if (uniqueKeyEntry.getValue() == otherUniqueKey) {
                        continue;
                    }
                    if (otherUniqueKey.getColumns().containsAll(uniqueKey.getColumns())
                            && uniqueKey.getColumns().containsAll(otherUniqueKey.getColumns())) {
                        removeIt = true;
                        break;
                    }
                }

                // condition 2 : check against pk
                if (isSameAsPrimaryKeyColumns(uniqueKeyEntry.getValue())) {
                    removeIt = true;
                }

                if (removeIt) {
                    //uniqueKeys.remove( uniqueKeyEntry.getKey() );
                    uniqueKeyEntries.remove();
                }
            }

        }
    }

    private boolean isSameAsPrimaryKeyColumns(UniqueKey uniqueKey) {
        if (primaryKey == null || !primaryKey.columnIterator().hasNext()) {
            // happens for many-to-many tables
            return false;
        }
        return primaryKey.getColumns().containsAll(uniqueKey.getColumns())
                && uniqueKey.getColumns().containsAll(primaryKey.getColumns());
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((catalog == null) ? 0 : catalog.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((schema == null) ? 0 : schema.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object object) {
        return object instanceof Table && equals((Table) object);
    }

    public boolean equals(Table table) {
        if (null == table) {
            return false;
        }
        if (this == table) {
            return true;
        }

        return Identifier.areEqual(name, table.name) && Identifier.areEqual(schema, table.schema)
                && Identifier.areEqual(catalog, table.catalog);
    }

    public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) {
        Iterator iter = getColumnIterator();
        while (iter.hasNext()) {
            Column col = (Column) iter.next();

            ColumnMetadata columnInfo = tableInfo.getColumnMetadata(col.getName());

            if (columnInfo == null) {
                throw new HibernateException("Missing column: " + col.getName() + " in "
                        + Table.qualify(tableInfo.getCatalog(), tableInfo.getSchema(), tableInfo.getName()));
            } else {
                final boolean typesMatch = col.getSqlType(dialect, mapping).toLowerCase(Locale.ROOT)
                        .startsWith(columnInfo.getTypeName().toLowerCase(Locale.ROOT))
                        || columnInfo.getTypeCode() == col.getSqlTypeCode(mapping);
                if (!typesMatch) {
                    throw new HibernateException(
                            "Wrong column type in "
                                    + Table.qualify(tableInfo.getCatalog(), tableInfo.getSchema(),
                                            tableInfo.getName())
                                    + " for column " + col.getName() + ". Found: "
                                    + columnInfo.getTypeName().toLowerCase(Locale.ROOT) + ", expected: "
                                    + col.getSqlType(dialect, mapping));
                }
            }
        }

    }

    public Iterator sqlAlterStrings(Dialect dialect, Metadata metadata, TableInformation tableInfo,
            Identifier defaultCatalog, Identifier defaultSchema) throws HibernateException {

        final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment();

        final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter()
                .format(new QualifiedTableName(catalog != null ? catalog : defaultCatalog,
                        schema != null ? schema : defaultSchema, name), dialect);

        StringBuilder root = new StringBuilder(dialect.getAlterTableString(tableName)).append(' ')
                .append(dialect.getAddColumnString());

        Iterator iter = getColumnIterator();
        List results = new ArrayList();

        while (iter.hasNext()) {
            final Column column = (Column) iter.next();
            final ColumnInformation columnInfo = tableInfo
                    .getColumn(Identifier.toIdentifier(column.getName(), column.isQuoted()));

            if (columnInfo == null) {
                // the column doesnt exist at all.
                StringBuilder alter = new StringBuilder(root.toString()).append(' ')
                        .append(column.getQuotedName(dialect)).append(' ')
                        .append(column.getSqlType(dialect, metadata));

                String defaultValue = column.getDefaultValue();
                if (defaultValue != null) {
                    alter.append(" default ").append(defaultValue);
                }

                if (column.isNullable()) {
                    alter.append(dialect.getNullColumnString());
                } else {
                    alter.append(" not null");
                }

                if (column.isUnique()) {
                    String keyName = Constraint.generateName("UK_", this, column);
                    UniqueKey uk = getOrCreateUniqueKey(keyName);
                    uk.addColumn(column);
                    alter.append(dialect.getUniqueDelegate().getColumnDefinitionUniquenessFragment(column));
                }

                if (column.hasCheckConstraint() && dialect.supportsColumnCheck()) {
                    alter.append(" check(").append(column.getCheckConstraint()).append(")");
                }

                String columnComment = column.getComment();
                if (columnComment != null) {
                    alter.append(dialect.getColumnComment(columnComment));
                }

                alter.append(dialect.getAddColumnSuffixString());

                results.add(alter.toString());
            }

        }

        if (results.isEmpty()) {
            log.debugf("No alter strings for table : %s", getQuotedName());
        }

        return results.iterator();
    }

    public boolean hasPrimaryKey() {
        return getPrimaryKey() != null;
    }

    public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) {
        StringBuilder buf = new StringBuilder(
                hasPrimaryKey() ? dialect.getCreateTableString() : dialect.getCreateMultisetTableString())
                        .append(' ').append(getQualifiedName(dialect, defaultCatalog, defaultSchema)).append(" (");

        boolean identityColumn = idValue != null
                && idValue.isIdentityColumn(p.getIdentifierGeneratorFactory(), dialect);

        // Try to find out the name of the primary key to create it as identity if the IdentityGenerator is used
        String pkname = null;
        if (hasPrimaryKey() && identityColumn) {
            pkname = ((Column) getPrimaryKey().getColumnIterator().next()).getQuotedName(dialect);
        }

        Iterator iter = getColumnIterator();
        while (iter.hasNext()) {
            Column col = (Column) iter.next();

            buf.append(col.getQuotedName(dialect)).append(' ');

            if (identityColumn && col.getQuotedName(dialect).equals(pkname)) {
                // to support dialects that have their own identity data type
                if (dialect.getIdentityColumnSupport().hasDataTypeInIdentityColumn()) {
                    buf.append(col.getSqlType(dialect, p));
                }
                buf.append(' ')
                        .append(dialect.getIdentityColumnSupport().getIdentityColumnString(col.getSqlTypeCode(p)));
            } else {

                buf.append(col.getSqlType(dialect, p));

                String defaultValue = col.getDefaultValue();
                if (defaultValue != null) {
                    buf.append(" default ").append(defaultValue);
                }

                if (col.isNullable()) {
                    buf.append(dialect.getNullColumnString());
                } else {
                    buf.append(" not null");
                }

            }

            if (col.isUnique()) {
                String keyName = Constraint.generateName("UK_", this, col);
                UniqueKey uk = getOrCreateUniqueKey(keyName);
                uk.addColumn(col);
                buf.append(dialect.getUniqueDelegate().getColumnDefinitionUniquenessFragment(col));
            }

            if (col.hasCheckConstraint() && dialect.supportsColumnCheck()) {
                buf.append(" check (").append(col.getCheckConstraint()).append(")");
            }

            String columnComment = col.getComment();
            if (columnComment != null) {
                buf.append(dialect.getColumnComment(columnComment));
            }

            if (iter.hasNext()) {
                buf.append(", ");
            }

        }
        if (hasPrimaryKey()) {
            buf.append(", ").append(getPrimaryKey().sqlConstraintString(dialect));
        }

        buf.append(dialect.getUniqueDelegate().getTableCreationUniqueConstraintsFragment(this));

        if (dialect.supportsTableCheck()) {
            for (String checkConstraint : checkConstraints) {
                buf.append(", check (").append(checkConstraint).append(')');
            }
        }

        buf.append(')');

        if (comment != null) {
            buf.append(dialect.getTableComment(comment));
        }

        return buf.append(dialect.getTableTypeString()).toString();
    }

    public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) {
        return dialect.getDropTableString(getQualifiedName(dialect, defaultCatalog, defaultSchema));
    }

    public PrimaryKey getPrimaryKey() {
        return primaryKey;
    }

    public void setPrimaryKey(PrimaryKey primaryKey) {
        this.primaryKey = primaryKey;
    }

    public Index getOrCreateIndex(String indexName) {

        Index index = indexes.get(indexName);

        if (index == null) {
            index = new Index();
            index.setName(indexName);
            index.setTable(this);
            indexes.put(indexName, index);
        }

        return index;
    }

    public Index getIndex(String indexName) {
        return indexes.get(indexName);
    }

    public Index addIndex(Index index) {
        Index current = indexes.get(index.getName());
        if (current != null) {
            throw new MappingException("Index " + index.getName() + " already exists!");
        }
        indexes.put(index.getName(), index);
        return index;
    }

    public UniqueKey addUniqueKey(UniqueKey uniqueKey) {
        UniqueKey current = uniqueKeys.get(uniqueKey.getName());
        if (current != null) {
            throw new MappingException("UniqueKey " + uniqueKey.getName() + " already exists!");
        }
        uniqueKeys.put(uniqueKey.getName(), uniqueKey);
        return uniqueKey;
    }

    public UniqueKey createUniqueKey(List keyColumns) {
        String keyName = Constraint.generateName("UK_", this, keyColumns);
        UniqueKey uk = getOrCreateUniqueKey(keyName);
        uk.addColumns(keyColumns.iterator());
        return uk;
    }

    public UniqueKey getUniqueKey(String keyName) {
        return uniqueKeys.get(keyName);
    }

    public UniqueKey getOrCreateUniqueKey(String keyName) {
        UniqueKey uk = uniqueKeys.get(keyName);

        if (uk == null) {
            uk = new UniqueKey();
            uk.setName(keyName);
            uk.setTable(this);
            uniqueKeys.put(keyName, uk);
        }
        return uk;
    }

    public void createForeignKeys() {
    }

    public ForeignKey createForeignKey(String keyName, List keyColumns, String referencedEntityName,
            String keyDefinition) {
        return createForeignKey(keyName, keyColumns, referencedEntityName, keyDefinition, null);
    }

    public ForeignKey createForeignKey(String keyName, List keyColumns, String referencedEntityName,
            String keyDefinition, List referencedColumns) {
        final ForeignKeyKey key = new ForeignKeyKey(keyColumns, referencedEntityName, referencedColumns);

        ForeignKey fk = foreignKeys.get(key);
        if (fk == null) {
            fk = new ForeignKey();
            fk.setTable(this);
            fk.setReferencedEntityName(referencedEntityName);
            fk.setKeyDefinition(keyDefinition);
            fk.addColumns(keyColumns.iterator());
            if (referencedColumns != null) {
                fk.addReferencedColumns(referencedColumns.iterator());
            }

            // NOTE : if the name is null, we will generate an implicit name during second pass processing
            // after we know the referenced table name (which might not be resolved yet).
            fk.setName(keyName);

            foreignKeys.put(key, fk);
        }

        if (keyName != null) {
            fk.setName(keyName);
        }

        return fk;
    }

    // This must be done outside of Table, rather than statically, to ensure
    // deterministic alias names.  See HHH-2448.
    public void setUniqueInteger(int uniqueInteger) {
        this.uniqueInteger = uniqueInteger;
    }

    public int getUniqueInteger() {
        return uniqueInteger;
    }

    public void setIdentifierValue(KeyValue idValue) {
        this.idValue = idValue;
    }

    public KeyValue getIdentifierValue() {
        return idValue;
    }

    public void addCheckConstraint(String constraint) {
        checkConstraints.add(constraint);
    }

    public boolean containsColumn(Column column) {
        return columns.containsValue(column);
    }

    public String getRowId() {
        return rowId;
    }

    public void setRowId(String rowId) {
        this.rowId = rowId;
    }

    public String toString() {
        StringBuilder buf = new StringBuilder().append(getClass().getName()).append('(');
        if (getCatalog() != null) {
            buf.append(getCatalog()).append(".");
        }
        if (getSchema() != null) {
            buf.append(getSchema()).append(".");
        }
        buf.append(getName()).append(')');
        return buf.toString();
    }

    public String getSubselect() {
        return subselect;
    }

    public void setSubselect(String subselect) {
        this.subselect = subselect;
    }

    public boolean isSubselect() {
        return subselect != null;
    }

    public boolean isAbstractUnionTable() {
        return hasDenormalizedTables() && isAbstract;
    }

    public boolean hasDenormalizedTables() {
        return hasDenormalizedTables;
    }

    void setHasDenormalizedTables() {
        hasDenormalizedTables = true;
    }

    public void setAbstract(boolean isAbstract) {
        this.isAbstract = isAbstract;
    }

    public boolean isAbstract() {
        return isAbstract;
    }

    public boolean isPhysicalTable() {
        return !isSubselect() && !isAbstractUnionTable();
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Iterator<String> getCheckConstraintsIterator() {
        return checkConstraints.iterator();
    }

    public Iterator sqlCommentStrings(Dialect dialect, String defaultCatalog, String defaultSchema) {
        List comments = new ArrayList();
        if (dialect.supportsCommentOn()) {
            String tableName = getQualifiedName(dialect, defaultCatalog, defaultSchema);
            if (comment != null) {
                comments.add("comment on table " + tableName + " is '" + comment + "'");
            }
            Iterator iter = getColumnIterator();
            while (iter.hasNext()) {
                Column column = (Column) iter.next();
                String columnComment = column.getComment();
                if (columnComment != null) {
                    comments.add("comment on column " + tableName + '.' + column.getQuotedName(dialect) + " is '"
                            + columnComment + "'");
                }
            }
        }
        return comments.iterator();
    }

    @Override
    public String getExportIdentifier() {
        return Table.qualify(render(catalog), render(schema), name.render());
    }

    private String render(Identifier identifier) {
        return identifier == null ? null : identifier.render();
    }

    public static class ForeignKeyKey implements Serializable {
        String referencedClassName;
        List columns;
        List referencedColumns;

        ForeignKeyKey(List columns, String referencedClassName, List referencedColumns) {
            this.referencedClassName = referencedClassName;
            this.columns = new ArrayList();
            this.columns.addAll(columns);
            if (referencedColumns != null) {
                this.referencedColumns = new ArrayList();
                this.referencedColumns.addAll(referencedColumns);
            } else {
                this.referencedColumns = Collections.EMPTY_LIST;
            }
        }

        public int hashCode() {
            return columns.hashCode() + referencedColumns.hashCode();
        }

        public boolean equals(Object other) {
            ForeignKeyKey fkk = (ForeignKeyKey) other;
            return fkk != null && fkk.columns.equals(columns) && fkk.referencedColumns.equals(referencedColumns);
        }

        @Override
        public String toString() {
            return "ForeignKeyKey{" + "columns=" + String.join(",", columns) + ", referencedClassName='"
                    + referencedClassName + '\'' + ", referencedColumns=" + String.join(",", referencedColumns)
                    + '}';
        }
    }

    public void addInitCommand(InitCommand command) {
        if (initCommands == null) {
            initCommands = new ArrayList<InitCommand>();
        }
        initCommands.add(command);
    }

    public List<InitCommand> getInitCommands() {
        if (initCommands == null) {
            return Collections.emptyList();
        } else {
            return Collections.unmodifiableList(initCommands);
        }
    }
}