Skip to content

Commit 18d583d

Browse files
committed
Introduce support for ordering by aliased columns.
If a projection of an HQL query is aliased, be that a function call or a simply alias, apply sorting should NOT result in that order parameter having the primary FROM clause's alias prefixed to. Resolves #2863. Related: #2626, #2322.
1 parent b3f2522 commit 18d583d

File tree

2 files changed

+152
-51
lines changed

2 files changed

+152
-51
lines changed

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

+57-18
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.Collections;
22+
import java.util.HashSet;
2223
import java.util.List;
24+
import java.util.Set;
2325

2426
import org.antlr.v4.runtime.ParserRuleContext;
2527
import org.springframework.data.domain.Sort;
@@ -41,13 +43,15 @@ class HqlQueryTransformer extends HqlQueryRenderer {
4143

4244
private final @Nullable String countProjection;
4345

44-
private @Nullable String alias = null;
46+
private @Nullable String primaryFromClauseAlias = null;
4547

4648
private List<JpaQueryParsingToken> projection = Collections.emptyList();
4749
private boolean projectionProcessed;
4850

4951
private boolean hasConstructorExpression = false;
5052

53+
private Set<String> projectionAliases;
54+
5155
HqlQueryTransformer() {
5256
this(Sort.unsorted(), false, null);
5357
}
@@ -67,11 +71,12 @@ private HqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String coun
6771
this.sort = sort;
6872
this.countQuery = countQuery;
6973
this.countProjection = countProjection;
74+
this.projectionAliases = new HashSet<>();
7075
}
7176

7277
@Nullable
7378
public String getAlias() {
74-
return this.alias;
79+
return this.primaryFromClauseAlias;
7580
}
7681

7782
public List<JpaQueryParsingToken> getProjection() {
@@ -137,13 +142,14 @@ public List<JpaQueryParsingToken> visitOrderedQuery(HqlParser.OrderedQueryContex
137142
if (order.isIgnoreCase()) {
138143
tokens.add(TOKEN_LOWER_FUNC);
139144
}
145+
140146
tokens.add(new JpaQueryParsingToken(() -> {
141147

142-
if (order.getProperty().contains("(")) {
148+
if (shouldAlias(order)) {
149+
return primaryFromClauseAlias + "." + order.getProperty();
150+
} else {
143151
return order.getProperty();
144152
}
145-
146-
return this.alias + "." + order.getProperty();
147153
}, true));
148154
if (order.isIgnoreCase()) {
149155
NOSPACE(tokens);
@@ -164,6 +170,33 @@ public List<JpaQueryParsingToken> visitOrderedQuery(HqlParser.OrderedQueryContex
164170
return tokens;
165171
}
166172

173+
/**
174+
* Determine when an {@link org.springframework.data.domain.Sort.Order} parameter should alias (or not).
175+
*
176+
* @param order
177+
* @return boolean whether or not to apply the primary FROM clause's alias
178+
*/
179+
private boolean shouldAlias(Sort.Order order) {
180+
181+
if (orderParameterIsAFunction(order)) {
182+
return false;
183+
}
184+
185+
if (orderParameterReferencesAProjectionAlias(order)) {
186+
return false;
187+
}
188+
189+
return true;
190+
}
191+
192+
private boolean orderParameterIsAFunction(Sort.Order order) {
193+
return order.getProperty().contains("(");
194+
}
195+
196+
private boolean orderParameterReferencesAProjectionAlias(Sort.Order order) {
197+
return projectionAliases.contains(order.getProperty());
198+
}
199+
167200
@Override
168201
public List<JpaQueryParsingToken> visitFromQuery(HqlParser.FromQueryContext ctx) {
169202

@@ -176,7 +209,7 @@ public List<JpaQueryParsingToken> visitFromQuery(HqlParser.FromQueryContext ctx)
176209
if (countProjection != null) {
177210
tokens.add(new JpaQueryParsingToken(countProjection));
178211
} else {
179-
tokens.add(new JpaQueryParsingToken(() -> this.alias, false));
212+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias, false));
180213
}
181214

182215
tokens.add(TOKEN_CLOSE_PAREN);
@@ -240,8 +273,8 @@ public List<JpaQueryParsingToken> visitFromRoot(HqlParser.FromRootContext ctx) {
240273
if (ctx.variable() != null) {
241274
tokens.addAll(visit(ctx.variable()));
242275

243-
if (this.alias == null && !isSubquery(ctx)) {
244-
this.alias = tokens.get(tokens.size() - 1).getToken();
276+
if (this.primaryFromClauseAlias == null && !isSubquery(ctx)) {
277+
this.primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
245278
}
246279
}
247280
} else if (ctx.subquery() != null) {
@@ -256,8 +289,8 @@ public List<JpaQueryParsingToken> visitFromRoot(HqlParser.FromRootContext ctx) {
256289
if (ctx.variable() != null) {
257290
tokens.addAll(visit(ctx.variable()));
258291

259-
if (this.alias == null && !isSubquery(ctx)) {
260-
this.alias = tokens.get(tokens.size() - 1).getToken();
292+
if (this.primaryFromClauseAlias == null && !isSubquery(ctx)) {
293+
this.primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
261294
}
262295
}
263296
}
@@ -268,16 +301,22 @@ public List<JpaQueryParsingToken> visitFromRoot(HqlParser.FromRootContext ctx) {
268301
@Override
269302
public List<JpaQueryParsingToken> visitAlias(HqlParser.AliasContext ctx) {
270303

271-
List<JpaQueryParsingToken> tokens = newArrayList();
304+
List<JpaQueryParsingToken> tokens = super.visitAlias(ctx);
272305

273-
if (ctx.AS() != null) {
274-
tokens.add(new JpaQueryParsingToken(ctx.AS()));
306+
if (primaryFromClauseAlias == null && !isSubquery(ctx)) {
307+
primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
275308
}
276309

277-
tokens.addAll(visit(ctx.identifier()));
310+
return tokens;
311+
}
312+
313+
@Override
314+
public List<JpaQueryParsingToken> visitVariable(HqlParser.VariableContext ctx) {
315+
316+
List<JpaQueryParsingToken> tokens = super.visitVariable(ctx);
278317

279-
if (this.alias == null && !isSubquery(ctx)) {
280-
this.alias = tokens.get(tokens.size() - 1).getToken();
318+
if (ctx.identifier() != null) {
319+
projectionAliases.add(tokens.get(tokens.size() - 1).getToken());
281320
}
282321

283322
return tokens;
@@ -312,13 +351,13 @@ public List<JpaQueryParsingToken> visitSelectClause(HqlParser.SelectClauseContex
312351

313352
if (selectionListTokens.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) {
314353
// constructor
315-
tokens.add(new JpaQueryParsingToken(() -> this.alias));
354+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias));
316355
} else {
317356
// keep all the select items to distinct against
318357
tokens.addAll(selectionListTokens);
319358
}
320359
} else {
321-
tokens.add(new JpaQueryParsingToken(() -> this.alias));
360+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias));
322361
}
323362
}
324363

0 commit comments

Comments
 (0)