From ff07248944b20248ce8fb0224180cb91582f9969 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 3 May 2024 12:29:38 +0200 Subject: [PATCH] HHH-18054 Support for JPA 3.2 @CheckConstraint --- .../community/dialect/MySQLLegacyDialect.java | 9 ++ .../dialect/OracleLegacyDialect.java | 9 ++ .../dialect/SQLServerLegacyDialect.java | 19 +++ .../boot/model/internal/AnnotatedColumn.java | 24 ++- .../model/internal/AnnotatedJoinColumn.java | 5 + .../boot/model/internal/CollectionBinder.java | 5 + .../boot/model/internal/EntityBinder.java | 9 +- .../boot/model/internal/TableBinder.java | 18 +++ .../java/org/hibernate/dialect/Dialect.java | 27 ++++ .../org/hibernate/dialect/MySQLDialect.java | 10 ++ .../org/hibernate/dialect/OracleDialect.java | 10 ++ .../hibernate/dialect/SQLServerDialect.java | 18 +++ .../hibernate/mapping/CheckConstraint.java | 25 +++ .../schema/internal/ColumnDefinitions.java | 4 +- .../internal/StandardTableExporter.java | 2 +- .../checkconstraint/column/Another.java | 4 + .../column/AnotherTestEntity.java | 16 ++ .../column/ColumnCheckConstraintTest.java | 153 ++++++++++++++++++ .../checkconstraint/column/TestEntity.java | 120 ++++++++++++++ .../checkconstraint/table/Another.java | 4 + .../table/AnotherTestEntity.java | 14 ++ .../table/EntityWithSecondaryTables.java | 111 +++++++++++++ .../table/TableCheckConstraintTest.java | 153 ++++++++++++++++++ .../checkconstraint/table/TestEntity.java | 25 +++ 24 files changed, 788 insertions(+), 6 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/Another.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/AnotherTestEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/ColumnCheckConstraintTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/TestEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/Another.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/AnotherTestEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/EntityWithSecondaryTables.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TableCheckConstraintTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TestEntity.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java index 4739b447ac..9795340fe3 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java @@ -41,6 +41,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.sqm.CastType; @@ -1429,4 +1431,11 @@ public class MySQLLegacyDialect extends Dialect { return true; } + @Override + public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return sqlCheckConstraint + " " + checkConstraint.getOptions(); + } + return sqlCheckConstraint; + } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index dd8384b4a6..a85f18f435 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -68,6 +68,7 @@ import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.UserDefinedType; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -1570,4 +1571,12 @@ public class OracleLegacyDialect extends Dialect { return false; } + + @Override + public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return sqlCheckConstraint + " " + checkConstraint.getOptions(); + } + return sqlCheckConstraint; + } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index 5e441106f4..924247be26 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -49,6 +49,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.FetchClauseType; @@ -1159,4 +1161,21 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { public boolean supportsFromClauseInUpdate() { return true; } + + @Override + public String getCheckConstraintString(CheckConstraint checkConstraint) { + final String constraintName = checkConstraint.getName(); + return constraintName == null + ? + " check " + getCheckConstraintOptions( checkConstraint ) + "(" + checkConstraint.getConstraint() + ")" + : + " constraint " + constraintName + " check " + getCheckConstraintOptions( checkConstraint ) + "(" + checkConstraint.getConstraint() + ")"; + } + + private String getCheckConstraintOptions(CheckConstraint checkConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return checkConstraint.getOptions() + " "; + } + return ""; + } } 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 861132369f..52d9e8bfb2 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 @@ -30,6 +30,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; @@ -213,6 +214,10 @@ public class AnnotatedColumn { checkConstraints.add( new CheckConstraint( name, constraint ) ); } + public void addCheckConstraint(String name, String constraint, String options) { + checkConstraints.add( new CheckConstraint( name, constraint, options ) ); + } + // public String getComment() { // return comment; // } @@ -800,6 +805,7 @@ public class AnnotatedColumn { annotatedColumn.setParent( parent ); annotatedColumn.applyColumnDefault( inferredData, numberOfColumns ); annotatedColumn.applyGeneratedAs( inferredData, numberOfColumns ); + annotatedColumn.applyColumnCheckConstraint( column ); annotatedColumn.applyCheckConstraint( inferredData, numberOfColumns ); annotatedColumn.extractDataFromPropertyData( propertyHolder, inferredData ); annotatedColumn.bind(); @@ -873,7 +879,23 @@ public class AnnotatedColumn { } } - private void applyCheckConstraint(PropertyData inferredData, int length) { + private void applyColumnCheckConstraint(AnnotationUsage column) { + applyCheckConstraints( column.findAttributeValue( "check" ) ); + } + + void applyCheckConstraints(List> checkConstraintAnnotationUsages) { + if ( CollectionHelper.isNotEmpty( checkConstraintAnnotationUsages ) ) { + for ( AnnotationUsage checkConstraintAnnotationUsage : checkConstraintAnnotationUsages ) { + addCheckConstraint( + checkConstraintAnnotationUsage.getString( "name" ), + checkConstraintAnnotationUsage.getString( "constraint" ), + checkConstraintAnnotationUsage.getString( "options" ) + ); + } + } + } + + void applyCheckConstraint(PropertyData inferredData, int length) { final MemberDetails attributeMember = inferredData.getAttributeMember(); if ( attributeMember != null ) { // if there are multiple annotations, they're not overrideable diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java index 58b0d9ed83..0ac73e6844 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java @@ -195,6 +195,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn { setInsertable( joinColumn.getBoolean( "insertable" ) ); setUpdatable( joinColumn.getBoolean( "updatable" ) ); setReferencedColumn( joinColumn.getString( "referencedColumnName" ) ); + applyColumnCheckConstraint( joinColumn ); final String table = joinColumn.getString( "table" ); if ( table.isEmpty() ) { @@ -516,4 +517,8 @@ public class AnnotatedJoinColumn extends AnnotatedColumn { public void setParent(AnnotatedJoinColumns parent) { super.setParent( parent ); } + + private void applyColumnCheckConstraint(AnnotationUsage column) { + applyCheckConstraints( column.findAttributeValue( "check" ) ); + } } 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 0fd74ac0a0..2e585ddcca 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 @@ -2584,6 +2584,11 @@ public abstract class CollectionBinder { property.forEachAnnotationUsage( Check.class, (usage) -> { addCheckToCollection( collectionTable, usage ); } ); + property.forEachAnnotationUsage( + jakarta.persistence.JoinTable.class, + (usage) -> + TableBinder.addTableCheck( collectionTable, usage.findAttributeValue( "check" ) ) + ); } private static void addCheckToCollection(Table collectionTable, AnnotationUsage check) { 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 4731ad83b8..a29c3c90a4 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 @@ -447,7 +447,9 @@ public class EntityBinder { final AnnotationUsage jpaTableUsage = annotatedClass.getAnnotationUsage( jakarta.persistence.Table.class ); if ( jpaTableUsage != null ) { - TableBinder.addJpaIndexes( persistentClass.getTable(), jpaTableUsage.getList( "indexes" ), context ); + final Table table = persistentClass.getTable(); + TableBinder.addJpaIndexes( table, jpaTableUsage.getList( "indexes" ), context ); + TableBinder.addTableCheck( table, jpaTableUsage.findAttributeValue( "check" ) ); } final InFlightMetadataCollector.EntityTableXref entityTableXref = context @@ -2167,7 +2169,7 @@ public class EntityBinder { //Used for @*ToMany @JoinTable public Join addJoinTable(AnnotationUsage joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) { - return addJoin( + final Join join = addJoin( holder, noDelayInPkColumnCreation, false, @@ -2177,6 +2179,8 @@ public class EntityBinder { joinTable.getList( "joinColumns" ), joinTable.getList( "uniqueConstraints" ) ); + TableBinder.addTableCheck( join.getTable(), joinTable.findAttributeValue( "check" )); + return join; } public Join addSecondaryTable(AnnotationUsage secondaryTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) { @@ -2192,6 +2196,7 @@ public class EntityBinder { ); final Table table = join.getTable(); new IndexBinder( context ).bindIndexes( table, secondaryTable.getList( "indexes" ) ); + TableBinder.addTableCheck( table, secondaryTable.findAttributeValue( "check" )); return join; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java index 166827addc..2de2908b1e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java @@ -23,6 +23,8 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.mapping.Any; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; @@ -870,6 +872,22 @@ public class TableBinder { new IndexBinder( context ).bindIndexes( table, indexes ); } + static void addTableCheck( + Table table, + List> checkConstraintAnnotationUsages) { + if ( CollectionHelper.isNotEmpty( checkConstraintAnnotationUsages ) ) { + for ( AnnotationUsage checkConstraintAnnotationUsage : checkConstraintAnnotationUsages ) { + table.addCheck( + new CheckConstraint( + checkConstraintAnnotationUsage.getString( "name" ), + checkConstraintAnnotationUsage.getString( "constraint" ), + checkConstraintAnnotationUsage.getString( "options" ) + ) + ); + } + } + } + public void setDefaultName( String ownerClassName, String ownerEntity, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 597c2d2486..f3efcad051 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -109,6 +109,7 @@ import org.hibernate.internal.util.MathHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; import org.hibernate.mapping.Constraint; import org.hibernate.mapping.ForeignKey; @@ -5627,4 +5628,30 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun return FunctionalDependencyAnalysisSupportImpl.NONE; } + /** + * Render a SQL check condition for {@link CheckConstraint} + * + * @return a SQL expression representing the {@link CheckConstraint} + */ + public String getCheckConstraintString(CheckConstraint checkConstraint) { + final String constraintName = checkConstraint.getName(); + String constraint = constraintName == null + ? " check (" + checkConstraint.getConstraint() + ")" + : " constraint " + constraintName + " check (" + checkConstraint.getConstraint() + ")"; + return appendCheckConstraintOptions( checkConstraint, constraint ); + } + + /** + * Append the {@link CheckConstraint} options to SQL check sqlCheckConstraint + * + * @param checkConstraint an instance of {@link CheckConstraint} + * @param sqlCheckConstraint the SQL to append the {@link CheckConstraint} options + * + * @return a SQL expression + */ + public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) { + return sqlCheckConstraint; + } + + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 08a425ddda..d107bd8939 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -47,6 +47,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.sqm.CastType; @@ -1571,4 +1573,12 @@ public class MySQLDialect extends Dialect { public boolean supportsFromClauseInUpdate() { return true; } + + @Override + public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return sqlCheckConstraint + " " + checkConstraint.getOptions(); + } + return sqlCheckConstraint; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index d5b1e2ecf2..9dc3f79952 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -49,8 +49,10 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.UserDefinedType; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.mutation.EntityMutationTarget; @@ -1692,4 +1694,12 @@ public class OracleDialect extends Dialect { return false; } + + @Override + public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return sqlCheckConstraint + " " + checkConstraint.getOptions(); + } + return sqlCheckConstraint; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index feb6c45c0f..bb7b925ee3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -55,6 +55,8 @@ import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.Column; import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.procedure.internal.SQLServerCallableStatementSupport; @@ -1193,4 +1195,20 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { return SQLServerCallableStatementSupport.INSTANCE; } + @Override + public String getCheckConstraintString(CheckConstraint checkConstraint) { + final String constraintName = checkConstraint.getName(); + return constraintName == null + ? + " check " + getCheckConstraintOptions( checkConstraint ) + "(" + checkConstraint.getConstraint() + ")" + : + " constraint " + constraintName + " check " + getCheckConstraintOptions( checkConstraint ) + "(" + checkConstraint.getConstraint() + ")"; + } + + private String getCheckConstraintOptions(CheckConstraint checkConstraint) { + if ( StringHelper.isNotEmpty( checkConstraint.getOptions() ) ) { + return checkConstraint.getOptions() + " "; + } + return ""; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java index 10de5db9f2..6d68e76800 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java @@ -8,6 +8,8 @@ package org.hibernate.mapping; import java.util.Objects; +import org.hibernate.dialect.Dialect; + /** * Represents a table or column level {@code check} constraint. * @@ -16,12 +18,19 @@ import java.util.Objects; public class CheckConstraint { private String name; private String constraint; + private String options; public CheckConstraint(String name, String constraint) { this.name = name; this.constraint = constraint; } + public CheckConstraint(String name, String constraint, String options) { + this.name = name; + this.constraint = constraint; + this.options = options; + } + public CheckConstraint(String constraint) { this.constraint = constraint; } @@ -54,12 +63,28 @@ public class CheckConstraint { this.constraint = constraint; } + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + + /** + * @deprecated use {@link #constraintString(Dialect)} instead. + */ + @Deprecated(since = "7.0") public String constraintString() { return name == null ? " check (" + constraint + ")" : " constraint " + name + " check (" + constraint + ")"; } + public String constraintString(Dialect dialect) { + return dialect.getCheckConstraintString( this ); + } + @Override public boolean equals(Object object) { if ( object instanceof CheckConstraint ) { 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 ac3bceff68..46ef3f07ef 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 @@ -137,7 +137,7 @@ class ColumnDefinitions { long anonConstraints = checkConstraints.stream().filter(CheckConstraint::isAnonymous).count(); if ( anonConstraints == 1 ) { for ( CheckConstraint constraint : checkConstraints ) { - definition.append( constraint.constraintString() ); + definition.append( constraint.constraintString( dialect ) ); } } else { @@ -159,7 +159,7 @@ class ColumnDefinitions { } for ( CheckConstraint constraint : checkConstraints ) { if ( constraint.isNamed() ) { - definition.append( constraint.constraintString() ); + definition.append( constraint.constraintString( dialect ) ); } } } 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 23f225a44f..a5d2418964 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 @@ -173,7 +173,7 @@ public class StandardTableExporter implements Exporter { protected void applyTableCheck(Table table, StringBuilder buf) { if ( dialect.supportsTableCheck() ) { for ( CheckConstraint constraint : table.getChecks() ) { - buf.append( "," ).append( constraint.constraintString() ); + buf.append( "," ).append( constraint.constraintString( dialect ) ); } final AggregateSupport aggregateSupport = dialect.getAggregateSupport(); if ( aggregateSupport != null && aggregateSupport.supportsComponentCheckConstraints() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/Another.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/Another.java new file mode 100644 index 0000000000..740b5e811a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/Another.java @@ -0,0 +1,4 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.column; + +public interface Another { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/AnotherTestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/AnotherTestEntity.java new file mode 100644 index 0000000000..ce3303cee1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/AnotherTestEntity.java @@ -0,0 +1,16 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.column; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity(name = "AnotherTestEntity") +@Table(name = "ANOTHER_TEST_ENTITY") +public class AnotherTestEntity implements Another { + @Id + private Long id; + + @Column(name = "FIRST_NAME") + private String firstName; +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/ColumnCheckConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/ColumnCheckConstraintTest.java new file mode 100644 index 0000000000..73379201d2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/ColumnCheckConstraintTest.java @@ -0,0 +1,153 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.column; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.Locale; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BaseUnitTest +@JiraKey("HHH-18054") +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsColumnCheck.class) +public class ColumnCheckConstraintTest { + static final String COLUMN_CONSTRAINTS = "name_column <> null"; + + static final String ONE_TO_ONE_JOIN_COLUMN_CONSTRAINTS = "ID <> null"; + static final String ONE_TO_MANY_JOIN_COLUMN_CONSTRAINTS = "ID > 2"; + static final String MANY_TO_ONE_JOIN_COLUMN_CONSTRAINTS = "ID > 3 "; + static final String MANY_TO_MANY_JOIN_COLUMN_CONSTRAINTS = "ID > 4"; + static final String MANY_TO_MANY_INVERSE_JOIN_COLUMN_CONSTRAINTS = "ID > 5"; + static final String ANY_JOIN_COLUMN_CONSTRAINTS = "ID > 5"; + + static final String ONE_TO_ONE_JOIN_COLUMN_NAME = "ONE_TO_ONE_JOIN_COLUMN_NAME"; + static final String ONE_TO_MANY_JOIN_COLUMN_NAME = "ONE_TO_MAIN_JOIN_COLUMN_NAME"; + static final String MANY_TO_ONE_JOIN_COLUMN_NAME = "MANY_TO_ONE_JOIN_COLUMN_NAME"; + static final String MANY_TO_MANY_JOIN_COLUMN_NAME = "MANY_TO_MANY_JOIN_COLUMN_NAME"; + static final String MANY_TO_MANY_INVERSE_JOIN_COLUMN_NAME = "MANY_TO_MANY_INVERSE_JOIN_COLUMN_NAME"; + + private File output; + private StandardServiceRegistry ssr; + private MetadataImplementor metadata; + + @BeforeEach + public void setUp() throws IOException { + output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + ssr = ServiceRegistryUtil.serviceRegistry(); + } + + @AfterEach + public void tearsDown() { + output.delete(); + StandardServiceRegistryBuilder.destroy( ssr ); + } + + @Test + public void testColumnConstraintsAreApplied() throws Exception { + createSchema( TestEntity.class, AnotherTestEntity.class ); + String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .replace( System.lineSeparator(), "" ); + assertThat( fileContent.toUpperCase( Locale.ROOT ) ).contains( COLUMN_CONSTRAINTS.toUpperCase( Locale.ROOT ) ); + } + + @Test + public void testJoinColumConstraintsAreApplied() throws Exception { + createSchema( TestEntity.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "TEST_ENTITY", + MANY_TO_ONE_JOIN_COLUMN_CONSTRAINTS + ), "Check Constraints on ManyToOne join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "TEST_ENTITY", + ONE_TO_ONE_JOIN_COLUMN_CONSTRAINTS + ), "Check Constraints on OneToOne join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "ANOTHER_TEST_ENTITY", + ONE_TO_MANY_JOIN_COLUMN_CONSTRAINTS + ), "Check Constraints on OneToOne join table have not been created" ); + } + + @Test + public void testJoinColumOfJoinTableConstraintsAreApplied() throws Exception { + createSchema( TestEntity.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "MANY_T0_MANY_TABLE", + MANY_TO_MANY_JOIN_COLUMN_CONSTRAINTS + ), "Join column Check Constraints on ManyToMany join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "MANY_T0_MANY_TABLE", + MANY_TO_MANY_INVERSE_JOIN_COLUMN_CONSTRAINTS + ), "Inverse join column Check Constraints on ManyToMany join table have not been created" ); + } + + @Test + public void testAnyJoinTableConstraintsAreApplied() throws Exception { + createSchema( TestEntity.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + "TEST_ENTITY", + ANY_JOIN_COLUMN_CONSTRAINTS + ), "Check Constraints on Any join table have not been created" ); + } + + private static boolean tableCreationStatementContainsConstraints( + String[] fileContent, + String tableName, + String secondaryTableConstraints) { + for ( int i = 0; i < fileContent.length; i++ ) { + String statement = fileContent[i].toUpperCase( Locale.ROOT ); + if ( statement.contains( "CREATE TABLE " + tableName.toUpperCase( Locale.ROOT ) ) ) { + if ( statement.contains( secondaryTableConstraints.toUpperCase( Locale.ROOT ) ) ) { + return true; + } + } + } + return false; + } + + private void createSchema(Class... annotatedClasses) { + final MetadataSources metadataSources = new MetadataSources( ssr ); + + for ( Class c : annotatedClasses ) { + metadataSources.addAnnotatedClass( c ); + } + metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.orderColumns( false ); + metadata.validate(); + new SchemaExport() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setFormat( false ) + .create( EnumSet.of( TargetType.SCRIPT ), metadata ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/TestEntity.java new file mode 100644 index 0000000000..3567720aec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/column/TestEntity.java @@ -0,0 +1,120 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.column; + +import java.util.List; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyDiscriminatorValues; +import org.hibernate.annotations.AnyKeyJavaClass; + +import jakarta.persistence.CheckConstraint; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +@Entity +@Table(name = "TEST_ENTITY") +public class TestEntity { + @Id + private Long id; + + @Column( + name = "name_column", + check = { + @CheckConstraint( + name = "column_constraint", + constraint = ColumnCheckConstraintTest.COLUMN_CONSTRAINTS, + options = "enforced" + ) + }) + private String name; + + @OneToOne + @JoinColumn( + name = ColumnCheckConstraintTest.ONE_TO_ONE_JOIN_COLUMN_NAME, + check = { + @CheckConstraint( + name = "ONE_TO_ONE_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.ONE_TO_ONE_JOIN_COLUMN_CONSTRAINTS + ) + } + ) + private AnotherTestEntity entity; + + @ManyToOne + @JoinColumn( + name = ColumnCheckConstraintTest.MANY_TO_ONE_JOIN_COLUMN_NAME, + check = { + @CheckConstraint( + name = "MANY_TO_ONE_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.MANY_TO_ONE_JOIN_COLUMN_CONSTRAINTS + ) + } + ) + private AnotherTestEntity testEntity; + + @OneToMany + @JoinColumn( + name = ColumnCheckConstraintTest.ONE_TO_MANY_JOIN_COLUMN_NAME, + check = { + @CheckConstraint( + name = "ONE_TO_MANY_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.ONE_TO_MANY_JOIN_COLUMN_CONSTRAINTS + ) + } + ) + private List testEntities; + + @ManyToMany + @JoinTable( + name = "MANY_T0_MANY_TABLE", + inverseJoinColumns = { + @JoinColumn( + name = ColumnCheckConstraintTest.MANY_TO_MANY_INVERSE_JOIN_COLUMN_NAME, + check = { + @CheckConstraint( + name = "MANY_TO_MANY_INVERSE_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.MANY_TO_MANY_INVERSE_JOIN_COLUMN_CONSTRAINTS + ) + } + ), + }, + joinColumns = { + @JoinColumn( + name = ColumnCheckConstraintTest.MANY_TO_MANY_JOIN_COLUMN_NAME, + check = { + @CheckConstraint( + name = "MANY_TO_MANY_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.MANY_TO_MANY_JOIN_COLUMN_CONSTRAINTS + ) + } + ), + } + ) + private List testEntities2; + + @Any + @AnyDiscriminator(DiscriminatorType.STRING) + @AnyDiscriminatorValues({ + @AnyDiscriminatorValue(discriminator = "S", entity = AnotherTestEntity.class), + }) + @AnyKeyJavaClass(Long.class) + @Column(name = "another_type") + @JoinColumn(name = "another_id", + check = { + @CheckConstraint( + name = "ANY_JOIN_COLUMN_CONSTRAINT", + constraint = ColumnCheckConstraintTest.ANY_JOIN_COLUMN_CONSTRAINTS + ) + }) + private Another another; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/Another.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/Another.java new file mode 100644 index 0000000000..2dbe102929 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/Another.java @@ -0,0 +1,4 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.table; + +public interface Another { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/AnotherTestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/AnotherTestEntity.java new file mode 100644 index 0000000000..9f0955a239 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/AnotherTestEntity.java @@ -0,0 +1,14 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.table; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity(name = "AnotherTestEntity") +public class AnotherTestEntity implements Another { + @Id + private Long id; + + @Column(name = "FIRST_NAME") + private String firstName; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/EntityWithSecondaryTables.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/EntityWithSecondaryTables.java new file mode 100644 index 0000000000..22e6d17688 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/EntityWithSecondaryTables.java @@ -0,0 +1,111 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.table; + +import java.util.List; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyDiscriminatorValues; +import org.hibernate.annotations.AnyKeyJavaClass; + +import jakarta.persistence.CheckConstraint; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.SecondaryTable; + +@Entity(name = "TEST_ENTITY_2") +@SecondaryTable( + name = TableCheckConstraintTest.SECONDARY_TABLE_NAME, + check = { + @CheckConstraint( + name = "TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.SECONDARY_TABLE_CONSTRAINTS + ) + } +) +public class EntityWithSecondaryTables { + @Id + private Long id; + + @Column(name = "NAME_COLUMN") + private String name; + + @Column(name = "SECOND_NAME", table = TableCheckConstraintTest.SECONDARY_TABLE_NAME) + private String secondName; + + @OneToOne + @JoinTable( + name = TableCheckConstraintTest.ONE_TO_ONE_JOIN_TABLE_NAME, + check = { + @CheckConstraint( + name = "ONE_TO_ONE_JOIN_TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.ONE_TO_ONE_JOIN_TABLE_CONSTRAINTS + ) + } + ) + private AnotherTestEntity entity; + + @ManyToOne + @JoinTable( + name = TableCheckConstraintTest.MANY_TO_ONE_JOIN_TABLE_NAME, + check = { + @CheckConstraint( + name = "MANY_TO_ONE_JOIN_TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.MANY_TO_ONE_JOIN_TABLE_CONSTRAINTS + ) + } + ) + private AnotherTestEntity testEntity; + + @OneToMany + @JoinTable( + name = TableCheckConstraintTest.ONE_TO_MANY_JOIN_TABLE_NAME, + check = { + @CheckConstraint( + name = "ONE_TO_MANY_JOIN_TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.ONE_TO_MANY_JOIN_TABLE_CONSTRAINTS + ) + } + ) + private List testEntities; + + @ManyToMany + @JoinTable( + name = TableCheckConstraintTest.MANY_TO_MANY_JOIN_TABLE_NAME, + check = { + @CheckConstraint( + name = "MANY_TO_MANY_JOIN_TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.MANY_TO_MANY_JOIN_TABLE_CONSTRAINTS + ) + } + ) + private List testEntities2; + + @Any + @AnyDiscriminator(DiscriminatorType.STRING) + @AnyDiscriminatorValues({ + @AnyDiscriminatorValue(discriminator = "S", entity = AnotherTestEntity.class), + }) + @AnyKeyJavaClass(Long.class) + @Column(name = "another_type") + @JoinColumn(name = "another_id") + @JoinTable( + name = TableCheckConstraintTest.ANY_JOIN_TABLE_NAME, + check = { + @CheckConstraint( + name = "ANY_JOIN_TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.ANY_JOIN_TABLE_CONSTRAINTS + ) + } + ) + private Another another; +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TableCheckConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TableCheckConstraintTest.java new file mode 100644 index 0000000000..9713f7a7f5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TableCheckConstraintTest.java @@ -0,0 +1,153 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.table; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.Locale; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BaseUnitTest +@JiraKey("HHH-18054") +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsColumnCheck.class) +public class TableCheckConstraintTest { + static final String CONSTRAINTS = "NAME_COLUMN <> null"; + static final String SECONDARY_TABLE_CONSTRAINTS = "SECOND_NAME <> null"; + static final String ONE_TO_ONE_JOIN_TABLE_CONSTRAINTS = "ID <> null"; + static final String ONE_TO_MANY_JOIN_TABLE_CONSTRAINTS = "ID > 2"; + static final String MANY_TO_ONE_JOIN_TABLE_CONSTRAINTS = "ID > 3 "; + static final String MANY_TO_MANY_JOIN_TABLE_CONSTRAINTS = "ID > 4"; + static final String ANY_JOIN_TABLE_CONSTRAINTS = "ID > 5"; + + static final String SECONDARY_TABLE_NAME = "SECOND_TABLE_NAME"; + static final String ONE_TO_ONE_JOIN_TABLE_NAME = "ONE_TO_ONE_JOIN_TABLE_NAME"; + static final String ONE_TO_MANY_JOIN_TABLE_NAME = "ONE_TO_MAIN_JOIN_TABLE_NAME"; + static final String MANY_TO_ONE_JOIN_TABLE_NAME = "MANY_TO_ONE_JOIN_TABLE_NAME"; + static final String MANY_TO_MANY_JOIN_TABLE_NAME = "MANY_TO_MANY_JOIN_TABLE_NAME"; + static final String ANY_JOIN_TABLE_NAME = "ANY_JOIN_TABLE_NAME"; + + private File output; + private StandardServiceRegistry ssr; + private MetadataImplementor metadata; + + @BeforeEach + public void setUp() throws IOException { + output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + ssr = ServiceRegistryUtil.serviceRegistry(); + } + + @AfterEach + public void tearsDown() { + output.delete(); + StandardServiceRegistryBuilder.destroy( ssr ); + } + + @Test + public void testTableConstraintsAreApplied() throws Exception { + createSchema( TestEntity.class ); + String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .replace( System.lineSeparator(), "" ); + assertThat( fileContent.toUpperCase( Locale.ROOT ) ).contains( CONSTRAINTS.toUpperCase( Locale.ROOT ) ); + } + + @Test + public void testSecondaryTableConstraintsAreApplied() throws Exception { + createSchema( EntityWithSecondaryTables.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + SECONDARY_TABLE_NAME, + SECONDARY_TABLE_CONSTRAINTS + ), "Check Constraints on secondary table have not been created" ); + } + + @Test + public void testJoinTableConstraintsAreApplied() throws Exception { + createSchema( EntityWithSecondaryTables.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + MANY_TO_ONE_JOIN_TABLE_NAME, + MANY_TO_ONE_JOIN_TABLE_CONSTRAINTS + ), "Check Constraints on ManyToOne join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + MANY_TO_MANY_JOIN_TABLE_NAME, + MANY_TO_MANY_JOIN_TABLE_CONSTRAINTS + ), "Check Constraints on ManyToMany join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + ONE_TO_ONE_JOIN_TABLE_NAME, + ONE_TO_ONE_JOIN_TABLE_CONSTRAINTS + ), "Check Constraints on OneToOne join table have not been created" ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + ONE_TO_MANY_JOIN_TABLE_NAME, + ONE_TO_MANY_JOIN_TABLE_CONSTRAINTS + ), "Check Constraints on OneToOne join table have not been created" ); + } + + @Test + public void testAnyJoinTableConstraintsAreApplied() throws Exception { + createSchema( EntityWithSecondaryTables.class, AnotherTestEntity.class ); + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + assertTrue( tableCreationStatementContainsConstraints( + fileContent, + ANY_JOIN_TABLE_NAME, + ANY_JOIN_TABLE_CONSTRAINTS + ), "Check Constraints on Any join table have not been created" ); + } + + private static boolean tableCreationStatementContainsConstraints( + String[] fileContent, + String tableName, + String secondaryTableConstraints) { + for ( int i = 0; i < fileContent.length; i++ ) { + String statement = fileContent[i].toUpperCase( Locale.ROOT ); + if ( statement.contains( "CREATE TABLE " + tableName.toUpperCase( Locale.ROOT ) ) ) { + if ( statement.contains( secondaryTableConstraints.toUpperCase( Locale.ROOT ) ) ) { + return true; + } + } + } + return false; + } + + private void createSchema(Class... annotatedClasses) { + final MetadataSources metadataSources = new MetadataSources( ssr ); + + for ( Class c : annotatedClasses ) { + metadataSources.addAnnotatedClass( c ); + } + metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.orderColumns( false ); + metadata.validate(); + new SchemaExport() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setFormat( false ) + .create( EnumSet.of( TargetType.SCRIPT ), metadata ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TestEntity.java new file mode 100644 index 0000000000..aba8989659 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/checkconstraint/table/TestEntity.java @@ -0,0 +1,25 @@ +package org.hibernate.orm.test.schemaupdate.checkconstraint.table; + +import jakarta.persistence.CheckConstraint; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity(name = "TEST_ENTITY_1") +@Table( + name = "TEST_ENTITY_TABLE", + check = { + @CheckConstraint( + name = "TABLE_CONSTRAINT", + constraint = TableCheckConstraintTest.CONSTRAINTS + ) + } +) +public class TestEntity { + @Id + private Long id; + + @Column(name = "NAME_COLUMN") + private String name; +}