From 64a98417e28f50c53497091a029423e22986e178 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 Aug 2024 00:31:28 +0200 Subject: [PATCH] HHH-18512 @EnumeratedValue and PostgreSQL named enum types Signed-off-by: Gavin King --- .../internal/EnumeratedValueConverter.java | 4 - .../hibernate/dialect/OracleEnumJdbcType.java | 22 ++- .../dialect/OracleOrdinalEnumJdbcType.java | 2 + .../dialect/PostgreSQLEnumJdbcType.java | 29 +-- .../PostgreSQLOrdinalEnumJdbcType.java | 7 +- .../org/hibernate/mapping/BasicValue.java | 1 + .../converter/internal/EnumHelper.java | 20 +- .../converter/spi/BasicValueConverter.java | 2 - .../type/descriptor/java/EnumJavaType.java | 13 +- .../type/descriptor/jdbc/JdbcType.java | 3 +- .../function/array/ArrayAggregateTest.java | 1 + ...ostgresqlNamedEnumEnumerateValueTests.java | 173 ++++++++++++++++++ 12 files changed, 238 insertions(+), 39 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/enumeratedvalue/PostgresqlNamedEnumEnumerateValueTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueConverter.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueConverter.java index 580479ae37..5193f98b8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueConverter.java @@ -7,18 +7,14 @@ package org.hibernate.boot.model.process.internal; import java.lang.reflect.Field; -import java.util.Collection; import java.util.Map; import java.util.Set; -import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleEnumJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleEnumJdbcType.java index f6a56e5de4..1592994407 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleEnumJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleEnumJdbcType.java @@ -12,7 +12,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.converter.internal.EnumHelper; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; @@ -30,6 +30,7 @@ import java.util.Arrays; import static java.util.Collections.emptySet; import static org.hibernate.type.SqlTypes.NAMED_ENUM; +import static org.hibernate.type.descriptor.converter.internal.EnumHelper.getEnumeratedValues; /** * Represents a named {@code enum} type on Oracle 23ai+. @@ -92,13 +93,13 @@ public class OracleEnumJdbcType implements JdbcType { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setString( index, ((Enum) value).name() ); + st.setString( index, getJavaType().unwrap( value, String.class, options ) ); } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { - st.setString( name, ((Enum) value).name() ); + st.setString( name, getJavaType().unwrap( value, String.class, options ) ); } }; } @@ -126,10 +127,11 @@ public class OracleEnumJdbcType implements JdbcType { @Override public void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Size columnSize, Database database, JdbcTypeIndicators context) { - addAuxiliaryDatabaseObjects( javaType, database, true ); + addAuxiliaryDatabaseObjects( javaType, valueConverter, database, true ); } @Override @@ -138,20 +140,26 @@ public class OracleEnumJdbcType implements JdbcType { Size columnSize, Database database, TypeConfiguration typeConfiguration) { - addAuxiliaryDatabaseObjects( javaType, database, true ); + addAuxiliaryDatabaseObjects( javaType, null, database, true ); } private void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Database database, boolean sortEnumValues) { - final Dialect dialect = database.getDialect(); + @SuppressWarnings("unchecked") final Class> enumClass = (Class>) javaType.getJavaType(); final String enumTypeName = enumClass.getSimpleName(); - final String[] enumeratedValues = EnumHelper.getEnumeratedValues( enumClass ); + @SuppressWarnings("unchecked") + final String[] enumeratedValues = + valueConverter == null + ? getEnumeratedValues( enumClass ) + : getEnumeratedValues( enumClass, (BasicValueConverter,?>) valueConverter ) ; if ( sortEnumValues ) { Arrays.sort( enumeratedValues ); } + final Dialect dialect = database.getDialect(); final String[] create = getCreateEnumTypeCommand( javaType.getJavaTypeClass().getSimpleName(), enumeratedValues diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOrdinalEnumJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOrdinalEnumJdbcType.java index 34fb5a7838..b552e7c69a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOrdinalEnumJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOrdinalEnumJdbcType.java @@ -14,6 +14,7 @@ import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.converter.internal.EnumHelper; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; @@ -116,6 +117,7 @@ public class OracleOrdinalEnumJdbcType extends OracleEnumJdbcType { @Override public void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Size columnSize, Database database, JdbcTypeIndicators context) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLEnumJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLEnumJdbcType.java index 255bdbfdbc..b7b84c2d53 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLEnumJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLEnumJdbcType.java @@ -12,7 +12,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.converter.internal.EnumHelper; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; @@ -28,11 +28,10 @@ import java.sql.SQLException; import java.sql.Types; import java.util.Arrays; -import jakarta.persistence.EnumType; - import static java.util.Collections.emptySet; import static org.hibernate.type.SqlTypes.NAMED_ENUM; import static org.hibernate.type.SqlTypes.OTHER; +import static org.hibernate.type.descriptor.converter.internal.EnumHelper.getEnumeratedValues; /** * Represents a named {@code enum} type on PostgreSQL. @@ -66,8 +65,9 @@ public class PostgreSQLEnumJdbcType implements JdbcType { @Override public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { - return (appender, value, dialect, wrapperOptions) -> appender.appendSql( "'" + ((Enum) value).name() + "'::" - + dialect.getEnumTypeDeclaration( (Class>) javaType.getJavaType() ) ); + return (appender, value, dialect, wrapperOptions) + -> appender.appendSql( "'" + ((Enum) value).name() + "'::" + + dialect.getEnumTypeDeclaration( (Class>) javaType.getJavaType() ) ); } @Override @@ -96,13 +96,13 @@ public class PostgreSQLEnumJdbcType implements JdbcType { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setObject( index, ((Enum) value).name(), Types.OTHER ); + st.setObject( index, getJavaType().unwrap( value, String.class, options ), Types.OTHER ); } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { - st.setObject( name, ((Enum) value).name(), Types.OTHER ); + st.setObject( name, getJavaType().unwrap( value, String.class, options ), Types.OTHER ); } }; } @@ -130,10 +130,11 @@ public class PostgreSQLEnumJdbcType implements JdbcType { @Override public void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Size columnSize, Database database, JdbcTypeIndicators context) { - addAuxiliaryDatabaseObjects( javaType, database, true ); + addAuxiliaryDatabaseObjects( javaType, valueConverter, database, true ); } @Override @@ -142,20 +143,26 @@ public class PostgreSQLEnumJdbcType implements JdbcType { Size columnSize, Database database, TypeConfiguration typeConfiguration) { - addAuxiliaryDatabaseObjects( javaType, database, true ); + addAuxiliaryDatabaseObjects( javaType, null, database, true ); } protected void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Database database, boolean sortEnumValues) { - final Dialect dialect = database.getDialect(); + @SuppressWarnings("unchecked") final Class> enumClass = (Class>) javaType.getJavaType(); final String enumTypeName = enumClass.getSimpleName(); - final String[] enumeratedValues = EnumHelper.getEnumeratedValues( enumClass ); + @SuppressWarnings("unchecked") + final String[] enumeratedValues = + valueConverter == null + ? getEnumeratedValues( enumClass ) + : getEnumeratedValues( enumClass, (BasicValueConverter,?>) valueConverter ) ; if ( sortEnumValues ) { Arrays.sort( enumeratedValues ); } + final Dialect dialect = database.getDialect(); final String[] create = dialect.getCreateEnumTypeCommand( javaType.getJavaTypeClass().getSimpleName(), enumeratedValues diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLOrdinalEnumJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLOrdinalEnumJdbcType.java index 8a57cf5f64..cd190e3d4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLOrdinalEnumJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLOrdinalEnumJdbcType.java @@ -8,6 +8,7 @@ package org.hibernate.dialect; import org.hibernate.boot.model.relational.Database; import org.hibernate.engine.jdbc.Size; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.spi.TypeConfiguration; @@ -42,10 +43,10 @@ public class PostgreSQLOrdinalEnumJdbcType extends PostgreSQLEnumJdbcType { @Override public void addAuxiliaryDatabaseObjects( JavaType javaType, - Size columnSize, + BasicValueConverter valueConverter, Size columnSize, Database database, JdbcTypeIndicators context) { - addAuxiliaryDatabaseObjects( javaType, database, false ); + addAuxiliaryDatabaseObjects( javaType, valueConverter, database, false ); } @Override @@ -54,6 +55,6 @@ public class PostgreSQLOrdinalEnumJdbcType extends PostgreSQLEnumJdbcType { Size columnSize, Database database, TypeConfiguration typeConfiguration) { - addAuxiliaryDatabaseObjects( javaType, database, false ); + addAuxiliaryDatabaseObjects( javaType, null, database, false ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 94ff2d2941..ad6fcff9eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -365,6 +365,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol resolution.getJdbcType().addAuxiliaryDatabaseObjects( resolution.getRelationalJavaType(), + resolution.getValueConverter(), size, getBuildingContext().getMetadataCollector().getDatabase(), this diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/EnumHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/EnumHelper.java index 87844749ff..56e0ed9933 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/EnumHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/EnumHelper.java @@ -8,9 +8,11 @@ package org.hibernate.type.descriptor.converter.internal; import java.util.Arrays; +import org.hibernate.HibernateException; import org.hibernate.type.BasicType; import org.hibernate.type.SqlTypes; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.jdbc.JdbcType; /** @@ -39,14 +41,28 @@ public class EnumHelper { } public static String[] getEnumeratedValues(Class> enumClass) { - Enum[] values = enumClass.getEnumConstants(); - String[] names = new String[values.length]; + final Enum[] values = enumClass.getEnumConstants(); + final String[] names = new String[values.length]; for ( int i = 0; i < values.length; i++ ) { names[i] = values[i].name(); } return names; } + public static String[] getEnumeratedValues( + Class> enumClass, BasicValueConverter,?> converter) { + final Enum[] values = enumClass.getEnumConstants(); + final String[] names = new String[values.length]; + for ( int i = 0; i < values.length; i++ ) { + final Object relationalValue = converter.toRelationalValue( values[i] ); + if ( relationalValue == null ) { + throw new HibernateException( "Enum value converter returned null for enum class '" + enumClass.getName() + "'" ); + } + names[i] = relationalValue.toString(); + } + return names; + } + public static String[] getSortedEnumeratedValues(Class> enumClass) { final String[] names = getEnumeratedValues( enumClass ); Arrays.sort( names ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/spi/BasicValueConverter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/spi/BasicValueConverter.java index a9b43d329a..ab728d13a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/spi/BasicValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/spi/BasicValueConverter.java @@ -7,9 +7,7 @@ package org.hibernate.type.descriptor.converter.spi; import org.hibernate.Incubating; -import org.hibernate.dialect.Dialect; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java index 378ca1942d..a6bf3c8349 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java @@ -248,25 +248,20 @@ public class EnumJavaType> extends AbstractClassJavaType { * Convert a value of the enum type to its name value */ public String toName(T domainForm) { - if ( domainForm == null ) { - return null; - } - return domainForm.name(); + return domainForm == null ? null : domainForm.name(); } /** * Interpret a string value as the named value of the enum type */ public T fromName(String relationalForm) { - if ( relationalForm == null ) { - return null; - } - return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() ); + return relationalForm == null ? null : Enum.valueOf( getJavaTypeClass(), relationalForm.trim() ); } @Override public String getCheckCondition(String columnName, JdbcType jdbcType, BasicValueConverter converter, Dialect dialect) { - if ( converter != null ) { + if ( converter != null + && jdbcType.getDefaultSqlTypeCode() != NAMED_ENUM ) { return renderConvertedEnumCheckConstraint( columnName, jdbcType, converter, dialect ); } else if ( jdbcType.isInteger() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java index 540c143cea..3f7b57272f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java @@ -373,7 +373,7 @@ public interface JdbcType extends Serializable { } /** - * @deprecated Use {@link #addAuxiliaryDatabaseObjects(JavaType, Size, Database, JdbcTypeIndicators)} instead + * @deprecated Use {@link #addAuxiliaryDatabaseObjects(JavaType, BasicValueConverter, Size, Database, JdbcTypeIndicators)} instead */ @Incubating @Deprecated(forRemoval = true) @@ -392,6 +392,7 @@ public interface JdbcType extends Serializable { @Incubating default void addAuxiliaryDatabaseObjects( JavaType javaType, + BasicValueConverter valueConverter, Size columnSize, Database database, JdbcTypeIndicators context) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java index 136338a4d4..06f48d3f09 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java @@ -77,6 +77,7 @@ public class ArrayAggregateTest { "StringArray" ).addAuxiliaryDatabaseObjects( new ArrayJavaType<>( javaTypeRegistry.getDescriptor( String.class ) ), + null, Size.nil(), metadata.getDatabase(), typeConfiguration.getCurrentBaseSqlTypeIndicators() diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/enumeratedvalue/PostgresqlNamedEnumEnumerateValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/enumeratedvalue/PostgresqlNamedEnumEnumerateValueTests.java new file mode 100644 index 0000000000..ada55c9d3e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/enumeratedvalue/PostgresqlNamedEnumEnumerateValueTests.java @@ -0,0 +1,173 @@ +/* + * 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.orm.test.mapping.enumeratedvalue; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.EnumeratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SybaseDialect; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.hibernate.type.SqlTypes; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * The spec says that for {@linkplain EnumType#STRING}, only {@linkplain String} + * is supported. But {@code char} / {@linkplain Character} make a lot of sense to support as well + * + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@RequiresDialect(PostgreSQLDialect.class) +public class PostgresqlNamedEnumEnumerateValueTests { + @Test + @DomainModel(annotatedClasses = Person.class) + @SessionFactory + void testBasicUsage(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new Person( 1, "John", Gender.MALE ) ); + } ); + scope.inTransaction( (session) -> { + assertEquals( Gender.MALE, session.find( Person.class, 1 ).gender ); + } ); + + scope.inTransaction( (session) -> { + session.doWork( (connection) -> { + try (Statement statement = connection.createStatement()) { + try (ResultSet resultSet = statement.executeQuery( "select gender from persons" )) { + assertThat( resultSet.next() ).isTrue(); + final String storedGender = resultSet.getString( 1 ); + assertThat( storedGender ).isEqualTo( "M" ); + } + } + } ); + } ); + } + + @DomainModel(annotatedClasses = Person.class) + @SessionFactory + @Test + void testNulls(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new Person( 1, "John", null ) ); + } ); + + scope.inTransaction( (session) -> { + session.doWork( (connection) -> { + try (Statement statement = connection.createStatement()) { + try (ResultSet resultSet = statement.executeQuery( "select gender from persons" )) { + assertThat( resultSet.next() ).isTrue(); + final String storedGender = resultSet.getString( 1 ); + assertThat( resultSet.wasNull() ).isTrue(); + assertThat( storedGender ).isNull(); + } + } + } ); + } ); + } + + @DomainModel(annotatedClasses = Person.class) + @SessionFactory + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsColumnCheck.class ) + @Test + void verifyCheckConstraints(SessionFactoryScope scope) { + scope.inTransaction( (session) -> session.doWork( (connection) -> { + try (PreparedStatement statement = connection.prepareStatement( "insert into persons (id, gender) values (?, ?)" ) ) { + statement.setInt( 1, 100 ); + statement.setString( 2, "X" ); + statement.executeUpdate(); + fail( "Expecting a failure" ); + } + catch (SQLException expected) { + } + } ) ); + } + + @DomainModel(annotatedClasses = Person.class) + @SessionFactory + @SkipForDialect( dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase (at least jTDS driver) truncates the value so the constraint is not violated" ) + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsColumnCheck.class ) + @Test + void verifyCheckConstraints2(SessionFactoryScope scope) { + scope.inTransaction( (session) -> session.doWork( (connection) -> { + try (PreparedStatement statement = connection.prepareStatement( "insert into persons (id, gender) values (?, ?)" ) ) { + statement.setInt( 1, 200 ); + // this would work without check constraints or with check constraints based solely on EnumType#STRING + statement.setString( 2, "MALE" ); + statement.executeUpdate(); + fail( "Expecting a failure" ); + } + catch (SQLException expected) { + } + } ) ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> session.createMutationQuery( "delete Person" ).executeUpdate() ); + } + + public enum Gender { + MALE( 'M' ), + FEMALE( 'F' ), + OTHER( 'U' ); + + @EnumeratedValue + private final char code; + + Gender(char code) { + this.code = code; + } + + public char getCode() { + return code; + } + } + + @SuppressWarnings({ "FieldCanBeLocal", "unused" }) + @Entity(name="Person") + @Table(name="persons") + public static class Person { + @Id + private Integer id; + private String name; + @Enumerated(EnumType.STRING) + @JdbcTypeCode(SqlTypes.NAMED_ENUM) + @Column(length = 1) + private Gender gender; + + public Person() { + } + + public Person(Integer id, String name, Gender gender) { + this.id = id; + this.name = name; + this.gender = gender; + } + } +}