Skip to content

Commit 37ef062

Browse files
committed
HHH-15106 - fk() HQL function
1 parent 2ced271 commit 37ef062

File tree

7 files changed

+183
-92
lines changed

7 files changed

+183
-92
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,15 @@ tokens
261261
return dot;
262262
}
263263
264+
protected AST lookupFkRefSource(AST path) throws SemanticException {
265+
if ( path.getType() == DOT ) {
266+
return lookupProperty( path, true, isInSelect() );
267+
}
268+
else {
269+
return lookupNonQualifiedProperty( path );
270+
}
271+
}
272+
264273
protected boolean isNonQualifiedPropertyRef(AST ident) { return false; }
265274
266275
protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { return property; }
@@ -746,12 +755,15 @@ identifier
746755
;
747756

748757
addrExpr! [ boolean root ]
749-
: #(d:DOT lhs:addrExprLhs rhs:propertyName ) {
758+
: #(d:DOT lhs:addrExprLhs rhs:propertyName ) {
750759
// This gives lookupProperty() a chance to transform the tree
751760
// to process collection properties (.elements, etc).
752761
#addrExpr = #(#d, #lhs, #rhs);
753762
#addrExpr = lookupProperty(#addrExpr,root,false);
754763
}
764+
| fk_ref:fkRef {
765+
#addrExpr = #fk_ref;
766+
}
755767
| #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr [ null ]) {
756768
#addrExpr = #(#i, #lhs2, #rhs2);
757769
processIndex(#addrExpr);
@@ -776,6 +788,12 @@ addrExpr! [ boolean root ]
776788
}
777789
;
778790

791+
fkRef
792+
: #( r:FK_REF p:propertyRef ) {
793+
#p = lookupProperty( #p, false, isInSelect() );
794+
}
795+
;
796+
779797
addrExprLhs
780798
: addrExpr [ false ]
781799
;
@@ -785,7 +803,6 @@ propertyName
785803
| CLASS
786804
| ELEMENTS
787805
| INDICES
788-
| FK_REF
789806
;
790807

