Skip to content

Commit afc77ca

Browse files
oscarfanchinmp911de
authored andcommitted
Add support for Set-Returning Functions (SRF) to HQL parser and query rendering.
Signed-off-by: oscarfanchin <[email protected]> Closes: #3864 Original pull request: #3879
1 parent 7b0eebf commit afc77ca

File tree

8 files changed

+395
-4
lines changed

8 files changed

+395
-4
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4

+11-1
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,25 @@ joinSpecifier
114114
fromRoot
115115
: entityName variable?
116116
| LATERAL? '(' subquery ')' variable?
117+
| functionCallAsFromSource variable?
117118
;
118119

120+
functionCallAsFromSource
121+
: identifier '(' (expression (',' expression)*)? ')'
122+
;
123+
119124
join
120125
: joinType JOIN FETCH? joinTarget joinRestriction? // Spec BNF says joinType isn't optional, but text says that it is.
121126
;
122127

123128
joinTarget
124129
: path variable? # JoinPath
125130
| LATERAL? '(' subquery ')' variable? # JoinSubquery
131+
| functionCallAsJoinTarget variable? # JoinFunctionCall
132+
;
133+
134+
functionCallAsJoinTarget
135+
: identifier '(' (expression (',' expression)*)? ')'
126136
;
127137

