diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CollectionPropertyReference.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CollectionPropertyReference.java new file mode 100644 index 0000000000..b947046962 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CollectionPropertyReference.java @@ -0,0 +1,22 @@ +/* + * 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 . + */ +package org.hibernate.hql.internal.ast.tree; + +import org.hibernate.type.Type; + +/** + * Represents a reference to one of the "collection properties". + * + * @see org.hibernate.hql.internal.CollectionProperties + * + * @author Steve Ebersole + */ +public interface CollectionPropertyReference { + Type getType(); + + String[] toColumns(String tableAlias); +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index ec1833c76f..0aeec2f06c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -500,6 +500,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa return elementType.getPropertyMapping( propertyName ); } + public CollectionPropertyReference getCollectionPropertyReference(String propertyName) { + return elementType.getCollectionPropertyReference( propertyName ); + } + public void setFetch(boolean fetch) { this.fetch = fetch; // Fetch can't be used with scroll() or iterate(). 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 446bb56ac6..30d69c2e2e 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 @@ -378,14 +378,15 @@ class FromElementType { // (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 ), - enabledFilters, - propertyMapping.toColumns( tableAlias, path ) - ); - LOG.debugf( "toColumns(%s,%s) : subquery = %s", tableAlias, path, subquery ); - return new String[] {"(" + subquery + ")"}; + + // we also need to account for cases where the property name is a CollectionProperty, but + // is also a property on the element-entity. This is handled inside `#getPropertyMapping` + // already; here just check for propertyMapping being the same as the entity persister. Yes + // this is hacky, but really this is difficult to handle given the current codebase. + if ( persister != propertyMapping ) { + // we want the subquery... + return getCollectionPropertyReference( path ).toColumns( tableAlias ); + } } if ( forceAlias ) { @@ -485,6 +486,18 @@ class FromElementType { // If the property is a special collection property name, return a CollectionPropertyMapping. if ( CollectionProperties.isCollectionProperty( propertyName ) ) { if ( collectionPropertyMapping == null ) { + // lets additionally make sure that the property name is not also the name + // of a property on the element, assuming that the element is an entity. + // todo : also consider composites? + if ( persister != null ) { + try { + if ( persister.getPropertyType( propertyName ) != null ) { + return (PropertyMapping) persister; + } + } + catch (QueryException ignore) { + } + } collectionPropertyMapping = new CollectionPropertyMapping( queryableCollection ); } return collectionPropertyMapping; @@ -524,6 +537,52 @@ class FromElementType { this.indexCollectionSelectorParamSpec = indexCollectionSelectorParamSpec; } + public CollectionPropertyReference getCollectionPropertyReference(final String propertyName) { + if ( queryableCollection == null ) { + throw new QueryException( "Not a collection reference" ); + } + + final PropertyMapping collectionPropertyMapping; + + if ( queryableCollection.isManyToMany() + && queryableCollection.hasIndex() + && SPECIAL_MANY2MANY_TREATMENT_FUNCTION_NAMES.contains( propertyName ) ) { + collectionPropertyMapping = new SpecialManyToManyCollectionPropertyMapping(); + } + else if ( CollectionProperties.isCollectionProperty( propertyName ) ) { + if ( this.collectionPropertyMapping == null ) { + this.collectionPropertyMapping = new CollectionPropertyMapping( queryableCollection ); + } + collectionPropertyMapping = this.collectionPropertyMapping; + } + else { + collectionPropertyMapping = queryableCollection; + } + + return new CollectionPropertyReference() { + @Override + public Type getType() { + return collectionPropertyMapping.toType( propertyName ); + } + + @Override + public String[] toColumns(final String tableAlias) { + if ( propertyName.equalsIgnoreCase( "index" ) ) { + return collectionPropertyMapping.toColumns( tableAlias, propertyName ); + } + + Map enabledFilters = fromElement.getWalker().getEnabledFilters(); + String subquery = CollectionSubqueryFactory.createCollectionSubquery( + joinSequence.copy().setUseThetaStyle( true ), + enabledFilters, + collectionPropertyMapping.toColumns( tableAlias, propertyName ) + ); + LOG.debugf( "toColumns(%s,%s) : subquery = %s", tableAlias, propertyName, subquery ); + return new String[] {"(" + subquery + ")"}; + } + }; + } + private class SpecialManyToManyCollectionPropertyMapping implements PropertyMapping { @Override public Type getType() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java index db2964fc11..97f80d3f73 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java @@ -15,6 +15,7 @@ import org.hibernate.hql.internal.ast.TypeDiscriminatorMetadata; import org.hibernate.hql.internal.ast.util.ASTUtil; import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.internal.CoreLogging; +import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.type.Type; @@ -148,7 +149,12 @@ public class MethodNode extends AbstractSelectExpression implements FunctionNode else { // Not elements(x) fromElement = collectionNode.getFromElement(); - setDataType( fromElement.getPropertyType( propertyName, propertyName ) ); + + final CollectionPropertyReference cpr = fromElement.getCollectionPropertyReference( propertyName ); + setDataType( cpr.getType() ); + selectColumns = cpr.toColumns( fromElement.getTableAlias() ); + +// setDataType( fromElement.getPropertyType( propertyName, propertyName ) ); selectColumns = fromElement.toColumns( fromElement.getTableAlias(), propertyName, inSelect ); } if ( collectionNode instanceof DotNode ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/SizeAttributeReferenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/SizeAttributeReferenceTest.java new file mode 100644 index 0000000000..434b3ac8c2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/SizeAttributeReferenceTest.java @@ -0,0 +1,66 @@ +/* + * 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 . + */ +package org.hibernate.test.hql; + +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Session; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +/** + * Historically HQL allowed syntax like {@code `... where someCollectionPath.size > 1`} where + * size refers to the collection size. However that disallows references to properties named + * size. + * + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-10024" ) +public class SizeAttributeReferenceTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void controlGroup() { + Session session = openSession(); + session.getTransaction().begin(); + session.createQuery( "from EntityWithAttributeNamedSize e join e.children c where size(c) > 1" ).list(); + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testSizeAttributeReference() { + Session session = openSession(); + session.getTransaction().begin(); + session.createQuery( "from EntityWithAttributeNamedSize e join e.children c where c.size = 'abc'" ).list(); + session.getTransaction().commit(); + session.close(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithAttributeNamedSize.class }; + } + + @Entity( name = "EntityWithAttributeNamedSize" ) + @Table( name = "EntityWithAttributeNamedSize" ) + public static class EntityWithAttributeNamedSize { + @Id + public Integer id; + @ManyToOne + public EntityWithAttributeNamedSize parent; + @OneToMany( mappedBy = "parent" ) + public Set children; + private String size; + } + +}