HHH-17103 massively simplify @Index + @UniqueKey handling

There was a whole completely unnecessary second-pass-based lifecycle
making everything way more complicated than it needed to be.
This commit is contained in:
Gavin King 2023-08-18 12:06:41 +02:00
parent 5bfe11fd27
commit 367a647412
10 changed files with 370 additions and 342 deletions

View File

@ -19,7 +19,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.DuplicateMappingException;
import org.hibernate.HibernateException;
@ -57,9 +56,6 @@ import org.hibernate.boot.model.internal.SetBasicValueTypeSecondPass;
import org.hibernate.boot.model.internal.UniqueConstraintHolder;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitForeignKeyNameSource;
import org.hibernate.boot.model.naming.ImplicitIndexNameSource;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.ExportableProducer;
@ -92,19 +88,15 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.DenormalizedTable;
import org.hibernate.mapping.FetchProfile;
import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.Formula;
import org.hibernate.mapping.IdentifierCollection;
import org.hibernate.mapping.Index;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.query.named.NamedObjectRepository;
@ -122,7 +114,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.MapsId;
import static java.util.Collections.emptyList;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
/**
@ -181,8 +172,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
private Map<String, String> mappedByResolver;
private Map<String, String> propertyRefResolver;
private Set<DelayedPropertyReferenceHandler> delayedPropertyReferenceHandlers;
private Map<Table, List<UniqueConstraintHolder>> uniqueConstraintHoldersByTable;
private Map<Table, List<IndexHolder>> indexHoldersByTable;
private List<Function<MetadataBuildingContext, Boolean>> valueResolvers;
public InFlightMetadataCollectorImpl(
@ -1440,59 +1429,16 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
@Override @Deprecated(forRemoval = true)
public void addUniqueConstraints(Table table, List<String[]> uniqueConstraints) {
List<UniqueConstraintHolder> constraintHolders = new ArrayList<>( uniqueConstraints.size() );
int keyNameBase = determineCurrentNumberOfUniqueConstraintHolders( table );
for ( String[] columns : uniqueConstraints ) {
final String keyName = "key" + keyNameBase++;
constraintHolders.add(
new UniqueConstraintHolder( keyName, columns )
);
}
addUniqueConstraintHolders( table, constraintHolders );
throw new UnsupportedOperationException("addUniqueConstraints() will be removed");
}
private int determineCurrentNumberOfUniqueConstraintHolders(Table table) {
List currentHolders = uniqueConstraintHoldersByTable == null ? null : uniqueConstraintHoldersByTable.get( table );
return currentHolders == null ? 0 : currentHolders.size();
}
@Override
@Override @Deprecated(forRemoval = true)
public void addUniqueConstraintHolders(Table table, List<UniqueConstraintHolder> holders) {
List<UniqueConstraintHolder> holderList = null;
if ( uniqueConstraintHoldersByTable == null ) {
uniqueConstraintHoldersByTable = new HashMap<>();
}
else {
holderList = uniqueConstraintHoldersByTable.get( table );
}
if ( holderList == null ) {
holderList = new ArrayList<>();
uniqueConstraintHoldersByTable.put( table, holderList );
}
holderList.addAll( holders );
throw new UnsupportedOperationException("addUniqueConstraintHolders() will be removed");
}
@Override
@Override @Deprecated(forRemoval = true)
public void addIndexHolders(Table table, List<IndexHolder> holders) {
List<IndexHolder> holderList = null;
if ( indexHoldersByTable == null ) {
indexHoldersByTable = new HashMap<>();
}
else {
holderList = indexHoldersByTable.get( table );
}
if ( holderList == null ) {
holderList = new ArrayList<>();
indexHoldersByTable.put( table, holderList );
}
holderList.addAll( holders );
throw new UnsupportedOperationException("addIndexHolders() will be removed");
}
private final Map<String,EntityTableXrefImpl> entityTableXrefMap = new HashMap<>();
@ -1848,9 +1794,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
secondPassCompileForeignKeys( buildingContext );
processUniqueConstraintHolders( buildingContext );
processIndexHolders( buildingContext );
processNaturalIdUniqueKeyBinders();
processCachingOverrides();
@ -2044,18 +1987,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
}
}
private List<Identifier> toIdentifiers(String[] names) {
if ( names == null ) {
return emptyList();
}
final List<Identifier> columnNames = arrayList( names.length );
for ( String name : names ) {
columnNames.add( getDatabase().toIdentifier( name ) );
}
return columnNames;
}
private List<Identifier> extractColumnNames(List<Column> columns) {
if ( columns == null || columns.isEmpty() ) {
return emptyList();
@ -2082,141 +2013,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
delayedPropertyReferenceHandlers.clear();
}
private void processUniqueConstraintHolders(MetadataBuildingContext buildingContext) {
if ( uniqueConstraintHoldersByTable == null ) {
return;
}
for ( Map.Entry<Table, List<UniqueConstraintHolder>> tableListEntry : uniqueConstraintHoldersByTable.entrySet() ) {
final Table table = tableListEntry.getKey();
final List<UniqueConstraintHolder> uniqueConstraints = tableListEntry.getValue();
for ( UniqueConstraintHolder holder : uniqueConstraints ) {
buildUniqueKeyFromColumnNames( table, holder.getName(), holder.isNameExplicit(), holder.getColumns(), buildingContext );
}
}
uniqueConstraintHoldersByTable.clear();
}
private void buildUniqueKeyFromColumnNames(
Table table,
String keyName,
boolean nameExplicit,
String[] columnNames,
MetadataBuildingContext context) {
buildUniqueKeyFromColumnNames( table, keyName, nameExplicit, columnNames, null, true, context );
}
private void buildUniqueKeyFromColumnNames(
final Table table,
String keyName,
boolean nameExplicit,
final String[] columnNames,
String[] orderings,
boolean unique,
final MetadataBuildingContext context) {
final int size = columnNames.length;
if ( size == 0 ) {
throw new AnnotationException( ( unique ? "Unique constraint" : "Index" )
+ ( isEmpty( keyName ) ? "" : " '" + keyName + "'" )
+ " on table '" + table.getName() + "' has no columns" );
}
final Selectable[] columns = new Selectable[size];
for ( int index = 0; index < size; index++ ) {
final String columnName = columnNames[index];
if ( isEmpty( columnName ) ) {
throw new AnnotationException( ( unique ? "Unique constraint" : "Index" )
+ ( isEmpty( keyName ) ? "" : " '" + keyName + "'" )
+ " on table '" + table.getName() + "' has an empty column name" );
}
columns[index] = indexColumn( table, context, columnName);
}
createIndexOrUniqueKey( table, keyName, nameExplicit, columnNames, orderings, unique, context, columns );
}
private static Selectable indexColumn(Table table, MetadataBuildingContext buildingContext, String logicalColumnName) {
if ( logicalColumnName.startsWith("(") ) {
return new Formula( logicalColumnName );
}
else {
Column column;
try {
column = table.getColumn( buildingContext.getMetadataCollector(), logicalColumnName );
}
catch (MappingException me) {
column = null;
}
if ( column != null ) {
return column;
}
else {
// assume it's a SQL formula with missing parens
return new Formula( "(" + logicalColumnName + ")" );
// throw new AnnotationException(
// "Table '" + table.getName() + "' has no column named '" + logicalColumnName
// + "' matching the column specified in '@UniqueConstraint'"
// );
}
}
}
private void createIndexOrUniqueKey(
Table table,
String originalKeyName,
boolean nameExplicit,
String[] columnNames,
String[] orderings,
boolean unique,
MetadataBuildingContext context,
Selectable[] columns) {
final ImplicitNamingStrategy naming = getMetadataBuildingOptions().getImplicitNamingStrategy();
final IndexOrUniqueKeyNameSource source =
new IndexOrUniqueKeyNameSource( context, table, columnNames, originalKeyName );
final Dialect dialect = getDatabase().getJdbcEnvironment().getDialect();
boolean hasFormula = false;
for ( Selectable selectable : columns ) {
if ( selectable.isFormula() ) {
hasFormula = true;
}
}
if ( unique && !hasFormula ) {
final String keyName = naming.determineUniqueKeyName( source ).render( dialect );
final UniqueKey uniqueKey = table.getOrCreateUniqueKey( keyName );
uniqueKey.setNameExplicit( nameExplicit );
for ( int i = 0; i < columns.length; i++ ) {
uniqueKey.addColumn( (Column) columns[i], orderings != null ? orderings[i] : null );
}
}
else {
final String keyName = naming.determineIndexName( source ).render( dialect );
final Index index = table.getOrCreateIndex( keyName );
index.setUnique( unique );
for ( int i = 0; i < columns.length; i++ ) {
index.addColumn( columns[i], orderings != null ? orderings[i] : null );
}
}
}
private void processIndexHolders(MetadataBuildingContext context) {
if ( indexHoldersByTable != null ) {
for ( Map.Entry<Table, List<IndexHolder>> entry : indexHoldersByTable.entrySet() ) {
final Table table = entry.getKey();
final List<IndexHolder> indexHolders = entry.getValue();
for ( IndexHolder holder : indexHolders) {
buildUniqueKeyFromColumnNames(
table,
holder.getName(),
!holder.getName().isEmpty(),
holder.getColumns(),
holder.getOrdering(),
holder.isUnique(),
context
);
}
}
}
}
private Map<String,NaturalIdUniqueKeyBinder> naturalIdUniqueKeyBinderMap;
@Override
@ -2394,46 +2190,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
}
}
private class IndexOrUniqueKeyNameSource implements ImplicitIndexNameSource, ImplicitUniqueKeyNameSource {
private final MetadataBuildingContext buildingContext;
private final Table table;
private final String[] columnNames;
private final String originalKeyName;
public IndexOrUniqueKeyNameSource(MetadataBuildingContext buildingContext, Table table, String[] columnNames, String originalKeyName) {
this.buildingContext = buildingContext;
this.table = table;
this.columnNames = columnNames;
this.originalKeyName = originalKeyName;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return buildingContext;
}
@Override
public Identifier getTableName() {
return table.getNameIdentifier();
}
private List<Identifier> columnNameIdentifiers;
@Override
public List<Identifier> getColumnNames() {
// be lazy about building these
if ( columnNameIdentifiers == null ) {
columnNameIdentifiers = toIdentifiers(columnNames);
}
return columnNameIdentifiers;
}
@Override
public Identifier getUserProvidedIdentifier() {
return originalKeyName != null ? Identifier.toIdentifier(originalKeyName) : null;
}
}
private class ForeignKeyNameSource implements ImplicitForeignKeyNameSource {
final List<Identifier> columnNames;
private final ForeignKey foreignKey;

View File

@ -26,6 +26,7 @@ import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.ObjectNameNormalizer;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.source.spi.AttributePath;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.CoreMessageLogger;
@ -433,17 +434,14 @@ public class AnnotatedColumn {
protected void addColumnBinding(SimpleValue value) {
final String logicalColumnName;
final MetadataBuildingContext context = getBuildingContext();
final InFlightMetadataCollector collector = context.getMetadataCollector();
if ( isNotEmpty( this.logicalColumnName ) ) {
logicalColumnName = this.logicalColumnName;
}
else {
final ObjectNameNormalizer normalizer = getBuildingContext().getObjectNameNormalizer();
final Database database = getBuildingContext().getMetadataCollector().getDatabase();
final ImplicitNamingStrategy implicitNamingStrategy = getBuildingContext().getBuildingOptions()
.getImplicitNamingStrategy();
final Identifier implicitName = normalizer.normalizeIdentifierQuoting(
implicitNamingStrategy.determineBasicColumnName(
final Identifier implicitName = context.getObjectNameNormalizer().normalizeIdentifierQuoting(
context.getBuildingOptions().getImplicitNamingStrategy().determineBasicColumnName(
new ImplicitBasicColumnNameSource() {
@Override
public AttributePath getAttributePath() {
@ -457,14 +455,14 @@ public class AnnotatedColumn {
@Override
public MetadataBuildingContext getBuildingContext() {
return AnnotatedColumn.this.getBuildingContext();
return context;
}
}
)
);
logicalColumnName = implicitName.render( database.getDialect() );
logicalColumnName = implicitName.render( collector.getDatabase().getDialect() );
}
getBuildingContext().getMetadataCollector().addColumnNameBinding( value.getTable(), logicalColumnName, getMappingColumn() );
collector.addColumnNameBinding( value.getTable(), logicalColumnName, getMappingColumn() );
}
public void forceNotNull() {

View File

@ -12,8 +12,6 @@ import java.util.Map;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.MutableInteger;
@ -69,26 +67,7 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
@Override
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
final PersistentClass referencedPersistentClass = persistentClasses.get( referencedEntityName );
if ( referencedPersistentClass == null ) {
// TODO: much better error message if this is something that can really happen!
throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'");
}
final KeyValue identifier = referencedPersistentClass.getIdentifier();
if ( !(identifier instanceof Component) ) {
// The entity with the @MapsId annotation has a composite
// id type, but the referenced entity has a basic-typed id.
// Therefore, the @MapsId annotation should have specified
// a property of the composite id that has the foreign key
throw new AnnotationException(
"Missing 'value' in '@MapsId' annotation of association '" + propertyName
+ "' of entity '" + component.getOwner().getEntityName()
+ "' with composite identifier type"
+ " ('@MapsId' must specify a property of the '@EmbeddedId' class which has the foreign key of '"
+ referencedEntityName + "')"
);
}
final Component referencedComponent = (Component) identifier;
final Component referencedComponent = getReferencedComponent( referencedPersistentClass );
//prepare column name structure
boolean isExplicitReference = true;
@ -113,7 +92,6 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
final Property property;
if ( referencedProperty.isComposite() ) {
property = createComponentProperty(
referencedPersistentClass,
isExplicitReference,
columnByReferencedName,
index,
@ -133,8 +111,29 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
}
}
private Component getReferencedComponent(PersistentClass referencedPersistentClass) {
if ( referencedPersistentClass == null ) {
// TODO: much better error message if this is something that can really happen!
throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'");
}
final KeyValue identifier = referencedPersistentClass.getIdentifier();
if ( !(identifier instanceof Component) ) {
// The entity with the @MapsId annotation has a composite
// id type, but the referenced entity has a basic-typed id.
// Therefore, the @MapsId annotation should have specified
// a property of the composite id that has the foreign key
throw new AnnotationException(
"Missing 'value' in '@MapsId' annotation of association '" + propertyName
+ "' of entity '" + component.getOwner().getEntityName()
+ "' with composite identifier type"
+ " ('@MapsId' must specify a property of the '@EmbeddedId' class which has the foreign key of '"
+ referencedEntityName + "')"
);
}
return (Component) identifier;
}
private Property createComponentProperty(
PersistentClass referencedPersistentClass,
boolean isExplicitReference,
Map<String, AnnotatedJoinColumn> columnByReferencedName,
MutableInteger index,
@ -156,12 +155,21 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
for ( Property referencedComponentProperty : referencedValue.getProperties() ) {
if ( referencedComponentProperty.isComposite() ) {
Property componentProperty = createComponentProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
value.addProperty( createComponentProperty(
isExplicitReference,
columnByReferencedName,
index,
referencedComponentProperty
) );
}
else {
Property componentProperty = createSimpleProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
value.addProperty( createSimpleProperty(
referencedValue.getOwner(),
isExplicitReference,
columnByReferencedName,
index,
referencedComponentProperty
) );
}
}
@ -228,10 +236,11 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
: joinColumn.getName();
final Database database = buildingContext.getMetadataCollector().getDatabase();
final PhysicalNamingStrategy physicalNamingStrategy = buildingContext.getBuildingOptions().getPhysicalNamingStrategy();
final Identifier explicitName = database.toIdentifier( columnName );
final Identifier physicalName = physicalNamingStrategy.toPhysicalColumnName( explicitName, database.getJdbcEnvironment() );
value.addColumn( new Column( physicalName.render( database.getDialect() ) ) );
final String physicalName =
buildingContext.getBuildingOptions().getPhysicalNamingStrategy()
.toPhysicalColumnName( database.toIdentifier( columnName ), database.getJdbcEnvironment() )
.render( database.getDialect() );
value.addColumn( new Column( physicalName ) );
if ( joinColumn != null ) {
applyComponentColumnSizeValueToJoinColumn( column, joinColumn );
joinColumn.linkWithValue( value );
@ -243,7 +252,7 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass {
}
private void applyComponentColumnSizeValueToJoinColumn(Column column, AnnotatedJoinColumn joinColumn) {
Column mappingColumn = joinColumn.getMappingColumn();
final Column mappingColumn = joinColumn.getMappingColumn();
mappingColumn.setLength( column.getLength() );
mappingColumn.setPrecision( column.getPrecision() );
mappingColumn.setScale( column.getScale() );

View File

@ -2051,8 +2051,7 @@ public class EntityBinder {
secondaryTable.uniqueConstraints()
);
final Table table = join.getTable();
context.getMetadataCollector()
.addIndexHolders( table, TableBinder.buildIndexHolders( secondaryTable.indexes() ) );
new IndexBinder( context ).bindIndexes( table, secondaryTable.indexes() );
return join;
}
@ -2239,9 +2238,7 @@ public class EntityBinder {
public void processComplementaryTableDefinitions(jakarta.persistence.Table table) {
if ( table != null ) {
final Table classTable = persistentClass.getTable();
context.getMetadataCollector()
.addIndexHolders( classTable, TableBinder.buildIndexHolders( table.indexes() ) );
new IndexBinder( context ).bindIndexes( persistentClass.getTable(), table.indexes() );
}
}

View File

@ -0,0 +1,288 @@
/*
* 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.boot.model.internal;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.AnnotationException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitIndexNameSource;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Formula;
import org.hibernate.mapping.Index;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import static java.util.Collections.emptyList;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
/**
* Responsible for interpreting {@link jakarta.persistence.Index} and
* {@link UniqueConstraint} annotations.
*
* @author Gavin King
*/
class IndexBinder {
private final MetadataBuildingContext context;
IndexBinder(MetadataBuildingContext context) {
this.context = context;
}
private Database getDatabase() {
return context.getMetadataCollector().getDatabase();
}
private ImplicitNamingStrategy getImplicitNamingStrategy() {
return context.getBuildingOptions().getImplicitNamingStrategy();
}
private PhysicalNamingStrategy getPhysicalNamingStrategy() {
return context.getBuildingOptions().getPhysicalNamingStrategy();
}
private Dialect getDialect() {
return getDatabase().getJdbcEnvironment().getDialect();
}
private Selectable selectable(Table table, String columnNameOrFormula) {
if ( columnNameOrFormula.startsWith("(") ) {
return new Formula( columnNameOrFormula );
}
else {
return createColumn( columnNameOrFormula );
// Column column;
// try {
// column = table.getColumn( context.getMetadataCollector(), columnNameOrFormula );
// }
// catch (MappingException me) {
// column = null;
// }
// return column == null
// // Assume it's actually a formula with missing parens
// ? new Formula( "(" + columnNameOrFormula + ")" )
// : column;
}
}
private Column column(Table table, String columnName) {
return createColumn( columnName );
// Column column;
// try {
// column = table.getColumn( context.getMetadataCollector(), columnName );
// }
// catch (MappingException me) {
// column = null;
// }
// if ( column != null ) {
// return column;
// }
// else {
// throw new AnnotationException(
// "Table '" + table.getName() + "' has no column named '" + columnName
// + "' matching the column specified in '@UniqueConstraint'"
// );
// }
}
private Column createColumn(String logicalName) {
final Database database = getDatabase();
final String physicalName =
getPhysicalNamingStrategy()
.toPhysicalColumnName( database.toIdentifier( logicalName ), database.getJdbcEnvironment() )
.render( getDialect() );
return new Column( physicalName );
}
private Selectable[] selectables(Table table, String name, final String[] columnNames) {
final int size = columnNames.length;
if ( size == 0 ) {
throw new AnnotationException( "Index"
+ ( isEmpty( name ) ? "" : " '" + name + "'" )
+ " on table '" + table.getName() + "' has no columns" );
}
final Selectable[] columns = new Selectable[size];
for ( int index = 0; index < size; index++ ) {
final String columnName = columnNames[index];
if ( isEmpty( columnName ) ) {
throw new AnnotationException( "Index"
+ ( isEmpty( name ) ? "" : " '" + name + "'" )
+ " on table '" + table.getName() + "' has an empty column name" );
}
columns[index] = selectable( table, columnName );
}
return columns;
}
private Column[] columns(Table table, String name, final String[] columnNames) {
final int size = columnNames.length;
if ( size == 0 ) {
throw new AnnotationException( "Unique constraint"
+ ( isEmpty( name ) ? "" : " '" + name + "'" )
+ " on table '" + table.getName() + "' has no columns" );
}
final Column[] columns = new Column[size];
for ( int index = 0; index < size; index++ ) {
final String columnName = columnNames[index];
if ( isEmpty( columnName ) ) {
throw new AnnotationException( "Unique constraint"
+ ( isEmpty( name ) ? "" : " '" + name + "'" )
+ " on table '" + table.getName() + "' has an empty column name" );
}
columns[index] = column( table, columnName );
}
return columns;
}
private void createIndexOrUniqueKey(
Table table,
String originalKeyName,
boolean nameExplicit,
String[] columnNames,
String[] orderings,
boolean unique,
Selectable[] columns) {
final IndexOrUniqueKeyNameSource source =
new IndexOrUniqueKeyNameSource( context, table, columnNames, originalKeyName );
boolean hasFormula = false;
for ( Selectable selectable : columns ) {
if ( selectable.isFormula() ) {
hasFormula = true;
}
}
if ( unique && !hasFormula ) {
final String keyName = getImplicitNamingStrategy().determineUniqueKeyName( source ).render( getDialect() );
final UniqueKey uniqueKey = table.getOrCreateUniqueKey( keyName );
uniqueKey.setNameExplicit( nameExplicit );
for ( int i = 0; i < columns.length; i++ ) {
uniqueKey.addColumn( (Column) columns[i], orderings != null ? orderings[i] : null );
}
}
else {
final String keyName = getImplicitNamingStrategy().determineIndexName( source ).render( getDialect() );
final Index index = table.getOrCreateIndex( keyName );
index.setUnique( unique );
for ( int i = 0; i < columns.length; i++ ) {
index.addColumn( columns[i], orderings != null ? orderings[i] : null );
}
}
}
void bindIndexes(Table table, jakarta.persistence.Index[] indexes) {
for ( jakarta.persistence.Index index : indexes ) {
final StringTokenizer tokenizer = new StringTokenizer( index.columnList(), "," );
final List<String> parsed = new ArrayList<>();
while ( tokenizer.hasMoreElements() ) {
final String trimmed = tokenizer.nextToken().trim();
if ( !trimmed.isEmpty() ) {
parsed.add( trimmed ) ;
}
}
final String[] columnExpressions = new String[parsed.size()];
final String[] ordering = new String[parsed.size()];
initializeColumns( columnExpressions, ordering, parsed );
final String name = index.name();
final boolean unique = index.unique();
createIndexOrUniqueKey( table, name, !name.isEmpty(), columnExpressions, ordering, unique,
selectables( table, name, columnExpressions ) );
}
}
void bindUniqueConstraints(Table table, UniqueConstraint[] constraints) {
for ( UniqueConstraint constraint : constraints ) {
final String name = constraint.name();
final String[] columnNames = constraint.columnNames();
createIndexOrUniqueKey( table, name, !name.isEmpty(), columnNames, null, true,
columns( table, name, columnNames ) );
}
}
private void initializeColumns(String[] columns, String[] ordering, List<String> list) {
for ( int i = 0, size = list.size(); i < size; i++ ) {
final String description = list.get( i );
final String tmp = description.toLowerCase(Locale.ROOT);
if ( tmp.endsWith( " desc" ) ) {
columns[i] = description.substring( 0, description.length() - 5 );
ordering[i] = "desc";
}
else if ( tmp.endsWith( " asc" ) ) {
columns[i] = description.substring( 0, description.length() - 4 );
ordering[i] = "asc";
}
else {
columns[i] = description;
ordering[i] = null;
}
}
}
private class IndexOrUniqueKeyNameSource implements ImplicitIndexNameSource, ImplicitUniqueKeyNameSource {
private final MetadataBuildingContext buildingContext;
private final Table table;
private final String[] columnNames;
private final String originalKeyName;
public IndexOrUniqueKeyNameSource(MetadataBuildingContext buildingContext, Table table, String[] columnNames, String originalKeyName) {
this.buildingContext = buildingContext;
this.table = table;
this.columnNames = columnNames;
this.originalKeyName = originalKeyName;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return buildingContext;
}
@Override
public Identifier getTableName() {
return table.getNameIdentifier();
}
private List<Identifier> columnNameIdentifiers;
@Override
public List<Identifier> getColumnNames() {
// be lazy about building these
if ( columnNameIdentifiers == null ) {
columnNameIdentifiers = toIdentifiers( columnNames );
}
return columnNameIdentifiers;
}
@Override
public Identifier getUserProvidedIdentifier() {
return originalKeyName != null ? Identifier.toIdentifier( originalKeyName ) : null;
}
}
private List<Identifier> toIdentifiers(String[] names) {
if ( names == null ) {
return emptyList();
}
final List<Identifier> columnNames = arrayList( names.length );
for ( String name : names ) {
columnNames.add( getDatabase().toIdentifier( name ) );
}
return columnNames;
}
}

