Skip to content

Commit 7f8b685

Browse files
committed
HHH-18929 generate check constraints for discriminator columns
1 parent 18d6a99 commit 7f8b685

File tree

7 files changed

+137
-86
lines changed

7 files changed

+137
-86
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: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
22+
public class DiscriminatorColumnSecondPass implements SecondPass {
23+
private final String rootEntityName;
24+
private final Dialect dialect;
25+
26+
public DiscriminatorColumnSecondPass(String rootEntityName, Dialect dialect) {
27+
this.rootEntityName = rootEntityName;
28+
this.dialect = dialect;
29+
}
30+
31+
@Override
32+
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
33+
final PersistentClass rootClass = persistentClasses.get( rootEntityName );
34+
if ( hasNullDiscriminatorValue( rootClass ) ) {
35+
for ( Selectable selectable: rootClass.getDiscriminator().getSelectables() ) {
36+
if ( selectable instanceof Column column ) {
37+
column.setNullable( true );
38+
}
39+
}
40+
}
41+
if ( !hasNotNullDiscriminatorValue( rootClass ) // a "not null" discriminator is a catch-all
42+
&& !rootClass.getDiscriminator().hasFormula() // can't add check constraints to formulas
43+
&& !rootClass.isForceDiscriminator() ) { // the usecase for "forced" discriminators is that there are some rogue values
44+
final Column column = rootClass.getDiscriminator().getColumns().get( 0 );
45+
column.addCheckConstraint( new CheckConstraint( checkConstraint( rootClass, column ) ) );
46+
}
47+
}
48+
49+
private boolean hasNullDiscriminatorValue(PersistentClass rootClass) {
50+
if ( rootClass.isDiscriminatorValueNull() ) {
51+
return true;
52+
}
53+
for ( Subclass subclass : rootClass.getSubclasses() ) {
54+
if ( subclass.isDiscriminatorValueNull() ) {
55+
return true;
56+
}
57+
}
58+
return false;
59+
}
60+
61+
private boolean hasNotNullDiscriminatorValue(PersistentClass rootClass) {
62+
if ( rootClass.isDiscriminatorValueNotNull() ) {
63+
return true;
64+
}
65+
for ( Subclass subclass : rootClass.getSubclasses() ) {
66+
if ( subclass.isDiscriminatorValueNotNull() ) {
67+
return true;
68+
}
69+
}
70+
return false;
71+
}
72+
73+
private String checkConstraint(PersistentClass rootClass, Column column) {
74+
return dialect.getCheckCondition(
75+
column.getQuotedName( dialect ),
76+
discriminatorValues( rootClass ),
77+
column.getType().getJdbcType()
78+
);
79+
}
80+
81+
private static List<String> discriminatorValues(PersistentClass rootClass) {
82+
final List<String> values = new ArrayList<>();
83+
if ( !rootClass.isAbstract()
84+
&& !rootClass.isDiscriminatorValueNull()
85+
&& !rootClass.isDiscriminatorValueNotNull() ) {
86+
values.add( DiscriminatorHelper.getDiscriminatorValue( rootClass ).toString() );
87+
}
88+
for ( Subclass subclass : rootClass.getSubclasses() ) {
89+
if ( !subclass.isAbstract()
90+
&& !subclass.isDiscriminatorValueNull()
91+
&& !subclass.isDiscriminatorValueNotNull() ) {
92+
values.add( DiscriminatorHelper.getDiscriminatorValue( subclass ).toString() );
93+
}
94+
}
95+
return values;
96+
}
97+
}

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/dialect/Dialect.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
import java.time.temporal.TemporalAccessor;
190190
import java.time.temporal.TemporalAmount;
191191
import java.util.Calendar;
192+
import java.util.Collection;
192193
import java.util.Date;
193194
import java.util.HashSet;
194195
import java.util.List;
@@ -839,11 +840,17 @@ public String getCheckCondition(String columnName, Long[] values) {
839840
}
840841

841842
/**
842-
* Generate a check condition for column with the given set of values.
843+
* Generate a SQL {@code check} condition for the given column,
844+
* constraining to the given values.
843845
*
844-
* @apiNote Only supports TINYINT, SMALLINT and (VAR)CHAR
846+
* @return a SQL expression that will occur in a {@code check} constraint
847+
*
848+
* @apiNote Only supports {@code TINYINT}, {@code SMALLINT}, {@code CHAR},
849+
* and {@code VARCHAR}
850+
*
851+
* @since 7.0
845852
*/
846-
public String getCheckCondition(String columnName, Set<?> valueSet, JdbcType jdbcType) {
853+
public String getCheckCondition(String columnName, Collection<?> valueSet, JdbcType jdbcType) {
847854
final boolean isCharacterJdbcType = isCharacterType( jdbcType.getJdbcTypeCode() );
848855
assert isCharacterJdbcType || isIntegral( jdbcType.getJdbcTypeCode() );
849856

@@ -856,11 +863,12 @@ public String getCheckCondition(String columnName, Set<?> valueSet, JdbcType jdb
856863
nullIsValid = true;
857864
continue;
858865
}
866+
check.append( separator );
859867
if ( isCharacterJdbcType ) {
860-
check.append( separator ).append('\'').append( value ).append('\'');
868+
check.append('\'').append( value ).append('\'');
861869
}
862870
else {
863-
check.append( separator ).append( value );
871+
check.append( value );
864872
}
865873
separator = ",";
866874
}

hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.time.temporal.TemporalAccessor;
1313
import java.time.temporal.TemporalAmount;
1414
import java.util.Calendar;
15+
import java.util.Collection;
1516
import java.util.Date;
1617
import java.util.List;
1718
import java.util.Map;
@@ -1681,7 +1682,7 @@ public String getCheckCondition(String columnName, Long[] values) {
16811682
}
16821683

16831684
@Override
1684-
public String getCheckCondition(String columnName, Set<?> valueSet, JdbcType jdbcType) {
1685+
public String getCheckCondition(String columnName, Collection<?> valueSet, JdbcType jdbcType) {
16851686
return wrapped.getCheckCondition( columnName, valueSet, jdbcType );
16861687
}
16871688

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)