From 78990a79108bb134d39ecb66add538717a9e620d Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 25 Jan 2024 14:19:58 +0100 Subject: [PATCH] HHH-17677 handle literal null arguments more elegantly in StandardFunctionReturnTypeResolvers resolves a very confusing error message --- .../StandardFunctionReturnTypeResolvers.java | 19 +++++++++++-------- .../results/internal/SqlSelectionImpl.java | 7 +++++-- .../orm/test/query/hql/FunctionTests.java | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java index af98edc2b2..59fc361a7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java @@ -12,16 +12,15 @@ import java.util.Locale; import java.util.function.Supplier; import org.hibernate.Internal; -import org.hibernate.QueryException; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.ReturnableType; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.NullSqmExpressible; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.type.BasicType; @@ -252,7 +251,10 @@ public class StandardFunctionReturnTypeResolvers { int position) { final SqmTypedNode specifiedArgument = arguments.get( position - 1 ); final SqmExpressible specifiedArgType = getArgumentExpressible( specifiedArgument ); - if ( specifiedArgType != null && !(specifiedArgType instanceof ReturnableType ) ) { + if ( specifiedArgType == null || specifiedArgType instanceof NullSqmExpressible ) { + return null; + } + else if ( !(specifiedArgType instanceof ReturnableType) ) { throw new FunctionArgumentException( String.format( Locale.ROOT, @@ -263,8 +265,9 @@ public class StandardFunctionReturnTypeResolvers { ) ); } - - return (ReturnableType) specifiedArgType; + else { + return (ReturnableType) specifiedArgType; + } } private static SqmExpressible getArgumentExpressible(SqmTypedNode specifiedArgument) { @@ -272,9 +275,9 @@ public class StandardFunctionReturnTypeResolvers { final SqmExpressible specifiedArgType = expressible instanceof SqmTypedNode ? ( (SqmTypedNode) expressible ).getNodeType() : expressible; - return specifiedArgType instanceof SqmPathSource ? - ( (SqmPathSource) specifiedArgType ).getSqmPathType() : - specifiedArgType; + return specifiedArgType instanceof SqmPathSource + ? ( (SqmPathSource) specifiedArgType ).getSqmPathType() + : specifiedArgType; } public static JdbcMapping extractArgumentJdbcMapping( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/SqlSelectionImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/SqlSelectionImpl.java index 92425cb67f..b407ace753 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/SqlSelectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/SqlSelectionImpl.java @@ -11,7 +11,6 @@ import java.util.Objects; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; -import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.spi.SqlExpressionAccess; import org.hibernate.sql.ast.spi.SqlSelection; @@ -93,7 +92,10 @@ public class SqlSelectionImpl implements SqlSelection, SqlExpressionAccess { } private static ValueExtractor determineValueExtractor(Expression sqlExpression, JavaType jdbcJavaType) { - final JdbcMapping jdbcMapping = sqlExpression.getExpressionType().getSingleJdbcMapping(); + final JdbcMappingContainer expressionType = sqlExpression.getExpressionType(); + final JdbcMapping jdbcMapping = expressionType == null + ? JavaObjectType.INSTANCE + : expressionType.getSingleJdbcMapping(); if ( jdbcJavaType == null || jdbcMapping.getMappedJavaType() == jdbcJavaType ) { return jdbcMapping.getJdbcValueExtractor(); } @@ -102,6 +104,7 @@ public class SqlSelectionImpl implements SqlSelection, SqlExpressionAccess { } } + @Override public Expression getExpression() { return sqlExpression; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index bf9264392b..6c3e770927 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -2248,4 +2248,22 @@ public class FunctionTests { } ); } + + @Test + @SkipForDialect(dialectClass = H2Dialect.class) + @SkipForDialect(dialectClass = DerbyDialect.class) + @SkipForDialect(dialectClass = HSQLDialect.class) + @SkipForDialect(dialectClass = DB2Dialect.class) + public void testNullInCoalesce(SessionFactoryScope scope) { + scope.inTransaction(s -> { + assertEquals("hello", + s.createQuery("select coalesce(null, :word)", String.class) + .setParameter("word", "hello") + .getSingleResultOrNull()); + assertEquals("hello", + s.createQuery("select coalesce(:word, null)", String.class) + .setParameter("word", "hello") + .getSingleResultOrNull()); + }); + } }