HHH-18054 Support for JPA 3.2 @CheckConstraint

This commit is contained in:
Andrea Boriero 2024-05-03 12:29:38 +02:00 committed by Steve Ebersole
parent 905e86a04d
commit ff07248944
24 changed files with 788 additions and 6 deletions

View File

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

View File

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

View File

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

View File

@ -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<jakarta.persistence.Column> column) {
applyCheckConstraints( column.findAttributeValue( "check" ) );
}
void applyCheckConstraints(List<AnnotationUsage<jakarta.persistence.CheckConstraint>> checkConstraintAnnotationUsages) {
if ( CollectionHelper.isNotEmpty( checkConstraintAnnotationUsages ) ) {
for ( AnnotationUsage<jakarta.persistence.CheckConstraint> 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

View File

@ -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<jakarta.persistence.JoinColumn> column) {
applyCheckConstraints( column.findAttributeValue( "check" ) );
}
}

View File

@ -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> check) {

View File

@ -447,7 +447,9 @@ public class EntityBinder {
final AnnotationUsage<jakarta.persistence.Table> 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> 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> 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;
}

View File

@ -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<AnnotationUsage<jakarta.persistence.CheckConstraint>> checkConstraintAnnotationUsages) {
if ( CollectionHelper.isNotEmpty( checkConstraintAnnotationUsages ) ) {
for ( AnnotationUsage<jakarta.persistence.CheckConstraint> checkConstraintAnnotationUsage : checkConstraintAnnotationUsages ) {
table.addCheck(
new CheckConstraint(
checkConstraintAnnotationUsage.getString( "name" ),
checkConstraintAnnotationUsage.getString( "constraint" ),
checkConstraintAnnotationUsage.getString( "options" )
)
);
}
}
}
public void setDefaultName(
String ownerClassName,
String ownerEntity,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -173,7 +173,7 @@ public class StandardTableExporter implements Exporter<Table> {
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() ) {

View File

@ -0,0 +1,4 @@
package org.hibernate.orm.test.schemaupdate.checkconstraint.column;
public interface Another {
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
package org.hibernate.orm.test.schemaupdate.checkconstraint.table;
public interface Another {
}

View File

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

View File

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

View File

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

View File

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