791808
propertyRef!
@@ -798,8 +815,7 @@ propertyRef!
798815
#propertyRef = #(#d, #lhs, #rhs);
799816
#propertyRef = lookupProperty(#propertyRef,false,true);
800817
}
801-
|
802-
p:identifier {
818+
| p:identifier {
803819
// In many cases, things other than property-refs are recognized
804820
// by this propertyRef rule. Some of those I have seen:
805821
// 1) select-clause from-aliases

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ tokens
4646
EXISTS="exists";
4747
FALSE="false";
4848
FETCH="fetch";
49+
FK_REF;
4950
FROM="from";
5051
FULL="full";
5152
GROUP="group";
@@ -722,7 +723,8 @@ atom
722723
723724
// level 0 - the basic element of an expression
724725
primaryExpression
725-
: { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
726+
: { validateSoftKeyword("fk") && LA(2) == OPEN }? fkRefPath
727+
| { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
726728
| { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction
727729
| { validateSoftKeyword("size") && LA(2) == OPEN }? collectionSizeFunction
728730
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
@@ -731,6 +733,12 @@ primaryExpression
731733
| OPEN! (expressionOrVector | subQuery) CLOSE!
732734
;
733735
736+
fkRefPath!
737+
: "fk" OPEN p:identPrimary CLOSE {
738+
#fkRefPath = #( [FK_REF], #p );
739+
}
740+
;
741+
734742
jpaFunctionSyntax!
735743
: i:IDENT OPEN n:QUOTED_STRING (COMMA a:exprList)? CLOSE {
736744
final String functionName = unquote( #n.getText() );
@@ -968,8 +976,6 @@ MOD: '%';
968976
COLON: ':';
969977
PARAM: '?';
970978

971-
FK_REF: "{fk}";
972-
973979
IDENT options { testLiterals=true; }
974980
: ID_START_LETTER ( ID_LETTER )*
975981
{

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ parameter
509509
510510
addrExpr
511511
: #(r:DOT . .) { out(r); }
512+
| #(fk:FK_REF .) { out(fk); }
512513
| i:ALIAS_REF { out(i); }
513514
| j:INDEX_OP { out(j); }
514515
| v:RESULT_VARIABLE_REF { out(v); }

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode;
1616
import org.hibernate.hql.internal.ast.tree.BooleanLiteralNode;
1717
import org.hibernate.hql.internal.ast.tree.CastFunctionNode;
18+
import org.hibernate.hql.internal.ast.tree.FkRefNode;
1819
import org.hibernate.hql.internal.ast.tree.NullNode;
1920
import org.hibernate.hql.internal.ast.tree.SearchedCaseNode;
2021
import org.hibernate.hql.internal.ast.tree.SimpleCaseNode;
@@ -196,6 +197,9 @@ public Class getASTNodeType(int tokenType) {
196197
case NULL : {
197198
return NullNode.class;
198199
}
200+
case FK_REF: {
201+
return FkRefNode.class;
202+
}
199203
default:
200204
return SqlNode.class;
201205
}

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

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ public QueryException buildIllegalCollectionDereferenceException(String property
6565
public static enum DereferenceType {
6666
UNKNOWN,
6767
ENTITY,
68-
FK_REF,
69-
FK_REF_LHS,
7068
COMPONENT,
7169
COLLECTION,
7270
PRIMITIVE,
@@ -267,41 +265,6 @@ private void initText() {
267265
private Type prepareLhs() throws SemanticException {
268266
final FromReferenceNode lhs = getLhs();
269267
lhs.prepareForDot( propertyName );
270-
271-
if ( "{fk}".equals( propertyName ) ) {
272-
// we are processing the {fk} node and "preparing" its
273-
// lhs, which is the to-one mapping. it is significantly
274-
// easiest to simply handle this case here. The other
275-
// option would be to muck around with the PropertyMapping
276-
// for the entity containing the to-one to add mapping for
277-
// the {fk} path; but unfortunately that still means
278-
// additional changes here
279-
final Type toOneType = lhs.getDataType();
280-
assert toOneType instanceof EntityType;
281-
282-
dereferenceType = DereferenceType.FK_REF;
283-
284-
final String toOnePropertyPath;
285-
if ( lhs instanceof DotNode ) {
286-
final DotNode lhsAsDotNode = (DotNode) lhs;
287-
toOnePropertyPath = lhsAsDotNode.propertyPath;
288-
}
289-
else {
290-
assert lhs instanceof IdentNode;
291-
final IdentNode lhsAsIdentNode = (IdentNode) lhs;
292-
toOnePropertyPath = lhsAsIdentNode.getOriginalText();
293-
}
294-
propertyPath = toOnePropertyPath + ".{fk}";
295-
296-
// resolve the fk key columns
297-
final String tableAlias = getLhs().getFromElement().getTableAlias();
298-
this.columns = getFromElement().getElementType().toColumns( tableAlias, toOnePropertyPath, false );
299-
300-
// and the key type
301-
final Type keyType = getFromElement().getElementType().getPropertyType( toOnePropertyPath, toOnePropertyPath );
302-
setDataType( keyType );
303-
}
304-
305268
return getDataType();
306269
}
307270

@@ -434,27 +397,9 @@ private void dereferenceEntity(
434397

435398
// our parent is another dot node, meaning we are being further de-referenced.
436399
// depending on the exact de-reference we may need to generate a physical join.
437-
// generally we will need the join unless:
438-
// * this is a to-one reference && either:
439-
// * the de-reference path is the associated entity's identifier
440-
// && the to-one is not marked with @NotFound
441-
// * the de-reference is the special {fk} token
442400

443401
property = parentAsDotNode.propertyName;
444402

445-
if ( "{fk}".equals( property ) ) {
446-
if ( parentAsDotNode.getNextSibling() != null ) {
447-
throw new QueryException(
448-
"{fk} reference cannot be further de-referenced - " + propertyPath + " -> " + parentAsDotNode.getNextSibling().getText()
449-
);
450-
}
451-
452-
dereferenceType = DereferenceType.FK_REF_LHS;
453-
454-
// dereferenceForeignKeyReference( toOneType, ( (DotNode) parentAsDotNode.getLhs() ).getPropertyPath() );
455-
return;
456-
}
457-
458403
if ( generateJoin ) {
459404
if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) {
460405
joinIsNeeded = true;
@@ -497,25 +442,6 @@ private static boolean isDotNode(AST n) {
497442
return n != null && n.getType() == SqlTokenTypes.DOT;
498443
}
499444

500-
private void dereferenceForeignKeyReference(EntityType toOneType, String toOnePropertyName) throws SemanticException {
501-
dereferenceType = DereferenceType.FK_REF;
502-
503-
if ( LOG.isDebugEnabled() ) {
504-
LOG.debugf( "dereferenceForeignKeyReference() : %s", getPath() );
505-
}
506-
507-
final String fkRefPath = propertyPath + ".{fk}";
508-
propertyPath = fkRefPath;
509-
510-
// resolve the fk key columns
511-
final String tableAlias = getLhs().getFromElement().getTableAlias();
512-
this.columns = getFromElement().getElementType().toColumns( tableAlias, fkRefPath, false );
513-
514-
// and the key type
515-
setDataType( getFromElement().getElementType().getPropertyType( toOnePropertyName, fkRefPath ) );
516-
}
517-
518-
519445
private void dereferenceEntityJoin(
520446
String classAlias,
521447
EntityType toOneType,
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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.QueryException;
10+
import org.hibernate.hql.internal.ast.InvalidPathException;
11+
import org.hibernate.type.BasicType;
12+
import org.hibernate.type.CompositeType;
13+
import org.hibernate.type.ManyToOneType;
14+
import org.hibernate.type.Type;
15+
16+
import antlr.SemanticException;
17+
import antlr.collections.AST;
18+
19+
/**
20+
* Represents a `fk()` pseudo-function
21+
*
22+
* @author Steve Ebersole
23+
*/
24+
public class FkRefNode
25+
extends HqlSqlWalkerNode
26+
implements ResolvableNode, DisplayableNode, PathNode {
27+
private FromReferenceNode toOnePath;
28+
29+
private Type fkType;
30+
private String[] columns;
31+
32+
private FromReferenceNode resolveToOnePath() {
33+
if ( toOnePath == null ) {
34+
try {
35+
resolve( false, true );
36+
}
37+
catch (SemanticException e) {
38+
final String msg = "Unable to resolve to-one path `fk(" + toOnePath.getPath() + "`)";
39+
throw new QueryException( msg, new InvalidPathException( msg ) );
40+
}
41+
}
42+
43+
assert toOnePath != null;
44+
return toOnePath;
45+
}
46+
47+
@Override
48+
public String getDisplayText() {
49+
final FromReferenceNode toOnePath = resolveToOnePath();
50+
return "fk(`" + toOnePath.getDisplayText() + "` )";
51+
}
52+
53+
@Override
54+
public String getPath() {
55+
return toOnePath.getDisplayText() + ".{fk}";
56+
}
57+
58+
@Override
59+
public void resolve(
60+
boolean generateJoin,
61+
boolean implicitJoin) throws SemanticException {
62+
if ( toOnePath != null ) {
63+
return;
64+
}
65+
66+
final AST firstChild = getFirstChild();
67+
assert firstChild instanceof FromReferenceNode;
68+
69+
toOnePath = (FromReferenceNode) firstChild;
70+
toOnePath.resolve( false, true, null, toOnePath.getFromElement() );
71+
72+
final Type sourcePathDataType = toOnePath.getDataType();
73+
if ( ! ( sourcePathDataType instanceof ManyToOneType ) ) {
74+
throw new InvalidPathException(
75+
"Argument to fk() function must be a to-one path, but found " + sourcePathDataType
76+
);
77+
}
78+
final ManyToOneType toOneType = (ManyToOneType) sourcePathDataType;
79+
final FromElement fromElement = toOnePath.getFromElement();
80+
81+
fkType = toOneType.getIdentifierOrUniqueKeyType( getSessionFactoryHelper().getFactory() );
82+
assert fkType instanceof BasicType
83+
|| fkType instanceof CompositeType;
84+
85+
columns = fromElement.getElementType().toColumns(
86+
fromElement.getTableAlias(),
87+
toOneType.getPropertyName(),
88+
getWalker().isInSelect()
89+
);
90+
assert columns != null && columns.length > 0;
91+
92+
setText( String.join( ", ", columns ) );
93+
}
94+
95+
@Override
96+
public void resolve(
97+
boolean generateJoin,
98+
boolean implicitJoin,
99+
String classAlias,
100+
AST parent,
101+
AST parentPredicate) throws SemanticException {
102+
resolve( false, true );
103+
}
104+
105+
@Override
106+
public void resolve(
107+
boolean generateJoin,
108+
boolean implicitJoin,
109+
String classAlias,
110+
AST parent) throws SemanticException {
111+
resolve( false, true );
112+
}
113+
114+
@Override
115+
public void resolve(
116+
boolean generateJoin,
117+
boolean implicitJoin,
118+
String classAlias) throws SemanticException {
119+
resolve( false, true );
120+
}
121+
122+
@Override
123+
public void resolveInFunctionCall(
124+
boolean generateJoin,
125+
boolean implicitJoin) throws SemanticException {
126+
resolve( false, true );
127+
}
128+
129+
@Override
130+
public void resolveIndex(AST parent) throws SemanticException {
131+
throw new InvalidPathException( "fk() paths cannot be de-referenced as indexed path" );
132+
}
133+
}

0 commit comments

Comments
 (0)