HHH-17103 allow SQL expressions in @Index(columnList)

This commit is contained in:
Gavin King 2023-08-17 20:55:06 +02:00
parent f1fa09ab42
commit 24646ece2c
13 changed files with 317 additions and 280 deletions

View File

@ -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<String, String> propertyRefResolver;
private Set<DelayedPropertyReferenceHandler> delayedPropertyReferenceHandlers;
private Map<Table, List<UniqueConstraintHolder>> uniqueConstraintHoldersByTable;
private Map<Table, List<JPAIndexHolder>> jpaIndexHoldersByTable;
private Map<Table, List<IndexHolder>> indexHoldersByTable;
private List<Function<MetadataBuildingContext, Boolean>> 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<UniqueConstraintHolder> uniqueConstraintHolders) {
public void addUniqueConstraintHolders(Table table, List<UniqueConstraintHolder> holders) {
List<UniqueConstraintHolder> 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<JPAIndexHolder> holders) {
List<JPAIndexHolder> holderList = null;
public void addIndexHolders(Table table, List<IndexHolder> holders) {
List<IndexHolder> 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<Column> unbound = new HashSet<>();
Set<Column> 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<Column> 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<Column> 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<Column> 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<Column> unbound,
Set<Column> 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<Table, List<JPAIndexHolder>> entry : jpaIndexHoldersByTable.entrySet() ) {
final Table table = entry.getKey();
final List<JPAIndexHolder> 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<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
);
}
}
}
}

View File

@ -644,21 +644,21 @@ public class EntityBinder {
final String schema;
final String table;
final String catalog;
final List<UniqueConstraintHolder> 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<UniqueConstraintHolder> 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<UniqueConstraintHolder> 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() ) );
}
}

View File

@ -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<String> tmp = new ArrayList<>();
public IndexHolder(Index index) {
final StringTokenizer tokenizer = new StringTokenizer( index.columnList(), "," );
final List<String> 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() {

View File

@ -65,7 +65,6 @@ public class TableBinder {
private String catalog;
private String name;
private boolean isAbstract;
private List<UniqueConstraintHolder> 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<JPAIndexHolder> 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<UniqueConstraintHolder> uniqueConstraints,
UniqueConstraint[] uniqueConstraints,
MetadataBuildingContext buildingContext) {
return buildAndFillTable(
schema,
@ -454,7 +454,7 @@ public class TableBinder {
String catalog,
Identifier logicalName,
boolean isAbstract,
List<UniqueConstraintHolder> uniqueConstraints,
UniqueConstraint[] uniqueConstraints,
MetadataBuildingContext buildingContext,
String subselect,
InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) {
@ -476,8 +476,8 @@ public class TableBinder {
String catalog,
Identifier logicalName,
boolean isAbstract,
List<UniqueConstraintHolder> uniqueConstraints,
List<JPAIndexHolder> 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<JPAIndexHolder> buildJpaIndexHolder(Index[] indexes) {
List<JPAIndexHolder> holders = new ArrayList<>( indexes.length );
/**
* 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 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<UniqueConstraintHolder> buildUniqueConstraintHolders(UniqueConstraint[] annotations) {
List<UniqueConstraintHolder> result;
if ( annotations == null || annotations.length == 0 ) {
result = emptyList();
static List<UniqueConstraintHolder> 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<UniqueConstraintHolder> result = arrayList( uniqueConstraints.length );
for ( UniqueConstraint uniqueConstraint : uniqueConstraints ) {
result.add( new UniqueConstraintHolder( uniqueConstraint ) );
}
return result;
}
return result;
}
public void setDefaultName(

View File

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

View File

@ -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<String[]> uniqueConstraints);
void addUniqueConstraintHolders(Table table, List<UniqueConstraintHolder> uniqueConstraints);
void addJpaIndexHolders(Table table, List<JPAIndexHolder> jpaIndexHolders);
void addIndexHolders(Table table, List<IndexHolder> indexHolders);
interface EntityTableXref {

View File

@ -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<Column> columns = uniqueKey.getColumns();
final Map<Column, String> 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 );
}
}
}

View File

@ -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

View File

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

View File

@ -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();
}
}

View File

@ -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<Column> columns = new ArrayList<>();
private final java.util.Map<Column, String> columnOrderMap = new HashMap<>( );
private boolean unique;
private final java.util.List<Selectable> selectables = new ArrayList<>();
private final java.util.Map<Selectable, String> 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<Selectable> getSelectables() {
return unmodifiableList( selectables );
}
public Map<Selectable, String> getSelectableOrderMap() {
return unmodifiableMap( selectableOrderMap );
}
/**
* @deprecated use {@link #getSelectables()}
*/
@Deprecated(since = "6.3")
public java.util.List<Column> 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<Column, String> 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<Column> 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() {

View File

@ -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<Index> {
@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<Column, String> columnOrderMap = index.getColumnOrderMap();
for ( Column column : index.getColumns() ) {
final Map<Selectable, String> 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<Index> {
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 };

View File

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