diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index fdf1b37582..6608171a6e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -1459,6 +1459,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase { public void test_hql_str_function_example_sql_server() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-str-function-example[] + // Special SQL Server function "str" that converts floats List timestamps = entityManager.createQuery( "select str( cast(duration as float) / 60, 4, 2 ) " + "from Call c ", String.class ) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index b1a3aa2331..9a0e3ef96a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -8,6 +8,8 @@ package org.hibernate.dialect; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.dialect.function.CastStrEmulation; +import org.hibernate.dialect.function.TransactSQLStrFunction; import org.hibernate.query.NullOrdering; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; @@ -116,6 +118,7 @@ public abstract class AbstractTransactSQLDialect extends Dialect { queryEngine.getSqmFunctionRegistry().register( "least", new CaseLeastGreatestEmulation( true ) ); queryEngine.getSqmFunctionRegistry().register( "greatest", new CaseLeastGreatestEmulation( false ) ); + queryEngine.getSqmFunctionRegistry().register( "str", new TransactSQLStrFunction() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java index fd7bd41138..b4eae3b4ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java @@ -10,6 +10,8 @@ import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor; import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.produce.function.ArgumentsValidator; +import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.tree.SqmTypedNode; @@ -35,13 +37,20 @@ public class CastStrEmulation ); } + protected CastStrEmulation( + String name, + ArgumentsValidator argumentsValidator, + FunctionReturnTypeResolver returnTypeResolver) { + super( name, argumentsValidator, returnTypeResolver ); + } + @Override protected SelfRenderingSqmFunction generateSqmFunctionExpression( List> arguments, AllowableFunctionReturnType impliedResultType, QueryEngine queryEngine, TypeConfiguration typeConfiguration) { - SqmTypedNode argument = arguments.get(0); + final SqmTypedNode argument = arguments.get( 0 ); return queryEngine.getSqmFunctionRegistry().findFunctionDescriptor( "cast" ) .generateSqmExpression( asList( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/TransactSQLStrFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/TransactSQLStrFunction.java new file mode 100644 index 0000000000..3381da032c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/TransactSQLStrFunction.java @@ -0,0 +1,71 @@ +/* + * 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.dialect.function; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.function.FunctionRenderingSupport; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * A special function that renders to the T-SQL "str" function if more than a single argument is given, + * otherwise renders a cast expression like {@link CastStrEmulation}. + * + * @author Christian Beikov + */ +public class TransactSQLStrFunction extends CastStrEmulation implements FunctionRenderingSupport { + + public TransactSQLStrFunction() { + super( + "str", + StandardArgumentsValidators.between( 1, 3 ), + StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.STRING ) + ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + AllowableFunctionReturnType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + if ( arguments.size() == 1 ) { + return super.generateSqmFunctionExpression( arguments, impliedResultType, queryEngine, typeConfiguration ); + } + + return new SelfRenderingSqmFunction<>( + this, + this, + arguments, + impliedResultType, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } + + @Override + public void render(SqlAppender sqlAppender, List arguments, SqlAstTranslator walker) { + sqlAppender.appendSql( "str(" ); + arguments.get( 0 ).accept( walker ); + for ( int i = 1; i < arguments.size(); i++ ) { + sqlAppender.appendSql( ", " ); + arguments.get( i ).accept( walker ); + } + sqlAppender.appendSql( ')' ); + } +}