All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.mapping.Table Maven / Gradle / Ivy

There is a newer version: 6.6.2.Final
Show newest version
/*
 * 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 .
 */
package org.hibernate.mapping;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.Remove;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.ContributableDatabaseObject;
import org.hibernate.boot.model.relational.InitCommand;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.extract.spi.TableInformation;

import org.hibernate.tool.schema.internal.StandardTableMigrator;
import org.jboss.logging.Logger;

import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.stream.Collectors.toList;

/**
 * A mapping model object representing a relational database {@linkplain jakarta.persistence.Table table}.
 *
 * @author Gavin King
 */
public class Table implements Serializable, ContributableDatabaseObject {
	private static final Logger log = Logger.getLogger( Table.class );
	private static final Column[] EMPTY_COLUMN_ARRAY = new Column[0];

	private final String contributor;

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

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

	private List> initCommandProducers;

	@Deprecated(since="6.2", forRemoval = true)
	public Table() {
		this( "orm" );
	}

	public Table(String contributor) {
		this( contributor, null );
	}

	public Table(String contributor, String name) {
		this.contributor = contributor;
		setName( name );
	}

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

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

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

	@Override
	public String getContributor() {
		return contributor;
	}

	public String getQualifiedName(SqlStringGenerationContext context) {
		return subselect != null
				? "( " + subselect + " )"
				: context.format( new QualifiedTableName( catalog, schema, name ) );
	}

