HHH-3646 - implement Criteria API querying of collection-of-component and collection-of-scalar

The general approach is:

* create an interface called the CriteriaInfoProvider which abstracts
  the operations that are different for the different types of Criteria
  targets.
* change the getPathEntityName method to be a factory method for
  creating the proper implementation of the interface
* change the rest of CriteriaQueryTranslator to use the interface
  instead of using the previous entity-only implementation
* implementations of the interface exist for
  Entity: this implements the same code as currently exists
  ComponentCollection: for collection-of-component
  ScalarCollection: for collection-of-value
  Component: for components
* update the logic in CriteriaJoinWalker which has to be very careful
  about how it works since the walker walks certain property paths twice.
This commit is contained in:
David Mansfield 2011-03-29 14:00:46 -04:00 committed by Steve Ebersole
parent 4ddaaa1deb
commit 9f311a4698
6 changed files with 338 additions and 32 deletions

View File

@ -0,0 +1,84 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Middleware LLC 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 Middleware LLC.
*
* 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.loader.criteria;
import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
/**
* @author David Mansfield
*/
class ComponentCollectionCriteriaInfoProvider implements CriteriaInfoProvider {
QueryableCollection persister;
Map /* <String,Type> */ subTypes = new HashMap /* <String,Type> */();
ComponentCollectionCriteriaInfoProvider(QueryableCollection persister) {
this.persister = persister;
if (!persister.getElementType().isComponentType()) {
throw new IllegalArgumentException("persister for role "+persister.getRole()+" is not a collection-of-component");
}
ComponentType componentType = (ComponentType)persister.getElementType();
String[] names = componentType.getPropertyNames();
Type[] types = componentType.getSubtypes();
for (int i = 0; i < names.length; i++) {
subTypes.put(names[i], types[i]);
}
}
public String getName() {
return persister.getRole();
}
public Serializable[] getSpaces() {
return persister.getCollectionSpaces();
}
public PropertyMapping getPropertyMapping() {
return (PropertyMapping)persister;
}
public Type getType(String relativePath) {
// TODO: can a component have a nested component? then we may need to do something more here...
if (relativePath.indexOf('.') >= 0)
throw new IllegalArgumentException("dotted paths not handled (yet?!) for collection-of-component");
Type type = (Type)subTypes.get(relativePath);
if (type == null)
throw new IllegalArgumentException("property "+relativePath+" not found in component of collection "+getName());
return type;
}
}

View File

@ -0,0 +1,41 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Middleware LLC 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 Middleware LLC.
*
* 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.loader.criteria;
import java.io.Serializable;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.type.Type;
/**
* @author David Mansfield
*/
interface CriteriaInfoProvider {
String getName();
Serializable[] getSpaces();
PropertyMapping getPropertyMapping();
Type getType(String relativePath);
}

View File

