Skip to content

Commit 2651227

Browse files
committed
HHH-19300 tests for constraint interpretation and tiny bugfixes
1 parent 5ecfd2b commit 2651227

File tree

9 files changed

+311
-12
lines changed

9 files changed

+311
-12
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE;
212212
import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS;
213213
import static org.hibernate.internal.util.MathHelper.ceilingPowerOfTwo;
214+
import static org.hibernate.internal.util.StringHelper.isBlank;
214215
import static org.hibernate.internal.util.StringHelper.isEmpty;
215216
import static org.hibernate.internal.util.StringHelper.splitAtCommas;
216217
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY;
@@ -5840,7 +5841,7 @@ public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSuppor
58405841
*/
58415842
public String getCheckConstraintString(CheckConstraint checkConstraint) {
58425843
final String constraintName = checkConstraint.getName();
5843-
final String constraint = constraintName == null
5844+
final String constraint = isBlank( constraintName )
58445845
? " check (" + checkConstraint.getConstraint() + ")"
58455846
: " constraint " + constraintName + " check (" + checkConstraint.getConstraint() + ")";
58465847
return appendCheckConstraintOptions( checkConstraint, constraint );

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
453453
new TemplatedViolatedConstraintNameExtractor( sqle ->
454454
switch ( extractErrorCode( sqle ) ) {
455455
case -8, -9, -104, -177, -157 -> extractUsingTemplate( "; ", " table: ", sqle.getMessage() );
456+
case -10 -> extractUsingTemplate( " column: ", "\n", sqle.getMessage() );
456457
default -> null;
457458
});
458459

@@ -463,7 +464,8 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
463464
case -10 ->
464465
// Not null constraint violation
465466
new ConstraintViolationException( message, sqlException, sql,
466-
ConstraintViolationException.ConstraintKind.NOT_NULL, null );
467+
ConstraintViolationException.ConstraintKind.NOT_NULL,
468+
getViolatedConstraintNameExtractor().extractConstraintName(sqlException) );
467469
case -104 ->
468470
// Unique constraint violation
469471
new ConstraintViolationException( message, sqlException, sql,
@@ -477,7 +479,7 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
477479
case -177 ->
478480
// Foreign key constraint violation
479481
new ConstraintViolationException( message, sqlException, sql,
480-
ConstraintViolationException.ConstraintKind.CHECK,
482+
ConstraintViolationException.ConstraintKind.FOREIGN_KEY,
481483
getViolatedConstraintNameExtractor().extractConstraintName(sqlException) );
482484
default -> null;
483485
};

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.hibernate.exception.LockAcquisitionException;
2929
import org.hibernate.exception.LockTimeoutException;
3030
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
31+
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
32+
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
3133
import org.hibernate.query.sqm.CastType;
3234
import org.hibernate.service.ServiceRegistry;
3335
import org.hibernate.sql.ast.SqlAstTranslator;
@@ -44,6 +46,7 @@
4446
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
4547
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
4648

49+
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
4750
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
4851
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
4952
import static org.hibernate.type.SqlTypes.GEOMETRY;
@@ -332,6 +335,18 @@ public String getDual() {
332335
return "dual";
333336
}
334337

338+
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
339+
return EXTRACTOR;
340+
}
341+
342+
private static final ViolatedConstraintNameExtractor EXTRACTOR =
343+
new TemplatedViolatedConstraintNameExtractor( sqle -> switch ( sqle.getErrorCode() ) {
344+
case 1062 -> extractUsingTemplate( " for key '", "'", sqle.getMessage() );
345+
case 1451, 1452, 4025 -> extractUsingTemplate( " CONSTRAINT `", "`", sqle.getMessage() );
346+
case 3819 -> extractUsingTemplate( " constraint '", "'", sqle.getMessage() );
347+
case 1048 -> extractUsingTemplate( "Column '", "'", sqle.getMessage() );
348+
default -> null;
349+
} );
335350
@Override
336351
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
337352
return (sqlException, message, sql) -> {
@@ -356,15 +371,18 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
356371
case 1048:
357372
// Null constraint violation
358373
return new ConstraintViolationException( message, sqlException, sql,
359-
ConstraintViolationException.ConstraintKind.NOT_NULL, null );
374+
ConstraintViolationException.ConstraintKind.NOT_NULL,
375+
getViolatedConstraintNameExtractor().extractConstraintName( sqlException ) );
360376
case 1451, 1452:
361377
// Foreign key constraint violation
362378
return new ConstraintViolationException( message, sqlException, sql,
363-
ConstraintViolationException.ConstraintKind.FOREIGN_KEY, null );
364-
case 3819:
379+
ConstraintViolationException.ConstraintKind.FOREIGN_KEY,
380+
getViolatedConstraintNameExtractor().extractConstraintName( sqlException ) );
381+
case 3819, 4025: // 4025 seems to usually be a check constraint violation
365382
// Check constraint violation
366383
return new ConstraintViolationException( message, sqlException, sql,
367-
ConstraintViolationException.ConstraintKind.CHECK, null );
384+
ConstraintViolationException.ConstraintKind.CHECK,
385+
getViolatedConstraintNameExtractor().extractConstraintName( sqlException ) );
368386
}
369387

