HHH-10024 - CriteriaQuery: Join on a field named 'size' backed by an enum throws IllegalArgumentException

This commit is contained in:
Steve Ebersole 2015-08-31 20:30:15 -05:00
parent 080014558d
commit 24901af4d9
5 changed files with 166 additions and 9 deletions

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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);
}

View File

@ -500,6 +500,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
return elementType.getPropertyMapping( propertyName ); return elementType.getPropertyMapping( propertyName );
} }
public CollectionPropertyReference getCollectionPropertyReference(String propertyName) {
return elementType.getCollectionPropertyReference( propertyName );
}
public void setFetch(boolean fetch) { public void setFetch(boolean fetch) {
this.fetch = fetch; this.fetch = fetch;
// Fetch can't be used with scroll() or iterate(). // Fetch can't be used with scroll() or iterate().

View File

@ -378,14 +378,15 @@ class FromElementType {
// (most likely minElement, maxElement as well) cases. // (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 // 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 the dialect supports select-clause subqueries we could go ahead and generate the subquery also
Map enabledFilters = fromElement.getWalker().getEnabledFilters();
String subquery = CollectionSubqueryFactory.createCollectionSubquery( // we also need to account for cases where the property name is a CollectionProperty, but
joinSequence.copy().setUseThetaStyle( true ), // is also a property on the element-entity. This is handled inside `#getPropertyMapping`
enabledFilters, // already; here just check for propertyMapping being the same as the entity persister. Yes
propertyMapping.toColumns( tableAlias, path ) // this is hacky, but really this is difficult to handle given the current codebase.
); if ( persister != propertyMapping ) {
LOG.debugf( "toColumns(%s,%s) : subquery = %s", tableAlias, path, subquery ); // we want the subquery...
return new String[] {"(" + subquery + ")"}; return getCollectionPropertyReference( path ).toColumns( tableAlias );
}
} }
if ( forceAlias ) { if ( forceAlias ) {
@ -485,6 +486,18 @@ class FromElementType {
// If the property is a special collection property name, return a CollectionPropertyMapping. // If the property is a special collection property name, return a CollectionPropertyMapping.
if ( CollectionProperties.isCollectionProperty( propertyName ) ) { if ( CollectionProperties.isCollectionProperty( propertyName ) ) {
if ( collectionPropertyMapping == null ) { 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 ); collectionPropertyMapping = new CollectionPropertyMapping( queryableCollection );
} }
return collectionPropertyMapping; return collectionPropertyMapping;
@ -524,6 +537,52 @@ class FromElementType {
this.indexCollectionSelectorParamSpec = indexCollectionSelectorParamSpec; 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 { private class SpecialManyToManyCollectionPropertyMapping implements PropertyMapping {
@Override @Override
public Type getType() { public Type getType() {

View File

@ -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.ASTUtil;
import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.persister.collection.CollectionPropertyMapping;
import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.CollectionPropertyNames;
import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -148,7 +149,12 @@ public class MethodNode extends AbstractSelectExpression implements FunctionNode
else { else {
// Not elements(x) // Not elements(x)
fromElement = collectionNode.getFromElement(); 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 ); selectColumns = fromElement.toColumns( fromElement.getTableAlias(), propertyName, inSelect );
} }
if ( collectionNode instanceof DotNode ) { if ( collectionNode instanceof DotNode ) {

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<EntityWithAttributeNamedSize> children;
private String size;
}
}