@ -40,6 +40,7 @@ import org.hibernate.loader.PropertyPath;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.type.AssociationType;
import org.hibernate.type.Type;
import org.hibernate.internal.util.collections.ArrayHelper;
@ -218,27 +219,48 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker {
}
protected String generateTableAlias(int n, PropertyPath path, Joinable joinable) {
// TODO: deal with side-effects (changes to includeInSelectList, userAliasList, resultTypeList)!!!
if ( joinable.consumesEntityAlias() ) {
// TODO: deal with side-effects (changes to includeInResultRowList, userAliasList, resultTypeList)!!!
// for collection-of-entity, we are called twice for given "path"
// once for the collection Joinable, once for the entity Joinable.
// the second call will/must "consume" the alias + perform side effects according to consumesEntityAlias()
// for collection-of-other, however, there is only one call
// it must "consume" the alias + perform side effects, despite what consumeEntityAlias() return says
//
// note: the logic for adding to the userAliasList is still strictly based on consumesEntityAlias return value
boolean checkForSqlAlias = joinable.consumesEntityAlias();
if ( !checkForSqlAlias && joinable.isCollection() ) {
// is it a collection-of-other (component or value) ?
CollectionPersister collectionPersister = (CollectionPersister)joinable;
Type elementType = collectionPersister.getElementType();
if ( elementType.isComponentType() || !elementType.isEntityType() ) {
checkForSqlAlias = true;
}
}
String sqlAlias = null;
if ( checkForSqlAlias ) {
final Criteria subcriteria = translator.getCriteria( path.getFullPath() );
String sqlAlias = subcriteria==null ? null : translator.getSQLAlias(subcriteria);
if (sqlAlias!=null) {
if ( ! translator.hasProjection() ) {
includeInResultRowList.add( subcriteria.getAlias() != null );
sqlAlias = subcriteria==null ? null : translator.getSQLAlias(subcriteria);
if (joinable.consumesEntityAlias() && ! translator.hasProjection()) {
includeInResultRowList.add( subcriteria != null && subcriteria.getAlias() != null );
if (sqlAlias!=null) {
if ( subcriteria.getAlias() != null ) {
userAliasList.add( subcriteria.getAlias() ); //alias may be null
userAliasList.add( subcriteria.getAlias() );
resultTypeList.add( translator.getResultType( subcriteria ) );
}
}
return sqlAlias; //EARLY EXIT
}
else {
if ( ! translator.hasProjection() ) {
includeInResultRowList.add( false );
}
}
}
return super.generateTableAlias( n + translator.getSQLAliasCount(), path, joinable );
if (sqlAlias == null) {
sqlAlias = super.generateTableAlias( n + translator.getSQLAliasCount(), path, joinable );
}
return sqlAlias;
}
protected String generateRootAlias(String tableName) {

View File

@ -55,6 +55,8 @@ import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.type.Type;
import org.hibernate.internal.util.StringHelper;
@ -73,7 +75,8 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
private final String rootSQLAlias;
private int aliasCount = 0;
private final Map criteriaEntityNames = new LinkedHashMap();
private final Map /* <Criteria, CriteriaInfoProvider> */ criteriaInfoMap = new LinkedHashMap();
private final Map /* <String, CriteriaInfoProvider> */ nameCriteriaInfoMap = new LinkedHashMap();
private final Map criteriaSQLAliasMap = new HashMap();
private final Map aliasCriteriaMap = new HashMap();
private final Map associationPathCriteriaMap = new LinkedHashMap();
@ -81,6 +84,7 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
private final Map withClauseMap = new HashMap();
private final SessionFactoryImplementor sessionFactory;
private final SessionFactoryHelper helper;
public CriteriaQueryTranslator(
final SessionFactoryImplementor factory,
@ -101,6 +105,7 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
this.rootEntityName = rootEntityName;
this.sessionFactory = factory;
this.rootSQLAlias = rootSQLAlias;
this.helper = new SessionFactoryHelper(factory);
createAliasCriteriaMap();
createAssociationPathCriteriaMap();
createCriteriaEntityNameMap();
@ -134,10 +139,10 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
public Set getQuerySpaces() {
Set result = new HashSet();
Iterator iter = criteriaEntityNames.values().iterator();
Iterator iter = criteriaInfoMap.values().iterator();
while ( iter.hasNext() ) {
String entityName = ( String ) iter.next();
result.addAll( Arrays.asList( getFactory().getEntityPersister( entityName ).getQuerySpaces() ) );
CriteriaInfoProvider info = ( CriteriaInfoProvider )iter.next();
result.addAll( Arrays.asList( info.getSpaces() ) );
}
return result;
}
@ -213,29 +218,54 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
}
private void createCriteriaEntityNameMap() {
criteriaEntityNames.put( rootCriteria, rootEntityName );
// initialize the rootProvider first
CriteriaInfoProvider rootProvider = new EntityCriteriaInfoProvider(( Queryable ) sessionFactory.getEntityPersister( rootEntityName ) );
criteriaInfoMap.put( rootCriteria, rootProvider);
nameCriteriaInfoMap.put ( rootProvider.getName(), rootProvider );
Iterator iter = associationPathCriteriaMap.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = ( Map.Entry ) iter.next();
criteriaEntityNames.put(
CriteriaInfoProvider info = getPathInfo((String)me.getKey());
criteriaInfoMap.put(
me.getValue(), //the criteria instance
getPathEntityName( ( String ) me.getKey() )
info
);
nameCriteriaInfoMap.put( info.getName(), info );
}
}
private String getPathEntityName(String path) {
Queryable persister = ( Queryable ) sessionFactory.getEntityPersister( rootEntityName );
private CriteriaInfoProvider getPathInfo(String path) {
StringTokenizer tokens = new StringTokenizer( path, "." );
String componentPath = "";
// start with the 'rootProvider'
CriteriaInfoProvider provider = ( CriteriaInfoProvider )nameCriteriaInfoMap.get( rootEntityName );
while ( tokens.hasMoreTokens() ) {
componentPath += tokens.nextToken();
Type type = persister.toType( componentPath );
Type type = provider.getType( componentPath );
if ( type.isAssociationType() ) {
// CollectionTypes are always also AssociationTypes - but there's not always an associated entity...
AssociationType atype = ( AssociationType ) type;
persister = ( Queryable ) sessionFactory.getEntityPersister(
atype.getAssociatedEntityName( sessionFactory )
);
CollectionType ctype = type.isCollectionType() ? (CollectionType)type : null;
Type elementType = (ctype != null) ? ctype.getElementType( sessionFactory ) : null;
// is the association a collection of components or value-types? (i.e a colloction of valued types?)
if ( ctype != null && elementType.isComponentType() ) {
provider = new ComponentCollectionCriteriaInfoProvider( helper.getCollectionPersister(ctype.getRole()) );
}
else if ( ctype != null && !elementType.isEntityType() ) {
provider = new ScalarCollectionCriteriaInfoProvider( helper, ctype.getRole() );
}
else {
provider = new EntityCriteriaInfoProvider(( Queryable ) sessionFactory.getEntityPersister(
atype.getAssociatedEntityName( sessionFactory )
));
}
componentPath = "";
}
else if ( type.isComponentType() ) {
@ -245,7 +275,8 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
throw new QueryException( "not an association: " + componentPath );
}
}
return persister.getEntityName();
return provider;
}
public int getSQLAliasCount() {
@ -254,13 +285,13 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
private void createCriteriaSQLAliasMap() {
int i = 0;
Iterator criteriaIterator = criteriaEntityNames.entrySet().iterator();
Iterator criteriaIterator = criteriaInfoMap.entrySet().iterator();
while ( criteriaIterator.hasNext() ) {
Map.Entry me = ( Map.Entry ) criteriaIterator.next();
Criteria crit = ( Criteria ) me.getKey();
String alias = crit.getAlias();
if ( alias == null ) {
alias = ( String ) me.getValue(); // the entity name
alias = (( CriteriaInfoProvider ) me.getValue()).getName(); // the entity name
}
criteriaSQLAliasMap.put( crit, StringHelper.generateAlias( alias, i++ ) );
}
@ -411,7 +442,7 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
}
public String getEntityName(Criteria criteria) {
return ( String ) criteriaEntityNames.get( criteria );
return (( CriteriaInfoProvider ) criteriaInfoMap.get( criteria )).getName();
}
public String getColumn(Criteria criteria, String propertyName) {
@ -596,7 +627,8 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
private PropertyMapping getPropertyMapping(String entityName)
throws MappingException {
return ( PropertyMapping ) sessionFactory.getEntityPersister( entityName );
CriteriaInfoProvider info = ( CriteriaInfoProvider )nameCriteriaInfoMap.get(entityName);
return info.getPropertyMapping();
}
//TODO: use these in methods above

View File

@ -0,0 +1,59 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Middleware LLC 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 Middleware LLC.
*
* 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.loader.criteria;
import java.io.Serializable;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.type.Type;
/**
* @author David Mansfield
*/
class EntityCriteriaInfoProvider implements CriteriaInfoProvider {
Queryable persister;
EntityCriteriaInfoProvider(Queryable persister) {
this.persister = persister;
}
public String getName() {
return persister.getEntityName();
}
public Serializable[] getSpaces() {
return persister.getQuerySpaces();
}
public PropertyMapping getPropertyMapping() {
return (PropertyMapping)persister;
}
public Type getType(String relativePath) {
return persister.toType(relativePath);
}
}

View File

@ -0,0 +1,68 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Middleware LLC 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 Middleware LLC.
*
* 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.loader.criteria;
import java.io.Serializable;
import org.hibernate.hql.ast.util.SessionFactoryHelper;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
/**
* @author David Mansfield
*/
class ScalarCollectionCriteriaInfoProvider implements CriteriaInfoProvider {
String role;
QueryableCollection persister;
SessionFactoryHelper helper;
ScalarCollectionCriteriaInfoProvider(SessionFactoryHelper helper, String role) {
this.role = role;
this.helper = helper;
this.persister = helper.requireQueryableCollection(role);
}
public String getName() {
return role;
}
public Serializable[] getSpaces() {
return persister.getCollectionSpaces();
}
public PropertyMapping getPropertyMapping() {
return helper.getCollectionPropertyMapping(role);
}
public Type getType(String relativePath) {
//not sure what things are going to be passed here, how about 'id', maybe 'index' or 'key' or 'elements' ???
// todo: wtf!
return getPropertyMapping().toType(relativePath);
}
}