Skip to content

Commit f4b5f2c

Browse files
committed
HHH-16 - Explicit joins on unrelated classes
HHH-7321 - HQL: Combining a CROSS JOIN with a LEFT JOIN which requires a WITH clause triggers an exception.
1 parent 5eb4f1c commit f4b5f2c

File tree

11 files changed

+642
-54
lines changed

11 files changed

+642
-54
lines changed

hibernate-core/src/main/antlr/hql-sql.g

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ tokens
3838
FROM_FRAGMENT; // A fragment of SQL that represents a table reference in a FROM clause.
3939
IMPLIED_FROM; // An implied FROM element.
4040
JOIN_FRAGMENT; // A JOIN fragment.
41+
ENTITY_JOIN; // An "ad-hoc" join to an entity
4142
SELECT_CLAUSE;
4243
LEFT_OUTER;
4344
RIGHT_OUTER;
@@ -260,6 +261,8 @@ tokens
260261
protected void processMapComponentReference(AST node) throws SemanticException { }
261262
262263
protected void validateMapPropertyExpression(AST node) throws SemanticException { }
264+
protected void finishFromClause (AST fromClause) throws SemanticException { }
265+
263266
}
264267
265268
// The main statement rule.
@@ -456,6 +459,7 @@ fromClause {
456459
prepareFromClauseInputTree(#fromClause_in);
457460
}
458461
: #(f:FROM { pushFromClause(#fromClause,f); handleClauseStart( FROM ); } fromElementList ) {
462+
finishFromClause( #f );
459463
handleClauseEnd();
460464
}
461465
;

hibernate-core/src/main/antlr/sql-gen.g

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ fromTable
294294
// Write the table node (from fragment) and all the join fragments associated with it.
295295
: #( a:FROM_FRAGMENT { out(a); } (tableJoin [ a ])* { fromFragmentSeparator(a); } )
296296
| #( b:JOIN_FRAGMENT { out(b); } (tableJoin [ b ])* { fromFragmentSeparator(b); } )
297+
| #( e:ENTITY_JOIN { out(e); } )
297298
;
298299
299300
tableJoin [ AST parent ]

hibernate-core/src/main/java/org/hibernate/hql/internal/QuerySplitter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public final class QuerySplitter {
3535
BEFORE_CLASS_TOKENS.add( "update" );
3636
//beforeClassTokens.add("new"); DEFINITELY DON'T HAVE THIS!!
3737
BEFORE_CLASS_TOKENS.add( "," );
38+
BEFORE_CLASS_TOKENS.add( "join" );
3839
NOT_AFTER_CLASS_TOKENS.add( "in" );
3940
//notAfterClassTokens.add(",");
4041
NOT_AFTER_CLASS_TOKENS.add( "from" );

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

Lines changed: 121 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Map;
1919
import java.util.Set;
2020

21-
import org.hibernate.HibernateException;
2221
import org.hibernate.QueryException;
2322
import org.hibernate.engine.internal.JoinSequence;
2423
import org.hibernate.engine.internal.ParameterBinder;
@@ -34,6 +33,7 @@
3433
import org.hibernate.hql.internal.ast.tree.ConstructorNode;
3534
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
3635
import org.hibernate.hql.internal.ast.tree.DotNode;
36+
import org.hibernate.hql.internal.ast.tree.EntityJoinFromElement;
3737
import org.hibernate.hql.internal.ast.tree.FromClause;
3838
import org.hibernate.hql.internal.ast.tree.FromElement;
3939
import org.hibernate.hql.internal.ast.tree.FromElementFactory;
@@ -74,10 +74,10 @@
7474
import org.hibernate.param.PositionalParameterSpecification;
7575
import org.hibernate.param.VersionTypeSeedParameterSpecification;
7676
import org.hibernate.persister.collection.QueryableCollection;
77+
import org.hibernate.persister.entity.EntityPersister;
7778
import org.hibernate.persister.entity.Queryable;
7879
import org.hibernate.sql.JoinType;
7980
import org.hibernate.type.AssociationType;
80-
import org.hibernate.type.ComponentType;
8181
import org.hibernate.type.CompositeType;
8282
import org.hibernate.type.DbTimestampType;
8383
import org.hibernate.type.Type;
@@ -363,50 +363,121 @@ protected void createFromJoinElement(
363363
if ( fetch && isSubQuery() ) {
364364
throw new QueryException( "fetch not allowed in subquery from-elements" );
365365
}
366-
// The path AST should be a DotNode, and it should have been evaluated already.
367-
if ( path.getType() != SqlTokenTypes.DOT ) {
368-
throw new SemanticException( "Path expected for join!" );
369-
}
370-
DotNode dot = (DotNode) path;
371-
JoinType hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
372-
dot.setJoinType( hibernateJoinType ); // Tell the dot node about the join type.
373-
dot.setFetch( fetch );
374-
// Generate an explicit join for the root dot node. The implied joins will be collected and passed up
375-
// to the root dot node.
376-
dot.resolve( true, false, alias == null ? null : alias.getText() );
377-
378-
final FromElement fromElement;
379-
if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) {
380-
if ( dot.getDataType().isAnyType() ) {
381-
throw new SemanticException( "An AnyType attribute cannot be join fetched" );
382-
// ^^ because the discriminator (aka, the "meta columns") must be known to the SQL in
383-
// a non-parameterized way.
384-
}
385-
FromElementFactory factory = new FromElementFactory(
386-
getCurrentFromClause(),
387-
dot.getLhs().getFromElement(),
388-
dot.getPropertyPath(),
389-
alias == null ? null : alias.getText(),
390-
null,
391-
false
366+
367+
368+
// the incoming "path" can be either:
369+
// 1) an implicit join path (join p.address.city)
370+
// 2) an entity-join (join com.acme.User)
371+
//
372+
// so make the proper interpretation here...
373+
374+
final EntityPersister entityJoinReferencedPersister = resolveEntityJoinReferencedPersister( path );
375+
if ( entityJoinReferencedPersister != null ) {
376+
// `path` referenced an entity
377+
final EntityJoinFromElement join = createEntityJoin(
378+
entityJoinReferencedPersister,
379+
alias,
380+
joinType,
381+
propertyFetch,
382+
with
392383
);
393-
fromElement = factory.createComponentJoin( (CompositeType) dot.getDataType() );
384+
385+
( (FromReferenceNode) path ).setFromElement( join );
394386
}
395387
else {
396-
fromElement = dot.getImpliedJoin();
397-
fromElement.setAllPropertyFetch( propertyFetch != null );
388+
if ( path.getType() != SqlTokenTypes.DOT ) {
389+
throw new SemanticException( "Path expected for join!" );
390+
}
398391

399-
if ( with != null ) {
400-
if ( fetch ) {
401-
throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
392+
DotNode dot = (DotNode) path;
393+
JoinType hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
394+
dot.setJoinType( hibernateJoinType ); // Tell the dot node about the join type.
395+
dot.setFetch( fetch );
396+
// Generate an explicit join for the root dot node. The implied joins will be collected and passed up
397+
// to the root dot node.
398+
dot.resolve( true, false, alias == null ? null : alias.getText() );
399+
400+
final FromElement fromElement;
401+
if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) {
402+
if ( dot.getDataType().isAnyType() ) {
403+
throw new SemanticException( "An AnyType attribute cannot be join fetched" );
404+
// ^^ because the discriminator (aka, the "meta columns") must be known to the SQL in
405+
// a non-parameterized way.
402406
}
403-
handleWithFragment( fromElement, with );
407+
FromElementFactory factory = new FromElementFactory(
408+
getCurrentFromClause(),
409+
dot.getLhs().getFromElement(),
410+
dot.getPropertyPath(),
411+
alias == null ? null : alias.getText(),
412+
null,
413+
false
414+
);
415+
fromElement = factory.createComponentJoin( (CompositeType) dot.getDataType() );
416+
}
417+
else {
418+
fromElement = dot.getImpliedJoin();
419+
fromElement.setAllPropertyFetch( propertyFetch != null );
420+
421+
if ( with != null ) {
422+
if ( fetch ) {
423+
throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
424+
}
425+
handleWithFragment( fromElement, with );
426+
}
427+
}
428+
429+
if ( LOG.isDebugEnabled() ) {
430+
LOG.debug(
431+
"createFromJoinElement() : "
432+
+ getASTPrinter().showAsString( fromElement, "-- join tree --" )
433+
);
404434
}
405435
}
436+
}
406437

407-
if ( LOG.isDebugEnabled() ) {
408-
LOG.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
438+
private EntityPersister resolveEntityJoinReferencedPersister(AST path) {
439+
if ( path.getType() == IDENT ) {
440+
final IdentNode pathIdentNode = (IdentNode) path;
441+
String name = path.getText();
442+
if ( name == null ) {
443+
name = pathIdentNode.getOriginalText();
444+
}
445+
return sessionFactoryHelper.findEntityPersisterByName( name );
446+
}
447+
else if ( path.getType() == DOT ) {
448+
final String pathText = ASTUtil.getPathText( path );
449+
return sessionFactoryHelper.findEntityPersisterByName( pathText );
409450
}
451+
return null;
452+
}
453+
454+
@Override
455+
protected void finishFromClause(AST fromClause) throws SemanticException {
456+
( (FromClause) fromClause ).finishInit();
457+
}
458+
459+
private EntityJoinFromElement createEntityJoin(
460+
EntityPersister entityPersister,
461+
AST aliasNode,
462+
int joinType,
463+
AST propertyFetch,
464+
AST with) throws SemanticException {
465+
final String alias = aliasNode == null ? null : aliasNode.getText();
466+
LOG.debugf( "Creating entity-join FromElement [%s -> %s]", alias, entityPersister.getEntityName() );
467+
EntityJoinFromElement join = new EntityJoinFromElement(
468+
this,
469+
getCurrentFromClause(),
470+
entityPersister,
471+
JoinProcessor.toHibernateJoinType( joinType ),
472+
propertyFetch != null,
473+
alias
474+
);
475+
476+
if ( with != null ) {
477+
handleWithFragment( join, with );
478+
}
479+
480+
return join;
410481
}
411482

412483
private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException {
@@ -429,16 +500,16 @@ private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws
429500
if ( withClauseJoinAlias == null ) {
430501
withClauseJoinAlias = fromElement.getCollectionTableAlias();
431502
}
432-
else {
433-
FromElement referencedFromElement = visitor.getReferencedFromElement();
434-
if ( referencedFromElement != fromElement ) {
435-
LOG.warnf(
436-
"with-clause expressions do not reference the from-clause element to which the " +
437-
"with-clause was associated. The query may not work as expected [%s]",
438-
queryTranslatorImpl.getQueryString()
439-
);
440-
}
441-
}
503+
// else {
504+
// FromElement referencedFromElement = visitor.getReferencedFromElement();
505+
// if ( referencedFromElement != fromElement ) {
506+
// LOG.warnf(
507+
// "with-clause expressions do not reference the from-clause element to which the " +
508+
// "with-clause was associated. The query may not work as expected [%s]",
509+
// queryTranslatorImpl.getQueryString()
510+
// );
511+
// }
512+
// }
442513

443514
SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() );
444515
sql.whereExpr( hqlSqlWithNode.getFirstChild() );
@@ -483,9 +554,9 @@ public void visit(AST node) {
483554
DotNode dotNode = (DotNode) node;
484555
FromElement fromElement = dotNode.getFromElement();
485556
if ( referencedFromElement != null ) {
486-
if ( fromElement != referencedFromElement ) {
487-
throw new HibernateException( "with-clause referenced two different from-clause elements" );
488-
}
557+
// if ( fromElement != referencedFromElement ) {
558+
// throw new HibernateException( "with-clause referenced two different from-clause elements" );
559+
// }
489560
}
490561
else {
491562
referencedFromElement = fromElement;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,10 @@ protected void fromFragmentSeparator(AST a) {
356356
return;
357357
}
358358

359-
if ( right.getRealOrigin() == left ||
359+
if ( right.getType() == ENTITY_JOIN ) {
360+
out( " " );
361+
}
362+
else if ( right.getRealOrigin() == left ||
360363
( right.getRealOrigin() != null && right.getRealOrigin() == left.getRealOrigin() ) ) {
361364
// right represents a joins originating from left; or
362365
// both right and left reprersent joins originating from the same FromElement

0 commit comments

Comments
 (0)