128138
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-update
@@ -1878,4 +1888,4 @@ ESCAPE_SEQUENCE
18781888
18791889
QUOTED_IDENTIFIER
18801890
: BACKTICK ( ESCAPE_SEQUENCE | '\\' BACKTICK | ~([`]) )* BACKTICK
1881-
;
1891+
;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateQueryInformation.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,29 @@
2323
* Hibernate-specific query details capturing common table expression details.
2424
*
2525
* @author Mark Paluch
26+
* @author oscar.fanchin
2627
* @since 3.5
2728
*/
2829
class HibernateQueryInformation extends QueryInformation {
2930

3031
private final boolean hasCte;
32+
33+
private final boolean hasFromFunction;
34+
3135

3236
public HibernateQueryInformation(@Nullable String alias, List<QueryToken> projection,
33-
boolean hasConstructorExpression, boolean hasCte) {
37+
boolean hasConstructorExpression, boolean hasCte,boolean hasFromFunction) {
3438
super(alias, projection, hasConstructorExpression);
3539
this.hasCte = hasCte;
40+
this.hasFromFunction = hasFromFunction;
3641
}
3742

3843
public boolean hasCte() {
3944
return hasCte;
4045
}
46+
47+
public boolean hasFromFunction() {
48+
return hasFromFunction;
49+
}
50+
4151
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Greg Turnquist
3131
* @author Christoph Strobl
3232
* @author Mark Paluch
33+
* @author oscar.fanchin
3334
* @since 3.1
3435
*/
3536
@SuppressWarnings("ConstantValue")
@@ -38,11 +39,13 @@ class HqlCountQueryTransformer extends HqlQueryRenderer {
3839
private final @Nullable String countProjection;
3940
private final @Nullable String primaryFromAlias;
4041
private final boolean containsCTE;
42+
private final boolean containsFromFunction;
4143

4244
HqlCountQueryTransformer(@Nullable String countProjection, HibernateQueryInformation queryInformation) {
4345
this.countProjection = countProjection;
4446
this.primaryFromAlias = queryInformation.getAlias();
4547
this.containsCTE = queryInformation.hasCte();
48+
this.containsFromFunction = queryInformation.hasFromFunction();
4649
}
4750

4851
@Override
@@ -156,11 +159,19 @@ public QueryRendererBuilder visitFromRoot(HqlParser.FromRootContext ctx) {
156159

157160
builder.appendExpression(nested);
158161

162+
if (ctx.variable() != null) {
163+
builder.appendExpression(visit(ctx.variable()));
164+
}
165+
} else if (ctx.functionCallAsFromSource() != null) {
166+
167+
builder.appendExpression(visit(ctx.functionCallAsFromSource()));
168+
159169
if (ctx.variable() != null) {
160170
builder.appendExpression(visit(ctx.variable()));
161171
}
162172
}
163173

174+
164175
return builder;
165176
}
166177

@@ -204,7 +215,7 @@ public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) {
204215
} else {
205216

206217
// with CTE primary alias fails with hibernate (WITH entities AS (…) SELECT count(c) FROM entities c)
207-
if (containsCTE) {
218+
if (containsCTE || containsFromFunction) {
208219
nested.append(QueryTokens.token("*"));
209220
} else {
210221

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* {@link ParsedQueryIntrospector} for HQL queries.
3030
*
3131
* @author Mark Paluch
32+
* @author oscar.fanchin
3233
*/
3334
@SuppressWarnings({ "UnreachableCode", "ConstantValue" })
3435
class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIntrospector<HibernateQueryInformation> {
@@ -40,11 +41,12 @@ class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIn
4041
private boolean projectionProcessed;
4142
private boolean hasConstructorExpression = false;
4243
private boolean hasCte = false;
44+
private boolean hasFromFunction = false;
4345

4446
@Override
4547
public HibernateQueryInformation getParsedQueryInformation() {
4648
return new HibernateQueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
47-
hasConstructorExpression, hasCte);
49+
hasConstructorExpression, hasCte, hasFromFunction);
4850
}
4951

5052
@Override
@@ -63,6 +65,12 @@ public Void visitCte(HqlParser.CteContext ctx) {
6365
this.hasCte = true;
6466
return super.visitCte(ctx);
6567
}
68+
69+
@Override
70+
public Void visitFunctionCallAsFromSource(HqlParser.FunctionCallAsFromSourceContext ctx) {
71+
this.hasFromFunction = true;
72+
return super.visitFunctionCallAsFromSource(ctx);
73+
}
6674

6775
@Override
6876
public Void visitFromRoot(HqlParser.FromRootContext ctx) {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java

+60
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*
3232
* @author Greg Turnquist
3333
* @author Christoph Strobl
34+
* @author Oscar Fanchin
3435
* @since 3.1
3536
*/
3637
@SuppressWarnings({ "ConstantConditions", "DuplicatedCode", "UnreachableCode" })
@@ -63,6 +64,24 @@ public QueryTokenStream visitStart(HqlParser.StartContext ctx) {
6364
return visit(ctx.ql_statement());
6465
}
6566

67+
@Override
68+
public QueryTokenStream visitFunctionCallAsFromSource(HqlParser.FunctionCallAsFromSourceContext ctx) {
69+
70+
QueryRendererBuilder builder = QueryRenderer.builder();
71+
72+
builder.append(visit(ctx.identifier()));
73+
74+
builder.append(TOKEN_OPEN_PAREN);
75+
76+
if (!ctx.expression().isEmpty()) {
77+
builder.append(QueryTokenStream.concatExpressions(ctx.expression(), this::visit, TOKEN_COMMA));
78+
}
79+
80+
builder.append(TOKEN_CLOSE_PAREN);
81+
82+
return builder;
83+
}
84+
6685
@Override
6786
public QueryTokenStream visitQl_statement(HqlParser.Ql_statementContext ctx) {
6887

@@ -376,6 +395,14 @@ public QueryTokenStream visitFromRoot(HqlParser.FromRootContext ctx) {
376395

377396
builder.appendExpression(nested);
378397

398+
if (ctx.variable() != null) {
399+
builder.appendExpression(visit(ctx.variable()));
400+
}
401+
402+
} else if (ctx.functionCallAsFromSource() != null) {
403+
404+
builder.appendExpression(visit(ctx.functionCallAsFromSource()));
405+
379406
if (ctx.variable() != null) {
380407
builder.appendExpression(visit(ctx.variable()));
381408
}
@@ -442,6 +469,39 @@ public QueryTokenStream visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) {
442469
return builder;
443470
}
444471

472+
@Override
473+
public QueryTokenStream visitJoinFunctionCall(HqlParser.JoinFunctionCallContext ctx) {
474+
475+
QueryRendererBuilder builder = QueryRenderer.builder();
476+
477+
builder.append(visit(ctx.functionCallAsJoinTarget()));
478+
479+
if (ctx.variable() != null) {
480+
builder.appendExpression(visit(ctx.variable()));
481+
}
482+
483+
return builder;
484+
485+
}
486+
487+
@Override
488+
public QueryTokenStream visitFunctionCallAsJoinTarget(HqlParser.FunctionCallAsJoinTargetContext ctx) {
489+
490+
QueryRendererBuilder builder = QueryRenderer.builder();
491+
492+
builder.append(visit(ctx.identifier()));
493+
494+
builder.append(TOKEN_OPEN_PAREN);
495+
496+
if (!ctx.expression().isEmpty()) {
497+
builder.append(QueryTokenStream.concatExpressions(ctx.expression(), this::visit, TOKEN_COMMA));
498+
}
499+
500+
builder.append(TOKEN_CLOSE_PAREN);
501+
502+
return builder;
503+
}
504+
445505
@Override
446506
public QueryTokenStream visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
447507

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*
3333
* @author Greg Turnquist
3434
* @author Christoph Strobl
35+
* @author oscar.fanchin
3536
* @since 3.1
3637
*/
3738
@SuppressWarnings("ConstantValue")
@@ -122,6 +123,19 @@ public QueryTokenStream visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) {
122123

123124
return tokens;
124125
}
126+
127+
@Override
128+
public QueryTokenStream visitJoinFunctionCall(HqlParser.JoinFunctionCallContext ctx) {
129+
130+
QueryTokenStream tokens = super.visitJoinFunctionCall(ctx);
131+
132+
if (ctx.variable() != null && !tokens.isEmpty()) {
133+
transformerSupport.registerAlias(tokens.getLast());
134+
}
135+
136+
return tokens;
137+
}
138+
125139

126140
@Override
127141
public QueryTokenStream visitVariable(HqlParser.VariableContext ctx) {

0 commit comments

Comments
 (0)