/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
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.dialect.Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.tool.hbm2ddl.ColumnMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata;

A relational table
Author:Gavin King
/** * A relational table * * @author Gavin King */
public class Table implements RelationalModel, Serializable { private String name; private String schema; private String catalog;
contains all columns, including the primary key
/** * contains all columns, including the primary key */
private Map columns = new LinkedHashMap(); private KeyValue idValue; private PrimaryKey primaryKey; private Map<String, Index> indexes = new LinkedHashMap<String, Index>(); private Map foreignKeys = new LinkedHashMap(); private Map<String,UniqueKey> uniqueKeys = new LinkedHashMap<String,UniqueKey>(); private int uniqueInteger; private boolean quoted; private boolean schemaQuoted; private boolean catalogQuoted; private List checkConstraints = new ArrayList(); private String rowId; private String subselect; private boolean isAbstract; private boolean hasDenormalizedTables; private String comment; 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.columns.equals( columns ) && fkk.referencedClassName.equals( referencedClassName ) && fkk.referencedColumns .equals( referencedColumns ); } } public Table() { } public Table(String name) { this(); setName( name ); } 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 ); } 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 String getName() { return name; }
returns quoted name as it would be in the mapping file.
/** * returns quoted name as it would be in the mapping file. */
public String getQuotedName() { return quoted ? "`" + name + "`" : name; } public String getQuotedName(Dialect dialect) { return quoted ? dialect.openQuote() + name + dialect.closeQuote() : name; }
returns quoted name as it is in the mapping file.
/** * returns quoted name as it is in the mapping file. */
public String getQuotedSchema() { return schemaQuoted ? "`" + schema + "`" : schema; } public String getQuotedSchema(Dialect dialect) { return schemaQuoted ? dialect.openQuote() + schema + dialect.closeQuote() : schema; } public String getQuotedCatalog() { return catalogQuoted ? "`" + catalog + "`" : catalog; } public String getQuotedCatalog(Dialect dialect) { return catalogQuoted ? dialect.openQuote() + catalog + dialect.closeQuote() : catalog; } public void setName(String name) { if ( name.charAt( 0 ) == '`' ) { quoted = true; this.name = name.substring( 1, name.length() - 1 ); } else { this.name = name; } }
Return the column which is identified by column provided as argument.
Params:
  • column – column with atleast a name.
Returns: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.
/** * 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(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 ) { columns.put( column.getCanonicalName(), column ); column.uniqueInteger = 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 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 : isCatalogQuoted() ? catalog.hashCode() : catalog.toLowerCase().hashCode()); result = prime * result + ((name == null) ? 0 : isQuoted() ? name.hashCode() : name.toLowerCase().hashCode()); result = prime * result + ((schema == null) ? 0 : isSchemaQuoted() ? schema.hashCode() : schema.toLowerCase().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 isQuoted() ? name.equals(table.getName()) : name.equalsIgnoreCase(table.getName()) && ((schema == null && table.getSchema() != null) ? false : (schema == null) ? true : isSchemaQuoted() ? schema.equals(table.getSchema()) : schema.equalsIgnoreCase(table.getSchema())) && ((catalog == null && table.getCatalog() != null) ? false : (catalog == null) ? true : isCatalogQuoted() ? catalog.equals(table.getCatalog()) : catalog.equalsIgnoreCase(table.getCatalog())); } 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, Mapping p, TableMetadata tableInfo, String defaultCatalog, String defaultSchema) throws HibernateException { StringBuilder root = new StringBuilder( "alter table " ) .append( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) .append( ' ' ) .append( dialect.getAddColumnString() ); Iterator iter = getColumnIterator(); List results = new ArrayList(); while ( iter.hasNext() ) { Column column = (Column) iter.next(); ColumnMetadata columnInfo = tableInfo.getColumnMetadata( column.getName() ); 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, p ) ); 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() ); } } return results.iterator(); } public boolean hasPrimaryKey() { return getPrimaryKey() != null; } public String sqlTemporaryTableCreateString(Dialect dialect, Mapping mapping) throws HibernateException { StringBuilder buffer = new StringBuilder( dialect.getCreateTemporaryTableString() ) .append( ' ' ) .append( name ) .append( " (" ); Iterator itr = getColumnIterator(); while ( itr.hasNext() ) { final Column column = (Column) itr.next(); buffer.append( column.getQuotedName( dialect ) ).append( ' ' ); buffer.append( column.getSqlType( dialect, mapping ) ); if ( column.isNullable() ) { buffer.append( dialect.getNullColumnString() ); } else { buffer.append( " not null" ); } if ( itr.hasNext() ) { buffer.append( ", " ); } } buffer.append( ") " ); buffer.append( dialect.getCreateTemporaryTablePostfix() ); return buffer.toString(); } 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.hasDataTypeInIdentityColumn() ) { buf.append( col.getSqlType( dialect, p ) ); } buf.append( ' ' ) .append( dialect.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() ) { Iterator chiter = checkConstraints.iterator(); while ( chiter.hasNext() ) { buf.append( ", check (" ) .append( chiter.next() ) .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) { return createForeignKey( keyName, keyColumns, referencedEntityName, null ); } public ForeignKey createForeignKey(String keyName, List keyColumns, String referencedEntityName, List referencedColumns) { Object key = new ForeignKeyKey( keyColumns, referencedEntityName, referencedColumns ); ForeignKey fk = (ForeignKey) foreignKeys.get( key ); if ( fk == null ) { fk = new ForeignKey(); fk.setTable( this ); fk.setReferencedEntityName( referencedEntityName ); fk.addColumns( keyColumns.iterator() ); if ( referencedColumns != null ) { fk.addReferencedColumns( referencedColumns.iterator() ); } if ( keyName != null ) { fk.setName( keyName ); } else { fk.setName( Constraint.generateName( fk.generatedConstraintNamePrefix(), this, keyColumns ) ); } foreignKeys.put( key, fk ); } if ( keyName != null ) { fk.setName( keyName ); } return fk; } public String getSchema() { return schema; } public void setSchema(String schema) { if ( schema != null && schema.charAt( 0 ) == '`' ) { schemaQuoted = true; this.schema = schema.substring( 1, schema.length() - 1 ); } else { this.schema = schema; } } public String getCatalog() { return catalog; } public void setCatalog(String catalog) { if ( catalog != null && catalog.charAt( 0 ) == '`' ) { catalogQuoted = true; this.catalog = catalog.substring( 1, catalog.length() - 1 ); } else { this.catalog = catalog; } } // 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 boolean isSchemaQuoted() { return schemaQuoted; } public boolean isCatalogQuoted() { return catalogQuoted; } public boolean isQuoted() { return quoted; } public void setQuoted(boolean quoted) { this.quoted = quoted; } 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() + "." ); } if ( getSchema() != null ) { buf.append( getSchema() + "." ); } 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 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 ) { StringBuilder buf = new StringBuilder() .append( "comment on table " ) .append( tableName ) .append( " is '" ) .append( comment ) .append( "'" ); comments.add( buf.toString() ); } Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { Column column = (Column) iter.next(); String columnComment = column.getComment(); if ( columnComment != null ) { StringBuilder buf = new StringBuilder() .append( "comment on column " ) .append( tableName ) .append( '.' ) .append( column.getQuotedName( dialect ) ) .append( " is '" ) .append( columnComment ) .append( "'" ); comments.add( buf.toString() ); } } } return comments.iterator(); } }