	/**
	 * @deprecated Should build a {@link QualifiedTableName}
	 * then use {@link SqlStringGenerationContext#format(QualifiedTableName)}.
	 */
	@Deprecated
	public static String qualify(String catalog, String schema, String table) {
		final 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 Identifier getSchemaIdentifier() {
		return schema;
	}

	public Identifier getCatalogIdentifier() {
		return catalog;
	}

	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() ) {
			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 at least 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;
		}
		else {
			final Column existing = columns.get( column.getCanonicalName() );
			return column.equals( existing ) ? existing : null;
		}
	}

	public Column getColumn(Identifier name) {
		if ( name == null ) {
			return null;
		}
		return columns.get( name.getCanonicalName() );
	}

	@Internal
	public Column getColumn(InFlightMetadataCollector collector, String logicalName) {
		if ( name == null ) {
			return null;
		}
		return getColumn( new Column( collector.getPhysicalColumnName( this, logicalName ) ) );
	}

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

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

	@Internal
	public void columnRenamed(Column column) {
		for ( Map.Entry entry : columns.entrySet() ) {
			if ( entry.getValue() == column ) {
				columns.remove( entry.getKey() );
				columns.put( column.getCanonicalName(), column );
				break;
			}
		}
	}

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

	public Collection getColumns() {
		return columns.values();
	}

	public Map getIndexes() {
		return unmodifiableMap( indexes );
	}

	@Deprecated(since = "6.0")
	public Iterator getForeignKeyIterator() {
		return getForeignKeys().values().iterator();
	}

	public Map getForeignKeys() {
		return unmodifiableMap( foreignKeys );
	}

	@Deprecated(since = "6.0")
	public Iterator getUniqueKeyIterator() {
		return getUniqueKeys().values().iterator();
	}

	public Map getUniqueKeys() {
		cleanseUniqueKeyMapIfNeeded();
		return unmodifiableMap( 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 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. Furthermore, we
		//		pass the unique key to a primary key for reordering columns specified by the unique key.
		if ( !uniqueKeys.isEmpty() ) {
			if ( uniqueKeys.size() == 1 ) {
				// we have to worry about condition 2 above, but not condition 1
				final Map.Entry 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> uniqueKeyEntries = uniqueKeys.entrySet().iterator();
				while ( uniqueKeyEntries.hasNext() ) {
					final Map.Entry uniqueKeyEntry = uniqueKeyEntries.next();
					final UniqueKey uniqueKey = uniqueKeyEntry.getValue();
					boolean removeIt = false;

					// Never remove explicit unique keys based on column matching
					if ( !uniqueKey.isExplicit() ) {
						// condition 1 : check against other unique keys
						for ( UniqueKey otherUniqueKey : uniqueKeys.values() ) {
							// make sure it's 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 ( !removeIt && isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) {
						primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue());
						removeIt = true;
					}

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

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

	@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;
		}
		else if ( this == table ) {
			return true;
		}
		else {
			return Identifier.areEqual( name, table.name )
				&& Identifier.areEqual( schema, table.schema )
				&& Identifier.areEqual( catalog, table.catalog );
		}
	}

	@Deprecated(since = "6.2") @Remove
	public Iterator sqlAlterStrings(
			Dialect dialect,
			Metadata metadata,
			TableInformation tableInfo,
			SqlStringGenerationContext sqlStringGenerationContext) throws HibernateException {
		return StandardTableMigrator.sqlAlterStrings(this, dialect, metadata, tableInfo, sqlStringGenerationContext )
				.iterator();
	}

	public boolean isPrimaryKey(Column column) {
		return hasPrimaryKey()
			&& getPrimaryKey().getColumnSpan() == 1
			&& getPrimaryKey().containsColumn( column );
	}

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

	public PrimaryKey getPrimaryKey() {
		return primaryKey;
	}

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

	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;
	}

	/**
	 * If there is one given column, mark it unique, otherwise
	 * create a {@link UniqueKey} comprising the given columns.
	 */
	public void createUniqueKey(List keyColumns) {
		if ( keyColumns.size() == 1 ) {
			keyColumns.get(0).setUnique( true );
		}
		else {
			String keyName = Constraint.generateName( "UK_", this, keyColumns );
			UniqueKey uniqueKey = getOrCreateUniqueKey( keyName );
			for ( Column keyColumn : keyColumns ) {
				uniqueKey.addColumn( keyColumn );
			}
		}
	}

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

	public UniqueKey getOrCreateUniqueKey(String keyName) {
		UniqueKey uniqueKey = uniqueKeys.get( keyName );
		if ( uniqueKey == null ) {
			uniqueKey = new UniqueKey();
			uniqueKey.setName( keyName );
			uniqueKey.setTable( this );
			uniqueKeys.put( keyName, uniqueKey );
		}
		return uniqueKey;
	}

	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 foreignKey = foreignKeys.get( key );
		if ( foreignKey == null ) {
			foreignKey = new ForeignKey();
			foreignKey.setTable( this );
			foreignKey.setReferencedEntityName( referencedEntityName );
			foreignKey.setKeyDefinition( keyDefinition );
			for ( Column keyColumn : keyColumns ) {
				foreignKey.addColumn( keyColumn );
			}
			if ( referencedColumns != null ) {
				foreignKey.addReferencedColumns( referencedColumns );
			}

			// 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).
			foreignKey.setName( keyName );

			foreignKeys.put( key, foreignKey );
		}

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

		return foreignKey;
	}

	/**
	 * Checks for uniqueKey containing only whole primary key and sets
	 * 	order of the columns accordingly
	 */
	private void checkPrimaryKeyUniqueKey() {
		final Iterator> uniqueKeyEntries = uniqueKeys.entrySet().iterator();
		while ( uniqueKeyEntries.hasNext() ) {
			final Map.Entry uniqueKeyEntry = uniqueKeyEntries.next();

			if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) {
				primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue());
				uniqueKeyEntries.remove();
			}
		}
	}

	// 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;
	}

	@Deprecated(since = "6.2")
	public void addCheckConstraint(String constraint) {
		addCheck( new CheckConstraint( constraint ) );
	}

	public void addCheck(CheckConstraint check) {
		checkConstraints.add( check );
	}

	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() {
		final StringBuilder string = new StringBuilder()
				.append( getClass().getSimpleName() )
				.append( '(' );
		if ( getCatalog() != null ) {
			string.append( getCatalog() ).append( "." );
		}
		if ( getSchema() != null ) {
			string.append( getSchema() ).append( "." );
		}
		string.append( getName() ).append( ')' );
		return string.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 boolean isView() {
		return viewQuery != null;
	}

	public String getComment() {
		return comment;
	}

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

	@Deprecated(since = "6.0")
	public Iterator getCheckConstraintsIterator() {
		return getCheckConstraints().iterator();
	}

	@Deprecated(since = "6.2")
	public List getCheckConstraints() {
		return checkConstraints.stream().map( CheckConstraint::getConstraint ).collect( toList() );
	}

	public List getChecks() {
		return unmodifiableList( checkConstraints );
	}

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

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

	@Internal
	public void reorderColumns(List columns) {
		assert this.columns.size() == columns.size() && this.columns.values().containsAll( columns );
		this.columns.clear();
		for ( Column column : columns ) {
			this.columns.put( column.getCanonicalName(), column );
		}
	}

	public String getViewQuery() {
		return viewQuery;
	}

	public void setViewQuery(String viewQuery) {
		this.viewQuery = viewQuery;
	}

	public static class ForeignKeyKey implements Serializable {
		private final String referencedClassName;
		private final Column[] columns;
		private final Column[] referencedColumns;

		ForeignKeyKey(List columns, String referencedClassName, List referencedColumns) {
			Objects.requireNonNull( columns );
			Objects.requireNonNull( referencedClassName );
			this.referencedClassName = referencedClassName;
			this.columns = columns.toArray( EMPTY_COLUMN_ARRAY );
			this.referencedColumns = referencedColumns != null
					? referencedColumns.toArray( EMPTY_COLUMN_ARRAY )
					: EMPTY_COLUMN_ARRAY;
		}

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

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

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

	/**
	 * @deprecated Use {@link #addInitCommand(Function)} instead.
	 */
	@Deprecated
	public void addInitCommand(InitCommand command) {
		addInitCommand( ignored -> command );
	}

	public void addInitCommand(Function commandProducer) {
		if ( initCommandProducers == null ) {
			initCommandProducers = new ArrayList<>();
		}
		initCommandProducers.add( commandProducer );
	}

	public List getInitCommands(SqlStringGenerationContext context) {
		if ( initCommandProducers == null ) {
			return emptyList();
		}
		else {
			final List initCommands = new ArrayList<>();
			for ( Function producer : initCommandProducers ) {
				initCommands.add( producer.apply( context ) );
			}
			return unmodifiableList( initCommands );
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy