diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index d2a050edd9..5f21d5c862 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -985,7 +985,9 @@ genericFunction ; /** - * The name of a generic function + * The name of a generic function, which may contain periods and quoted identifiers + * + * Names of generic functions are resolved against the SqmFunctionRegistry */ genericFunctionName : simplePath diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index ac8929275f..3eadd6634a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -3155,9 +3155,19 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } + @Override + public String visitGenericFunctionName(HqlParser.GenericFunctionNameContext ctx) { + StringBuilder functionName = new StringBuilder( visitIdentifier( ctx.simplePath().identifier() ) ); + for ( HqlParser.SimplePathElementContext sp: ctx.simplePath().simplePathElement() ) { + // allow function names of form foo.bar to be located in the registry + functionName.append('.').append( visitIdentifier( sp.identifier() ) ); + } + return functionName.toString(); + } + @Override public Object visitGenericFunction(HqlParser.GenericFunctionContext ctx) { - final String originalFunctionName = ctx.getChild( 0 ).getText(); + final String originalFunctionName = visitGenericFunctionName( ctx.genericFunctionName() ); final String functionName = originalFunctionName.toLowerCase(); if ( creationOptions.useStrictJpaCompliance() && !JPA_STANDARD_FUNCTIONS.contains( functionName ) ) { throw new StrictJpaComplianceViolation( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java index ccc5f83dd3..1c74f13068 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java @@ -9,6 +9,7 @@ package org.hibernate.orm.test.hql; import java.util.List; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -46,6 +47,13 @@ public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { } ); } + @After + public void tearDown() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery("delete `The Person`").executeUpdate(); + } ); + } + @Test public void testQuotedIdentifier() { doInHibernate( this::sessionFactory, session -> { @@ -60,6 +68,20 @@ public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { } ); } + @Test + public void testQuotedFunctionName() { + doInHibernate( this::sessionFactory, session -> { + TypedQuery query = session.createQuery( + "select `upper`(`the person`.`name`) as `The person name` " + + "from `The Person` `the person`", + Tuple.class + ); + List resultList = query.getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( "CHUCK", resultList.get( 0 ).get( "The person name" ) ); + } ); + } + @Entity(name = "The Person") public static class Person {