diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index d4de91e6bb..af7fb977d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -68,6 +68,7 @@ import org.hibernate.dialect.function.LocatePositionEmulation; import org.hibernate.dialect.function.LpadRpadPadEmulation; import org.hibernate.dialect.function.SqlFunction; import org.hibernate.dialect.function.TrimFunction; +import org.hibernate.dialect.function.OrdinalFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupportImpl; import org.hibernate.dialect.lock.LockingStrategy; @@ -1227,6 +1228,11 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun functionContributions.getFunctionRegistry().register( "str", new CastStrEmulation( typeConfiguration ) ); + // Function to convert enum mapped as Ordinal to their ordinal value + + functionContributions.getFunctionRegistry().register( "ordinal", + new OrdinalFunction( typeConfiguration ) ); + //format() function for datetimes, emulated on many databases using the //Oracle-style to_char() function, and on others using their native //formatting functions diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java index b4d7fd0e39..c9cbe48372 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java @@ -312,7 +312,8 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport private String buildDropTypeSqlString(String arrayTypeName) { if ( dialect.supportsIfExistsBeforeTypeName() ) { return "drop type if exists " + arrayTypeName + " force"; - } else { + } + else { return "drop type " + arrayTypeName + " force"; } } @@ -320,7 +321,8 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport private String buildDropFunctionSqlString(String functionTypeName) { if ( supportsIfExistsBeforeFunctionName() ) { return "drop function if exists " + functionTypeName; - } else { + } + else { return "drop function " + functionTypeName; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/OrdinalFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/OrdinalFunction.java new file mode 100644 index 0000000000..f743d4c32b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/OrdinalFunction.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function; + +import java.util.List; + +import org.hibernate.QueryException; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.query.ReturnableType; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.java.EnumJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ENUM; + + +/** + * The HQL {@code ordinal()} function returns the ordinal value of an enum + *
+ * For enum fields mapped as ORDINAL it's a synonym for {@code cast(x as Integer)}. Same as {@link CastStrEmulation} but for Integer.
+ * For enum fields mapped as STRING or ENUM it's a case statement that returns the ordinal value.
+ *
+ * @author Luca Molteni
+ */
+public class OrdinalFunction
+ extends AbstractSqmSelfRenderingFunctionDescriptor {
+
+ public OrdinalFunction(TypeConfiguration typeConfiguration) {
+ super(
+ "ordinal",
+ new ArgumentTypesValidator( null, ENUM ),
+ StandardFunctionReturnTypeResolvers.invariant(
+ typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER )
+ ),
+ null
+ );
+ }
+
+ @Override
+ public void render(
+ SqlAppender sqlAppender,
+ List extends SqlAstNode> arguments,
+ ReturnableType> returnType,
+ SqlAstTranslator> walker) {
+ Expression singleExpression = (Expression) arguments.get( 0 );
+
+ JdbcMapping singleJdbcMapping = singleExpression.getExpressionType().getSingleJdbcMapping();
+ JdbcType argumentType = singleJdbcMapping.getJdbcType();
+
+ if ( argumentType.isInteger() ) {
+ singleExpression.accept( walker );
+ }
+ else if ( argumentType.isString() || argumentType.getDefaultSqlTypeCode() == SqlTypes.ENUM ) {
+
+ EnumJavaType> enumJavaType = (EnumJavaType>) singleJdbcMapping.getMappedJavaType();
+ Object[] enumConstants = enumJavaType.getJavaTypeClass().getEnumConstants();
+
+ sqlAppender.appendSql( "case " );
+ singleExpression.accept( walker );
+ for ( Object e : enumConstants ) {
+ Enum> enumValue = (Enum>) e;
+ sqlAppender.appendSql( " when " );
+ sqlAppender.appendSingleQuoteEscapedString( (String) singleJdbcMapping.convertToRelationalValue(
+ enumValue.toString() ) );
+ sqlAppender.appendSql( " then " );
+ sqlAppender.appendSql( enumValue.ordinal() );
+ }
+ sqlAppender.appendSql( " end" );
+ }
+ else {
+ throw new QueryException( "Unsupported enum type passed to 'ordinal()' function: " + argumentType );
+ }
+ }
+
+ @Override
+ public String getArgumentListSignature() {
+ return "(ENUM arg)";
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java
index 05cb05fdb8..721f49f3c4 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java
@@ -220,7 +220,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
@Internal
public static void checkArgumentType(
int paramNumber, String functionName, FunctionParameterType type, JdbcType jdbcType, Type javaType) {
- if ( !isCompatible( type, jdbcType )
+ if ( !isCompatible( type, jdbcType, javaType )
// as a special case, we consider a binary column
// comparable when it is mapped by a Java UUID
&& !( type == COMPARABLE && isBinaryUuid( jdbcType, javaType ) ) ) {
@@ -234,7 +234,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
}
@Internal
- private static boolean isCompatible(FunctionParameterType type, JdbcType jdbcType) {
+ private static boolean isCompatible(FunctionParameterType type, JdbcType jdbcType, Type javaType) {
return switch (type) {
case COMPARABLE -> jdbcType.isComparable();
case STRING -> jdbcType.isStringLikeExcludingClob();
@@ -253,6 +253,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
case IMPLICIT_JSON -> jdbcType.isImplicitJson();
case XML -> jdbcType.isXml();
case IMPLICIT_XML -> jdbcType.isImplicitXml();
+ case ENUM -> javaType instanceof Class> clz && clz.isEnum();
default -> true; // TODO: should we throw here?
};
}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java
index d2ba1aa06c..cdf9d20fde 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java
@@ -98,6 +98,12 @@ public enum FunctionParameterType {
* @since 7.0
*/
IMPLICIT_JSON,
+ /**
+ * Indicates that the argument should be an ENUM type
+ * @see org.hibernate.type.SqlTypes#isEnumType(int)
+ * @since 7.0
+ */
+ ENUM,
/**
* Indicates that the argument should be a XML type
* @see org.hibernate.type.SqlTypes#isXmlType(int)
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/EnumTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/EnumTest.java
new file mode 100644
index 0000000000..be69ee55c1
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/EnumTest.java
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.orm.test.hql;
+
+import java.util.List;
+
+import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
+import org.hibernate.testing.orm.junit.DomainModel;
+import org.hibernate.testing.orm.junit.Jira;
+import org.hibernate.testing.orm.junit.SessionFactory;
+import org.hibernate.testing.orm.junit.SessionFactoryScope;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DomainModel(annotatedClasses = {
+ EntityOfBasics.class,
+ EntityOfBasics.Gender.class,
+})
+@SessionFactory
+@Jira("https://hibernate.atlassian.net/browse/HHH-16861")
+public class EnumTest {
+
+
+ @BeforeAll
+ public void setUp(SessionFactoryScope scope) {
+ scope.inTransaction(session -> {
+ EntityOfBasics male = new EntityOfBasics();
+ male.setId( 20_000_000 );
+ male.setGender( EntityOfBasics.Gender.MALE ); // Ordinal 0
+ male.setOrdinalGender( EntityOfBasics.Gender.MALE ); // Ordinal 0
+
+ EntityOfBasics female = new EntityOfBasics();
+ female.setId( 20_000_001 );
+ female.setGender( EntityOfBasics.Gender.FEMALE ); // Ordinal 1
+ female.setOrdinalGender( EntityOfBasics.Gender.FEMALE ); // Ordinal 1
+
+ session.persist( male );
+ session.persist( female );
+ });
+ }
+
+
+ @Test
+ public void testOrdinalFunctionOnOrdinalEnum(SessionFactoryScope scope) {
+ scope.inTransaction( session -> {
+
+ List