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 f566d03caf27..4118eec33340 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -36,6 +36,7 @@ import org.hibernate.dialect.function.LpadRpadPadEmulation; import org.hibernate.dialect.function.OrdinalFunction; import org.hibernate.dialect.function.SqlFunction; +import org.hibernate.dialect.function.StringFunction; import org.hibernate.dialect.function.TrimFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupportImpl; @@ -1218,6 +1219,11 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionContributions.getFunctionRegistry().register( "ordinal", new OrdinalFunction( typeConfiguration ) ); + // Function to convert enum mapped as String to their string value + + functionContributions.getFunctionRegistry().register( "string", + new StringFunction( 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/function/StringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/StringFunction.java new file mode 100644 index 000000000000..9b4835822531 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/StringFunction.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function; + +import org.hibernate.QueryException; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.model.domain.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 java.util.List; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ENUM; + + +/** + * The HQL {@code string()} function returns the string value of an enum + *
+ * For enum fields mapped as STRING or ENUM it's a synonym for {@code cast(x as String)}. Same as {@link CastStrEmulation}.
+ * For enum fields mapped as ORDINAL it's a case statement that returns the bane if enum.
+ *
+ * @author Luca Molteni, Cedomir Igaly
+ */
+public class StringFunction
+		extends AbstractSqmSelfRenderingFunctionDescriptor {
+
+	public StringFunction(TypeConfiguration typeConfiguration) {
+		super(
+				"string",
+				new ArgumentTypesValidator( null, ENUM ),
+				StandardFunctionReturnTypeResolvers.invariant(
+						typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
+				),
+				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.isString() || argumentType.getDefaultSqlTypeCode() == SqlTypes.ENUM ) {
+			singleExpression.accept( walker );
+		}
+		else if ( argumentType.isInteger() ) {
+			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.appendSql( enumValue.ordinal() );
+				sqlAppender.appendSql( " then " );
+				sqlAppender.appendSingleQuoteEscapedString( enumValue.name() );
+			}
+			sqlAppender.appendSql( " end" );
+		}
+		else {
+			throw new QueryException( "Unsupported enum type passed to 'string()' function: " + argumentType );
+		}
+	}
+
+	@Override
+	public String getArgumentListSignature() {
+		return "(ENUM arg)";
+	}
+}
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
index 8b64001b1061..30163fa481bc 100644
--- 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
@@ -4,8 +4,6 @@
  */
 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;
@@ -14,6 +12,8 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 @DomainModel(annotatedClasses = {
@@ -22,6 +22,7 @@
 })
 @SessionFactory
 @Jira("https://hibernate.atlassian.net/browse/HHH-16861")
+@Jira("https://hibernate.atlassian.net/browse/HHH-18708")
 public class EnumTest {
 
 
@@ -97,4 +98,56 @@ public void testOrdinalFunctionOnStringEnum(SessionFactoryScope scope) {
 
 	}
 
+	@Test
+	public void testStringFunctionOnStringEnum(SessionFactoryScope scope) {
+		scope.inTransaction( session -> {
+
+			List