automatically detect when a @Check refers to a @SecondaryTable

- also support named check constraints (multiple of them)
- also support check constraints on collection tables
This commit is contained in:
Gavin 2023-01-02 00:07:32 +01:00 committed by Gavin King
parent abb89a32b1
commit 1657c22aca
21 changed files with 413 additions and 250 deletions

View File

@ -6,10 +6,12 @@
*/ */
package org.hibernate.userguide.schema; package org.hibernate.userguide.schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.PersistenceException; import jakarta.persistence.PersistenceException;
import jakarta.persistence.SecondaryTable;
import org.hibernate.annotations.Check; import org.hibernate.annotations.Check;
import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.NaturalId;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;
@ -20,6 +22,8 @@ import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
import org.junit.Test; import org.junit.Test;
import java.time.LocalDate;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -120,7 +124,9 @@ public class CheckTest extends BaseEntityManagerFunctionalTestCase {
//tag::schema-generation-database-checks-example[] //tag::schema-generation-database-checks-example[]
@Entity(name = "Book") @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 { public static class Book {
@Id @Id
@ -133,6 +139,12 @@ public class CheckTest extends BaseEntityManagerFunctionalTestCase {
private Double price; private Double price;
@Column(table = "BookEdition")
private int edition = 1;
@Column(table = "BookEdition")
private LocalDate editionDate;
//Getters and setters omitted for brevity //Getters and setters omitted for brevity
//end::schema-generation-database-checks-example[] //end::schema-generation-database-checks-example[]

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.annotations; package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; 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. * Specifies a {@code check} constraint to be included in the generated DDL.
* <ul> * <ul>
* <li>When a field or property is annotated, the check constraint is added to the column definition. * <li>When a basic-typed field or property is annotated, the check constraint is
* <li>When an entity class is annotated, the check constraint is added to the primary table. * added to the {@linkplain jakarta.persistence.Column column} definition.
* <li>When a {@link jakarta.persistence.ManyToOne} association is annotated, the
* check constraint is added to the {@linkplain jakarta.persistence.JoinColumn
* join column} definition.
* <li>When an owned collection is annotated, the check constraint is added to the
* {@linkplain jakarta.persistence.CollectionTable collection table} or
* {@linkplain jakarta.persistence.JoinTable association join table}.
* <li>When an entity class is annotated, the check constraint is added to either
* the {@linkplain jakarta.persistence.Table primary table} or to a
* {@linkplain jakarta.persistence.SecondaryTable secondary table}, depending
* on which columns are involved in the constraint expression specified by
* {@link #constraints()}.
* </ul> * </ul>
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
@ -27,7 +39,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*/ */
@Target({TYPE, METHOD, FIELD}) @Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME) @Retention(RUNTIME)
@Repeatable(Checks.class)
public @interface Check { public @interface Check {
/**
* The optional name of the check constraint.
*/
String name() default "";
/** /**
* The check constraint, written in native SQL. * The check constraint, written in native SQL.
*/ */

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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();
}

View File

@ -22,10 +22,16 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* *
* @see jakarta.persistence.Table * @see jakarta.persistence.Table
* @see jakarta.persistence.SecondaryTable * @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) @Target(TYPE)
@Retention(RUNTIME) @Retention(RUNTIME)
@Repeatable(Tables.class) @Repeatable(Tables.class)
@Deprecated(since = "6.2", forRemoval = true)
public @interface Table { public @interface Table {
/** /**
* The name of the targeted table. * The name of the targeted table.
@ -43,11 +49,10 @@ public @interface Table {
/** /**
* A check constraint, written in native SQL. * A check constraint, written in native SQL.
* <p>
* <em>Useful for secondary tables, otherwise use {@link Check}.</em>
* *
* @see Check * @deprecated use {@link Check}.
*/ */
@Deprecated(since = "6.2")
String checkConstraint() default ""; 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. * 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 use {@link jakarta.persistence.SecondaryTable#foreignKey()}
*/ */
@Deprecated(since = "6.0", forRemoval = true) @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. * If enabled, Hibernate will never insert or update the columns of the secondary table.
* <p>
* <em>Only applies to secondary tables.</em>
* *
* @apiNote Only relevant to secondary tables
* @deprecated use {@link SecondaryRow#owned()} * @deprecated use {@link SecondaryRow#owned()}
*/ */
@Deprecated(since = "6.2") @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, * 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. * 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 use {@link SecondaryRow#optional()}
*/ */
@Deprecated(since = "6.2") @Deprecated(since = "6.2")
@ -95,9 +100,8 @@ public @interface Table {
/** /**
* Defines a custom SQL insert statement. * Defines a custom SQL insert statement.
* <p>
* <em>Only applies to secondary tables.</em>
* *
* @apiNote Only relevant to secondary tables
* @deprecated use {@link SQLInsert#table()} to specify the secondary table * @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/ */
@Deprecated(since="6.2") @Deprecated(since="6.2")
@ -105,9 +109,8 @@ public @interface Table {
/** /**
* Defines a custom SQL update statement. * Defines a custom SQL update statement.
* <p>
* <em>Only applies to secondary tables.</em>
* *
* @apiNote Only relevant to secondary tables
* @deprecated use {@link SQLInsert#table()} to specify the secondary table * @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/ */
@Deprecated(since="6.2") @Deprecated(since="6.2")
@ -115,9 +118,8 @@ public @interface Table {
/** /**
* Defines a custom SQL delete statement. * Defines a custom SQL delete statement.
* <p>
* <em>Only applies to secondary tables.</em>
* *
* @apiNote Only relevant to secondary tables
* @deprecated use {@link SQLInsert#table()} to specify the secondary table * @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/ */
@Deprecated(since="6.2") @Deprecated(since="6.2")

View File

@ -16,9 +16,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* A grouping of {@link Table}s. * A grouping of {@link Table}s.
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
*
* @deprecated since {@link Table} is deprecated
*/ */
@Target(TYPE) @Target(TYPE)
@Retention(RUNTIME) @Retention(RUNTIME)
@Deprecated(since = "6.2", forRemoval = true)
public @interface Tables { public @interface Tables {
/** /**
* The table grouping. * The table grouping.

View File

@ -27,6 +27,7 @@ import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.Formula; import org.hibernate.mapping.Formula;
@ -83,6 +84,7 @@ public class AnnotatedColumn {
private String generatedAs; private String generatedAs;
// private String comment; // private String comment;
private String checkConstraintName;
private String checkConstraint; private String checkConstraint;
private AnnotatedColumns parent; private AnnotatedColumns parent;
@ -124,10 +126,6 @@ public class AnnotatedColumn {
return isNotEmpty( formulaString ); return isNotEmpty( formulaString );
} }
public String getFormulaString() {
return formulaString;
}
public String getExplicitTableName() { public String getExplicitTableName() {
return explicitTableName; return explicitTableName;
} }
@ -188,16 +186,13 @@ public class AnnotatedColumn {
return defaultValue; return defaultValue;
} }
public String getCheckConstraint() {
return checkConstraint;
}
public void setDefaultValue(String defaultValue) { public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
public void setCheckConstraint(String checkConstraint) { public void setCheckConstraint(String name, String constraint) {
this.checkConstraint = checkConstraint; this.checkConstraintName = name;
this.checkConstraint = constraint;
} }
// public String getComment() { // public String getComment() {
@ -241,7 +236,7 @@ public class AnnotatedColumn {
mappingColumn.setDefaultValue( defaultValue ); mappingColumn.setDefaultValue( defaultValue );
} }
if ( checkConstraint != null ) { if ( checkConstraint != null ) {
mappingColumn.setCheckConstraint( checkConstraint ); mappingColumn.setCheck( new CheckConstraint( checkConstraintName, checkConstraint ) );
} }
// if ( isNotEmpty( comment ) ) { // if ( isNotEmpty( comment ) ) {
// mappingColumn.setComment( comment ); // mappingColumn.setComment( comment );
@ -280,7 +275,9 @@ public class AnnotatedColumn {
mappingColumn.setNullable( nullable ); mappingColumn.setNullable( nullable );
mappingColumn.setSqlType( sqlType ); mappingColumn.setSqlType( sqlType );
mappingColumn.setUnique( unique ); mappingColumn.setUnique( unique );
mappingColumn.setCheckConstraint( checkConstraint ); if ( checkConstraint != null ) {
mappingColumn.setCheck( new CheckConstraint( checkConstraintName, checkConstraint ) );
}
mappingColumn.setDefaultValue( defaultValue ); mappingColumn.setDefaultValue( defaultValue );
if ( writeExpression != null ) { if ( writeExpression != null ) {
@ -817,7 +814,7 @@ public class AnnotatedColumn {
throw new AnnotationException("'@Check' may only be applied to single-column mappings but '" throw new AnnotationException("'@Check' may only be applied to single-column mappings but '"
+ property.getName() + "' maps to " + length + " columns (use a table-level '@Check')" ); + property.getName() + "' maps to " + length + " columns (use a table-level '@Check')" );
} }
setCheckConstraint( check.constraints() ); setCheckConstraint( check.name().isEmpty() ? null : check.name(), check.constraints() );
} }
} }
else { else {

View File

@ -226,7 +226,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
return join; return join;
} }
private void addPropertyToPersistentClass(Property prop, XClass declaringClass) { private void addPropertyToPersistentClass(Property property, XClass declaringClass) {
if ( declaringClass != null ) { if ( declaringClass != null ) {
final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass ); final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass );
if ( inheritanceState == null ) { if ( inheritanceState == null ) {
@ -235,15 +235,15 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
); );
} }
if ( inheritanceState.isEmbeddableSuperclass() ) { if ( inheritanceState.isEmbeddableSuperclass() ) {
persistentClass.addMappedSuperclassProperty( prop ); persistentClass.addMappedSuperclassProperty( property );
addPropertyToMappedSuperclass( prop, declaringClass ); addPropertyToMappedSuperclass( property, declaringClass );
} }
else { else {
persistentClass.addProperty( prop ); persistentClass.addProperty( property );
} }
} }
else { 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 ) { if ( declaringClass != null ) {
final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass ); final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass );
if ( inheritanceState == null ) { if ( inheritanceState == null ) {
@ -368,15 +368,15 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
); );
} }
if ( inheritanceState.isEmbeddableSuperclass() ) { if ( inheritanceState.isEmbeddableSuperclass() ) {
join.addMappedsuperclassProperty(prop); join.addMappedsuperclassProperty( property );
addPropertyToMappedSuperclass( prop, declaringClass ); addPropertyToMappedSuperclass( property, declaringClass );
} }
else { else {
join.addProperty( prop ); join.addProperty( property );
} }
} }
else { else {
join.addProperty( prop ); join.addProperty( property );
} }
} }

View File

@ -23,6 +23,7 @@ import org.hibernate.annotations.Bag;
import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.Cascade; import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Check;
import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.CollectionIdJavaType; import org.hibernate.annotations.CollectionIdJavaType;
import org.hibernate.annotations.CollectionIdJdbcType; import org.hibernate.annotations.CollectionIdJdbcType;
@ -84,6 +85,7 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Any; import org.hibernate.mapping.Any;
import org.hibernate.mapping.Backref; import org.hibernate.mapping.Backref;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
@ -1626,7 +1628,7 @@ public abstract class CollectionBinder {
handleWhere( false ); handleWhere( false );
final PersistentClass targetEntity = persistentClasses.get( getElementType().getName() ); final PersistentClass targetEntity = persistentClasses.get( getElementType().getName() );
bindCollectionSecondPass( targetEntity, foreignJoinColumns, onDeleteAction); bindCollectionSecondPass( targetEntity, foreignJoinColumns );
if ( !collection.isInverse() && !collection.getKey().isNullable() ) { if ( !collection.isInverse() && !collection.getKey().isNullable() ) {
createOneToManyBackref( oneToMany ); createOneToManyBackref( oneToMany );
@ -2056,84 +2058,35 @@ public abstract class CollectionBinder {
logManyToManySecondPass( oneToMany, isCollectionOfEntities, isManyToAny ); logManyToManySecondPass( oneToMany, isCollectionOfEntities, isManyToAny );
//check for user error //check for user error
detectManyToManyProblems( detectManyToManyProblems( elementType, isCollectionOfEntities, isManyToAny );
elementType,
property,
propertyHolder,
isCollectionOfEntities,
isManyToAny
);
if ( isUnownedCollection() ) { if ( isUnownedCollection() ) {
handleUnownedManyToMany( handleUnownedManyToMany( elementType, targetEntity, isCollectionOfEntities );
collection,
joinColumns,
elementType,
targetEntity,
isCollectionOfEntities
);
} }
else { else {
handleOwnedManyToMany( handleOwnedManyToMany( targetEntity, isCollectionOfEntities );
collection,
joinColumns,
tableBinder,
property,
buildingContext,
targetEntity,
isCollectionOfEntities
);
} }
bindFilters( isCollectionOfEntities ); bindFilters( isCollectionOfEntities );
handleWhere( isCollectionOfEntities ); handleWhere( isCollectionOfEntities );
bindCollectionSecondPass( targetEntity, joinColumns, onDeleteAction); bindCollectionSecondPass( targetEntity, joinColumns );
if ( isCollectionOfEntities ) { if ( isCollectionOfEntities ) {
final ManyToOne element = handleCollectionOfEntities( final ManyToOne element = handleCollectionOfEntities( elementType, targetEntity, hqlOrderBy );
collection,
elementType,
notFoundAction,
property,
buildingContext,
targetEntity,
hqlOrderBy
);
bindManyToManyInverseForeignKey( targetEntity, inverseJoinColumns, element, oneToMany ); bindManyToManyInverseForeignKey( targetEntity, inverseJoinColumns, element, oneToMany );
} }
else if ( isManyToAny ) { else if ( isManyToAny ) {
handleManyToAny( handleManyToAny();
collection,
inverseJoinColumns,
onDeleteAction,
property,
buildingContext
);
} }
else { else {
handleElementCollection( handleElementCollection( elementType, hqlOrderBy );
collection,
elementColumns,
isEmbedded,
elementType,
property,
propertyHolder,
hqlOrderBy
);
} }
checkFilterConditions( collection ); checkFilterConditions( collection );
} }
private void handleElementCollection( private void handleElementCollection(XClass elementType, String hqlOrderBy) {
Collection collection, // 'propertyHolder' is the PropertyHolder for the owner of the collection
AnnotatedColumns elementColumns,
boolean isEmbedded,
XClass elementType,
XProperty property,
PropertyHolder parentPropertyHolder,
String hqlOrderBy) {
// 'parentPropertyHolder' is the PropertyHolder for the owner of the collection
// 'holder' is the CollectionPropertyHolder. // 'holder' is the CollectionPropertyHolder.
// 'property' is the collection XProperty // 'property' is the collection XProperty
@ -2141,7 +2094,7 @@ public abstract class CollectionBinder {
final AnnotatedClassType classType = annotatedElementType( isEmbedded, property, elementType ); final AnnotatedClassType classType = annotatedElementType( isEmbedded, property, elementType );
final boolean primitive = classType == NONE; final boolean primitive = classType == NONE;
if ( !primitive ) { if ( !primitive ) {
parentPropertyHolder.startingProperty( property ); propertyHolder.startingProperty( property );
} }
final CollectionPropertyHolder holder = buildPropertyHolder( final CollectionPropertyHolder holder = buildPropertyHolder(
@ -2149,7 +2102,7 @@ public abstract class CollectionBinder {
collection.getRole(), collection.getRole(),
elementClass, elementClass,
property, property,
parentPropertyHolder, propertyHolder,
buildingContext buildingContext
); );
holder.prepare( property ); holder.prepare( property );
@ -2157,18 +2110,15 @@ public abstract class CollectionBinder {
final Class<? extends CompositeUserType<?>> compositeUserType = final Class<? extends CompositeUserType<?>> compositeUserType =
resolveCompositeUserType( property, elementClass, buildingContext ); resolveCompositeUserType( property, elementClass, buildingContext );
if ( classType == EMBEDDABLE || compositeUserType != null ) { if ( classType == EMBEDDABLE || compositeUserType != null ) {
handleCompositeCollectionElement( collection, property, hqlOrderBy, elementClass, holder, compositeUserType ); handleCompositeCollectionElement( hqlOrderBy, elementClass, holder, compositeUserType );
} }
else { else {
handleCollectionElement( collection, elementColumns, elementType, property, hqlOrderBy, elementClass, holder ); handleCollectionElement( elementType, hqlOrderBy, elementClass, holder );
} }
} }
private void handleCollectionElement( private void handleCollectionElement(
Collection collection,
AnnotatedColumns elementColumns,
XClass elementType, XClass elementType,
XProperty property,
String hqlOrderBy, String hqlOrderBy,
XClass elementClass, XClass elementClass,
CollectionPropertyHolder holder) { CollectionPropertyHolder holder) {
@ -2199,8 +2149,6 @@ public abstract class CollectionBinder {
} }
private void handleCompositeCollectionElement( private void handleCompositeCollectionElement(
Collection collection,
XProperty property,
String hqlOrderBy, String hqlOrderBy,
XClass elementClass, XClass elementClass,
CollectionPropertyHolder holder, CollectionPropertyHolder holder,
@ -2256,7 +2204,7 @@ public abstract class CollectionBinder {
} }
} }
AnnotatedClassType annotatedElementType( private AnnotatedClassType annotatedElementType(
boolean isEmbedded, boolean isEmbedded,
XProperty property, XProperty property,
XClass elementType) { XClass elementType) {
@ -2301,14 +2249,7 @@ public abstract class CollectionBinder {
return elementColumns; return elementColumns;
} }
private ManyToOne handleCollectionOfEntities( private ManyToOne handleCollectionOfEntities(XClass elementType, PersistentClass collectionEntity, String hqlOrderBy) {
Collection collection,
XClass elementType,
NotFoundAction notFoundAction,
XProperty property,
MetadataBuildingContext buildingContext,
PersistentClass collectionEntity,
String hqlOrderBy) {
final ManyToOne element = new ManyToOne( buildingContext, collection.getCollectionTable() ); final ManyToOne element = new ManyToOne( buildingContext, collection.getCollectionTable() );
collection.setElement( element ); collection.setElement( element );
element.setReferencedEntityName( elementType.getName() ); element.setReferencedEntityName( elementType.getName() );
@ -2339,8 +2280,9 @@ public abstract class CollectionBinder {
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition(); foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
} }
} }
if ( joinTableAnn.inverseForeignKey().value() == NO_CONSTRAINT final ConstraintMode constraintMode = joinTableAnn.inverseForeignKey().value();
|| joinTableAnn.inverseForeignKey().value() == PROVIDER_DEFAULT if ( constraintMode == NO_CONSTRAINT
|| constraintMode == PROVIDER_DEFAULT
&& buildingContext.getBuildingOptions().isNoConstraintByDefault() ) { && buildingContext.getBuildingOptions().isNoConstraintByDefault() ) {
element.disableForeignKey(); element.disableForeignKey();
} }
@ -2353,12 +2295,7 @@ public abstract class CollectionBinder {
return element; return element;
} }
private void handleManyToAny( private void handleManyToAny() {
Collection collection,
AnnotatedJoinColumns inverseJoinColumns,
OnDeleteAction onDeleteAction,
XProperty property,
MetadataBuildingContext buildingContext) {
//@ManyToAny //@ManyToAny
//Make sure that collTyp is never used during the @ManyToAny branch: it will be set to void.class //Make sure that collTyp is never used during the @ManyToAny branch: it will be set to void.class
final PropertyData inferredData = new PropertyInferredData( final PropertyData inferredData = new PropertyInferredData(
@ -2414,25 +2351,20 @@ public abstract class CollectionBinder {
} }
private void handleOwnedManyToMany( private void handleOwnedManyToMany(
Collection collection,
AnnotatedJoinColumns joinColumns,
TableBinder associationTableBinder,
XProperty property,
MetadataBuildingContext context,
PersistentClass collectionEntity, PersistentClass collectionEntity,
boolean isCollectionOfEntities) { boolean isCollectionOfEntities) {
//TODO: only for implicit columns? //TODO: only for implicit columns?
//FIXME NamingStrategy //FIXME NamingStrategy
final InFlightMetadataCollector collector = context.getMetadataCollector(); final InFlightMetadataCollector collector = buildingContext.getMetadataCollector();
final PersistentClass owner = collection.getOwner(); final PersistentClass owner = collection.getOwner();
joinColumns.setMappedBy( joinColumns.setMappedBy(
owner.getEntityName(), owner.getEntityName(),
collector.getLogicalTableName( owner.getTable() ), collector.getLogicalTableName( owner.getTable() ),
collector.getFromMappedBy( owner.getEntityName(), joinColumns.getPropertyName() ) collector.getFromMappedBy( owner.getEntityName(), joinColumns.getPropertyName() )
); );
if ( isEmpty( associationTableBinder.getName() ) ) { if ( isEmpty( tableBinder.getName() ) ) {
//default value //default value
associationTableBinder.setDefaultName( tableBinder.setDefaultName(
owner.getClassName(), owner.getClassName(),
owner.getEntityName(), owner.getEntityName(),
owner.getJpaEntityName(), owner.getJpaEntityName(),
@ -2444,15 +2376,26 @@ public abstract class CollectionBinder {
joinColumns.getPropertyName() joinColumns.getPropertyName()
); );
} }
associationTableBinder.setJPA2ElementCollection( tableBinder.setJPA2ElementCollection(
!isCollectionOfEntities && property.isAnnotationPresent( ElementCollection.class ) !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( private void handleUnownedManyToMany(
Collection collection,
AnnotatedJoinColumns joinColumns,
XClass elementType, XClass elementType,
PersistentClass collectionEntity, PersistentClass collectionEntity,
boolean isCollectionOfEntities) { boolean isCollectionOfEntities) {
@ -2484,8 +2427,6 @@ public abstract class CollectionBinder {
private void detectManyToManyProblems( private void detectManyToManyProblems(
XClass elementType, XClass elementType,
XProperty property,
PropertyHolder parentPropertyHolder,
boolean isCollectionOfEntities, boolean isCollectionOfEntities,
boolean isManyToAny) { boolean isManyToAny) {
@ -2495,13 +2436,13 @@ public abstract class CollectionBinder {
+ "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" ); + "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" );
} }
else if (isManyToAny) { else if (isManyToAny) {
if ( parentPropertyHolder.getJoinTable( property ) == null ) { if ( propertyHolder.getJoinTable( property ) == null ) {
throw new AnnotationException( "Association '" + safeCollectionRole() throw new AnnotationException( "Association '" + safeCollectionRole()
+ "' is a '@ManyToAny' and must specify a '@JoinTable'" ); + "' is a '@ManyToAny' and must specify a '@JoinTable'" );
} }
} }
else { else {
final JoinTable joinTableAnn = parentPropertyHolder.getJoinTable( property ); final JoinTable joinTableAnn = propertyHolder.getJoinTable( property );
if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) { if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) {
throw new AnnotationException( "Association '" + safeCollectionRole() throw new AnnotationException( "Association '" + safeCollectionRole()
+ " has a '@JoinTable' with 'inverseJoinColumns' and targets the type '" + " has a '@JoinTable' with 'inverseJoinColumns' and targets the type '"
@ -2576,10 +2517,7 @@ public abstract class CollectionBinder {
} }
} }
private void bindCollectionSecondPass( private void bindCollectionSecondPass(PersistentClass targetEntity, AnnotatedJoinColumns joinColumns) {
PersistentClass targetEntity,
AnnotatedJoinColumns joinColumns,
OnDeleteAction onDeleteAction) {
if ( !isUnownedCollection() ) { if ( !isUnownedCollection() ) {
createSyntheticPropertyReference( createSyntheticPropertyReference(
@ -2710,8 +2648,8 @@ public abstract class CollectionBinder {
this.foreignJoinColumns = annotatedJoinColumns; this.foreignJoinColumns = annotatedJoinColumns;
} }
public void setExplicitAssociationTable(boolean explicitAssocTable) { public void setExplicitAssociationTable(boolean isExplicitAssociationTable) {
this.isExplicitAssociationTable = explicitAssocTable; this.isExplicitAssociationTable = isExplicitAssociationTable;
} }
public void setElementColumns(AnnotatedColumns elementColumns) { public void setElementColumns(AnnotatedColumns elementColumns) {

View File

@ -45,6 +45,7 @@ import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Check; import org.hibernate.annotations.Check;
import org.hibernate.annotations.Checks;
import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.annotations.DiscriminatorFormula;
import org.hibernate.annotations.DiscriminatorOptions; import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicInsert;
@ -52,6 +53,7 @@ import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Filters; import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ForeignKey; import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Loader; import org.hibernate.annotations.Loader;
import org.hibernate.annotations.NaturalIdCache; import org.hibernate.annotations.NaturalIdCache;
@ -101,6 +103,7 @@ import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.DependantValue; import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.Join; import org.hibernate.mapping.Join;
@ -210,6 +213,7 @@ public class EntityBinder {
entityBinder.bindEntity(); entityBinder.bindEntity();
entityBinder.handleClassTable( inheritanceState, superEntity ); entityBinder.handleClassTable( inheritanceState, superEntity );
entityBinder.handleSecondaryTables(); entityBinder.handleSecondaryTables();
entityBinder.handleCheck();
final PropertyHolder holder = buildPropertyHolder( final PropertyHolder holder = buildPropertyHolder(
clazzToProcess, clazzToProcess,
persistentClass, persistentClass,
@ -238,6 +242,33 @@ public class EntityBinder {
entityBinder.callTypeBinders( persistentClass ); 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) { private void callTypeBinders(PersistentClass persistentClass) {
for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class ) ) { for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class ) ) {
final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class ); final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class );
@ -643,14 +674,12 @@ public class EntityBinder {
String catalog, String catalog,
List<UniqueConstraintHolder> uniqueConstraints, List<UniqueConstraintHolder> uniqueConstraints,
InFlightMetadataCollector collector) { InFlightMetadataCollector collector) {
final Check check = getOverridableAnnotation( annotatedClass, Check.class, context );
final RowId rowId = annotatedClass.getAnnotation( RowId.class ); final RowId rowId = annotatedClass.getAnnotation( RowId.class );
bindTable( bindTable(
schema, schema,
catalog, catalog,
table, table,
uniqueConstraints, uniqueConstraints,
check == null ? null : check.constraints(),
rowId == null ? null : rowId.value(), rowId == null ? null : rowId.value(),
inheritanceState.hasDenormalizedTable() inheritanceState.hasDenormalizedTable()
? collector.getEntityTableXref( superEntity.getEntityName() ) ? collector.getEntityTableXref( superEntity.getEntityName() )
@ -1662,7 +1691,6 @@ public class EntityBinder {
String catalog, String catalog,
String tableName, String tableName,
List<UniqueConstraintHolder> uniqueConstraints, List<UniqueConstraintHolder> uniqueConstraints,
String checkConstraint,
String rowId, String rowId,
InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) {
@ -1687,9 +1715,6 @@ public class EntityBinder {
); );
table.setRowId( rowId ); table.setRowId( rowId );
if ( checkConstraint != null ) {
table.addCheckConstraint( checkConstraint );
}
// final Comment comment = annotatedClass.getAnnotation( Comment.class ); // final Comment comment = annotatedClass.getAnnotation( Comment.class );
// if ( comment != null ) { // if ( comment != null ) {

View File

@ -182,19 +182,8 @@ public class OneToOneSecondPass implements SecondPass {
manyToOne.markAsLogicalOneToOne(); manyToOne.markAsLogicalOneToOne();
property.setValue( manyToOne ); property.setValue( manyToOne );
for ( Column column: otherSideJoin.getKey().getColumns() ) { for ( Column column: otherSideJoin.getKey().getColumns() ) {
Column copy = new Column(); Column copy = column.clone();
copy.setLength( column.getLength() );
copy.setScale( column.getScale() );
copy.setValue( manyToOne ); 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 ); manyToOne.addColumn( copy );
} }
mappedByJoin.addProperty( property ); mappedByJoin.addProperty( property );
@ -275,25 +264,16 @@ public class OneToOneSecondPass implements SecondPass {
join.setOptional( true ); join.setOptional( true );
key.setOnDeleteAction( null ); key.setOnDeleteAction( null );
for ( Column column: otherSideProperty.getValue().getColumns() ) { for ( Column column: otherSideProperty.getValue().getColumns() ) {
Column copy = new Column(); Column copy = column.clone();
copy.setLength( column.getLength() );
copy.setScale( column.getScale() );
copy.setValue( key ); 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 ); key.addColumn( copy );
} }
if ( otherSideProperty.getValue() instanceof SortableValue if ( otherSideProperty.getValue() instanceof SortableValue ) {
&& !( (SortableValue) otherSideProperty.getValue() ).isSorted() ) { final SortableValue value = (SortableValue) otherSideProperty.getValue();
if ( !value.isSorted() ) {
key.sortProperties(); key.sortProperties();
} }
}
persistentClass.addJoin( join ); persistentClass.addJoin( join );
return join; return join;
} }

View File

@ -31,7 +31,7 @@ public class AggregateColumn extends Column {
setSqlType( column.getSqlType() ); setSqlType( column.getSqlType() );
setSqlTypeCode( column.getSqlTypeCode() ); setSqlTypeCode( column.getSqlTypeCode() );
uniqueInteger = column.uniqueInteger; //usually useless uniqueInteger = column.uniqueInteger; //usually useless
setCheckConstraint( column.getCheckConstraint() ); checkConstraint = column.checkConstraint;
setComment( column.getComment() ); setComment( column.getComment() );
setDefaultValue( column.getDefaultValue() ); setDefaultValue( column.getDefaultValue() );
setGeneratedAs( column.getGeneratedAs() ); setGeneratedAs( column.getGeneratedAs() );

View File

@ -334,18 +334,18 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
} }
if ( resolution.getValueConverter() != null ) { if ( resolution.getValueConverter() != null ) {
column.setSpecializedTypeDeclaration( final String declaration = resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration(dialect);
resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration( dialect ) if ( declaration != null ) {
); column.setSpecializedTypeDeclaration( declaration );
}
} }
if ( dialect.supportsColumnCheck() && !column.hasCheckConstraint() ) { if ( dialect.supportsColumnCheck() && !column.hasCheckConstraint() ) {
column.setCheckConstraint( final String checkCondition = resolution.getLegacyResolvedBasicType()
resolution.getLegacyResolvedBasicType().getCheckCondition( .getCheckCondition( column.getQuotedName( dialect ), dialect );
column.getQuotedName( dialect ), if ( checkCondition != null ) {
dialect column.setCheck( new CheckConstraint( checkCondition ) );
) }
);
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 + ")";
}
}

View File

@ -58,7 +58,7 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
private Integer sqlTypeCode; private Integer sqlTypeCode;
private boolean quoted; private boolean quoted;
int uniqueInteger; int uniqueInteger;
private String checkConstraint; CheckConstraint checkConstraint;
private String comment; private String comment;
private String defaultValue; private String defaultValue;
private String generatedAs; private String generatedAs;
@ -490,20 +490,31 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
return specializedTypeDeclaration != null; return specializedTypeDeclaration != null;
} }
@Deprecated(since = "6.2")
public String getCheckConstraint() { public String getCheckConstraint() {
return checkConstraint; return checkConstraint == null ? null : checkConstraint.getConstraint();
} }
public void setCheckConstraint(String checkConstraint) { @Deprecated(since = "6.2")
this.checkConstraint = checkConstraint; public void setCheckConstraint(String constraint) {
checkConstraint = constraint == null ? null : new CheckConstraint( constraint );
}
public void setCheck(CheckConstraint check) {
checkConstraint = check;
} }
public boolean hasCheckConstraint() { public boolean hasCheckConstraint() {
return checkConstraint != null; return checkConstraint != null;
} }
@Deprecated(since = "6.2")
public String checkConstraint() { public String checkConstraint() {
return checkConstraint == null ? null : " check (" + checkConstraint + ")"; return checkConstraint == null ? null : checkConstraint.constraintString();
}
public CheckConstraint getCheck() {
return checkConstraint;
} }
@Override @Override
@ -632,24 +643,27 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
@Override @Override
public Column clone() { public Column clone() {
Column copy = new Column(); Column copy = new Column();
copy.setLength( length ); copy.length = length;
copy.setScale( scale ); copy.precision = precision;
copy.setValue( value ); copy.scale = scale;
copy.setTypeIndex( typeIndex ); copy.value = value;
copy.setName( getQuotedName() ); copy.typeIndex = typeIndex;
copy.setNullable( nullable ); copy.name = name;
copy.setPrecision( precision ); copy.quoted = quoted;
copy.setUnique( unique ); copy.nullable = nullable;
copy.setSqlType( sqlTypeName ); copy.unique = unique;
copy.setSqlTypeCode( sqlTypeCode ); copy.sqlTypeName = sqlTypeName;
copy.sqlTypeCode = sqlTypeCode;
copy.uniqueInteger = uniqueInteger; //usually useless copy.uniqueInteger = uniqueInteger; //usually useless
copy.setCheckConstraint( checkConstraint ); copy.checkConstraint = checkConstraint;
copy.setComment( comment ); copy.comment = comment;
copy.setDefaultValue( defaultValue ); copy.defaultValue = defaultValue;
copy.setGeneratedAs( generatedAs ); copy.generatedAs = generatedAs;
copy.setAssignmentExpression( assignmentExpression ); copy.assignmentExpression = assignmentExpression;
copy.setCustomRead( customRead ); copy.customRead = customRead;
copy.setCustomWrite( customWrite ); copy.customWrite = customWrite;
copy.specializedTypeDeclaration = specializedTypeDeclaration;
copy.columnSize = columnSize;
return copy; return copy;
} }

View File

@ -48,15 +48,15 @@ public class Join implements AttributeContainer, Serializable {
private ExecuteUpdateResultCheckStyle deleteCheckStyle; private ExecuteUpdateResultCheckStyle deleteCheckStyle;
@Override @Override
public void addProperty(Property prop) { public void addProperty(Property property) {
properties.add(prop); properties.add( property );
declaredProperties.add(prop); declaredProperties.add( property );
prop.setPersistentClass( getPersistentClass() ); property.setPersistentClass( getPersistentClass() );
} }
public void addMappedsuperclassProperty(Property prop) { public void addMappedsuperclassProperty( Property property ) {
properties.add(prop); properties.add( property );
prop.setPersistentClass( getPersistentClass() ); property.setPersistentClass( getPersistentClass() );
} }
public List<Property> getDeclaredProperties() { public List<Property> getDeclaredProperties() {
@ -72,8 +72,8 @@ public class Join implements AttributeContainer, Serializable {
return declaredProperties.iterator(); return declaredProperties.iterator();
} }
public boolean containsProperty(Property prop) { public boolean containsProperty(Property property) {
return properties.contains(prop); return properties.contains( property );
} }
@Deprecated(since = "6.0") @Deprecated(since = "6.0")
@ -84,6 +84,7 @@ public class Join implements AttributeContainer, Serializable {
public Table getTable() { public Table getTable() {
return table; return table;
} }
public void setTable(Table table) { public void setTable(Table table) {
this.table = table; this.table = table;
} }
@ -91,6 +92,7 @@ public class Join implements AttributeContainer, Serializable {
public KeyValue getKey() { public KeyValue getKey() {
return key; return key;
} }
public void setKey(KeyValue key) { public void setKey(KeyValue key) {
this.key = key; this.key = key;
} }
@ -116,11 +118,11 @@ public class Join implements AttributeContainer, Serializable {
public void createPrimaryKey() { public void createPrimaryKey() {
//Primary key constraint //Primary key constraint
PrimaryKey pk = new PrimaryKey( table ); PrimaryKey primaryKey = new PrimaryKey( table );
pk.setName( PK_ALIAS.toAliasString( table.getName() ) ); primaryKey.setName( PK_ALIAS.toAliasString( table.getName() ) );
table.setPrimaryKey(pk); table.setPrimaryKey(primaryKey);
pk.addColumns( getKey() ); primaryKey.addColumns( getKey() );
} }
public int getPropertySpan() { public int getPropertySpan() {
@ -194,9 +196,8 @@ public class Join implements AttributeContainer, Serializable {
} }
public boolean isLazy() { public boolean isLazy() {
Iterator<Property> iter = getPropertyIterator(); for ( Property property : properties ) {
while ( iter.hasNext() ) { if ( !property.isLazy() ) {
if ( !iter.next().isLazy() ) {
return false; return false;
} }
} }

View File

@ -9,7 +9,6 @@ package org.hibernate.mapping;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -26,19 +25,24 @@ import org.hibernate.boot.spi.ClassLoaderAccess;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.FilterConfiguration; import org.hibernate.internal.FilterConfiguration;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.JoinedIterator; import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.internal.util.collections.JoinedList; import org.hibernate.internal.util.collections.JoinedList;
import org.hibernate.internal.util.collections.SingletonIterator; import org.hibernate.internal.util.collections.SingletonIterator;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.jpa.event.spi.CallbackDefinition; import org.hibernate.jpa.event.spi.CallbackDefinition;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.Alias; import org.hibernate.sql.Alias;
import org.hibernate.sql.Template;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.spi.TypeConfiguration;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList; 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; import static org.hibernate.internal.util.StringHelper.root;
/** /**
@ -98,6 +102,8 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
private Component identifierMapper; private Component identifierMapper;
private List<CallbackDefinition> callbackDefinitions; private List<CallbackDefinition> callbackDefinitions;
private final List<CheckConstraint> checkConstraints = new ArrayList<>();
// Custom SQL // Custom SQL
private String customSQLInsert; private String customSQLInsert;
private boolean customInsertCallable; private boolean customInsertCallable;
@ -727,7 +733,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
final int actualColumns = prop.getColumnSpan(); final int actualColumns = prop.getColumnSpan();
final int requiredColumns = type.getColumnSpan( mapping ); final int requiredColumns = type.getColumnSpan( mapping );
throw new MappingException( throw new MappingException(
"Property '" + StringHelper.qualify( getEntityName(), prop.getName() ) "Property '" + qualify( getEntityName(), prop.getName() )
+ "' maps to " + actualColumns + " columns but " + requiredColumns + "' maps to " + actualColumns + " columns but " + requiredColumns
+ " columns are required (type '" + type.getName() + " columns are required (type '" + type.getName()
+ "' spans " + requiredColumns + " columns)" + "' spans " + requiredColumns + " columns)"
@ -1219,7 +1225,52 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
// End of @MappedSuperclass support // End of @MappedSuperclass support
public void prepareForMappingModel() { 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<String> 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<String> 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<CheckConstraint> getCheckConstraints() {
return checkConstraints;
}
} }

View File

@ -38,6 +38,7 @@ import org.jboss.logging.Logger;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap; 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}. * 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<String, Index> indexes = new LinkedHashMap<>(); private final Map<String, Index> indexes = new LinkedHashMap<>();
private final Map<String,UniqueKey> uniqueKeys = new LinkedHashMap<>(); private final Map<String,UniqueKey> uniqueKeys = new LinkedHashMap<>();
private int uniqueInteger; private int uniqueInteger;
private final List<String> checkConstraints = new ArrayList<>(); private final List<CheckConstraint> checkConstraints = new ArrayList<>();
private String rowId; private String rowId;
private String subselect; private String subselect;
private boolean isAbstract; private boolean isAbstract;
@ -593,8 +594,13 @@ public class Table implements Serializable, ContributableDatabaseObject {
return idValue; return idValue;
} }
@Deprecated(since = "6.2")
public void addCheckConstraint(String constraint) { 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) { public boolean containsColumn(Column column) {
@ -672,7 +678,12 @@ public class Table implements Serializable, ContributableDatabaseObject {
return getCheckConstraints().iterator(); return getCheckConstraints().iterator();
} }
@Deprecated(since = "6.2")
public List<String> getCheckConstraints() { public List<String> getCheckConstraints() {
return checkConstraints.stream().map( CheckConstraint::getConstraint ).collect( toList() );
}
public List<CheckConstraint> getChecks() {
return unmodifiableList( checkConstraints ); return unmodifiableList( checkConstraints );
} }

View File

@ -252,6 +252,9 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
bootModel.getNamedEntityGraphs().values(), bootModel.getNamedEntityGraphs().values(),
runtimeModelCreationContext runtimeModelCreationContext
); );
bootModel.getEntityBindings().forEach( persistentClass -> persistentClass.mappingModelReady( this ) );
} }
private void registerEmbeddableMappingType(MetadataImplementor bootModel) { private void registerEmbeddableMappingType(MetadataImplementor bootModel) {

View File

@ -15,6 +15,7 @@ import java.util.StringTokenizer;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -31,6 +32,8 @@ public final class Template {
private static final Set<String> KEYWORDS = new HashSet<>(); private static final Set<String> KEYWORDS = new HashSet<>();
private static final Set<String> BEFORE_TABLE_KEYWORDS = new HashSet<>(); private static final Set<String> BEFORE_TABLE_KEYWORDS = new HashSet<>();
private static final Set<String> FUNCTION_KEYWORDS = new HashSet<>(); private static final Set<String> FUNCTION_KEYWORDS = new HashSet<>();
public static final String PUNCTUATION = "=><!+-*/()',|&`";
static { static {
KEYWORDS.add("and"); KEYWORDS.add("and");
KEYWORDS.add("or"); KEYWORDS.add("or");
@ -128,7 +131,7 @@ public final class Template {
// identifier references. // identifier references.
String symbols = new StringBuilder() String symbols = new StringBuilder()
.append( "=><!+-*/()',|&`" ) .append( PUNCTUATION )
.append( WHITESPACE ) .append( WHITESPACE )
.append( dialect.openQuote() ) .append( dialect.openQuote() )
.append( dialect.closeQuote() ) .append( dialect.closeQuote() )
@ -358,6 +361,40 @@ public final class Template {
return result.toString(); return result.toString();
} }
public static List<String> collectColumnNames(
String checkConstraint,
TypeConfiguration typeConfiguration,
SessionFactoryImplementor sessionFactory) {
final String template = renderWhereStringTemplate(
checkConstraint,
sessionFactory.getJdbcServices().getDialect(),
typeConfiguration,
sessionFactory.getQueryEngine().getSqmFunctionRegistry()
);
final List<String> 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. // * Takes the where condition provided in the mapping attribute and interpolates the alias.
// * Handles sub-selects, quoted identifiers, quoted strings, expressions, SQL functions, // * Handles sub-selects, quoted identifiers, quoted strings, expressions, SQL functions,

View File

@ -101,7 +101,7 @@ class ColumnDefinitions {
} }
if ( dialect.supportsColumnCheck() && column.hasCheckConstraint() ) { if ( dialect.supportsColumnCheck() && column.hasCheckConstraint() ) {
definition.append( column.checkConstraint() ); definition.append( column.getCheck().constraintString() );
} }
} }

View File

@ -20,6 +20,7 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
@ -151,8 +152,8 @@ public class StandardTableExporter implements Exporter<Table> {
protected void applyTableCheck(Table table, StringBuilder buf) { protected void applyTableCheck(Table table, StringBuilder buf) {
if ( dialect.supportsTableCheck() ) { if ( dialect.supportsTableCheck() ) {
for ( String constraint : table.getCheckConstraints() ) { for ( CheckConstraint constraint : table.getChecks() ) {
buf.append( ", check (" ).append( constraint ).append( ')' ); buf.append( "," ).append( constraint.constraintString() );
} }
final AggregateSupport aggregateSupport = dialect.getAggregateSupport(); final AggregateSupport aggregateSupport = dialect.getAggregateSupport();
if ( aggregateSupport != null && aggregateSupport.supportsComponentCheckConstraints() ) { if ( aggregateSupport != null && aggregateSupport.supportsComponentCheckConstraints() ) {
@ -231,7 +232,8 @@ public class StandardTableExporter implements Exporter<Table> {
} }
else { else {
for ( Column subColumn : value.getColumns() ) { 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 ) { if ( !subColumn.isNullable() || checkConstraint != null ) {
final String subColumnName = subColumn.getQuotedName( dialect ); final String subColumnName = subColumn.getQuotedName( dialect );
final String columnExpression = aggregateSupport.aggregateComponentCustomReadExpression( final String columnExpression = aggregateSupport.aggregateComponentCustomReadExpression(