Skip to content

Commit 634707c

Browse files
committed
HHH-10125 - KEY() function in HQL causes inaccurate SQL when map key is an entity;
HHH-10132 - ENTRY() function in HQL causes invalid SQL when map key is an entity (cherry picked from commit 3cdc447)
1 parent 5cbefff commit 634707c

File tree

8 files changed

+371
-43
lines changed

8 files changed

+371
-43
lines changed

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* @author Steve Ebersole
2525
*/
2626
public abstract class AbstractMapComponentNode extends FromReferenceNode implements HqlSqlTokenTypes {
27+
private FromElement mapFromElement;
2728
private String[] columns;
2829

2930
public FromReferenceNode getMapReference() {
@@ -72,13 +73,19 @@ public void resolve(
7273
throw nonMap();
7374
}
7475

76+
mapFromElement = sourceFromElement;
77+
7578
setFromElement( sourceFromElement );
7679
setDataType( resolveType( sourceFromElement.getQueryableCollection() ) );
7780
this.columns = resolveColumns( sourceFromElement.getQueryableCollection() );
7881
initText( this.columns );
7982
setFirstChild( null );
8083
}
8184

85+
public FromElement getMapFromElement() {
86+
return mapFromElement;
87+
}
88+
8289
private boolean isAliasRef(FromReferenceNode mapReference) {
8390
return ALIAS_REF == mapReference.getType();
8491
}
@@ -107,4 +114,19 @@ protected SemanticException nonMap() {
107114
public void resolveIndex(AST parent) throws SemanticException {
108115
throw new UnsupportedOperationException( expressionDescription() + " expression cannot be the source for an index operation" );
109116
}
117+
118+
protected MapKeyEntityFromElement findOrAddMapKeyEntityFromElement(QueryableCollection collectionPersister) {
119+
if ( !collectionPersister.getIndexType().isEntityType() ) {
120+
return null;
121+
}
122+
123+
124+
for ( FromElement destination : getFromElement().getDestinations() ) {
125+
if ( destination instanceof MapKeyEntityFromElement ) {
126+
return (MapKeyEntityFromElement) destination;
127+
}
128+
}
129+
130+
return MapKeyEntityFromElement.buildKeyJoin( getFromElement() );
131+
}
110132
}

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.hql.internal.ast.tree;
88

99
import java.util.ArrayList;
10+
import java.util.Collections;
1011
import java.util.LinkedList;
1112
import java.util.List;
1213
import java.util.Set;
@@ -64,7 +65,7 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
6465
private boolean initialized;
6566
private FromElementType elementType;
6667
private boolean useWhereFragment = true;
67-
private List destinations = new LinkedList();
68+
private List<FromElement> destinations;
6869
private boolean manyToMany;
6970
private String withClauseFragment;
7071
private String withClauseJoinAlias;
@@ -218,6 +219,14 @@ String renderPropertySelect(int size, int k) {
218219
return elementType.renderPropertySelect( size, k, isAllPropertyFetch );
219220
}
220221

222+
public String renderMapKeyPropertySelectFragment(int size, int k) {
223+
return elementType.renderMapKeyPropertySelectFragment( size, k );
224+
}
225+
226+
public String renderMapEntryPropertySelectFragment(int size, int k) {
227+
return elementType.renderMapEntryPropertySelectFragment( size, k );
228+
}
229+
221230
String renderCollectionSelectFragment(int size, int k) {
222231
return elementType.renderCollectionSelectFragment( size, k );
223232
}
@@ -415,11 +424,19 @@ public boolean isManyToMany() {
415424
}
416425

417426
private void addDestination(FromElement fromElement) {
427+
if ( destinations == null ) {
428+
destinations = new LinkedList<FromElement>();
429+
}
418430
destinations.add( fromElement );
419431
}
420432

421-
public List getDestinations() {
422-
return destinations;
433+
public List<FromElement> getDestinations() {
434+
if ( destinations == null ) {
435+
return Collections.emptyList();
436+
}
437+
else {
438+
return destinations;
439+
}
423440
}
424441

425442
public FromElement getOrigin() {

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import org.hibernate.MappingException;
1515
import org.hibernate.QueryException;
1616
import org.hibernate.engine.internal.JoinSequence;
17+
import org.hibernate.engine.spi.SessionFactoryImplementor;
1718
import org.hibernate.hql.internal.CollectionProperties;
1819
import org.hibernate.hql.internal.CollectionSubqueryFactory;
1920
import org.hibernate.hql.internal.NameGenerator;
2021
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
22+
import org.hibernate.hql.internal.ast.HqlSqlWalker;
23+
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
2124
import org.hibernate.internal.CoreLogging;
2225
import org.hibernate.internal.CoreMessageLogger;
2326
import org.hibernate.internal.log.DeprecationLogger;
@@ -215,6 +218,46 @@ String renderPropertySelect(int size, int k, boolean allProperties) {
215218
}
216219
}
217220

221+
public String renderMapKeyPropertySelectFragment(int size, int k) {
222+
if ( persister == null ) {
223+
throw new IllegalStateException( "Unexpected state in call to renderMapKeyPropertySelectFragment" );
224+
}
225+
226+
final String fragment = ( (Queryable) persister ).propertySelectFragment(
227+
getTableAlias(),
228+
getSuffix( size, k ),
229+
false
230+
);
231+
return trimLeadingCommaAndSpaces( fragment );
232+
233+
// if ( queryableCollection == null
234+
// || !Map.class.isAssignableFrom( queryableCollection.getCollectionType().getReturnedClass() ) ) {
235+
// throw new IllegalStateException( "Illegal call to renderMapKeyPropertySelectFragment() when FromElement is not a Map" );
236+
// }
237+
//
238+
// if ( !queryableCollection.getIndexType().isEntityType() ) {
239+
// return null;
240+
// }
241+
//
242+
// final HqlSqlWalker walker = fromElement.getWalker();
243+
// final SessionFactoryHelper sfh = walker.getSessionFactoryHelper();
244+
// final SessionFactoryImplementor sf = sfh.getFactory();
245+
//
246+
// final EntityType indexEntityType = (EntityType) queryableCollection.getIndexType();
247+
// final EntityPersister indexEntityPersister = (EntityPersister) indexEntityType.getAssociatedJoinable( sf );
248+
//
249+
// final String fragment = ( (Queryable) indexEntityPersister ).propertySelectFragment(
250+
// getTableAlias(),
251+
// getSuffix( size, k ),
252+
// false
253+
// );
254+
// return trimLeadingCommaAndSpaces( fragment );
255+
}
256+
257+
public String renderMapEntryPropertySelectFragment(int size, int k) {
258+
return null;
259+
}
260+
218261
String renderCollectionSelectFragment(int size, int k) {
219262
if ( queryableCollection == null ) {
220263
return "";

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,11 @@ private void determineKeySelectExpressions(QueryableCollection collectionPersist
9595
AliasGenerator aliasGenerator = new LocalAliasGenerator( 0 );
9696
appendSelectExpressions( collectionPersister.getIndexColumnNames(), selections, aliasGenerator );
9797
Type keyType = collectionPersister.getIndexType();
98-
if ( keyType.isAssociationType() ) {
99-
EntityType entityType = (EntityType) keyType;
100-
Queryable keyEntityPersister = (Queryable) sfi().getEntityPersister(
101-
entityType.getAssociatedEntityName( sfi() )
102-
);
98+
if ( keyType.isEntityType() ) {
99+
MapKeyEntityFromElement mapKeyEntityFromElement = findOrAddMapKeyEntityFromElement( collectionPersister );
100+
Queryable keyEntityPersister = mapKeyEntityFromElement.getQueryable();
103101
SelectFragment fragment = keyEntityPersister.propertySelectFragmentFragment(
104-
collectionTableAlias(),
102+
mapKeyEntityFromElement.getTableAlias(),
105103
null,
106104
false
107105
);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.hql.internal.ast.tree;
8+
9+
import org.hibernate.engine.internal.JoinSequence;
10+
import org.hibernate.engine.spi.SessionFactoryImplementor;
11+
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
12+
import org.hibernate.hql.internal.ast.HqlSqlWalker;
13+
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
14+
import org.hibernate.persister.collection.QueryableCollection;
15+
import org.hibernate.persister.entity.EntityPersister;
16+
import org.hibernate.persister.entity.Joinable;
17+
import org.hibernate.sql.JoinType;
18+
import org.hibernate.type.EntityType;
19+
import org.hibernate.type.Type;
20+
21+
/**
22+
* @author Steve Ebersole
23+
*/
24+
public class MapKeyEntityFromElement extends FromElement {
25+
private final boolean useThetaJoin;
26+
27+
public MapKeyEntityFromElement(boolean useThetaJoin) {
28+
super();
29+
this.useThetaJoin = useThetaJoin;
30+
}
31+
32+
@Override
33+
public boolean isImplied() {
34+
return useThetaJoin;
35+
}
36+
37+
@Override
38+
public int getType() {
39+
return useThetaJoin ? HqlSqlTokenTypes.FROM_FRAGMENT : HqlSqlTokenTypes.JOIN_FRAGMENT;
40+
}
41+
42+
public static MapKeyEntityFromElement buildKeyJoin(FromElement collectionFromElement) {
43+
final HqlSqlWalker walker = collectionFromElement.getWalker();
44+
final SessionFactoryHelper sfh = walker.getSessionFactoryHelper();
45+
final SessionFactoryImplementor sf = sfh.getFactory();
46+
47+
final QueryableCollection collectionPersister = collectionFromElement.getQueryableCollection();
48+
final Type indexType = collectionPersister.getIndexType();
49+
if ( indexType == null ) {
50+
throw new IllegalArgumentException( "Given collection is not indexed" );
51+
}
52+
if ( !indexType.isEntityType() ) {
53+
throw new IllegalArgumentException( "Given collection does not have an entity index" );
54+
}
55+
56+
final EntityType indexEntityType = (EntityType) indexType;
57+
final EntityPersister indexEntityPersister = (EntityPersister) indexEntityType.getAssociatedJoinable( sf );
58+
59+
final String rhsAlias = walker.getAliasGenerator().createName( indexEntityPersister.getEntityName() );
60+
final boolean useThetaJoin = collectionFromElement.getJoinSequence().isThetaStyle();
61+
62+
MapKeyEntityFromElement join = new MapKeyEntityFromElement( useThetaJoin );
63+
join.initialize( HqlSqlTokenTypes.JOIN_FRAGMENT, ( (Joinable) indexEntityPersister ).getTableName() );
64+
join.initialize( collectionFromElement.getWalker() );
65+
66+
join.initializeEntity(
67+
collectionFromElement.getFromClause(),
68+
indexEntityPersister.getEntityName(),
69+
indexEntityPersister,
70+
indexEntityType,
71+
"<map-key-join-" + collectionFromElement.getClassAlias() + ">",
72+
rhsAlias
73+
);
74+
75+
// String[] joinColumns = determineJoinColuns( collectionPersister, joinTableAlias );
76+
// todo : assumes columns, no formulas
77+
String[] joinColumns = collectionPersister.getIndexColumnNames( collectionFromElement.getCollectionTableAlias() );
78+
79+
JoinSequence joinSequence = sfh.createJoinSequence(
80+
useThetaJoin,
81+
indexEntityType,
82+
rhsAlias,
83+
// todo : ever a time when INNER is appropriate?
84+
//JoinType.LEFT_OUTER_JOIN,
85+
// needs to be an inner join because of how JoinSequence/JoinFragment work - ugh
86+
JoinType.INNER_JOIN,
87+
joinColumns
88+
);
89+
join.setJoinSequence( joinSequence );
90+
91+
join.setOrigin( collectionFromElement, collectionPersister.isManyToMany() );
92+
join.setColumns( joinColumns );
93+
94+
join.setUseFromFragment( collectionFromElement.useFromFragment() );
95+
join.setUseWhereFragment( collectionFromElement.useWhereFragment() );
96+
97+
walker.addQuerySpaces( indexEntityPersister.getQuerySpaces() );
98+
99+
return join;
100+
}
101+
}

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapKeyNode.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@
1515
* @author Steve Ebersole
1616
*/
1717
public class MapKeyNode extends AbstractMapComponentNode {
18+
private MapKeyEntityFromElement mapKeyEntityFromElement;
19+
1820
@Override
1921
protected String expressionDescription() {
2022
return "key(*)";
2123
}
2224

2325
@Override
2426
protected String[] resolveColumns(QueryableCollection collectionPersister) {
25-
final FromElement fromElement = getFromElement();
27+
this.mapKeyEntityFromElement = findOrAddMapKeyEntityFromElement( collectionPersister );
28+
if ( mapKeyEntityFromElement != null ) {
29+
setFromElement( mapKeyEntityFromElement );
30+
}
31+
32+
final FromElement fromElement = getMapFromElement();
2633
return fromElement.toColumns(
2734
fromElement.getCollectionTableAlias(),
2835
"index", // the JPA KEY "qualifier" is the same concept as the HQL INDEX function/property
@@ -34,4 +41,8 @@ protected String[] resolveColumns(QueryableCollection collectionPersister) {
3441
protected Type resolveType(QueryableCollection collectionPersister) {
3542
return collectionPersister.getIndexType();
3643
}
44+
45+
public MapKeyEntityFromElement getMapKeyEntityFromElement() {
46+
return mapKeyEntityFromElement;
47+
}
3748
}

0 commit comments

Comments
 (0)