Skip to content

Commit 65abc53

Browse files
committed
HHH-18929 generate check constraints for discriminator columns
1 parent 7a4ba83 commit 65abc53

File tree

5 files changed

+119
-80
lines changed

5 files changed

+119
-80
lines changed

hibernate-core/src/main/java/org/hibernate/annotations/DiscriminatorOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
* instances of a root entity and its subtypes. This is useful if
2929
* there are discriminator column values which do <em>not</em>
3030
* map to any subtype of the root entity type.
31+
* <p>
32+
* This setting has the side effect of suppressing the generation
33+
* of a {@code check} constraint in the DDL for the discriminator
34+
* column.
3135
*
3236
* @return {@code true} if allowed discriminator values must always
3337
* be explicitly enumerated
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.model.internal;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import org.hibernate.MappingException;
12+
import org.hibernate.boot.spi.SecondPass;
13+
import org.hibernate.dialect.Dialect;
14+
import org.hibernate.mapping.CheckConstraint;
15+
import org.hibernate.mapping.Column;
16+
import org.hibernate.mapping.PersistentClass;
17+
import org.hibernate.mapping.Selectable;
18+
import org.hibernate.mapping.Subclass;
19+
import org.hibernate.persister.entity.DiscriminatorHelper;
20+
21+
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY;
22+
23+
public class DiscriminatorColumnSecondPass implements SecondPass {
24+
private final String rootEntityName;
25+
private final Dialect dialect;
26+
27+
public DiscriminatorColumnSecondPass(String rootEntityName, Dialect dialect) {
28+
this.rootEntityName = rootEntityName;
29+
this.dialect = dialect;
30+
}
31+
32+
@Override
33+
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
34+
final PersistentClass rootClass = persistentClasses.get( rootEntityName );
35+
if ( hasNullDiscriminatorValue( rootClass ) ) {
36+
for ( Selectable selectable: rootClass.getDiscriminator().getSelectables() ) {
37+
if ( selectable instanceof Column column ) {
38+
column.setNullable( true );
39+
}
40+
}
41+
}
42+
if ( !hasNotNullDiscriminatorValue( rootClass ) // a "not null" discriminator is a catch-all
43+
&& !rootClass.getDiscriminator().hasFormula() // can't add check constraints to formulas
44+
&& !rootClass.isForceDiscriminator() ) { // the usecase for "forced" discriminators is that there are some rogue values
45+
final Column column = rootClass.getDiscriminator().getColumns().get( 0 );
46+
column.addCheckConstraint( new CheckConstraint( checkConstraint( rootClass, column ) ) );
47+
}
48+
}
49+
50+
private boolean hasNullDiscriminatorValue(PersistentClass rootClass) {
51+
if ( rootClass.isDiscriminatorValueNull() ) {
52+
return true;
53+
}
54+
for ( Subclass subclass : rootClass.getSubclasses() ) {
55+
if ( subclass.isDiscriminatorValueNull() ) {
56+
return true;
57+
}
58+
}
59+
return false;
60+
}
61+
62+
private boolean hasNotNullDiscriminatorValue(PersistentClass rootClass) {
63+
if ( rootClass.isDiscriminatorValueNotNull() ) {
64+
return true;
65+
}
66+
for ( Subclass subclass : rootClass.getSubclasses() ) {
67+
if ( subclass.isDiscriminatorValueNotNull() ) {
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
73+
74+
private String checkConstraint(PersistentClass rootClass, Column column) {
75+
return dialect.getCheckCondition( column.getQuotedName( dialect ), discriminatorValues( rootClass ) );
76+
}
77+
78+
private static String[] discriminatorValues(PersistentClass rootClass) {
79+
final List<String> values = new ArrayList<>();
80+
if ( !rootClass.isAbstract()
81+
&& !rootClass.isDiscriminatorValueNull()
82+
&& !rootClass.isDiscriminatorValueNotNull() ) {
83+
values.add( DiscriminatorHelper.getDiscriminatorValue( rootClass ).toString() );
84+
}
85+
for ( Subclass subclass : rootClass.getSubclasses() ) {
86+
if ( !subclass.isAbstract()
87+
&& !subclass.isDiscriminatorValueNull()
88+
&& !subclass.isDiscriminatorValueNotNull() ) {
89+
values.add( DiscriminatorHelper.getDiscriminatorValue( subclass ).toString() );
90+
}
91+
}
92+
return values.toArray( EMPTY_STRING_ARRAY );
93+
}
94+
}

hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,9 @@ private void bindDiscriminatorColumnToRootPersistentClass(
958958
rootClass.setPolymorphic( true );
959959
final String rootEntityName = rootClass.getEntityName();
960960
LOG.tracev( "Setting discriminator for entity {0}", rootEntityName);
961-
getMetadataCollector().addSecondPass( new NullableDiscriminatorColumnSecondPass( rootEntityName ) );
961+
getMetadataCollector()
962+
.addSecondPass( new DiscriminatorColumnSecondPass( rootEntityName,
963+
context.getMetadataCollector().getDatabase().getDialect() ) );
962964
}
963965
}
964966

hibernate-core/src/main/java/org/hibernate/boot/model/internal/NullableDiscriminatorColumnSecondPass.java

Lines changed: 0 additions & 46 deletions
This file was deleted.

hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorHelper.java

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,26 @@ public class DiscriminatorHelper {
3737
* and the {@link org.hibernate.type.descriptor.jdbc.JdbcType}.
3838
*/
3939
static BasicType<?> getDiscriminatorType(PersistentClass persistentClass) {
40-
Type discriminatorType = persistentClass.getDiscriminator().getType();
41-
if ( discriminatorType instanceof BasicType ) {
42-
return (BasicType<?>) discriminatorType;
40+
final Type discriminatorType = persistentClass.getDiscriminator().getType();
41+
if ( discriminatorType instanceof BasicType<?> basicType ) {
42+
return basicType;
4343
}
4444
else {
4545
throw new MappingException( "Illegal discriminator type: " + discriminatorType.getName() );
4646
}
4747
}
4848

4949
public static BasicType<?> getDiscriminatorType(Component component) {
50-
Type discriminatorType = component.getDiscriminator().getType();
51-
if ( discriminatorType instanceof BasicType ) {
52-
return (BasicType<?>) discriminatorType;
50+
final Type discriminatorType = component.getDiscriminator().getType();
51+
if ( discriminatorType instanceof BasicType<?> basicType ) {
52+
return basicType;
5353
}
5454
else {
5555
throw new MappingException( "Illegal discriminator type: " + discriminatorType.getName() );
5656
}
5757
}
5858

59-
static String getDiscriminatorSQLValue(PersistentClass persistentClass, Dialect dialect) {
59+
public static String getDiscriminatorSQLValue(PersistentClass persistentClass, Dialect dialect) {
6060
if ( persistentClass.isDiscriminatorValueNull() ) {
6161
return InFragment.NULL;
6262
}
@@ -69,16 +69,18 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) {
6969
}
7070

7171
private static Object parseDiscriminatorValue(PersistentClass persistentClass) {
72-
BasicType<?> discriminatorType = getDiscriminatorType( persistentClass );
72+
final BasicType<?> discriminatorType = getDiscriminatorType( persistentClass );
73+
final String discriminatorValue = persistentClass.getDiscriminatorValue();
7374
try {
74-
return discriminatorType.getJavaTypeDescriptor().fromString( persistentClass.getDiscriminatorValue() );
75+
return discriminatorType.getJavaTypeDescriptor().fromString( discriminatorValue );
7576
}
7677
catch ( Exception e ) {
77-
throw new MappingException( "Could not parse discriminator value", e );
78+
throw new MappingException( "Could not parse discriminator value '" + discriminatorValue
79+
+ "' as discriminator type '" + discriminatorType.getName() + "'", e );
7880
}
7981
}
8082

81-
static Object getDiscriminatorValue(PersistentClass persistentClass) {
83+
public static Object getDiscriminatorValue(PersistentClass persistentClass) {
8284
if ( persistentClass.isDiscriminatorValueNull() ) {
8385
return NULL_DISCRIMINATOR;
8486
}
@@ -101,10 +103,7 @@ private static <T> String discriminatorSqlLiteral(
101103
);
102104
}
103105

104-
public static <T> String jdbcLiteral(
105-
T value,
106-
JdbcLiteralFormatter<T> formatter,
107-
Dialect dialect) {
106+
public static <T> String jdbcLiteral(T value, JdbcLiteralFormatter<T> formatter, Dialect dialect) {
108107
try {
109108
return formatter.toJdbcLiteral( value, dialect, null );
110109
}
@@ -119,26 +118,12 @@ public static <T> String jdbcLiteral(
119118
* domain types, or to {@link StandardBasicTypes#CLASS Class} for non-inherited ones.
120119
*/
121120
public static <T> SqmExpressible<? super T> getDiscriminatorType(
122-
SqmPathSource<T> domainType,
123-
NodeBuilder nodeBuilder) {
121+
SqmPathSource<T> domainType, NodeBuilder nodeBuilder) {
124122
final SqmPathSource<?> subPathSource = domainType.findSubPathSource( DISCRIMINATOR_ROLE_NAME );
125-
final SqmExpressible<?> type;
126-
if ( subPathSource != null ) {
127-
type = subPathSource.getSqmPathType();
128-
}
129-
else {
130-
type = nodeBuilder.getTypeConfiguration()
131-
.getBasicTypeRegistry()
132-
.resolve( StandardBasicTypes.CLASS );
133-
}
123+
final SqmExpressible<?> type = subPathSource != null
124+
? subPathSource.getSqmPathType()
125+
: nodeBuilder.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.CLASS );
134126
//noinspection unchecked
135127
return (SqmExpressible<? super T>) type;
136128
}
137-
138-
static String discriminatorLiteral(JdbcLiteralFormatter<Object> formatter, Dialect dialect, Object value) {
139-
return value == NULL_DISCRIMINATOR || value == NOT_NULL_DISCRIMINATOR
140-
? null
141-
: jdbcLiteral( value, formatter, dialect );
142-
}
143-
144129
}

0 commit comments

Comments
 (0)