From 67c5000885a6721b0b0e69164f0a48830ff1d4ce Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 6 Jun 2012 17:22:58 -0500 Subject: [PATCH] HHH-7374 - Support KEY, ENTRY and VALUE qualifiers in WHERE clause --- hibernate-core/src/main/antlr/hql-sql.g | 3 + hibernate-core/src/main/antlr/hql.g | 6 +- hibernate-core/src/main/antlr/sql-gen.g | 1 + .../ast/tree/AbstractMapComponentNode.java | 1 + .../internal/ast/tree/FromElementType.java | 20 +- .../hql/internal/ast/tree/MapKeyNode.java | 7 +- .../hql/internal/ast/tree/MapValueNode.java | 7 +- .../test/hql/ASTParserLoadingTest.java | 198 +++++++++++++++--- .../src/test/resources/log4j.properties | 4 +- 9 files changed, 201 insertions(+), 46 deletions(-) diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 87f92a69ea..80af06eb79 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -652,6 +652,9 @@ addrExpr! [ boolean root ] #addrExpr = #(#i, #lhs2, #rhs2); processIndex(#addrExpr); } + | mcr:mapComponentReference { + #addrExpr = #mcr; + } | p:identifier { // #addrExpr = #p; // resolve(#addrExpr); diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index f6ed120684..9289b0e070 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -645,13 +645,13 @@ identPrimary ( options { greedy=true; } : ( op:OPEN^ { #op.setType(METHOD_CALL);} e:exprList CLOSE! ) { AST path = #e.getFirstChild(); - if ( #i.getText().equals( "key" ) ) { + if ( #i.getText().equalsIgnoreCase( "key" ) ) { #identPrimary = #( [KEY], path ); } - else if ( #i.getText().equals( "value" ) ) { + else if ( #i.getText().equalsIgnoreCase( "value" ) ) { #identPrimary = #( [VALUE], path ); } - else if ( #i.getText().equals( "entry" ) ) { + else if ( #i.getText().equalsIgnoreCase( "entry" ) ) { #identPrimary = #( [ENTRY], path ); } } diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g index b5640d4ace..4abb0cfbfc 100644 --- a/hibernate-core/src/main/antlr/sql-gen.g +++ b/hibernate-core/src/main/antlr/sql-gen.g @@ -436,6 +436,7 @@ addrExpr | i:ALIAS_REF { out(i); } | j:INDEX_OP { out(j); } | v:RESULT_VARIABLE_REF { out(v); } + | mcr:mapComponentReference { out(mcr); } ; sqlToken diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java index f664920f4c..a382f5c7d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java @@ -28,6 +28,7 @@ import java.util.Map; import antlr.SemanticException; import antlr.collections.AST; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.internal.util.StringHelper; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 4c323a76fc..fea26dbc6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -210,7 +210,7 @@ class FromElementType { getTableAlias(), getSuffix( size, k ), allProperties - ); + ); return trimLeadingCommaAndSpaces( fragment ); } } @@ -327,15 +327,17 @@ class FromElementType { String[] toColumns(String tableAlias, String path, boolean inSelect, boolean forceAlias) { checkInitialized(); PropertyMapping propertyMapping = getPropertyMapping( path ); - // If this from element is a collection and the path is a collection property (maxIndex, etc.) then - // generate a sub-query. - // - // NOTE : in the case of this being a collection property in the select, not generating the subquery - // will not generally work. The specific cases I am thinking about are the minIndex, maxIndex - // (most likely minElement, maxElement as well) cases. - // todo : if ^^ is the case we should thrown an exception here rather than waiting for the sql error - // if the dialect supports select-clause subqueries we could go ahead and generate the subquery also + if ( !inSelect && queryableCollection != null && CollectionProperties.isCollectionProperty( path ) ) { + // If this from element is a collection and the path is a collection property (maxIndex, etc.) + // requiring a sub-query then generate a sub-query. + //h + // Unless we are in the select clause, because some dialects do not support + // Note however, that some dialects do not However, in the case of this being a collection property reference being in the select, not generating the subquery + // will not generally work. The specific cases I am thinking about are the minIndex, maxIndex + // (most likely minElement, maxElement as well) cases. + // todo : if ^^ is the case we should thrown an exception here rather than waiting for the sql error + // if the dialect supports select-clause subqueries we could go ahead and generate the subquery also Map enabledFilters = fromElement.getWalker().getEnabledFilters(); String subquery = CollectionSubqueryFactory.createCollectionSubquery( joinSequence.copy().setUseThetaStyle( true ), diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java index e77692d179..9d61b44b92 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java @@ -39,7 +39,12 @@ public class MapKeyNode extends AbstractMapComponentNode { @Override protected String[] resolveColumns(QueryableCollection collectionPersister) { - return collectionPersister.getIndexColumnNames(); + final FromElement fromElement = getFromElement(); + return fromElement.toColumns( + fromElement.getCollectionTableAlias(), + "index", // the JPA KEY "qualifier" is the same concept as the HQL INDEX function/property + getWalker().isInSelect() + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java index fd2b808a7a..7a545e5026 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java @@ -39,7 +39,12 @@ public class MapValueNode extends AbstractMapComponentNode { @Override protected String[] resolveColumns(QueryableCollection collectionPersister) { - return collectionPersister.getElementColumnNames(); + final FromElement fromElement = getFromElement(); + return fromElement.toColumns( + fromElement.getCollectionTableAlias(), + "elements", // the JPA VALUE "qualifier" is the same concept as the HQL ELEMENTS function/property + getWalker().isInSelect() + ); } @Override diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java index ed5a711700..fe5534d723 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -250,7 +250,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { @Test @SuppressWarnings( {"unchecked"}) - public void testJPAQLQualifiedIdentificationVariables() { + public void testJPAQLMapKeyQualifier() { Session s = openSession(); s.beginTransaction(); Human me = new Human(); @@ -264,47 +264,183 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { s.getTransaction().commit(); s.close(); - s = openSession(); - s.beginTransaction(); - List results = s.createQuery( "select entry(h.family) from Human h" ).list(); - assertEquals( 1, results.size() ); - Object result = results.get(0); - assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) ); - Map.Entry entry = (Map.Entry) result; - assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) ); - assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) ); - s.getTransaction().commit(); - s.close(); + // in SELECT clause + { + // hibernate-only form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select distinct key(h.family) from Human h" ).list(); + assertEquals( 1, results.size() ); + Object key = results.get(0); + assertTrue( String.class.isAssignableFrom( key.getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + { + // jpa form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select distinct KEY(f) from Human h join h.family f" ).list(); + assertEquals( 1, results.size() ); + Object key = results.get(0); + assertTrue( String.class.isAssignableFrom( key.getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + // in WHERE clause + { + // hibernate-only form + s = openSession(); + s.beginTransaction(); + Long count = (Long) s.createQuery( "select count(*) from Human h where KEY(h.family) = 'son'" ).uniqueResult(); + assertEquals( (Long)1L, count ); + s.getTransaction().commit(); + s.close(); + } + + { + // jpa form + s = openSession(); + s.beginTransaction(); + Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where key(f) = 'son'" ).uniqueResult(); + assertEquals( (Long)1L, count ); + s.getTransaction().commit(); + s.close(); + } s = openSession(); s.beginTransaction(); - results = s.createQuery( "select entry(f) from Human h join h.family f" ).list(); - assertEquals( 1, results.size() ); - result = results.get(0); - assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) ); - entry = (Map.Entry) result; - assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) ); - assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) ); + s.delete( me ); + s.delete( joe ); + s.getTransaction().commit(); + s.close(); + } + + @Test + @SuppressWarnings( {"unchecked"}) + public void testJPAQLMapEntryQualifier() { + Session s = openSession(); + s.beginTransaction(); + Human me = new Human(); + me.setName( new Name( "Steve", null, "Ebersole" ) ); + Human joe = new Human(); + me.setName( new Name( "Joe", null, "Ebersole" ) ); + me.setFamily( new HashMap() ); + me.getFamily().put( "son", joe ); + s.save( me ); + s.save( joe ); s.getTransaction().commit(); s.close(); - s = openSession(); - s.beginTransaction(); - results = s.createQuery( "select distinct key(h.family) from Human h" ).list(); - assertEquals( 1, results.size() ); - Object key = results.get(0); - assertTrue( String.class.isAssignableFrom( key.getClass() ) ); - s.getTransaction().commit(); - s.close(); + // in SELECT clause + { + // hibernate-only form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select entry(h.family) from Human h" ).list(); + assertEquals( 1, results.size() ); + Object result = results.get(0); + assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) ); + Map.Entry entry = (Map.Entry) result; + assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) ); + assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + { + // jpa form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select ENTRY(f) from Human h join h.family f" ).list(); + assertEquals( 1, results.size() ); + Object result = results.get(0); + assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) ); + Map.Entry entry = (Map.Entry) result; + assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) ); + assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + // not exactly sure of the syntax of ENTRY in the WHERE clause... + s = openSession(); s.beginTransaction(); - results = s.createQuery( "select distinct key(f) from Human h join h.family f" ).list(); - assertEquals( 1, results.size() ); - key = results.get(0); - assertTrue( String.class.isAssignableFrom( key.getClass() ) ); + s.delete( me ); + s.delete( joe ); s.getTransaction().commit(); s.close(); + } + + @Test + @SuppressWarnings( {"unchecked"}) + public void testJPAQLMapValueQualifier() { + Session s = openSession(); + s.beginTransaction(); + Human me = new Human(); + me.setName( new Name( "Steve", null, "Ebersole" ) ); + Human joe = new Human(); + me.setName( new Name( "Joe", null, "Ebersole" ) ); + me.setFamily( new HashMap() ); + me.getFamily().put( "son", joe ); + s.save( me ); + s.save( joe ); + s.getTransaction().commit(); + s.close(); + + // in SELECT clause + { + // hibernate-only form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select value(h.family) from Human h" ).list(); + assertEquals( 1, results.size() ); + Object result = results.get(0); + assertTrue( Human.class.isAssignableFrom( result.getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + { + // jpa form + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select VALUE(f) from Human h join h.family f" ).list(); + assertEquals( 1, results.size() ); + Object result = results.get(0); + assertTrue( Human.class.isAssignableFrom( result.getClass() ) ); + s.getTransaction().commit(); + s.close(); + } + + // in WHERE clause + { + // hibernate-only form + s = openSession(); + s.beginTransaction(); + Long count = (Long) s.createQuery( "select count(*) from Human h where VALUE(h.family) = :joe" ).setParameter( "joe", joe ).uniqueResult(); + // ACTUALLY EXACTLY THE SAME AS: + // select count(*) from Human h where h.family = :joe + assertEquals( (Long)1L, count ); + s.getTransaction().commit(); + s.close(); + } + + { + // jpa form + s = openSession(); + s.beginTransaction(); + Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where value(f) = :joe" ).setParameter( "joe", joe ).uniqueResult(); + // ACTUALLY EXACTLY THE SAME AS: + // select count(*) from Human h join h.family f where f = :joe + assertEquals( (Long)1L, count ); + s.getTransaction().commit(); + s.close(); + } s = openSession(); s.beginTransaction(); diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index 8f3a744085..1fef067054 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -10,4 +10,6 @@ log4j.logger.org.hibernate.tool.hbm2ddl=trace log4j.logger.org.hibernate.testing.cache=debug # SQL Logging - HHH-6833 -log4j.logger.org.hibernate.SQL=debug \ No newline at end of file +log4j.logger.org.hibernate.SQL=debug + +log4j.logger.org.hibernate.hql.internal.ast=debug \ No newline at end of file