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:
parent
4ddaaa1deb
commit
9f311a4698
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue