diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java index e9eab1f153..f664920f4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -34,7 +36,7 @@ import org.hibernate.type.Type; /** - * TODO : javadoc + * Basic support for KEY, VALUE and ENTRY based "qualified identification variables". * * @author Steve Ebersole */ @@ -49,10 +51,12 @@ public String[] getColumns() { return columns; } + @Override public void setScalarColumnText(int i) throws SemanticException { ColumnHelper.generateScalarColumns( this, getColumns(), i ); } + @Override public void resolve( boolean generateJoin, boolean implicitJoin, @@ -64,20 +68,36 @@ public void resolve( FromReferenceNode mapReference = getMapReference(); mapReference.resolve( true, true ); - if ( mapReference.getDataType().isCollectionType() ) { - CollectionType collectionType = (CollectionType) mapReference.getDataType(); - if ( Map.class.isAssignableFrom( collectionType.getReturnedClass() ) ) { - FromElement sourceFromElement = mapReference.getFromElement(); - setFromElement( sourceFromElement ); - setDataType( resolveType( sourceFromElement.getQueryableCollection() ) ); - this.columns = resolveColumns( sourceFromElement.getQueryableCollection() ); - initText( this.columns ); - setFirstChild( null ); - return; + + FromElement sourceFromElement = null; + if ( isAliasRef( mapReference ) ) { + QueryableCollection collectionPersister = mapReference.getFromElement().getQueryableCollection(); + if ( Map.class.isAssignableFrom( collectionPersister.getCollectionType().getReturnedClass() ) ) { + sourceFromElement = mapReference.getFromElement(); + } + } + else { + if ( mapReference.getDataType().isCollectionType() ) { + CollectionType collectionType = (CollectionType) mapReference.getDataType(); + if ( Map.class.isAssignableFrom( collectionType.getReturnedClass() ) ) { + sourceFromElement = mapReference.getFromElement(); + } } } - throw nonMap(); + if ( sourceFromElement == null ) { + throw nonMap(); + } + + setFromElement( sourceFromElement ); + setDataType( resolveType( sourceFromElement.getQueryableCollection() ) ); + this.columns = resolveColumns( sourceFromElement.getQueryableCollection() ); + initText( this.columns ); + setFirstChild( null ); + } + + private boolean isAliasRef(FromReferenceNode mapReference) { + return ALIAS_REF == mapReference.getType(); } private void initText(String[] columns) { @@ -100,6 +120,7 @@ protected SemanticException nonMap() { return new SemanticException( expressionDescription() + " expression did not reference map property" ); } + @Override public void resolveIndex(AST parent) throws SemanticException { throw new UnsupportedOperationException( expressionDescription() + " expression cannot be the source for an index operation" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java index d795db0d8d..223ed20588 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.hql.internal.ast.tree; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -41,7 +44,7 @@ import org.hibernate.type.Type; /** - * TODO : javadoc + * Tree node representing reference to the entry ({@link Map.Entry}) of a Map association. * * @author Steve Ebersole */ @@ -61,6 +64,7 @@ public String generateAlias(String sqlExpression) { private int scalarColumnIndex = -1; + @Override protected String expressionDescription() { return "entry(*)"; } @@ -70,6 +74,8 @@ public Class getAggregationResultType() { return Map.Entry.class; } + @Override + @SuppressWarnings("unchecked") protected Type resolveType(QueryableCollection collectionPersister) { Type keyType = collectionPersister.getIndexType(); Type valueType = collectionPersister.getElementType(); @@ -81,6 +87,7 @@ protected Type resolveType(QueryableCollection collectionPersister) { return null; } + @Override protected String[] resolveColumns(QueryableCollection collectionPersister) { List selections = new ArrayList(); determineKeySelectExpressions( collectionPersister, selections ); @@ -118,8 +125,9 @@ private void determineKeySelectExpressions(QueryableCollection collectionPersist } } + @SuppressWarnings({"unchecked", "ForLoopReplaceableByForEach"}) private void appendSelectExpressions(String[] columnNames, List selections, AliasGenerator aliasGenerator) { - for ( int i = 0; i < columnNames.length; i++ ) { + for ( int i = 0; i < columnNames.length; i++ ) { selections.add( new BasicSelectExpression( collectionTableAlias() + '.' + columnNames[i], @@ -129,6 +137,7 @@ private void appendSelectExpressions(String[] columnNames, List selections, Alia } } + @SuppressWarnings({"unchecked", "WhileLoopReplaceableByForEach"}) private void appendSelectExpressions(SelectFragment fragment, List selections, AliasGenerator aliasGenerator) { Iterator itr = fragment.getColumns().iterator(); while ( itr.hasNext() ) { @@ -176,10 +185,12 @@ private BasicSelectExpression(String expression, String alias) { this.alias = alias; } + @Override public String getExpression() { return expression; } + @Override public String getAlias() { return alias; } @@ -189,6 +200,7 @@ public SessionFactoryImplementor sfi() { return getSessionFactoryHelper().getFactory(); } + @Override public void setText(String s) { if ( isResolved() ) { return; @@ -196,17 +208,21 @@ public void setText(String s) { super.setText( s ); } + @Override public void setScalarColumn(int i) throws SemanticException { this.scalarColumnIndex = i; } + @Override public int getScalarColumnIndex() { return scalarColumnIndex; } + @Override public void setScalarColumnText(int i) throws SemanticException { } + @Override public boolean isScalar() { // Constructors are always considered scalar results. return true; @@ -214,23 +230,27 @@ public boolean isScalar() { private List types = new ArrayList(4); // size=4 to prevent resizing + @Override public List getAggregatedSelectionTypeList() { return types; } private static final String[] ALIASES = { null, null }; + @Override public String[] getAggregatedAliases() { return ALIASES; } private MapEntryBuilder mapEntryBuilder; + @Override public ResultTransformer getResultTransformer() { return mapEntryBuilder; } private static class MapEntryBuilder extends BasicTransformerAdapter { + @Override public Object transformTuple(Object[] tuple, String[] aliases) { if ( tuple.length != 2 ) { throw new HibernateException( "Expecting exactly 2 tuples to transform into Map.Entry" ); @@ -248,20 +268,24 @@ private EntryAdapter(Object key, Object value) { this.value = value; } + @Override public Object getValue() { return value; } + @Override public Object getKey() { return key; } + @Override public Object setValue(Object value) { Object old = this.value; this.value = value; return old; } + @Override public boolean equals(Object o) { // IMPL NOTE : nulls are considered equal for keys and values according to Map.Entry contract if ( this == o ) { @@ -278,6 +302,7 @@ public boolean equals(Object o) { } + @Override public int hashCode() { int keyHash = key == null ? 0 : key.hashCode(); int valueHash = value == null ? 0 : value.hashCode(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java index 966af6d391..e77692d179 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,23 +22,27 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.hql.internal.ast.tree; + import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.type.Type; /** - * TODO : javadoc + * Tree node representing reference to the key of a Map association. * * @author Steve Ebersole */ public class MapKeyNode extends AbstractMapComponentNode { + @Override protected String expressionDescription() { return "key(*)"; } + @Override protected String[] resolveColumns(QueryableCollection collectionPersister) { return collectionPersister.getIndexColumnNames(); } + @Override protected Type resolveType(QueryableCollection collectionPersister) { return collectionPersister.getIndexType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java index d674137504..fd2b808a7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapValueNode.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,23 +22,27 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.hql.internal.ast.tree; + import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.type.Type; /** - * TODO : javadoc + * Tree node representing reference to the value of a Map association. * * @author Steve Ebersole */ public class MapValueNode extends AbstractMapComponentNode { + @Override protected String expressionDescription() { return "value(*)"; } + @Override protected String[] resolveColumns(QueryableCollection collectionPersister) { return collectionPersister.getElementColumnNames(); } + @Override protected Type resolveType(QueryableCollection collectionPersister) { return collectionPersister.getElementType(); } diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java index f7484f71e1..f2bf117e67 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -276,6 +276,18 @@ public void testJPAQLQualifiedIdentificationVariables() { s.getTransaction().commit(); s.close(); + s = openSession(); + s.beginTransaction(); + results = s.createQuery( "select entry(f) from Human h from h.family f" ).list(); + assertEquals( 1, results.size() ); + result = results.get(0); + assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) ); + entry = (Map.Entry) result; + assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) ); + assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) ); + s.getTransaction().commit(); + s.close(); + s = openSession(); s.beginTransaction(); results = s.createQuery( "select distinct key(h.family) from Human h" ).list(); @@ -285,6 +297,15 @@ public void testJPAQLQualifiedIdentificationVariables() { s.getTransaction().commit(); s.close(); + s = openSession(); + s.beginTransaction(); + results = s.createQuery( "select distinct key(f) from Human h join h.family f" ).list(); + assertEquals( 1, results.size() ); + key = results.get(0); + assertTrue( String.class.isAssignableFrom( key.getClass() ) ); + s.getTransaction().commit(); + s.close(); + s = openSession(); s.beginTransaction(); s.delete( me );