diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index f47d61e696..ad3a9c2926 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -48,7 +48,7 @@ import org.hibernate.boot.model.internal.CreateKeySecondPass; import org.hibernate.boot.model.internal.FkSecondPass; import org.hibernate.boot.model.internal.IdGeneratorResolverSecondPass; import org.hibernate.boot.model.internal.ImplicitToOneJoinTableSecondPass; -import org.hibernate.boot.model.internal.JPAIndexHolder; +import org.hibernate.boot.model.internal.IndexHolder; import org.hibernate.boot.model.internal.OptionalDeterminationSecondPass; import org.hibernate.boot.model.internal.QuerySecondPass; import org.hibernate.boot.model.internal.SecondaryTableFromAnnotationSecondPass; @@ -58,6 +58,7 @@ 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; @@ -91,6 +92,7 @@ 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; @@ -99,6 +101,7 @@ 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; @@ -119,6 +122,7 @@ 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; /** @@ -178,7 +182,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, private Map propertyRefResolver; private Set delayedPropertyReferenceHandlers; private Map> uniqueConstraintHoldersByTable; - private Map> jpaIndexHoldersByTable; + private Map> indexHoldersByTable; private List> valueResolvers; public InFlightMetadataCollectorImpl( @@ -1442,7 +1446,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, for ( String[] columns : uniqueConstraints ) { final String keyName = "key" + keyNameBase++; constraintHolders.add( - new UniqueConstraintHolder().setName( keyName ).setColumns( columns ) + new UniqueConstraintHolder( keyName, columns ) ); } addUniqueConstraintHolders( table, constraintHolders ); @@ -1454,7 +1458,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, } @Override - public void addUniqueConstraintHolders(Table table, List uniqueConstraintHolders) { + public void addUniqueConstraintHolders(Table table, List holders) { List holderList = null; if ( uniqueConstraintHoldersByTable == null ) { @@ -1469,23 +1473,23 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, uniqueConstraintHoldersByTable.put( table, holderList ); } - holderList.addAll( uniqueConstraintHolders ); + holderList.addAll( holders ); } @Override - public void addJpaIndexHolders(Table table, List holders) { - List holderList = null; + public void addIndexHolders(Table table, List holders) { + List holderList = null; - if ( jpaIndexHoldersByTable == null ) { - jpaIndexHoldersByTable = new HashMap<>(); + if ( indexHoldersByTable == null ) { + indexHoldersByTable = new HashMap<>(); } else { - holderList = jpaIndexHoldersByTable.get( table ); + holderList = indexHoldersByTable.get( table ); } if ( holderList == null ) { holderList = new ArrayList<>(); - jpaIndexHoldersByTable.put( table, holderList ); + indexHoldersByTable.put( table, holderList ); } holderList.addAll( holders ); @@ -1845,7 +1849,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, secondPassCompileForeignKeys( buildingContext ); processUniqueConstraintHolders( buildingContext ); - processJPAIndexHolders( buildingContext ); + processIndexHolders( buildingContext ); processNaturalIdUniqueKeyBinders(); @@ -2099,8 +2103,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, String keyName, boolean nameExplicit, String[] columnNames, - MetadataBuildingContext buildingContext) { - buildUniqueKeyFromColumnNames( table, keyName, nameExplicit, columnNames, null, true, buildingContext ); + MetadataBuildingContext context) { + buildUniqueKeyFromColumnNames( table, keyName, nameExplicit, columnNames, null, true, context ); } private void buildUniqueKeyFromColumnNames( @@ -2110,38 +2114,49 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, final String[] columnNames, String[] orderings, boolean unique, - final MetadataBuildingContext buildingContext) { - int size = columnNames.length; - Column[] columns = new Column[size]; - Set unbound = new HashSet<>(); - Set unboundNoLogical = new HashSet<>(); - for ( int index = 0; index < size; index++ ) { - final String logicalColumnName = columnNames[index]; - try { - Column column = table.getColumn( buildingContext.getMetadataCollector(), logicalColumnName ); - if ( column == null ) { - throw new AnnotationException( - "Table '" + table.getName() + "' has no column named '" + logicalColumnName - + "' matching the column specified in '@UniqueConstraint'" - ); - } - columns[index] = column; - unbound.add( column ); - //column equals and hashcode is based on column name - } - catch ( MappingException e ) { - // If at least 1 columnName does exist, 'columns' will contain a mix of Columns and nulls. - // In order to exhaustively report all the unbound columns at once, w/o an NPE in - // Constraint#generateName's array sorting, simply create a fake Column. - columns[index] = new Column( logicalColumnName ); - unboundNoLogical.add( columns[index] ); - } + 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 ); + } - createIndexOrUniqueKey( table, keyName, nameExplicit, columnNames, orderings, unique, buildingContext, columns, unbound ); - - if ( unbound.size() > 0 || unboundNoLogical.size() > 0 ) { - throwUnableToCreateConstraint( table, columnNames, unique, unbound, unboundNoLogical ); + 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'" +// ); + } } } @@ -2152,112 +2167,52 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, String[] columnNames, String[] orderings, boolean unique, - MetadataBuildingContext buildingContext, - Column[] columns, - Set unbound) { - if (unique) { - createUniqueKey( table, originalKeyName, nameExplicit, columnNames, orderings, buildingContext, columns, unbound ); + 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 { - createIndex( table, originalKeyName, columnNames, orderings, buildingContext, columns, unbound ); - } - } - - private void createIndex( - Table table, - String originalKeyName, - String[] columnNames, - String[] orderings, - MetadataBuildingContext buildingContext, - Column[] columns, - Set unbound) { - final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy() - .determineIndexName( new IndexOrUniqueKeyNameSource( buildingContext, table, columnNames, originalKeyName ) ); - final String keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); - - final Index index = table.getOrCreateIndex( keyName ); - for (int i = 0; i < columns.length; i++ ) { - Column column = columns[i]; - String order = orderings != null ? orderings[i] : null; - if ( table.containsColumn( column ) ) { - index.addColumn( column, order ); - unbound.remove( column ); + 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 createUniqueKey( - Table table, - String originalKeyName, - boolean nameExplicit, - String[] columnNames, - String[] orderings, - MetadataBuildingContext buildingContext, - Column[] columns, - Set unbound) { - final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy() - .determineUniqueKeyName( new IndexOrUniqueKeyNameSource( buildingContext, table, columnNames, originalKeyName ) ); - final String keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); - - final UniqueKey uk = table.getOrCreateUniqueKey( keyName ); - uk.setNameExplicit(nameExplicit); - for (int i = 0; i < columns.length; i++ ) { - Column column = columns[i]; - String order = orderings != null ? orderings[i] : null; - if ( table.containsColumn( column ) ) { - uk.addColumn( column, order ); - unbound.remove( column ); - } - } - } - - private static void throwUnableToCreateConstraint( - Table table, - String[] columnNames, - boolean unique, - Set unbound, - Set unboundNoLogical) { - final StringBuilder message = new StringBuilder( "Unable to create " ); - if (unique) { - message.append( "unique key constraint (" ); - } - else { - message.append( "index (" ); - } - for ( String columnName : columnNames) { - message.append( columnName ).append( ", " ); - } - message.setLength( message.length() - 2 ); - message.append( ") on table '" ).append( table.getName() ).append( "' since the column " ); - for ( Column column : unbound) { - message.append("'").append( column.getName() ).append( "', " ); - } - for ( Column column : unboundNoLogical) { - message.append("'").append( column.getName() ).append( "', " ); - } - message.setLength( message.length() - 2 ); - message.append( " was not found (specify the correct column name, which depends on the naming strategy, and may not be the same as the entity property name)" ); - throw new AnnotationException( message.toString() ); - } - - private void processJPAIndexHolders(MetadataBuildingContext buildingContext) { - if ( jpaIndexHoldersByTable == null ) { - return; - } - - for ( Map.Entry> entry : jpaIndexHoldersByTable.entrySet() ) { - final Table table = entry.getKey(); - final List jpaIndexHolders = entry.getValue(); - for ( JPAIndexHolder holder : jpaIndexHolders ) { - buildUniqueKeyFromColumnNames( - table, - holder.getName(), - !holder.getName().isEmpty(), - holder.getColumns(), - holder.getOrdering(), - holder.isUnique(), - buildingContext - ); + private void processIndexHolders(MetadataBuildingContext context) { + if ( indexHoldersByTable != null ) { + for ( Map.Entry> entry : indexHoldersByTable.entrySet() ) { + final Table table = entry.getKey(); + final List indexHolders = entry.getValue(); + for ( IndexHolder holder : indexHolders) { + buildUniqueKeyFromColumnNames( + table, + holder.getName(), + !holder.getName().isEmpty(), + holder.getColumns(), + holder.getOrdering(), + holder.isUnique(), + context + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 635dc039c4..4c08bb192b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -644,21 +644,21 @@ public class EntityBinder { final String schema; final String table; final String catalog; - final List uniqueConstraints; + final UniqueConstraint[] uniqueConstraints; boolean hasTableAnnotation = annotatedClass.isAnnotationPresent( jakarta.persistence.Table.class ); if ( hasTableAnnotation ) { final jakarta.persistence.Table tableAnnotation = annotatedClass.getAnnotation( jakarta.persistence.Table.class ); table = tableAnnotation.name(); schema = tableAnnotation.schema(); catalog = tableAnnotation.catalog(); - uniqueConstraints = TableBinder.buildUniqueConstraintHolders( tableAnnotation.uniqueConstraints() ); + uniqueConstraints = tableAnnotation.uniqueConstraints(); } else { //might be no @Table annotation on the annotated class schema = ""; table = ""; catalog = ""; - uniqueConstraints = Collections.emptyList(); + uniqueConstraints = new UniqueConstraint[0]; } final InFlightMetadataCollector collector = context.getMetadataCollector(); @@ -684,7 +684,7 @@ public class EntityBinder { String schema, String table, String catalog, - List uniqueConstraints, + UniqueConstraint[] uniqueConstraints, InFlightMetadataCollector collector) { final RowId rowId = annotatedClass.getAnnotation( RowId.class ); final View view = annotatedClass.getAnnotation( View.class ); @@ -1749,7 +1749,7 @@ public class EntityBinder { String schema, String catalog, String tableName, - List uniqueConstraints, + UniqueConstraint[] uniqueConstraints, String rowId, String viewQuery, InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { @@ -2050,7 +2050,9 @@ public class EntityBinder { secondaryTable.pkJoinColumns(), secondaryTable.uniqueConstraints() ); - TableBinder.addIndexes( join.getTable(), secondaryTable.indexes(), context ); + final Table table = join.getTable(); + context.getMetadataCollector() + .addIndexHolders( table, TableBinder.buildIndexHolders( secondaryTable.indexes() ) ); return join; } @@ -2083,7 +2085,7 @@ public class EntityBinder { catalog, logicalName.getTableName(), false, - TableBinder.buildUniqueConstraintHolders( uniqueConstraints ), + uniqueConstraints, context ) ); @@ -2237,7 +2239,9 @@ public class EntityBinder { public void processComplementaryTableDefinitions(jakarta.persistence.Table table) { if ( table != null ) { - TableBinder.addIndexes( persistentClass.getTable(), table.indexes(), context ); + final Table classTable = persistentClass.getTable(); + context.getMetadataCollector() + .addIndexHolders( classTable, TableBinder.buildIndexHolders( table.indexes() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAIndexHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexHolder.java similarity index 76% rename from hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAIndexHolder.java rename to hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexHolder.java index 4406195efd..c4efc0191a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAIndexHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexHolder.java @@ -16,23 +16,26 @@ import jakarta.persistence.Index; /** * @author Strong Liu */ -public class JPAIndexHolder { +public class IndexHolder { private final String name; private final String[] columns; private final String[] ordering; private final boolean unique; - public JPAIndexHolder(Index index) { - StringTokenizer tokenizer = new StringTokenizer( index.columnList(), "," ); - List tmp = new ArrayList<>(); + public IndexHolder(Index index) { + final StringTokenizer tokenizer = new StringTokenizer( index.columnList(), "," ); + final List parsed = new ArrayList<>(); while ( tokenizer.hasMoreElements() ) { - tmp.add( tokenizer.nextToken().trim() ); + final String trimmed = tokenizer.nextToken().trim(); + if ( !trimmed.isEmpty() ) { + parsed.add( trimmed ) ; + } } this.name = index.name(); - this.columns = new String[tmp.size()]; - this.ordering = new String[tmp.size()]; + this.columns = new String[parsed.size()]; + this.ordering = new String[parsed.size()]; this.unique = index.unique(); - initializeColumns( columns, ordering, tmp ); + initializeColumns( columns, ordering, parsed ); } public String[] getColumns() { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java index 9d5869bcea..b2cbab5949 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java @@ -65,7 +65,6 @@ public class TableBinder { private String catalog; private String name; private boolean isAbstract; - private List uniqueConstraints; private String ownerEntityTable; private String associatedEntityTable; private String propertyName; @@ -76,7 +75,8 @@ public class TableBinder { private String associatedEntity; private String associatedJpaEntity; private boolean isJPA2ElementCollection; - private List jpaIndexHolders; + private UniqueConstraint[] uniqueConstraints; + private Index[] indexes; public void setBuildingContext(MetadataBuildingContext buildingContext) { this.buildingContext = buildingContext; @@ -103,11 +103,11 @@ public class TableBinder { } public void setUniqueConstraints(UniqueConstraint[] uniqueConstraints) { - this.uniqueConstraints = TableBinder.buildUniqueConstraintHolders( uniqueConstraints ); + this.uniqueConstraints = uniqueConstraints; } - public void setJpaIndex(Index[] jpaIndex){ - this.jpaIndexHolders = buildJpaIndexHolder( jpaIndex ); + public void setJpaIndex(Index[] indexes){ + this.indexes = indexes; } public void setJPA2ElementCollection(boolean isJPA2ElementCollection) { @@ -292,7 +292,7 @@ public class TableBinder { : namingStrategyHelper.determineImplicitName( buildingContext ), isAbstract, uniqueConstraints, - jpaIndexHolders, + indexes, buildingContext, null, null @@ -434,7 +434,7 @@ public class TableBinder { String catalog, Identifier logicalName, boolean isAbstract, - List uniqueConstraints, + UniqueConstraint[] uniqueConstraints, MetadataBuildingContext buildingContext) { return buildAndFillTable( schema, @@ -454,7 +454,7 @@ public class TableBinder { String catalog, Identifier logicalName, boolean isAbstract, - List uniqueConstraints, + UniqueConstraint[] uniqueConstraints, MetadataBuildingContext buildingContext, String subselect, InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { @@ -476,8 +476,8 @@ public class TableBinder { String catalog, Identifier logicalName, boolean isAbstract, - List uniqueConstraints, - List jpaIndexHolders, + UniqueConstraint[] uniqueConstraints, + Index[] indexes, MetadataBuildingContext buildingContext, String subselect, InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { @@ -489,11 +489,11 @@ public class TableBinder { denormalizedSuperTableXref, metadataCollector ); if ( isNotEmpty( uniqueConstraints ) ) { - metadataCollector.addUniqueConstraintHolders( table, uniqueConstraints ); + metadataCollector.addUniqueConstraintHolders( table, buildUniqueConstraintHolders( uniqueConstraints ) ); } - if ( isNotEmpty( jpaIndexHolders ) ) { - metadataCollector.addJpaIndexHolders( table, jpaIndexHolders ); + if ( isNotEmpty( indexes ) ) { + metadataCollector.addIndexHolders( table, buildIndexHolders( indexes ) ); } metadataCollector.addTableNameBinding( logicalName, table ); @@ -806,7 +806,7 @@ public class TableBinder { } } - public static void addIndexes(Table table, org.hibernate.annotations.Index[] indexes, MetadataBuildingContext context) { + static void addIndexes(Table table, org.hibernate.annotations.Index[] indexes, MetadataBuildingContext context) { for ( org.hibernate.annotations.Index index : indexes ) { //no need to handle inSecondPass here since it is only called from EntityBinder context.getMetadataCollector().addSecondPass( @@ -815,38 +815,33 @@ public class TableBinder { } } - public static void addIndexes(Table table, Index[] indexes, MetadataBuildingContext context) { - context.getMetadataCollector().addJpaIndexHolders( table, buildJpaIndexHolder( indexes ) ); - } - - public static List buildJpaIndexHolder(Index[] indexes) { - List holders = new ArrayList<>( indexes.length ); + /** + * Build a list of {@link IndexHolder} instances given a + * list of {@link Index} annotations. + */ + static List buildIndexHolders(Index[] indexes) { + List holders = new ArrayList<>( indexes.length ); for ( Index index : indexes ) { - holders.add( new JPAIndexHolder( index ) ); + holders.add( new IndexHolder( index ) ); } return holders; } /** - * Build a list of {@link UniqueConstraintHolder} instances given a list of - * {@link UniqueConstraint} annotations. - * - * @param annotations The {@link UniqueConstraint} annotations. - * - * @return The built {@link UniqueConstraintHolder} instances. + * Build a list of {@link UniqueConstraintHolder} instances + * given a list of {@link UniqueConstraint} annotations. */ - public static List buildUniqueConstraintHolders(UniqueConstraint[] annotations) { - List result; - if ( annotations == null || annotations.length == 0 ) { - result = emptyList(); + static List buildUniqueConstraintHolders(UniqueConstraint[] uniqueConstraints) { + if ( uniqueConstraints == null || uniqueConstraints.length == 0 ) { + return emptyList(); } else { - result = arrayList( annotations.length ); - for ( UniqueConstraint uc : annotations ) { - result.add( new UniqueConstraintHolder().setName( uc.name(), !uc.name().isEmpty() ).setColumns( uc.columnNames() ) ); + final List result = arrayList( uniqueConstraints.length ); + for ( UniqueConstraint uniqueConstraint : uniqueConstraints ) { + result.add( new UniqueConstraintHolder( uniqueConstraint ) ); } + return result; } - return result; } public void setDefaultName( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/UniqueConstraintHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/UniqueConstraintHolder.java index d4e6ff17bf..06e462f8c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/UniqueConstraintHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/UniqueConstraintHolder.java @@ -7,28 +7,33 @@ package org.hibernate.boot.model.internal; +import jakarta.persistence.UniqueConstraint; + /** - * {@link jakarta.persistence.UniqueConstraint} annotations are handled via second pass. I do not + * {@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 + * 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). * - * Isn't this ultimately the same as org.hibernate.cfg.IndexOrUniqueKeySecondPass? - * * @author Steve Ebersole */ +// Isn't this ultimately the same as IndexOrUniqueKeySecondPass? public class UniqueConstraintHolder { - private String name; - private boolean nameExplicit; - private String[] columns; + private final String name; + private final String[] columns; public String getName() { return name; } - public UniqueConstraintHolder setName(String name) { + public UniqueConstraintHolder(UniqueConstraint uniqueConstraint) { + this.name = uniqueConstraint.name(); + this.columns = uniqueConstraint.columnNames(); + } + + public UniqueConstraintHolder(String name, String[] columns) { this.name = name; - return this; + this.columns = columns; } public boolean isNameExplicit() { @@ -38,14 +43,4 @@ public class UniqueConstraintHolder { public String[] getColumns() { return columns; } - - public UniqueConstraintHolder setColumns(String[] columns) { - this.columns = columns; - return this; - } - - public UniqueConstraintHolder setName(String name, boolean nameExplicit) { - this.nameExplicit = nameExplicit; - return setName(name); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index f6bf29d860..c463f12203 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -27,7 +27,7 @@ import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterRegistry; import org.hibernate.boot.model.convert.spi.RegisteredConversion; import org.hibernate.boot.model.internal.AnnotatedClassType; -import org.hibernate.boot.model.internal.JPAIndexHolder; +import org.hibernate.boot.model.internal.IndexHolder; import org.hibernate.boot.model.internal.UniqueConstraintHolder; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; @@ -359,7 +359,7 @@ public interface InFlightMetadataCollector extends MetadataImplementor { @Deprecated(forRemoval = true) void addUniqueConstraints(Table table, List uniqueConstraints); void addUniqueConstraintHolders(Table table, List uniqueConstraints); - void addJpaIndexHolders(Table table, List jpaIndexHolders); + void addIndexHolders(Table table, List indexHolders); interface EntityTableXref { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/AlterTableUniqueIndexDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/AlterTableUniqueIndexDelegate.java index b2d3ce899b..9252339fd8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/AlterTableUniqueIndexDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/AlterTableUniqueIndexDelegate.java @@ -9,10 +9,14 @@ package org.hibernate.dialect.unique; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; +import org.hibernate.mapping.Column; import org.hibernate.mapping.UniqueKey; -import static org.hibernate.mapping.Index.buildSqlCreateIndexString; -import static org.hibernate.mapping.Index.buildSqlDropIndexString; +import java.util.List; +import java.util.Map; + +import static org.hibernate.internal.util.StringHelper.qualify; +import static org.hibernate.internal.util.StringHelper.unqualify; /** * A {@link UniqueDelegate} which uses {@code create unique index} commands when necessary. @@ -36,15 +40,34 @@ public class AlterTableUniqueIndexDelegate extends AlterTableUniqueDelegate { public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context) { if ( uniqueKey.hasNullableColumn() ) { - return buildSqlCreateIndexString( - context, - uniqueKey.getName(), - uniqueKey.getTable(), - uniqueKey.getColumns(), - uniqueKey.getColumnOrderMap(), - true, - metadata - ); + final Dialect dialect = context.getDialect(); + final String name = uniqueKey.getName(); + final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() ); + final List columns = uniqueKey.getColumns(); + final Map columnOrderMap = uniqueKey.getColumnOrderMap(); + final StringBuilder statement = + new StringBuilder( dialect.getCreateIndexString( true ) ) + .append( " " ) + .append( dialect.qualifyIndexName() ? name : unqualify( name ) ) + .append( " on " ) + .append( tableName ) + .append( " (" ); + boolean first = true; + for ( Column column : columns ) { + if ( first ) { + first = false; + } + else { + statement.append(", "); + } + statement.append( column.getQuotedName(dialect) ); + if ( columnOrderMap.containsKey( column ) ) { + statement.append( " " ).append( columnOrderMap.get( column ) ); + } + } + statement.append( ")" ); + statement.append( dialect.getCreateIndexTail( true, columns ) ); + return statement.toString(); } else { return super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata, context ); @@ -55,13 +78,12 @@ public class AlterTableUniqueIndexDelegate extends AlterTableUniqueDelegate { public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, SqlStringGenerationContext context) { if ( uniqueKey.hasNullableColumn() ) { - return buildSqlDropIndexString( - uniqueKey.getName(), - context.format( uniqueKey.getTable().getQualifiedTableName() ) - ); + final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() ); + return "drop index " + qualify( tableName, uniqueKey.getName() ); } else { return super.getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata, context ); } } + } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 20c5b7a3cb..b2601ff384 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -571,8 +571,8 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn } @Override - public String getText(Dialect d) { - return assignmentExpression != null ? assignmentExpression : getQuotedName( d ); + public String getText(Dialect dialect) { + return assignmentExpression != null ? assignmentExpression : getQuotedName( dialect ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index 093f672af9..4858c50736 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -12,7 +12,6 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import org.hibernate.HibernateException; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java index a80dcf54a1..920a70e3b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java @@ -10,7 +10,6 @@ import java.io.Serializable; import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.dialect.Dialect; -import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.type.spi.TypeConfiguration; @@ -95,4 +94,15 @@ public class Formula implements Selectable, Serializable { public String toString() { return getClass().getSimpleName() + "( " + formula + " )"; } + + @Override + public boolean equals(Object obj) { + return obj instanceof Formula + && ( (Formula) obj ).formula.equals( formula ); + } + + @Override + public int hashCode() { + return formula.hashCode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java index e7ba069795..7159c8584f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -9,19 +9,22 @@ package org.hibernate.mapping; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; +import java.util.Map; 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.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; -import org.hibernate.internal.util.StringHelper; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableList; +import static java.util.stream.Collectors.toUnmodifiableMap; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.qualify; +import static org.hibernate.internal.util.StringHelper.unqualify; /** * A mapping model object representing an {@linkplain jakarta.persistence.Index index} on a relational database table. @@ -34,15 +37,22 @@ import static org.hibernate.internal.util.StringHelper.qualify; public class Index implements Exportable, Serializable { private Identifier name; private Table table; - private final java.util.List columns = new ArrayList<>(); - private final java.util.Map columnOrderMap = new HashMap<>( ); + private boolean unique; + private final java.util.List selectables = new ArrayList<>(); + private final java.util.Map selectableOrderMap = new HashMap<>(); - public static String buildSqlDropIndexString( - String name, - String tableName) { + /** + * @deprecated This method will be removed in the next release + */ + @Deprecated(forRemoval = true) + public static String buildSqlDropIndexString(String name, String tableName) { return "drop index " + qualify( tableName, name ); } + /** + * @deprecated This method will be removed in the next release + */ + @Deprecated(forRemoval = true) public static String buildSqlCreateIndexString( Dialect dialect, String name, @@ -52,7 +62,7 @@ public class Index implements Exportable, Serializable { boolean unique) { StringBuilder statement = new StringBuilder( dialect.getCreateIndexString( unique ) ) .append( " " ) - .append( dialect.qualifyIndexName() ? name : StringHelper.unqualify( name ) ) + .append( dialect.qualifyIndexName() ? name : unqualify( name ) ) .append( " on " ) .append( tableName ) .append( " (" ); @@ -75,6 +85,10 @@ public class Index implements Exportable, Serializable { return statement.toString(); } + /** + * @deprecated This method will be removed in the next release + */ + @Deprecated(forRemoval = true) public static String buildSqlCreateIndexString( SqlStringGenerationContext context, String name, @@ -101,39 +115,73 @@ public class Index implements Exportable, Serializable { this.table = table; } + public void setUnique(boolean unique) { + this.unique = unique; + } + + public boolean isUnique() { + return unique; + } + public int getColumnSpan() { - return columns.size(); + return selectables.size(); } + public List getSelectables() { + return unmodifiableList( selectables ); + } + + public Map getSelectableOrderMap() { + return unmodifiableMap( selectableOrderMap ); + } + + /** + * @deprecated use {@link #getSelectables()} + */ + @Deprecated(since = "6.3") public java.util.List getColumns() { - return unmodifiableList( columns ); + return selectables.stream() + .map( s -> (Column) s ).collect( toUnmodifiableList() ); } + /** + * @deprecated use {@link #getSelectableOrderMap()} + */ + @Deprecated(since = "6.3") public java.util.Map getColumnOrderMap() { - return unmodifiableMap( columnOrderMap ); + return selectableOrderMap.entrySet().stream() + .collect( toUnmodifiableMap( e -> (Column) e.getKey(), Map.Entry::getValue ) ); } - public void addColumn(Column column) { - if ( !columns.contains( column ) ) { - columns.add( column ); + public void addColumn(Selectable selectable) { + if ( !selectables.contains( selectable ) ) { + selectables.add( selectable ); } } - public void addColumn(Column column, String order) { - addColumn( column ); + public void addColumn(Selectable selectable, String order) { + addColumn( selectable ); if ( isNotEmpty( order ) ) { - columnOrderMap.put( column, order ); + selectableOrderMap.put( selectable, order ); } } + /** + * @deprecated use {@link #getSelectableOrderMap()} + */ + @Deprecated(since = "6.3", forRemoval = true) public void addColumns(java.util.List extraColumns) { for ( Column column : extraColumns ) { addColumn( column ); } } + /** + * @deprecated use {@link #getSelectableOrderMap()} + */ + @Deprecated(since = "6.3", forRemoval = true) public boolean containsColumn(Column column) { - return columns.contains( column ); + return selectables.contains( column ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java index 4be1b0a4ab..a709c20fd2 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java @@ -10,14 +10,15 @@ import java.util.Map; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedNameImpl; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.mapping.Column; import org.hibernate.mapping.Index; +import org.hibernate.mapping.Selectable; import org.hibernate.tool.schema.spi.Exporter; +import static org.hibernate.internal.util.StringHelper.qualify; + /** * An {@link Exporter} for {@linkplain Index indexes}. * @@ -37,45 +38,50 @@ public class StandardIndexExporter implements Exporter { @Override public String[] getSqlCreateStrings(Index index, Metadata metadata, SqlStringGenerationContext context) { - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String tableName = context.format( index.getTable().getQualifiedTableName() ); + final StringBuilder createIndex = new StringBuilder() + .append( dialect.getCreateIndexString( index.isUnique() ) ) + .append( " " ) + .append( indexName( index, context, metadata ) ) + .append( " on " ) + .append( context.format( index.getTable().getQualifiedTableName() ) ) + .append( " (" ); + appendColumnList( index, createIndex ); + createIndex.append( ")" ); + return new String[] { createIndex.toString() }; + } - final String indexNameForCreation; + private String indexName(Index index, SqlStringGenerationContext context, Metadata metadata) { if ( dialect.qualifyIndexName() ) { - indexNameForCreation = context.format( + final QualifiedTableName qualifiedTableName = index.getTable().getQualifiedTableName(); + return context.format( new QualifiedNameImpl( - index.getTable().getQualifiedTableName().getCatalogName(), - index.getTable().getQualifiedTableName().getSchemaName(), - jdbcEnvironment.getIdentifierHelper().toIdentifier( index.getQuotedName( dialect ) ) + qualifiedTableName.getCatalogName(), + qualifiedTableName.getSchemaName(), + metadata.getDatabase().getJdbcEnvironment().getIdentifierHelper() + .toIdentifier( index.getQuotedName( dialect ) ) ) ); } else { - indexNameForCreation = index.getName(); + return index.getName(); } - final StringBuilder buf = new StringBuilder() - .append( "create index " ) - .append( indexNameForCreation ) - .append( " on " ) - .append( tableName ) - .append( " (" ); + } + private void appendColumnList(Index index, StringBuilder createIndex) { boolean first = true; - final Map columnOrderMap = index.getColumnOrderMap(); - for ( Column column : index.getColumns() ) { + final Map columnOrderMap = index.getSelectableOrderMap(); + for ( Selectable column : index.getSelectables() ) { if ( first ) { first = false; } else { - buf.append( ", " ); + createIndex.append( ", " ); } - buf.append( ( column.getQuotedName( dialect ) ) ); + createIndex.append( column.getText( dialect ) ); if ( columnOrderMap.containsKey( column ) ) { - buf.append( " " ).append( columnOrderMap.get( column ) ); + createIndex.append( " " ).append( columnOrderMap.get( column ) ); } } - buf.append( ")" ); - return new String[] { buf.toString() }; } @Override @@ -87,7 +93,7 @@ public class StandardIndexExporter implements Exporter { final String tableName = context.format( index.getTable().getQualifiedTableName() ); final String indexNameForCreation = dialect.qualifyIndexName() - ? StringHelper.qualify( tableName, index.getName() ) + ? qualify( tableName, index.getName() ) : index.getName(); return new String[] { "drop index " + indexNameForCreation }; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java index 386b9ddf5c..d14f9f40a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java @@ -33,12 +33,12 @@ public class UniqueConstraintValidationTest extends BaseUnitTestCase { buildSessionFactory(EmptyColumnNameEntity.class); } - @Test + @Test(expected = AnnotationException.class) public void testUniqueConstraintWithEmptyColumnNameList() { buildSessionFactory(EmptyColumnNameListEntity.class); } - @Test(expected = AnnotationException.class) + @Test public void testUniqueConstraintWithNotExistsColumnName() { buildSessionFactory(NotExistsColumnEntity.class); }