View File

@ -15,7 +15,10 @@ import jakarta.persistence.Index;
/**
* @author Strong Liu
*
* @deprecated no longer used, will be removed in next release
*/
@Deprecated(since = "6.3", forRemoval = true)
public class IndexHolder {
private final String name;
private final String[] columns;

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.boot.model.internal;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.AnnotationException;
@ -43,13 +42,10 @@ import org.jboss.logging.Logger;
import jakarta.persistence.Index;
import jakarta.persistence.UniqueConstraint;
import static java.util.Collections.emptyList;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.isQuoted;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.unquote;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
/**
* Stateful binder responsible for producing instances of {@link Table}.
@ -488,12 +484,12 @@ public class TableBinder {
logicalName, isAbstract, buildingContext, subselect,
denormalizedSuperTableXref, metadataCollector );
if ( isNotEmpty( uniqueConstraints ) ) {
metadataCollector.addUniqueConstraintHolders( table, buildUniqueConstraintHolders( uniqueConstraints ) );
if ( uniqueConstraints != null ) {
new IndexBinder( buildingContext ).bindUniqueConstraints( table, uniqueConstraints );
}
if ( isNotEmpty( indexes ) ) {
metadataCollector.addIndexHolders( table, buildIndexHolders( indexes ) );
if ( indexes != null ) {
new IndexBinder( buildingContext ).bindIndexes( table, indexes );
}
metadataCollector.addTableNameBinding( logicalName, table );
@ -815,35 +811,6 @@ public class TableBinder {
}
}
/**
* Build a list of {@link IndexHolder} instances given a
* list of {@link Index} annotations.
*/
static List<IndexHolder> buildIndexHolders(Index[] indexes) {
List<IndexHolder> holders = new ArrayList<>( indexes.length );
for ( Index index : indexes ) {
holders.add( new IndexHolder( index ) );
}
return holders;
}
/**
* Build a list of {@link UniqueConstraintHolder} instances
* given a list of {@link UniqueConstraint} annotations.
*/
static List<UniqueConstraintHolder> buildUniqueConstraintHolders(UniqueConstraint[] uniqueConstraints) {
if ( uniqueConstraints == null || uniqueConstraints.length == 0 ) {
return emptyList();
}
else {
final List<UniqueConstraintHolder> result = arrayList( uniqueConstraints.length );
for ( UniqueConstraint uniqueConstraint : uniqueConstraints ) {
result.add( new UniqueConstraintHolder( uniqueConstraint ) );
}
return result;
}
}
public void setDefaultName(
String ownerClassName,
String ownerEntity,

View File

@ -10,14 +10,13 @@ package org.hibernate.boot.model.internal;
import jakarta.persistence.UniqueConstraint;
/**
* {@link jakarta.persistence.UniqueConstraint} annotations are handled via second pass. I do not
* understand the reasons why at this time, so here I use a holder object to hold the information
* needed to create the unique constraint. The ability to name it is new, and so the code used to
* simply keep this as a String array (the column names).
* {@link jakarta.persistence.UniqueConstraint} annotations are handled via second pass.
*
* @author Steve Ebersole
*
* @deprecated no longer used, will be removed in next release
*/
// Isn't this ultimately the same as IndexOrUniqueKeySecondPass?
@Deprecated(since = "6.3", forRemoval = true)
public class UniqueConstraintHolder {
private final String name;
private final String[] columns;

View File

@ -354,11 +354,19 @@ public interface InFlightMetadataCollector extends MetadataImplementor {
String getFromMappedBy(String ownerEntityName, String propertyName);
/**
* @deprecated no longer used
* @deprecated no longer implemented, will be removed in next release
*/
@Deprecated(forRemoval = true)
void addUniqueConstraints(Table table, List<String[]> uniqueConstraints);
/**
* @deprecated no longer implemented, will be removed in next release
*/
@Deprecated(forRemoval = true)
void addUniqueConstraintHolders(Table table, List<UniqueConstraintHolder> uniqueConstraints);
/**
* @deprecated no longer implemented, will be removed in next release
*/
@Deprecated(forRemoval = true)
void addIndexHolders(Table table, List<IndexHolder> indexHolders);

View File

@ -12,6 +12,8 @@ import java.util.Map;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
/**
* A mapping model object representing a {@linkplain jakarta.persistence.UniqueConstraint unique key}
* constraint on a relational database table.
@ -35,7 +37,7 @@ public class UniqueKey extends Constraint {
public void addColumn(Column column, String order) {
addColumn( column );
if ( StringHelper.isNotEmpty( order ) ) {
if ( isNotEmpty( order ) ) {
columnOrderMap.put( column, order );
}
}
@ -63,7 +65,8 @@ public class UniqueKey extends Constraint {
public boolean hasNullableColumn() {
for ( Column column : getColumns() ) {
if ( column.isNullable() ) {
final Column tableColumn = getTable().getColumn( column );
if ( tableColumn != null && tableColumn.isNullable() ) {
return true;
}
}