370388
final String sqlState = extractSqlState( sqlException );

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
9898
import static org.hibernate.internal.util.JdbcExceptionHelper.extractErrorCode;
9999
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
100+
import static org.hibernate.internal.util.StringHelper.isBlank;
100101
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
101102
import static org.hibernate.query.common.TemporalUnit.NANOSECOND;
102103
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
@@ -1217,7 +1218,7 @@ public CallableStatementSupport getCallableStatementSupport() {
12171218
@Override
12181219
public String getCheckConstraintString(CheckConstraint checkConstraint) {
12191220
final String constraintName = checkConstraint.getName();
1220-
return constraintName == null
1221+
return isBlank( constraintName )
12211222
? " check " + getCheckConstraintOptions( checkConstraint )
12221223
+ "(" + checkConstraint.getConstraint() + ")"
12231224
: " constraint " + constraintName + " check " + getCheckConstraintOptions( checkConstraint )

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -678,9 +678,12 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
678678
case 546, 548 ->
679679
// Foreign key or check constraint violation
680680
extractUsingTemplate( "constraint name = '", "'", sqle.getMessage() );
681-
case 151 ->
681+
case 515 ->
682682
// Not null violation
683683
extractUsingTemplate( "column '", "'", sqle.getMessage() );
684+
case 233 ->
685+
// Not null violation
686+
extractUsingTemplate( "The column ", " ", sqle.getMessage() );
684687
default -> null;
685688
};
686689
default -> null;
@@ -704,7 +707,7 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
704707
case "S1000":
705708
case "23000":
706709
switch ( errorCode ) {
707-
case 515:
710+
case 515, 233:
708711
// Attempt to insert NULL value into column; column does not allow nulls.
709712
return new ConstraintViolationException( message, sqlException, sql,
710713
ConstraintViolationException.ConstraintKind.NOT_NULL,

hibernate-core/src/main/java/org/hibernate/exception/spi/TemplatedViolatedConstraintNameExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public TemplatedViolatedConstraintNameExtractor(Function<SQLException,String> ex
6666
}
6767

6868
int start = templateStartPosition + templateStart.length();
69-
int end = message.indexOf( templateEnd, start );
69+
int end = templateEnd.equals("\n") ? -1 : message.indexOf( templateEnd, start );
7070
if ( end < 0 ) {
7171
end = message.length();
7272
}

hibernate-core/src/main/java/org/hibernate/mapping/CheckConstraint.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import org.hibernate.dialect.Dialect;
1010

11+
import static org.hibernate.internal.util.StringHelper.isBlank;
12+
1113
/**
1214
* Represents a table or column level {@code check} constraint.
1315
*
@@ -74,7 +76,7 @@ public void setOptions(String options) {
7476
*/
7577
@Deprecated(since = "7.0")
7678
public String constraintString() {
77-
return name == null
79+
return isBlank( name )
7880
? " check (" + constraint + ")"
7981
: " constraint " + name + " check (" + constraint + ")";
8082
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.constraint;
6+
7+
import jakarta.persistence.CheckConstraint;
8+
import jakarta.persistence.Column;
9+
import jakarta.persistence.Entity;
10+
import jakarta.persistence.ForeignKey;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.JoinColumn;
13+
import jakarta.persistence.ManyToOne;
14+
import jakarta.persistence.Table;
15+
import jakarta.persistence.UniqueConstraint;
16+
import org.hibernate.dialect.H2Dialect;
17+
import org.hibernate.dialect.HSQLDialect;
18+
import org.hibernate.dialect.MariaDBDialect;
19+
import org.hibernate.dialect.MySQLDialect;
20+
import org.hibernate.dialect.OracleDialect;
21+
import org.hibernate.dialect.PostgreSQLDialect;
22+
import org.hibernate.dialect.SQLServerDialect;
23+
import org.hibernate.dialect.SybaseASEDialect;
24+
import org.hibernate.exception.ConstraintViolationException;
25+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
26+
import org.hibernate.testing.orm.junit.Jpa;
27+
import org.hibernate.testing.orm.junit.RequiresDialect;
28+
import org.hibernate.testing.orm.junit.SkipForDialect;
29+
import org.junit.jupiter.api.Test;
30+
31+
32+
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertTrue;
34+
import static org.junit.jupiter.api.Assertions.fail;
35+
36+
37+
@Jpa(annotatedClasses = {ConstraintInterpretationTest.Enttity1.class, ConstraintInterpretationTest.Entity2.class})
38+
@RequiresDialect( PostgreSQLDialect.class )
39+
@RequiresDialect( MySQLDialect.class )
40+
@RequiresDialect( H2Dialect.class )
41+
@RequiresDialect( HSQLDialect.class )
42+
@RequiresDialect( SQLServerDialect.class )
43+
@RequiresDialect( SybaseASEDialect.class )
44+
@RequiresDialect( OracleDialect.class )
45+
@SkipForDialect(dialectClass = MariaDBDialect.class) // Maria doesn't allow named column-level check constraints
46+
public class ConstraintInterpretationTest {
47+
@Test void testNotNullPrimaryKey(EntityManagerFactoryScope scope) {
48+
scope.inTransaction( em -> {
49+
try {
50+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (null, 'test', 'abc123')" ).executeUpdate();
51+
fail();
52+
}
53+
catch (ConstraintViolationException cve) {
54+
assertEquals( ConstraintViolationException.ConstraintKind.NOT_NULL, cve.getKind() );
55+
assertTrue( cve.getConstraintName().toLowerCase().endsWith( "id" ) );
56+
}
57+
} );
58+
}
59+
@Test void testUniquePrimaryKey(EntityManagerFactoryScope scope) {
60+
scope.inTransaction( em -> {
61+
try {
62+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (1, 'test1', 'abc123')" ).executeUpdate();
63+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (1, 'test2', 'xyz456')" ).executeUpdate();
64+
fail();
65+
}
66+
catch (ConstraintViolationException cve) {
67+
assertEquals( ConstraintViolationException.ConstraintKind.UNIQUE, cve.getKind() );
68+
}
69+
} );
70+
}
71+
@Test void testNotNull(EntityManagerFactoryScope scope) {
72+
scope.inTransaction( em -> {
73+
try {
74+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (1, null, 'abc123')" ).executeUpdate();
75+
fail();
76+
}
77+
catch (ConstraintViolationException cve) {
78+
assertEquals( ConstraintViolationException.ConstraintKind.NOT_NULL, cve.getKind() );
79+
assertTrue( cve.getConstraintName().toLowerCase().endsWith( "name" ) );
80+
}
81+
} );
82+
}
83+
@Test void testUnique(EntityManagerFactoryScope scope) {
84+
scope.inTransaction( em -> {
85+
try {
86+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (1, 'test1', 'abc123')" ).executeUpdate();
87+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (2, 'test2', 'abc123')" ).executeUpdate();
88+
fail();
89+
}
90+
catch (ConstraintViolationException cve) {
91+
assertEquals( ConstraintViolationException.ConstraintKind.UNIQUE, cve.getKind() );
92+
assertTrue( cve.getConstraintName().toLowerCase().contains( "ssnuk" ) );
93+
}
94+
} );
95+
}
96+
@Test void testCheck(EntityManagerFactoryScope scope) {
97+
scope.inTransaction( em -> {
98+
try {
99+
em.createNativeQuery( "insert into table_1 (id, name, ssn) values (1, ' ', 'abc123')" ).executeUpdate();
100+
fail();
101+
}
102+
catch (ConstraintViolationException cve) {
103+
assertEquals( ConstraintViolationException.ConstraintKind.CHECK, cve.getKind() );
104+
assertTrue( cve.getConstraintName().toLowerCase().endsWith( "namecheck" ) );
105+
}
106+
} );
107+
}
108+
@Test void testForeignKey(EntityManagerFactoryScope scope) {
109+
scope.inTransaction( em -> {
110+
try {
111+
em.createNativeQuery( "insert into table_2 (id, id1) values (1, 69)" ).executeUpdate();
112+
fail();
113+
}
114+
catch (ConstraintViolationException cve) {
115+
assertEquals( ConstraintViolationException.ConstraintKind.FOREIGN_KEY, cve.getKind() );
116+
assertTrue( cve.getConstraintName().toLowerCase().endsWith( "id2to1fk" ) );
117+
}
118+
} );
119+
}
120+
@Entity @Table(name = "table_1",
121+
uniqueConstraints = @UniqueConstraint(name = "ssnuk", columnNames = "ssn"))
122+
static class Enttity1 {
123+
@Id Long id;
124+
@Column(nullable = false,
125+
check = @CheckConstraint(name = "namecheck", constraint = "name <> ' '"))
126+
String name;
127+
@Column(nullable = false)
128+
String ssn;
129+
}
130+
@Entity @Table(name = "table_2")
131+
static class Entity2 {
132+
@Id Long id;
133+
@JoinColumn(name = "id1", foreignKey = @ForeignKey(name = "id2to1fk"))
134+
@ManyToOne Enttity1 other;
135+
@Column(unique = true)
136+
String whatever;
137+
}
138+
}

0 commit comments

Comments
 (0)