From 1657c22aca5b8a68bd844b34ab5466103a32ccb1 Mon Sep 17 00:00:00 2001 From: Gavin Date: Mon, 2 Jan 2023 00:07:32 +0100 Subject: [PATCH] automatically detect when a @Check refers to a @SecondaryTable - also support named check constraints (multiple of them) - also support check constraints on collection tables --- .../hibernate/userguide/schema/CheckTest.java | 14 +- .../java/org/hibernate/annotations/Check.java | 21 ++- .../org/hibernate/annotations/Checks.java | 22 +++ .../java/org/hibernate/annotations/Table.java | 26 +-- .../org/hibernate/annotations/Tables.java | 3 + .../boot/model/internal/AnnotatedColumn.java | 25 ++- .../model/internal/ClassPropertyHolder.java | 20 +-- .../boot/model/internal/CollectionBinder.java | 164 ++++++------------ .../boot/model/internal/EntityBinder.java | 37 +++- .../model/internal/OneToOneSecondPass.java | 34 +--- .../hibernate/mapping/AggregateColumn.java | 2 +- .../org/hibernate/mapping/BasicValue.java | 18 +- .../hibernate/mapping/CheckConstraint.java | 48 +++++ .../java/org/hibernate/mapping/Column.java | 68 +++++--- .../main/java/org/hibernate/mapping/Join.java | 33 ++-- .../hibernate/mapping/PersistentClass.java | 61 ++++++- .../java/org/hibernate/mapping/Table.java | 15 +- .../domain/internal/MappingMetamodelImpl.java | 3 + .../main/java/org/hibernate/sql/Template.java | 39 ++++- .../schema/internal/ColumnDefinitions.java | 2 +- .../internal/StandardTableExporter.java | 8 +- 21 files changed, 413 insertions(+), 250 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/annotations/Checks.java create mode 100644 hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/CheckTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/CheckTest.java index ed05b46322..62a145a2c9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/CheckTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/CheckTest.java @@ -6,10 +6,12 @@ */ package org.hibernate.userguide.schema; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.PersistenceException; +import jakarta.persistence.SecondaryTable; import org.hibernate.annotations.Check; import org.hibernate.annotations.NaturalId; import org.hibernate.dialect.H2Dialect; @@ -20,6 +22,8 @@ import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; import org.junit.Test; +import java.time.LocalDate; + import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -120,7 +124,9 @@ public class CheckTest extends BaseEntityManagerFunctionalTestCase { //tag::schema-generation-database-checks-example[] @Entity(name = "Book") - @Check(constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END") + @Check(name = "ValidIsbn", constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END") + @SecondaryTable(name = "BookEdition") + @Check(name = "PositiveEdition", constraints = "edition > 0") public static class Book { @Id @@ -133,6 +139,12 @@ public class CheckTest extends BaseEntityManagerFunctionalTestCase { private Double price; + @Column(table = "BookEdition") + private int edition = 1; + + @Column(table = "BookEdition") + private LocalDate editionDate; + //Getters and setters omitted for brevity //end::schema-generation-database-checks-example[] diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Check.java b/hibernate-core/src/main/java/org/hibernate/annotations/Check.java index 5b5d041b5c..d22c04b164 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Check.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Check.java @@ -6,6 +6,7 @@ */ package org.hibernate.annotations; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -17,8 +18,19 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Specifies a {@code check} constraint to be included in the generated DDL. * * * @author Emmanuel Bernard @@ -27,7 +39,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; */ @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) +@Repeatable(Checks.class) public @interface Check { + /** + * The optional name of the check constraint. + */ + String name() default ""; /** * The check constraint, written in native SQL. */ diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Checks.java b/hibernate-core/src/main/java/org/hibernate/annotations/Checks.java new file mode 100644 index 0000000000..a1e23d8cbf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Checks.java @@ -0,0 +1,22 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A list of {@link Check}s. + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface Checks { + Check[] value(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Table.java b/hibernate-core/src/main/java/org/hibernate/annotations/Table.java index c51659d757..35dcb4a08a 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Table.java @@ -22,10 +22,16 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * * @see jakarta.persistence.Table * @see jakarta.persistence.SecondaryTable + * + * @deprecated The options available here are all now offered by other newer and better-designed + * annotations in this package. This annotation will soon be removed, since it's very + * annoying to have two annotations named {@code @Table}. + * */ @Target(TYPE) @Retention(RUNTIME) @Repeatable(Tables.class) +@Deprecated(since = "6.2", forRemoval = true) public @interface Table { /** * The name of the targeted table. @@ -43,11 +49,10 @@ public @interface Table { /** * A check constraint, written in native SQL. - *

- * Useful for secondary tables, otherwise use {@link Check}. * - * @see Check + * @deprecated use {@link Check}. */ + @Deprecated(since = "6.2") String checkConstraint() default ""; /** @@ -61,6 +66,7 @@ public @interface Table { /** * Specifies a foreign key of a secondary table, which points back to the primary table. * + * @apiNote Only relevant to secondary tables * @deprecated use {@link jakarta.persistence.SecondaryTable#foreignKey()} */ @Deprecated(since = "6.0", forRemoval = true) @@ -74,9 +80,8 @@ public @interface Table { /** * If enabled, Hibernate will never insert or update the columns of the secondary table. - *

- * Only applies to secondary tables. * + * @apiNote Only relevant to secondary tables * @deprecated use {@link SecondaryRow#owned()} */ @Deprecated(since = "6.2") @@ -87,7 +92,7 @@ public @interface Table { * would not all be null, and will always use an outer join to read the columns. Thus, * by default, Hibernate avoids creating a row of null values. * - * @apiNote Only relevant for secondary tables + * @apiNote Only relevant to secondary tables * @deprecated use {@link SecondaryRow#optional()} */ @Deprecated(since = "6.2") @@ -95,9 +100,8 @@ public @interface Table { /** * Defines a custom SQL insert statement. - *

- * Only applies to secondary tables. * + * @apiNote Only relevant to secondary tables * @deprecated use {@link SQLInsert#table()} to specify the secondary table */ @Deprecated(since="6.2") @@ -105,9 +109,8 @@ public @interface Table { /** * Defines a custom SQL update statement. - *

- * Only applies to secondary tables. * + * @apiNote Only relevant to secondary tables * @deprecated use {@link SQLInsert#table()} to specify the secondary table */ @Deprecated(since="6.2") @@ -115,9 +118,8 @@ public @interface Table { /** * Defines a custom SQL delete statement. - *

- * Only applies to secondary tables. * + * @apiNote Only relevant to secondary tables * @deprecated use {@link SQLInsert#table()} to specify the secondary table */ @Deprecated(since="6.2") diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Tables.java b/hibernate-core/src/main/java/org/hibernate/annotations/Tables.java index 40d0825ea5..e1379f6802 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Tables.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Tables.java @@ -16,9 +16,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * A grouping of {@link Table}s. * * @author Emmanuel Bernard + * + * @deprecated since {@link Table} is deprecated */ @Target(TYPE) @Retention(RUNTIME) +@Deprecated(since = "6.2", forRemoval = true) public @interface Tables { /** * The table grouping. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java index eb36e3da6a..1405ab420c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java @@ -27,6 +27,7 @@ import org.hibernate.boot.spi.PropertyData; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.AggregateColumn; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.Formula; @@ -83,6 +84,7 @@ public class AnnotatedColumn { private String generatedAs; // private String comment; + private String checkConstraintName; private String checkConstraint; private AnnotatedColumns parent; @@ -124,10 +126,6 @@ public class AnnotatedColumn { return isNotEmpty( formulaString ); } - public String getFormulaString() { - return formulaString; - } - public String getExplicitTableName() { return explicitTableName; } @@ -188,16 +186,13 @@ public class AnnotatedColumn { return defaultValue; } - public String getCheckConstraint() { - return checkConstraint; - } - public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } - public void setCheckConstraint(String checkConstraint) { - this.checkConstraint = checkConstraint; + public void setCheckConstraint(String name, String constraint) { + this.checkConstraintName = name; + this.checkConstraint = constraint; } // public String getComment() { @@ -240,8 +235,8 @@ public class AnnotatedColumn { if ( defaultValue != null ) { mappingColumn.setDefaultValue( defaultValue ); } - if ( checkConstraint !=null ) { - mappingColumn.setCheckConstraint( checkConstraint ); + if ( checkConstraint != null ) { + mappingColumn.setCheck( new CheckConstraint( checkConstraintName, checkConstraint ) ); } // if ( isNotEmpty( comment ) ) { // mappingColumn.setComment( comment ); @@ -280,7 +275,9 @@ public class AnnotatedColumn { mappingColumn.setNullable( nullable ); mappingColumn.setSqlType( sqlType ); mappingColumn.setUnique( unique ); - mappingColumn.setCheckConstraint( checkConstraint ); + if ( checkConstraint != null ) { + mappingColumn.setCheck( new CheckConstraint( checkConstraintName, checkConstraint ) ); + } mappingColumn.setDefaultValue( defaultValue ); if ( writeExpression != null ) { @@ -817,7 +814,7 @@ public class AnnotatedColumn { throw new AnnotationException("'@Check' may only be applied to single-column mappings but '" + property.getName() + "' maps to " + length + " columns (use a table-level '@Check')" ); } - setCheckConstraint( check.constraints() ); + setCheckConstraint( check.name().isEmpty() ? null : check.name(), check.constraints() ); } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java index 3396418b1b..e9652c1a1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java @@ -226,7 +226,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { return join; } - private void addPropertyToPersistentClass(Property prop, XClass declaringClass) { + private void addPropertyToPersistentClass(Property property, XClass declaringClass) { if ( declaringClass != null ) { final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass ); if ( inheritanceState == null ) { @@ -235,15 +235,15 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { ); } if ( inheritanceState.isEmbeddableSuperclass() ) { - persistentClass.addMappedSuperclassProperty( prop ); - addPropertyToMappedSuperclass( prop, declaringClass ); + persistentClass.addMappedSuperclassProperty( property ); + addPropertyToMappedSuperclass( property, declaringClass ); } else { - persistentClass.addProperty( prop ); + persistentClass.addProperty( property ); } } else { - persistentClass.addProperty( prop ); + persistentClass.addProperty( property ); } } @@ -359,7 +359,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } } - private void addPropertyToJoin(Property prop, XClass declaringClass, Join join) { + private void addPropertyToJoin(Property property, XClass declaringClass, Join join) { if ( declaringClass != null ) { final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass ); if ( inheritanceState == null ) { @@ -368,15 +368,15 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { ); } if ( inheritanceState.isEmbeddableSuperclass() ) { - join.addMappedsuperclassProperty(prop); - addPropertyToMappedSuperclass( prop, declaringClass ); + join.addMappedsuperclassProperty( property ); + addPropertyToMappedSuperclass( property, declaringClass ); } else { - join.addProperty( prop ); + join.addProperty( property ); } } else { - join.addProperty( prop ); + join.addProperty( property ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index 4ab3abbcc0..b14d7727c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -23,6 +23,7 @@ import org.hibernate.annotations.Bag; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.Check; import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.CollectionIdJavaType; import org.hibernate.annotations.CollectionIdJdbcType; @@ -84,6 +85,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.Backref; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; @@ -1616,7 +1618,7 @@ public abstract class CollectionBinder { foreignJoinColumns.getBuildingContext(), inheritanceStatePerClass ) ); - foreignJoinColumns.setJoins( joins); + foreignJoinColumns.setJoins( joins ); collection.setCollectionTable( foreignJoinColumns.getTable() ); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() ); @@ -1626,7 +1628,7 @@ public abstract class CollectionBinder { handleWhere( false ); final PersistentClass targetEntity = persistentClasses.get( getElementType().getName() ); - bindCollectionSecondPass( targetEntity, foreignJoinColumns, onDeleteAction); + bindCollectionSecondPass( targetEntity, foreignJoinColumns ); if ( !collection.isInverse() && !collection.getKey().isNullable() ) { createOneToManyBackref( oneToMany ); @@ -2056,84 +2058,35 @@ public abstract class CollectionBinder { logManyToManySecondPass( oneToMany, isCollectionOfEntities, isManyToAny ); //check for user error - detectManyToManyProblems( - elementType, - property, - propertyHolder, - isCollectionOfEntities, - isManyToAny - ); + detectManyToManyProblems( elementType, isCollectionOfEntities, isManyToAny ); - if (isUnownedCollection()) { - handleUnownedManyToMany( - collection, - joinColumns, - elementType, - targetEntity, - isCollectionOfEntities - ); + if ( isUnownedCollection() ) { + handleUnownedManyToMany( elementType, targetEntity, isCollectionOfEntities ); } else { - handleOwnedManyToMany( - collection, - joinColumns, - tableBinder, - property, - buildingContext, - targetEntity, - isCollectionOfEntities - ); + handleOwnedManyToMany( targetEntity, isCollectionOfEntities ); } bindFilters( isCollectionOfEntities ); handleWhere( isCollectionOfEntities ); - bindCollectionSecondPass( targetEntity, joinColumns, onDeleteAction); + bindCollectionSecondPass( targetEntity, joinColumns ); if ( isCollectionOfEntities ) { - final ManyToOne element = handleCollectionOfEntities( - collection, - elementType, - notFoundAction, - property, - buildingContext, - targetEntity, - hqlOrderBy - ); + final ManyToOne element = handleCollectionOfEntities( elementType, targetEntity, hqlOrderBy ); bindManyToManyInverseForeignKey( targetEntity, inverseJoinColumns, element, oneToMany ); } else if ( isManyToAny ) { - handleManyToAny( - collection, - inverseJoinColumns, - onDeleteAction, - property, - buildingContext - ); + handleManyToAny(); } else { - handleElementCollection( - collection, - elementColumns, - isEmbedded, - elementType, - property, - propertyHolder, - hqlOrderBy - ); + handleElementCollection( elementType, hqlOrderBy ); } checkFilterConditions( collection ); } - private void handleElementCollection( - Collection collection, - AnnotatedColumns elementColumns, - boolean isEmbedded, - XClass elementType, - XProperty property, - PropertyHolder parentPropertyHolder, - String hqlOrderBy) { - // 'parentPropertyHolder' is the PropertyHolder for the owner of the collection + private void handleElementCollection(XClass elementType, String hqlOrderBy) { + // 'propertyHolder' is the PropertyHolder for the owner of the collection // 'holder' is the CollectionPropertyHolder. // 'property' is the collection XProperty @@ -2141,7 +2094,7 @@ public abstract class CollectionBinder { final AnnotatedClassType classType = annotatedElementType( isEmbedded, property, elementType ); final boolean primitive = classType == NONE; if ( !primitive ) { - parentPropertyHolder.startingProperty( property ); + propertyHolder.startingProperty( property ); } final CollectionPropertyHolder holder = buildPropertyHolder( @@ -2149,7 +2102,7 @@ public abstract class CollectionBinder { collection.getRole(), elementClass, property, - parentPropertyHolder, + propertyHolder, buildingContext ); holder.prepare( property ); @@ -2157,18 +2110,15 @@ public abstract class CollectionBinder { final Class> compositeUserType = resolveCompositeUserType( property, elementClass, buildingContext ); if ( classType == EMBEDDABLE || compositeUserType != null ) { - handleCompositeCollectionElement( collection, property, hqlOrderBy, elementClass, holder, compositeUserType ); + handleCompositeCollectionElement( hqlOrderBy, elementClass, holder, compositeUserType ); } else { - handleCollectionElement( collection, elementColumns, elementType, property, hqlOrderBy, elementClass, holder ); + handleCollectionElement( elementType, hqlOrderBy, elementClass, holder ); } } private void handleCollectionElement( - Collection collection, - AnnotatedColumns elementColumns, XClass elementType, - XProperty property, String hqlOrderBy, XClass elementClass, CollectionPropertyHolder holder) { @@ -2187,20 +2137,18 @@ public abstract class CollectionBinder { property, elementClass, collection.getOwnerEntityName(), - holder.resolveElementAttributeConverterDescriptor(property, elementClass) + holder.resolveElementAttributeConverterDescriptor( property, elementClass ) ); elementBinder.setPersistentClassName( propertyHolder.getEntityName() ); elementBinder.setAccessType( accessType ); collection.setElement( elementBinder.make() ); - final String orderBy = adjustUserSuppliedValueCollectionOrderingFragment(hqlOrderBy); + final String orderBy = adjustUserSuppliedValueCollectionOrderingFragment( hqlOrderBy ); if ( orderBy != null ) { collection.setOrderBy( orderBy ); } } private void handleCompositeCollectionElement( - Collection collection, - XProperty property, String hqlOrderBy, XClass elementClass, CollectionPropertyHolder holder, @@ -2256,7 +2204,7 @@ public abstract class CollectionBinder { } } - AnnotatedClassType annotatedElementType( + private AnnotatedClassType annotatedElementType( boolean isEmbedded, XProperty property, XClass elementType) { @@ -2301,14 +2249,7 @@ public abstract class CollectionBinder { return elementColumns; } - private ManyToOne handleCollectionOfEntities( - Collection collection, - XClass elementType, - NotFoundAction notFoundAction, - XProperty property, - MetadataBuildingContext buildingContext, - PersistentClass collectionEntity, - String hqlOrderBy) { + private ManyToOne handleCollectionOfEntities(XClass elementType, PersistentClass collectionEntity, String hqlOrderBy) { final ManyToOne element = new ManyToOne( buildingContext, collection.getCollectionTable() ); collection.setElement( element ); element.setReferencedEntityName( elementType.getName() ); @@ -2339,8 +2280,9 @@ public abstract class CollectionBinder { foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition(); } } - if ( joinTableAnn.inverseForeignKey().value() == NO_CONSTRAINT - || joinTableAnn.inverseForeignKey().value() == PROVIDER_DEFAULT + final ConstraintMode constraintMode = joinTableAnn.inverseForeignKey().value(); + if ( constraintMode == NO_CONSTRAINT + || constraintMode == PROVIDER_DEFAULT && buildingContext.getBuildingOptions().isNoConstraintByDefault() ) { element.disableForeignKey(); } @@ -2353,12 +2295,7 @@ public abstract class CollectionBinder { return element; } - private void handleManyToAny( - Collection collection, - AnnotatedJoinColumns inverseJoinColumns, - OnDeleteAction onDeleteAction, - XProperty property, - MetadataBuildingContext buildingContext) { + private void handleManyToAny() { //@ManyToAny //Make sure that collTyp is never used during the @ManyToAny branch: it will be set to void.class final PropertyData inferredData = new PropertyInferredData( @@ -2414,25 +2351,20 @@ public abstract class CollectionBinder { } private void handleOwnedManyToMany( - Collection collection, - AnnotatedJoinColumns joinColumns, - TableBinder associationTableBinder, - XProperty property, - MetadataBuildingContext context, PersistentClass collectionEntity, boolean isCollectionOfEntities) { //TODO: only for implicit columns? //FIXME NamingStrategy - final InFlightMetadataCollector collector = context.getMetadataCollector(); + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); final PersistentClass owner = collection.getOwner(); joinColumns.setMappedBy( owner.getEntityName(), collector.getLogicalTableName( owner.getTable() ), collector.getFromMappedBy( owner.getEntityName(), joinColumns.getPropertyName() ) ); - if ( isEmpty( associationTableBinder.getName() ) ) { + if ( isEmpty( tableBinder.getName() ) ) { //default value - associationTableBinder.setDefaultName( + tableBinder.setDefaultName( owner.getClassName(), owner.getEntityName(), owner.getJpaEntityName(), @@ -2444,15 +2376,26 @@ public abstract class CollectionBinder { joinColumns.getPropertyName() ); } - associationTableBinder.setJPA2ElementCollection( - !isCollectionOfEntities && property.isAnnotationPresent(ElementCollection.class) + tableBinder.setJPA2ElementCollection( + !isCollectionOfEntities && property.isAnnotationPresent( ElementCollection.class ) ); - collection.setCollectionTable( associationTableBinder.bind() ); + Table collectionTable = tableBinder.bind(); + collection.setCollectionTable( collectionTable ); + handleCheck( collectionTable ); + } + + private void handleCheck(Table collectionTable) { + final Check check = getOverridableAnnotation( property, Check.class, buildingContext ); + if ( check != null ) { + final String name = check.name(); + final String constraint = check.constraints(); + collectionTable.addCheck( name.isEmpty() + ? new CheckConstraint( constraint ) + : new CheckConstraint( name, constraint ) ); + } } private void handleUnownedManyToMany( - Collection collection, - AnnotatedJoinColumns joinColumns, XClass elementType, PersistentClass collectionEntity, boolean isCollectionOfEntities) { @@ -2468,7 +2411,7 @@ public abstract class CollectionBinder { try { otherSideProperty = collectionEntity.getRecursiveProperty( mappedBy ); } - catch (MappingException e) { + catch ( MappingException e ) { throw new AnnotationException( "Association '" + safeCollectionRole() + "is 'mappedBy' a property named '" + mappedBy + "' which does not exist in the target entity '" + elementType.getName() + "'" ); @@ -2484,8 +2427,6 @@ public abstract class CollectionBinder { private void detectManyToManyProblems( XClass elementType, - XProperty property, - PropertyHolder parentPropertyHolder, boolean isCollectionOfEntities, boolean isManyToAny) { @@ -2495,13 +2436,13 @@ public abstract class CollectionBinder { + "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" ); } else if (isManyToAny) { - if ( parentPropertyHolder.getJoinTable( property ) == null ) { + if ( propertyHolder.getJoinTable( property ) == null ) { throw new AnnotationException( "Association '" + safeCollectionRole() + "' is a '@ManyToAny' and must specify a '@JoinTable'" ); } } else { - final JoinTable joinTableAnn = parentPropertyHolder.getJoinTable( property ); + final JoinTable joinTableAnn = propertyHolder.getJoinTable( property ); if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) { throw new AnnotationException( "Association '" + safeCollectionRole() + " has a '@JoinTable' with 'inverseJoinColumns' and targets the type '" @@ -2576,10 +2517,7 @@ public abstract class CollectionBinder { } } - private void bindCollectionSecondPass( - PersistentClass targetEntity, - AnnotatedJoinColumns joinColumns, - OnDeleteAction onDeleteAction) { + private void bindCollectionSecondPass(PersistentClass targetEntity, AnnotatedJoinColumns joinColumns) { if ( !isUnownedCollection() ) { createSyntheticPropertyReference( @@ -2710,8 +2648,8 @@ public abstract class CollectionBinder { this.foreignJoinColumns = annotatedJoinColumns; } - public void setExplicitAssociationTable(boolean explicitAssocTable) { - this.isExplicitAssociationTable = explicitAssocTable; + public void setExplicitAssociationTable(boolean isExplicitAssociationTable) { + this.isExplicitAssociationTable = isExplicitAssociationTable; } public void setElementColumns(AnnotatedColumns elementColumns) { 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 19e1ee6fa2..7e86f72139 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 @@ -45,6 +45,7 @@ import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Check; +import org.hibernate.annotations.Checks; import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.annotations.DiscriminatorOptions; import org.hibernate.annotations.DynamicInsert; @@ -52,6 +53,7 @@ import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filters; import org.hibernate.annotations.ForeignKey; +import org.hibernate.annotations.ForeignKey; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Loader; import org.hibernate.annotations.NaturalIdCache; @@ -101,6 +103,7 @@ import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Component; import org.hibernate.mapping.DependantValue; import org.hibernate.mapping.Join; @@ -210,6 +213,7 @@ public class EntityBinder { entityBinder.bindEntity(); entityBinder.handleClassTable( inheritanceState, superEntity ); entityBinder.handleSecondaryTables(); + entityBinder.handleCheck(); final PropertyHolder holder = buildPropertyHolder( clazzToProcess, persistentClass, @@ -238,6 +242,33 @@ public class EntityBinder { entityBinder.callTypeBinders( persistentClass ); } + private void handleCheck() { + if ( annotatedClass.isAnnotationPresent( Checks.class ) ) { + // if we have more than one of them they are not overrideable :-/ + for ( Check check : annotatedClass.getAnnotation( Checks.class ).value() ) { + addCheckToEntity( check ); + } + } + else { + final Check check = getOverridableAnnotation( annotatedClass, Check.class, context ); + if ( check != null ) { + addCheckToEntity( check ); + } + } + } + + /** + * For now, we store it on the entity. + * Later we will come back and figure out which table it belongs to. + */ + private void addCheckToEntity(Check check) { + final String name = check.name(); + final String constraint = check.constraints(); + persistentClass.addCheckConstraint( name.isEmpty() + ? new CheckConstraint( constraint ) + : new CheckConstraint( name, constraint ) ); + } + private void callTypeBinders(PersistentClass persistentClass) { for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class ) ) { final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class ); @@ -643,14 +674,12 @@ public class EntityBinder { String catalog, List uniqueConstraints, InFlightMetadataCollector collector) { - final Check check = getOverridableAnnotation( annotatedClass, Check.class, context ); final RowId rowId = annotatedClass.getAnnotation( RowId.class ); bindTable( schema, catalog, table, uniqueConstraints, - check == null ? null : check.constraints(), rowId == null ? null : rowId.value(), inheritanceState.hasDenormalizedTable() ? collector.getEntityTableXref( superEntity.getEntityName() ) @@ -1662,7 +1691,6 @@ public class EntityBinder { String catalog, String tableName, List uniqueConstraints, - String checkConstraint, String rowId, InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { @@ -1687,9 +1715,6 @@ public class EntityBinder { ); table.setRowId( rowId ); - if ( checkConstraint != null ) { - table.addCheckConstraint( checkConstraint ); - } // final Comment comment = annotatedClass.getAnnotation( Comment.class ); // if ( comment != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java index a051bfed10..232ec5ec97 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java @@ -182,19 +182,8 @@ public class OneToOneSecondPass implements SecondPass { manyToOne.markAsLogicalOneToOne(); property.setValue( manyToOne ); for ( Column column: otherSideJoin.getKey().getColumns() ) { - Column copy = new Column(); - copy.setLength( column.getLength() ); - copy.setScale( column.getScale() ); + Column copy = column.clone(); copy.setValue( manyToOne ); - copy.setName( column.getQuotedName() ); - copy.setNullable( column.isNullable() ); - copy.setPrecision( column.getPrecision() ); - copy.setUnique( column.isUnique() ); - copy.setSqlType( column.getSqlType() ); - copy.setCheckConstraint( column.getCheckConstraint() ); - copy.setComment( column.getComment() ); - copy.setDefaultValue( column.getDefaultValue() ); - copy.setGeneratedAs( column.getGeneratedAs() ); manyToOne.addColumn( copy ); } mappedByJoin.addProperty( property ); @@ -275,24 +264,15 @@ public class OneToOneSecondPass implements SecondPass { join.setOptional( true ); key.setOnDeleteAction( null ); for ( Column column: otherSideProperty.getValue().getColumns() ) { - Column copy = new Column(); - copy.setLength( column.getLength() ); - copy.setScale( column.getScale() ); + Column copy = column.clone(); copy.setValue( key ); - copy.setName( column.getQuotedName() ); - copy.setNullable( column.isNullable() ); - copy.setPrecision( column.getPrecision() ); - copy.setUnique( column.isUnique() ); - copy.setSqlType( column.getSqlType() ); - copy.setCheckConstraint( column.getCheckConstraint() ); - copy.setComment( column.getComment() ); - copy.setDefaultValue( column.getDefaultValue() ); - column.setGeneratedAs( column.getGeneratedAs() ); key.addColumn( copy ); } - if ( otherSideProperty.getValue() instanceof SortableValue - && !( (SortableValue) otherSideProperty.getValue() ).isSorted() ) { - key.sortProperties(); + if ( otherSideProperty.getValue() instanceof SortableValue ) { + final SortableValue value = (SortableValue) otherSideProperty.getValue(); + if ( !value.isSorted() ) { + key.sortProperties(); + } } persistentClass.addJoin( join ); return join; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java b/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java index 54d13437b0..efd14f655e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java @@ -31,7 +31,7 @@ public class AggregateColumn extends Column { setSqlType( column.getSqlType() ); setSqlTypeCode( column.getSqlTypeCode() ); uniqueInteger = column.uniqueInteger; //usually useless - setCheckConstraint( column.getCheckConstraint() ); + checkConstraint = column.checkConstraint; setComment( column.getComment() ); setDefaultValue( column.getDefaultValue() ); setGeneratedAs( column.getGeneratedAs() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 5615322efe..79cc13c0ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -334,18 +334,18 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol } if ( resolution.getValueConverter() != null ) { - column.setSpecializedTypeDeclaration( - resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration( dialect ) - ); + final String declaration = resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration(dialect); + if ( declaration != null ) { + column.setSpecializedTypeDeclaration( declaration ); + } } if ( dialect.supportsColumnCheck() && !column.hasCheckConstraint() ) { - column.setCheckConstraint( - resolution.getLegacyResolvedBasicType().getCheckCondition( - column.getQuotedName( dialect ), - dialect - ) - ); + final String checkCondition = resolution.getLegacyResolvedBasicType() + .getCheckCondition( column.getQuotedName( dialect ), dialect ); + if ( checkCondition != null ) { + column.setCheck( new CheckConstraint( checkCondition ) ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java new file mode 100644 index 0000000000..a31ccc430e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.mapping; + +/** + * Represents a table or column level {@code check} constraint. + * + * @author Gavin King + */ +public class CheckConstraint { + private String name; + private String constraint; + + public CheckConstraint(String name, String constraint) { + this.name = name; + this.constraint = constraint; + } + + public CheckConstraint(String constraint) { + this.constraint = constraint; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getConstraint() { + return constraint; + } + + public void setConstraint(String constraint) { + this.constraint = constraint; + } + + public String constraintString() { + return name == null + ? " check (" + constraint + ")" + : " constraint " + name + " check (" + constraint + ")"; + } +} 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 02eb46de55..b8f993b6f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -58,7 +58,7 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn private Integer sqlTypeCode; private boolean quoted; int uniqueInteger; - private String checkConstraint; + CheckConstraint checkConstraint; private String comment; private String defaultValue; private String generatedAs; @@ -404,15 +404,15 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn int typeStartIndex = 0; for ( Type subtype : subtypes ) { final int columnSpan = subtype.getColumnSpan(mapping); - if (typeStartIndex + columnSpan > typeIndex) { + if ( typeStartIndex + columnSpan > typeIndex ) { final int subtypeIndex = typeIndex - typeStartIndex; - if (subtype instanceof EntityType) { + if ( subtype instanceof EntityType ) { return getTypeForEntityValue(mapping, subtype, subtypeIndex); } - if (subtype instanceof ComponentType) { + if ( subtype instanceof ComponentType ) { return getTypeForComponentValue(mapping, subtype, subtypeIndex); } - if (subtypeIndex == 0) { + if ( subtypeIndex == 0 ) { return subtype; } break; @@ -437,7 +437,7 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn return getTypeForEntityValue( mapping, entityType.getIdentifierOrUniqueKeyType( mapping ), typeIndex ); } else if ( type instanceof ComponentType ) { - for (Type subtype : ((ComponentType) type).getSubtypes() ) { + for ( Type subtype : ((ComponentType) type).getSubtypes() ) { final Type result = getTypeForEntityValue( mapping, subtype, typeIndex - index ); if ( result != null ) { return result; @@ -490,20 +490,31 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn return specializedTypeDeclaration != null; } + @Deprecated(since = "6.2") public String getCheckConstraint() { - return checkConstraint; + return checkConstraint == null ? null : checkConstraint.getConstraint(); } - public void setCheckConstraint(String checkConstraint) { - this.checkConstraint = checkConstraint; + @Deprecated(since = "6.2") + public void setCheckConstraint(String constraint) { + checkConstraint = constraint == null ? null : new CheckConstraint( constraint ); + } + + public void setCheck(CheckConstraint check) { + checkConstraint = check; } public boolean hasCheckConstraint() { return checkConstraint != null; } + @Deprecated(since = "6.2") public String checkConstraint() { - return checkConstraint == null ? null : " check (" + checkConstraint + ")"; + return checkConstraint == null ? null : checkConstraint.constraintString(); + } + + public CheckConstraint getCheck() { + return checkConstraint; } @Override @@ -632,24 +643,27 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn @Override public Column clone() { Column copy = new Column(); - copy.setLength( length ); - copy.setScale( scale ); - copy.setValue( value ); - copy.setTypeIndex( typeIndex ); - copy.setName( getQuotedName() ); - copy.setNullable( nullable ); - copy.setPrecision( precision ); - copy.setUnique( unique ); - copy.setSqlType( sqlTypeName ); - copy.setSqlTypeCode( sqlTypeCode ); + copy.length = length; + copy.precision = precision; + copy.scale = scale; + copy.value = value; + copy.typeIndex = typeIndex; + copy.name = name; + copy.quoted = quoted; + copy.nullable = nullable; + copy.unique = unique; + copy.sqlTypeName = sqlTypeName; + copy.sqlTypeCode = sqlTypeCode; copy.uniqueInteger = uniqueInteger; //usually useless - copy.setCheckConstraint( checkConstraint ); - copy.setComment( comment ); - copy.setDefaultValue( defaultValue ); - copy.setGeneratedAs( generatedAs ); - copy.setAssignmentExpression( assignmentExpression ); - copy.setCustomRead( customRead ); - copy.setCustomWrite( customWrite ); + copy.checkConstraint = checkConstraint; + copy.comment = comment; + copy.defaultValue = defaultValue; + copy.generatedAs = generatedAs; + copy.assignmentExpression = assignmentExpression; + copy.customRead = customRead; + copy.customWrite = customWrite; + copy.specializedTypeDeclaration = specializedTypeDeclaration; + copy.columnSize = columnSize; return copy; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Join.java b/hibernate-core/src/main/java/org/hibernate/mapping/Join.java index fe5ca485e0..c2f896be74 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Join.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Join.java @@ -48,15 +48,15 @@ public class Join implements AttributeContainer, Serializable { private ExecuteUpdateResultCheckStyle deleteCheckStyle; @Override - public void addProperty(Property prop) { - properties.add(prop); - declaredProperties.add(prop); - prop.setPersistentClass( getPersistentClass() ); + public void addProperty(Property property) { + properties.add( property ); + declaredProperties.add( property ); + property.setPersistentClass( getPersistentClass() ); } - public void addMappedsuperclassProperty(Property prop) { - properties.add(prop); - prop.setPersistentClass( getPersistentClass() ); + public void addMappedsuperclassProperty( Property property ) { + properties.add( property ); + property.setPersistentClass( getPersistentClass() ); } public List getDeclaredProperties() { @@ -72,8 +72,8 @@ public class Join implements AttributeContainer, Serializable { return declaredProperties.iterator(); } - public boolean containsProperty(Property prop) { - return properties.contains(prop); + public boolean containsProperty(Property property) { + return properties.contains( property ); } @Deprecated(since = "6.0") @@ -84,6 +84,7 @@ public class Join implements AttributeContainer, Serializable { public Table getTable() { return table; } + public void setTable(Table table) { this.table = table; } @@ -91,6 +92,7 @@ public class Join implements AttributeContainer, Serializable { public KeyValue getKey() { return key; } + public void setKey(KeyValue key) { this.key = key; } @@ -116,11 +118,11 @@ public class Join implements AttributeContainer, Serializable { public void createPrimaryKey() { //Primary key constraint - PrimaryKey pk = new PrimaryKey( table ); - pk.setName( PK_ALIAS.toAliasString( table.getName() ) ); - table.setPrimaryKey(pk); + PrimaryKey primaryKey = new PrimaryKey( table ); + primaryKey.setName( PK_ALIAS.toAliasString( table.getName() ) ); + table.setPrimaryKey(primaryKey); - pk.addColumns( getKey() ); + primaryKey.addColumns( getKey() ); } public int getPropertySpan() { @@ -194,9 +196,8 @@ public class Join implements AttributeContainer, Serializable { } public boolean isLazy() { - Iterator iter = getPropertyIterator(); - while ( iter.hasNext() ) { - if ( !iter.next().isLazy() ) { + for ( Property property : properties ) { + if ( !property.isLazy() ) { return false; } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 938d7e75f4..6f0e785215 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -9,7 +9,6 @@ package org.hibernate.mapping; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -26,19 +25,24 @@ import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.FilterConfiguration; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.JoinedIterator; import org.hibernate.internal.util.collections.JoinedList; import org.hibernate.internal.util.collections.SingletonIterator; +import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.jpa.event.spi.CallbackDefinition; import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.Alias; +import org.hibernate.sql.Template; import org.hibernate.type.Type; +import org.hibernate.type.spi.TypeConfiguration; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; +import static java.util.Comparator.comparing; +import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.root; /** @@ -98,6 +102,8 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl private Component identifierMapper; private List callbackDefinitions; + private final List checkConstraints = new ArrayList<>(); + // Custom SQL private String customSQLInsert; private boolean customInsertCallable; @@ -725,9 +731,9 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl if ( !prop.isValid( mapping ) ) { final Type type = prop.getType(); final int actualColumns = prop.getColumnSpan(); - final int requiredColumns = type.getColumnSpan(mapping); + final int requiredColumns = type.getColumnSpan( mapping ); throw new MappingException( - "Property '" + StringHelper.qualify( getEntityName(), prop.getName() ) + "Property '" + qualify( getEntityName(), prop.getName() ) + "' maps to " + actualColumns + " columns but " + requiredColumns + " columns are required (type '" + type.getName() + "' spans " + requiredColumns + " columns)" @@ -1219,7 +1225,52 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl // End of @MappedSuperclass support public void prepareForMappingModel() { - properties.sort( Comparator.comparing( Property::getName ) ); + properties.sort( comparing( Property::getName ) ); } + public void mappingModelReady(MappingMetamodel mappingMetamodel) { + for ( CheckConstraint checkConstraint : checkConstraints ) { + final TypeConfiguration typeConfiguration = mappingMetamodel.getTypeConfiguration(); + final SessionFactoryImplementor sessionFactory = typeConfiguration.getSessionFactory(); + final List constrainedColumnNames = + Template.collectColumnNames( checkConstraint.getConstraint(), typeConfiguration, sessionFactory ); + final Table primary = getTable(); + long matches = matchesInTable( constrainedColumnNames, primary ); + if ( matches == constrainedColumnNames.size() ) { + // perfect, all columns matched in the primary table + primary.addCheck( checkConstraint ); + } + else { + // go searching for a secondary table which better matches + Table table = primary; + long max = matches; + for ( Join join : getJoins() ) { + final Table secondary = join.getTable(); + long secondaryMatches = matchesInTable( constrainedColumnNames, secondary ); + if ( secondaryMatches > max ) { + table = secondary; + max = secondaryMatches; + } + } + table.addCheck( checkConstraint ); + } + } + } + + private static long matchesInTable(List names, Table table) { + return table.getColumns().stream() + .filter( col -> col.isQuoted() + ? names.contains( col.getName() ) + : names.stream().anyMatch( name -> name.equalsIgnoreCase( col.getName() ) ) + ) + .count(); + } + + public void addCheckConstraint(CheckConstraint checkConstraint) { + checkConstraints.add( checkConstraint ); + } + + public List getCheckConstraints() { + return checkConstraints; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 6213c48207..3dc486bd4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -38,6 +38,7 @@ import org.jboss.logging.Logger; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collectors.toList; /** * A mapping model object representing a relational database {@linkplain jakarta.persistence.Table table}. @@ -64,7 +65,7 @@ public class Table implements Serializable, ContributableDatabaseObject { private final Map indexes = new LinkedHashMap<>(); private final Map uniqueKeys = new LinkedHashMap<>(); private int uniqueInteger; - private final List checkConstraints = new ArrayList<>(); + private final List checkConstraints = new ArrayList<>(); private String rowId; private String subselect; private boolean isAbstract; @@ -593,8 +594,13 @@ public class Table implements Serializable, ContributableDatabaseObject { return idValue; } + @Deprecated(since = "6.2") public void addCheckConstraint(String constraint) { - checkConstraints.add( constraint ); + addCheck( new CheckConstraint( constraint ) ); + } + + public void addCheck(CheckConstraint check) { + checkConstraints.add( check ); } public boolean containsColumn(Column column) { @@ -672,7 +678,12 @@ public class Table implements Serializable, ContributableDatabaseObject { return getCheckConstraints().iterator(); } + @Deprecated(since = "6.2") public List getCheckConstraints() { + return checkConstraints.stream().map( CheckConstraint::getConstraint ).collect( toList() ); + } + + public List getChecks() { return unmodifiableList( checkConstraints ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index fdc1a6ed26..601d9ef260 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -252,6 +252,9 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo bootModel.getNamedEntityGraphs().values(), runtimeModelCreationContext ); + + bootModel.getEntityBindings().forEach( persistentClass -> persistentClass.mappingModelReady( this ) ); + } private void registerEmbeddableMappingType(MetadataImplementor bootModel) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Template.java b/hibernate-core/src/main/java/org/hibernate/sql/Template.java index 5ed0b13f26..2628870a26 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Template.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Template.java @@ -15,6 +15,7 @@ import java.util.StringTokenizer; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.type.spi.TypeConfiguration; @@ -31,6 +32,8 @@ public final class Template { private static final Set KEYWORDS = new HashSet<>(); private static final Set BEFORE_TABLE_KEYWORDS = new HashSet<>(); private static final Set FUNCTION_KEYWORDS = new HashSet<>(); + public static final String PUNCTUATION = "=> collectColumnNames( + String checkConstraint, + TypeConfiguration typeConfiguration, + SessionFactoryImplementor sessionFactory) { + final String template = renderWhereStringTemplate( + checkConstraint, + sessionFactory.getJdbcServices().getDialect(), + typeConfiguration, + sessionFactory.getQueryEngine().getSqmFunctionRegistry() + ); + final List names = new ArrayList<>(); + int begin = 0; + int match; + while ( ( match = template.indexOf(TEMPLATE, begin) ) >= 0 ) { + int start = match + TEMPLATE.length() + 1; + for ( int loc = start;; loc++ ) { + if ( loc == template.length() - 1 ) { + names.add( template.substring( start ) ); + begin = template.length(); + break; + } + else { + char ch = template.charAt( loc ); + if ( PUNCTUATION.indexOf(ch) >= 0 || WHITESPACE.indexOf(ch) >= 0 ) { + names.add( template.substring( start, loc ) ); + begin = loc; + break; + } + } + } + } + return names; + } + // /** // * Takes the where condition provided in the mapping attribute and interpolates the alias. // * Handles sub-selects, quoted identifiers, quoted strings, expressions, SQL functions, diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/ColumnDefinitions.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/ColumnDefinitions.java index 9756325823..616c855a9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/ColumnDefinitions.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/ColumnDefinitions.java @@ -101,7 +101,7 @@ class ColumnDefinitions { } if ( dialect.supportsColumnCheck() && column.hasCheckConstraint() ) { - definition.append( column.checkConstraint() ); + definition.append( column.getCheck().constraintString() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java index 3326a6743a..35e4f92d7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java @@ -20,6 +20,7 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.mapping.AggregateColumn; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; @@ -151,8 +152,8 @@ public class StandardTableExporter implements Exporter { protected void applyTableCheck(Table table, StringBuilder buf) { if ( dialect.supportsTableCheck() ) { - for ( String constraint : table.getCheckConstraints() ) { - buf.append( ", check (" ).append( constraint ).append( ')' ); + for ( CheckConstraint constraint : table.getChecks() ) { + buf.append( "," ).append( constraint.constraintString() ); } final AggregateSupport aggregateSupport = dialect.getAggregateSupport(); if ( aggregateSupport != null && aggregateSupport.supportsComponentCheckConstraints() ) { @@ -231,7 +232,8 @@ public class StandardTableExporter implements Exporter
{ } else { for ( Column subColumn : value.getColumns() ) { - final String checkConstraint = subColumn.getCheckConstraint(); + final CheckConstraint check = subColumn.getCheck(); + final String checkConstraint = check == null ? null : check.getConstraint(); if ( !subColumn.isNullable() || checkConstraint != null ) { final String subColumnName = subColumn.getQuotedName( dialect ); final String columnExpression = aggregateSupport.aggregateComponentCustomReadExpression(