From 1ed895a3737c211e8c895b0297f801daccfe85a9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 15 May 2014 09:05:22 -0500 Subject: [PATCH] HHH-9154 - keywords as parameter names (cherry picked from commit 5aff092e2bd06bcfc18eb6eec9983b2f97237d64) Conflicts: hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java --- hibernate-core/src/main/antlr/hql.g | 34 +++++---- .../hibernate/hql/internal/ast/HqlParser.java | 19 +++++ .../test/hql/ASTParserLoadingTest.java | 73 +++++++----------- .../org/hibernate/test/hql/ParameterTest.java | 74 +++++++++++++++++++ 4 files changed, 138 insertions(+), 62 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/ParameterTest.java diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 6522203ac9..c86afb55df 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -198,6 +198,13 @@ tokens public void weakKeywords() throws TokenStreamException { } + /** + * Called after we have recognized ':'. The expectation is to handle converting + * any non-IDENT token where possibleID == true into an IDENT + */ + public void expectNamedParameterName() throws TokenStreamException { + } + public void processMemberOf(Token n,AST p,ASTPair currentAST) { } @@ -666,9 +673,7 @@ quantifiedExpression // * method call ( '.' ident '(' exprList ') ) // * function : differentiated from method call via explicit keyword atom - : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax - | { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction - | primaryExpression + : primaryExpression ( DOT^ identifier ( options { greedy=true; } : @@ -677,6 +682,17 @@ atom )* ; + +// level 0 - the basic element of an expression +primaryExpression + : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax + | { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction + | identPrimary ( options {greedy=true;} : DOT^ "class" )? + | constant + | parameter + | OPEN! (expressionOrVector | subQuery) CLOSE! + ; + jpaFunctionSyntax! : i:IDENT OPEN n:QUOTED_STRING COMMA a:exprList CLOSE { final String functionName = unquote( #n.getText() ); @@ -710,18 +726,8 @@ castTargetType : identifier { handleDotIdent(); } ( options { greedy=true; } : DOT^ identifier )* ; - -// level 0 - the basic element of an expression -primaryExpression - : identPrimary ( options {greedy=true;} : DOT^ "class" )? - | constant - | parameter - // TODO: Add parens to the tree so the user can control the operator evaluation order. - | OPEN! (expressionOrVector | subQuery) CLOSE! - ; - parameter - : COLON^ identifier + : COLON^ { expectNamedParameterName(); } IDENT | PARAM^ (NUM_INT)? ; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java index 79b5e44711..ce5616c400 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java @@ -414,6 +414,25 @@ public final class HqlParser extends HqlBaseParser { } } + @Override + public void expectNamedParameterName() throws TokenStreamException { + // we expect the token following a COLON (':') to be the name of a named parameter. + // if the following token is anything other than IDENT we convert its type if possible. + + // NOTE : the LT() call is more expensive than the LA() call; so we + // use LA() first to see if LT() is needed. + if ( LA( 1 ) != IDENT ) { + final HqlToken nextToken = (HqlToken) LT( 1 ); + if ( nextToken.isPossibleID() ) { + LOG.debugf( + "Converting keyword [%s] following COLON to IDENT as an expected parameter name", + nextToken.getText() + ); + nextToken.setType( IDENT ); + } + } + } + @Override public void handleDotIdent() throws TokenStreamException { // This handles HHH-354, where there is a strange property name in a where clause. diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index 6b4598f908..116ed87b84 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -23,15 +23,6 @@ */ package org.hibernate.test.hql; -import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; @@ -74,6 +65,20 @@ import org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.DiscriminatorType; import org.hibernate.stat.QueryStatistics; +import org.hibernate.transform.DistinctRootEntityResultTransformer; +import org.hibernate.transform.Transformers; +import org.hibernate.type.ComponentType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.Type; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.any.IntegerPropertyValue; import org.hibernate.test.any.PropertySet; import org.hibernate.test.any.PropertyValue; @@ -98,6 +103,17 @@ import org.hibernate.type.Type; import org.jboss.logging.Logger; import org.junit.Test; +import org.jboss.logging.Logger; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * Tests the integration of the new AST parser into the loading of query results using * the Hibernate persisters and loaders. @@ -269,45 +285,6 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { s.close(); } - @Test - @FailureExpected( jiraKey = "HHH-9154" ) - public void testClassAsParameter() { - Session s = openSession(); - s.beginTransaction(); - - Type[] types = s.createQuery( "select h.name from Human h" ).getReturnTypes(); - assertEquals( 1, types.length ); - assertTrue( types[0] instanceof ComponentType ); - - s.createQuery( "from Human h where h.name = :class" ).setParameter( "class", new Name() ).list(); - s.createQuery( "from Human where name = :class" ).setParameter( "class", new Name() ).list(); - s.createQuery( "from Human h where :class = h.name" ).setParameter( "class", new Name() ).list(); - s.createQuery( "from Human h where :class <> h.name" ).setParameter( "class", new Name() ).list(); - - s.getTransaction().commit(); - s.close(); - } - - - @Test - @FailureExpected( jiraKey = "HHH-9154" ) - public void testObjectAsParameter() { - Session s = openSession(); - s.beginTransaction(); - - Type[] types = s.createQuery( "select h.name from Human h" ).getReturnTypes(); - assertEquals( 1, types.length ); - assertTrue( types[0] instanceof ComponentType ); - - s.createQuery( "from Human h where h.name = :OBJECT" ).setParameter( "OBJECT", new Name() ).list(); - s.createQuery( "from Human where name = :OBJECT" ).setParameter( "OBJECT", new Name() ).list(); - s.createQuery( "from Human h where :OBJECT = h.name" ).setParameter( "OBJECT", new Name() ).list(); - s.createQuery( "from Human h where :OBJECT <> h.name" ).setParameter( "OBJECT", new Name() ).list(); - - s.getTransaction().commit(); - s.close(); - } - @Test public void testComponentJoins() { Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ParameterTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ParameterTest.java new file mode 100644 index 0000000000..2446d194e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ParameterTest.java @@ -0,0 +1,74 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.hql; + +import org.hibernate.Session; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * Isolated test for various usages of parameters + * + * @author Steve Ebersole + */ +public class ParameterTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "hql/Animal.hbm.xml" }; + } + + @Test + @TestForIssue( jiraKey = "HHH-9154" ) + public void testClassAsParameter() { + Session s = openSession(); + s.beginTransaction(); + + s.createQuery( "from Human h where h.name = :class" ).setParameter( "class", new Name() ).list(); + s.createQuery( "from Human where name = :class" ).setParameter( "class", new Name() ).list(); + s.createQuery( "from Human h where :class = h.name" ).setParameter( "class", new Name() ).list(); + s.createQuery( "from Human h where :class <> h.name" ).setParameter( "class", new Name() ).list(); + + s.getTransaction().commit(); + s.close(); + } + + + @Test + @TestForIssue( jiraKey = "HHH-9154" ) + public void testObjectAsParameter() { + Session s = openSession(); + s.beginTransaction(); + + s.createQuery( "from Human h where h.name = :OBJECT" ).setParameter( "OBJECT", new Name() ).list(); + s.createQuery( "from Human where name = :OBJECT" ).setParameter( "OBJECT", new Name() ).list(); + s.createQuery( "from Human h where :OBJECT = h.name" ).setParameter( "OBJECT", new Name() ).list(); + s.createQuery( "from Human h where :OBJECT <> h.name" ).setParameter( "OBJECT", new Name() ).list(); + + s.getTransaction().commit(); + s.close(); + } +}