HHH-4879 - Support HQL index-refering functions for many-to-many, indexed collections
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18687 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
c86d471453
commit
46bc41c226
|
@ -25,6 +25,7 @@
|
|||
package org.hibernate.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -66,7 +67,7 @@ public class JoinSequence {
|
|||
return buf.append( '}' ).toString();
|
||||
}
|
||||
|
||||
final class Join {
|
||||
public final class Join {
|
||||
|
||||
private final AssociationType associationType;
|
||||
private final Joinable joinable;
|
||||
|
@ -83,23 +84,23 @@ public class JoinSequence {
|
|||
this.lhsColumns = lhsColumns;
|
||||
}
|
||||
|
||||
String getAlias() {
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
AssociationType getAssociationType() {
|
||||
public AssociationType getAssociationType() {
|
||||
return associationType;
|
||||
}
|
||||
|
||||
Joinable getJoinable() {
|
||||
public Joinable getJoinable() {
|
||||
return joinable;
|
||||
}
|
||||
|
||||
int getJoinType() {
|
||||
public int getJoinType() {
|
||||
return joinType;
|
||||
}
|
||||
|
||||
String[] getLHSColumns() {
|
||||
public String[] getLHSColumns() {
|
||||
return lhsColumns;
|
||||
}
|
||||
|
||||
|
@ -283,7 +284,15 @@ public class JoinSequence {
|
|||
public int getJoinCount() {
|
||||
return joins.size();
|
||||
}
|
||||
|
||||
|
||||
public Iterator iterateJoins() {
|
||||
return joins.iterator();
|
||||
}
|
||||
|
||||
public Join getFirstJoin() {
|
||||
return (Join) joins.get( 0 );
|
||||
}
|
||||
|
||||
public static interface Selector {
|
||||
public boolean includeSubclasses(String alias);
|
||||
}
|
||||
|
|
|
@ -24,11 +24,15 @@
|
|||
*/
|
||||
package org.hibernate.hql.ast.tree;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.param.ParameterSpecification;
|
||||
import org.hibernate.persister.collection.CollectionPropertyNames;
|
||||
import org.hibernate.type.CollectionType;
|
||||
import org.hibernate.util.ArrayHelper;
|
||||
import org.hibernate.engine.JoinSequence;
|
||||
import org.hibernate.hql.CollectionProperties;
|
||||
|
@ -324,10 +328,16 @@ class FromElementType {
|
|||
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 ) ) {
|
||||
Map enabledFilters = fromElement.getWalker().getEnabledFilters();
|
||||
String subquery = CollectionSubqueryFactory.createCollectionSubquery(
|
||||
joinSequence,
|
||||
joinSequence.copy().setUseThetaStyle( true ),
|
||||
enabledFilters,
|
||||
propertyMapping.toColumns( tableAlias, path )
|
||||
);
|
||||
|
@ -397,11 +407,29 @@ class FromElementType {
|
|||
return fromElement.getQueryable().getTableName();
|
||||
}
|
||||
|
||||
private static final List SPECIAL_MANY2MANY_TREATMENT_FUNCTION_NAMES = java.util.Arrays.asList(
|
||||
new String[] {
|
||||
CollectionPropertyNames.COLLECTION_INDEX,
|
||||
CollectionPropertyNames.COLLECTION_MIN_INDEX,
|
||||
CollectionPropertyNames.COLLECTION_MAX_INDEX
|
||||
}
|
||||
);
|
||||
|
||||
PropertyMapping getPropertyMapping(String propertyName) {
|
||||
checkInitialized();
|
||||
if ( queryableCollection == null ) { // Not a collection?
|
||||
return ( PropertyMapping ) persister; // Return the entity property mapping.
|
||||
}
|
||||
|
||||
// indexed, many-to-many collections must be treated specially here if the property to
|
||||
// be mapped touches on the index as we must adjust the alias to use the alias from
|
||||
// the association table (which i different than the one passed in
|
||||
if ( queryableCollection.isManyToMany()
|
||||
&& queryableCollection.hasIndex()
|
||||
&& SPECIAL_MANY2MANY_TREATMENT_FUNCTION_NAMES.contains( propertyName ) ) {
|
||||
return new SpecialManyToManyCollectionPropertyMapping();
|
||||
}
|
||||
|
||||
// If the property is a special collection property name, return a CollectionPropertyMapping.
|
||||
if ( CollectionProperties.isCollectionProperty( propertyName ) ) {
|
||||
if ( collectionPropertyMapping == null ) {
|
||||
|
@ -409,12 +437,14 @@ class FromElementType {
|
|||
}
|
||||
return collectionPropertyMapping;
|
||||
}
|
||||
|
||||
if ( queryableCollection.getElementType().isAnyType() ) {
|
||||
// collection of <many-to-any/> mappings...
|
||||
// used to circumvent the component-collection check below...
|
||||
return queryableCollection;
|
||||
|
||||
}
|
||||
|
||||
if ( queryableCollection.getElementType().isComponentType() ) {
|
||||
// Collection of components.
|
||||
if ( propertyName.equals( EntityPersister.ENTITY_ID ) ) {
|
||||
|
@ -425,17 +455,9 @@ class FromElementType {
|
|||
}
|
||||
|
||||
public boolean isCollectionOfValuesOrComponents() {
|
||||
if ( persister == null ) {
|
||||
if ( queryableCollection == null ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return !queryableCollection.getElementType().isEntityType();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return persister == null
|
||||
&& queryableCollection != null
|
||||
&& !queryableCollection.getElementType().isEntityType();
|
||||
}
|
||||
|
||||
public boolean isEntity() {
|
||||
|
@ -449,4 +471,62 @@ class FromElementType {
|
|||
public void setIndexCollectionSelectorParamSpec(ParameterSpecification indexCollectionSelectorParamSpec) {
|
||||
this.indexCollectionSelectorParamSpec = indexCollectionSelectorParamSpec;
|
||||
}
|
||||
|
||||
private class SpecialManyToManyCollectionPropertyMapping implements PropertyMapping {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Type getType() {
|
||||
return queryableCollection.getCollectionType();
|
||||
}
|
||||
|
||||
private void validate(String propertyName) {
|
||||
if ( ! ( CollectionPropertyNames.COLLECTION_INDEX.equals( propertyName )
|
||||
|| CollectionPropertyNames.COLLECTION_MAX_INDEX.equals( propertyName )
|
||||
|| CollectionPropertyNames.COLLECTION_MIN_INDEX.equals( propertyName ) ) ) {
|
||||
throw new IllegalArgumentException( "Expecting index-related function call" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Type toType(String propertyName) throws QueryException {
|
||||
validate( propertyName );
|
||||
return queryableCollection.getIndexType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String[] toColumns(String alias, String propertyName) throws QueryException {
|
||||
validate( propertyName );
|
||||
final String joinTableAlias = joinSequence.getFirstJoin().getAlias();
|
||||
if ( CollectionPropertyNames.COLLECTION_INDEX.equals( propertyName ) ) {
|
||||
return queryableCollection.toColumns( joinTableAlias, propertyName );
|
||||
}
|
||||
|
||||
final String[] cols = queryableCollection.getIndexColumnNames( joinTableAlias );
|
||||
if ( CollectionPropertyNames.COLLECTION_MIN_INDEX.equals( propertyName ) ) {
|
||||
if ( cols.length != 1 ) {
|
||||
throw new QueryException( "composite collection index in minIndex()" );
|
||||
}
|
||||
return new String[] { "min(" + cols[0] + ')' };
|
||||
}
|
||||
else {
|
||||
if ( cols.length != 1 ) {
|
||||
throw new QueryException( "composite collection index in maxIndex()" );
|
||||
}
|
||||
return new String[] { "max(" + cols[0] + ')' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String[] toColumns(String propertyName) throws QueryException, UnsupportedOperationException {
|
||||
validate( propertyName );
|
||||
return queryableCollection.toColumns( propertyName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1510,27 +1510,33 @@ public abstract class AbstractCollectionPersister
|
|||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String[] toColumns(String alias, String propertyName)
|
||||
throws QueryException {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String[] toColumns(String alias, String propertyName) throws QueryException {
|
||||
if ( "index".equals( propertyName ) ) {
|
||||
if ( isManyToMany() ) {
|
||||
throw new QueryException( "index() function not supported for many-to-many association" );
|
||||
}
|
||||
return StringHelper.qualify( alias, indexColumnNames );
|
||||
return qualify( alias, indexColumnNames, indexFormulaTemplates );
|
||||
}
|
||||
|
||||
return elementPropertyMapping.toColumns( alias, propertyName );
|
||||
}
|
||||
|
||||
public String[] toColumns(String propertyName)
|
||||
throws QueryException {
|
||||
private String[] indexFragments;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String[] toColumns(String propertyName) throws QueryException {
|
||||
if ( "index".equals( propertyName ) ) {
|
||||
if ( isManyToMany() ) {
|
||||
throw new QueryException( "index() function not supported for many-to-many association" );
|
||||
if ( indexFragments == null ) {
|
||||
String[] tmp = new String[indexColumnNames.length];
|
||||
for ( int i = 0; i < indexColumnNames.length; i++ ) {
|
||||
tmp[i] = indexColumnNames[i] == null
|
||||
? indexFormulas[i]
|
||||
: indexColumnNames[i];
|
||||
indexFragments = tmp;
|
||||
}
|
||||
}
|
||||
return indexColumnNames;
|
||||
return indexFragments;
|
||||
}
|
||||
|
||||
return elementPropertyMapping.toColumns( propertyName );
|
||||
|
|
|
@ -27,7 +27,20 @@ public class MapIndexFormulaTest extends FunctionalTestCase {
|
|||
public static Test suite() {
|
||||
return new FunctionalTestClassTestSuite( MapIndexFormulaTest.class );
|
||||
}
|
||||
|
||||
|
||||
public void testIndexFunctionOnManyToManyMap() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
s.createQuery( "from Group g join g.users u where g.name = 'something' and index(u) = 'nada'" )
|
||||
.list();
|
||||
s.createQuery( "from Group g join g.users u where g.name = 'something' and minindex(u) = 'nada'" )
|
||||
.list();
|
||||
s.createQuery( "from Group g join g.users u where g.name = 'something' and maxindex(u) = 'nada'" )
|
||||
.list();
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
public void testIndexFormulaMap() {
|
||||
Session s = openSession();
|
||||
Transaction t = s.beginTransaction();
|
||||
|
|
|
@ -112,5 +112,17 @@ public class TernaryTest extends FunctionalTestCase {
|
|||
s.close();
|
||||
}
|
||||
|
||||
public void testIndexRelatedFunctions() {
|
||||
Session session = openSession();
|
||||
session.beginTransaction();
|
||||
session.createQuery( "from Employee e join e.managerBySite as m where index(m) is not null" )
|
||||
.list();
|
||||
session.createQuery( "from Employee e join e.managerBySite as m where minIndex(m) is not null" )
|
||||
.list();
|
||||
session.createQuery( "from Employee e join e.managerBySite as m where maxIndex(m) is not null" )
|
||||
.list();
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue