Skip to content

Commit c832520

Browse files
committed
polish
1 parent 7d4753d commit c832520

File tree

5 files changed

+161
-142
lines changed

5 files changed

+161
-142
lines changed

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

+16-46
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.Collections;
22-
import java.util.HashSet;
2322
import java.util.List;
24-
import java.util.Set;
2523

2624
import org.antlr.v4.runtime.ParserRuleContext;
2725
import org.springframework.data.domain.Sort;
@@ -34,7 +32,7 @@
3432
* @author Greg Turnquist
3533
* @since 3.1
3634
*/
37-
class HqlQueryTransformer extends HqlQueryRenderer implements QueryTransformer {
35+
class HqlQueryTransformer extends HqlQueryRenderer {
3836

3937
// TODO: Separate input from result parameters, encapsulation...
4038

@@ -43,14 +41,14 @@ class HqlQueryTransformer extends HqlQueryRenderer implements QueryTransformer {
4341

4442
private final @Nullable String countProjection;
4543

46-
private @Nullable String primaryFromClauseAlias = null;
44+
private @Nullable String primaryFromAlias = null;
4745

4846
private List<JpaQueryParsingToken> projection = Collections.emptyList();
4947
private boolean projectionProcessed;
5048

5149
private boolean hasConstructorExpression = false;
5250

53-
private Set<String> projectionAliases;
51+
private JpaQueryTransformerSupport transformerSupport;
5452

5553
HqlQueryTransformer() {
5654
this(Sort.unsorted(), false, null);
@@ -71,12 +69,12 @@ private HqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String coun
7169
this.sort = sort;
7270
this.countQuery = countQuery;
7371
this.countProjection = countProjection;
74-
this.projectionAliases = new HashSet<>();
72+
this.transformerSupport = new JpaQueryTransformerSupport();
7573
}
7674

7775
@Nullable
7876
public String getAlias() {
79-
return this.primaryFromClauseAlias;
77+
return this.primaryFromAlias;
8078
}
8179

8280
public List<JpaQueryParsingToken> getProjection() {
@@ -87,11 +85,6 @@ public boolean hasConstructorExpression() {
8785
return this.hasConstructorExpression;
8886
}
8987

90-
@Override
91-
public Set<String> getProjectionAliases() {
92-
return this.projectionAliases;
93-
}
94-
9588
/**
9689
* Is this select clause a {@literal subquery}?
9790
*
@@ -140,30 +133,7 @@ public List<JpaQueryParsingToken> visitOrderedQuery(HqlParser.OrderedQueryContex
140133
tokens.add(TOKEN_ORDER_BY);
141134
}
142135

143-
this.sort.forEach(order -> {
144-
145-
JpaQueryParserSupport.checkSortExpression(order);
146-
147-
if (order.isIgnoreCase()) {
148-
tokens.add(TOKEN_LOWER_FUNC);
149-
}
150-
151-
tokens.add(new JpaQueryParsingToken(() -> {
152-
153-
if (shouldAlias(order)) {
154-
return primaryFromClauseAlias + "." + order.getProperty();
155-
} else {
156-
return order.getProperty();
157-
}
158-
}, true));
159-
if (order.isIgnoreCase()) {
160-
NOSPACE(tokens);
161-
tokens.add(TOKEN_CLOSE_PAREN);
162-
}
163-
tokens.add(order.isDescending() ? TOKEN_DESC : TOKEN_ASC);
164-
tokens.add(TOKEN_COMMA);
165-
});
166-
CLIP(tokens);
136+
tokens.addAll(transformerSupport.generateOrderByArguments(primaryFromAlias, this.sort));
167137
}
168138
} else {
169139

@@ -187,7 +157,7 @@ public List<JpaQueryParsingToken> visitFromQuery(HqlParser.FromQueryContext ctx)
187157
if (countProjection != null) {
188158
tokens.add(new JpaQueryParsingToken(countProjection));
189159
} else {
190-
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias, false));
160+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromAlias, false));
191161
}
192162

193163
tokens.add(TOKEN_CLOSE_PAREN);
@@ -251,8 +221,8 @@ public List<JpaQueryParsingToken> visitFromRoot(HqlParser.FromRootContext ctx) {
251221
if (ctx.variable() != null) {
252222
tokens.addAll(visit(ctx.variable()));
253223

254-
if (primaryFromClauseAlias == null && !isSubquery(ctx)) {
255-
primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
224+
if (primaryFromAlias == null && !isSubquery(ctx)) {
225+
primaryFromAlias = tokens.get(tokens.size() - 1).getToken();
256226
}
257227
}
258228
} else if (ctx.subquery() != null) {
@@ -267,8 +237,8 @@ public List<JpaQueryParsingToken> visitFromRoot(HqlParser.FromRootContext ctx) {
267237
if (ctx.variable() != null) {
268238
tokens.addAll(visit(ctx.variable()));
269239

270-
if (primaryFromClauseAlias == null && !isSubquery(ctx)) {
271-
primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
240+
if (primaryFromAlias == null && !isSubquery(ctx)) {
241+
primaryFromAlias = tokens.get(tokens.size() - 1).getToken();
272242
}
273243
}
274244
}
@@ -281,8 +251,8 @@ public List<JpaQueryParsingToken> visitAlias(HqlParser.AliasContext ctx) {
281251

282252
List<JpaQueryParsingToken> tokens = super.visitAlias(ctx);
283253

284-
if (primaryFromClauseAlias == null && !isSubquery(ctx)) {
285-
primaryFromClauseAlias = tokens.get(tokens.size() - 1).getToken();
254+
if (primaryFromAlias == null && !isSubquery(ctx)) {
255+
primaryFromAlias = tokens.get(tokens.size() - 1).getToken();
286256
}
287257

288258
return tokens;
@@ -294,7 +264,7 @@ public List<JpaQueryParsingToken> visitVariable(HqlParser.VariableContext ctx) {
294264
List<JpaQueryParsingToken> tokens = super.visitVariable(ctx);
295265

296266
if (ctx.identifier() != null) {
297-
projectionAliases.add(tokens.get(tokens.size() - 1).getToken());
267+
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
298268
}
299269

300270
return tokens;
@@ -329,13 +299,13 @@ public List<JpaQueryParsingToken> visitSelectClause(HqlParser.SelectClauseContex
329299

330300
if (selectionListTokens.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) {
331301
// constructor
332-
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias));
302+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromAlias));
333303
} else {
334304
// keep all the select items to distinct against
335305
tokens.addAll(selectionListTokens);
336306
}
337307
} else {
338-
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromClauseAlias));
308+
tokens.add(new JpaQueryParsingToken(() -> this.primaryFromAlias));
339309
}
340310
}
341311

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

-26
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,12 @@
1818
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
1919

2020
import java.util.List;
21-
import java.util.regex.Pattern;
2221

2322
import org.antlr.v4.runtime.Lexer;
2423
import org.antlr.v4.runtime.Parser;
2524
import org.antlr.v4.runtime.ParserRuleContext;
2625
import org.antlr.v4.runtime.atn.PredictionMode;
27-
import org.springframework.dao.InvalidDataAccessApiUsageException;
2826
import org.springframework.data.domain.Sort;
29-
import org.springframework.data.jpa.domain.JpaSort;
3027
import org.springframework.data.util.Lazy;
3128
import org.springframework.lang.Nullable;
3229

@@ -39,12 +36,6 @@
3936
*/
4037
abstract class JpaQueryParserSupport {
4138

42-
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile(".*((?![._])[\\p{Punct}|\\s])");
43-
44-
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
45-
+ "aliases used in the select clause; If you really want to use something other than that for sorting, please use "
46-
+ "JpaSort.unsafe(…)";
47-
4839
private final ParseState state;
4940

5041
JpaQueryParserSupport(String query) {
@@ -177,23 +168,6 @@ protected abstract List<JpaQueryParsingToken> doCreateCountQuery(ParserRuleConte
177168

178169
protected abstract boolean doCheckForConstructor(ParserRuleContext parsedQuery);
179170

180-
/**
181-
* Check any given {@link JpaSort.JpaOrder#isUnsafe()} order for presence of at least one property offending the
182-
* {@link #PUNCTUATION_PATTERN} and throw an {@link Exception} indicating potential unsafe order by expression.
183-
*
184-
* @param order
185-
*/
186-
static void checkSortExpression(Sort.Order order) {
187-
188-
if (order instanceof JpaSort.JpaOrder && ((JpaSort.JpaOrder) order).isUnsafe()) {
189-
return;
190-
}
191-
192-
if (PUNCTUATION_PATTERN.matcher(order.getProperty()).find()) {
193-
throw new InvalidDataAccessApiUsageException(String.format(UNSAFE_PROPERTY_REFERENCE, order));
194-
}
195-
}
196-
197171
/**
198172
* Parser state capturing the lazily-parsed parser context.
199173
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package org.springframework.data.jpa.repository.query;
2+
3+
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
4+
5+
import java.util.ArrayList;
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
import java.util.regex.Pattern;
10+
11+
import org.springframework.dao.InvalidDataAccessApiUsageException;
12+
import org.springframework.data.domain.Sort;
13+
import org.springframework.data.jpa.domain.JpaSort;
14+
import org.springframework.lang.Nullable;
15+
16+
/**
17+
* Transformational operations needed to support either {@link HqlQueryTransformer} or {@link JpqlQueryTransformer}.
18+
*
19+
* @author Greg Turnquist
20+
* @since 3.1
21+
*/
22+
class JpaQueryTransformerSupport {
23+
24+
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile(".*((?![._])[\\p{Punct}|\\s])");
25+
26+
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
27+
+ "aliases used in the select clause; If you really want to use something other than that for sorting, please use "
28+
+ "JpaSort.unsafe(…)";
29+
30+
private Set<String> projectionAliases;
31+
32+
JpaQueryTransformerSupport() {
33+
this.projectionAliases = new HashSet<>();
34+
}
35+
36+
/**
37+
* Register an {@literal alias} so it can later be evaluated when applying {@link Sort}s.
38+
*
39+
* @param token
40+
*/
41+
void registerAlias(String token) {
42+
projectionAliases.add(token);
43+
}
44+
45+
/**
46+
* Using the primary {@literal FROM} clause's alias and a {@link Sort}, construct all the {@literal ORDER BY}
47+
* arguments.
48+
*
49+
* @param primaryFromAlias
50+
* @param sort
51+
* @return
52+
*/
53+
List<JpaQueryParsingToken> generateOrderByArguments(String primaryFromAlias, Sort sort) {
54+
55+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
56+
57+
sort.forEach(order -> {
58+
59+
checkSortExpression(order);
60+
61+
if (order.isIgnoreCase()) {
62+
tokens.add(TOKEN_LOWER_FUNC);
63+
}
64+
65+
tokens.add(new JpaQueryParsingToken(() -> generateOrderByArgument(primaryFromAlias, order)));
66+
67+
if (order.isIgnoreCase()) {
68+
NOSPACE(tokens);
69+
tokens.add(TOKEN_CLOSE_PAREN);
70+
}
71+
tokens.add(order.isDescending() ? TOKEN_DESC : TOKEN_ASC);
72+
tokens.add(TOKEN_COMMA);
73+
});
74+
CLIP(tokens);
75+
76+
return tokens;
77+
}
78+
79+
/**
80+
* Check any given {@link JpaSort.JpaOrder#isUnsafe()} order for presence of at least one property offending the
81+
* {@link #PUNCTUATION_PATTERN} and throw an {@link Exception} indicating potential unsafe order by expression.
82+
*
83+
* @param order
84+
*/
85+
private void checkSortExpression(Sort.Order order) {
86+
87+
if (order instanceof JpaSort.JpaOrder && ((JpaSort.JpaOrder) order).isUnsafe()) {
88+
return;
89+
}
90+
91+
if (PUNCTUATION_PATTERN.matcher(order.getProperty()).find()) {
92+
throw new InvalidDataAccessApiUsageException(String.format(UNSAFE_PROPERTY_REFERENCE, order));
93+
}
94+
}
95+
96+
/**
97+
* Using the provided {@code primaryFromAlias} and the {@link org.springframework.data.domain.Sort.Order}, construct a
98+
* suitable argument to be added to an {@literal ORDER BY} expression.
99+
*
100+
* @param primaryFromAlias
101+
* @param order
102+
* @return
103+
*/
104+
private String generateOrderByArgument(@Nullable String primaryFromAlias, Sort.Order order) {
105+
106+
if (shouldPrefixWithAlias(order)) {
107+
return primaryFromAlias + "." + order.getProperty();
108+
} else {
109+
return order.getProperty();
110+
}
111+
}
112+
113+
/**
114+
* Determine when an {@link org.springframework.data.domain.Sort.Order} parameter should be prefixed with the primary
115+
* FROM clause's alias.
116+
*
117+
* @param order
118+
* @return boolean whether or not to apply the primary FROM clause's alias as a prefix
119+
*/
120+
private boolean shouldPrefixWithAlias(Sort.Order order) {
121+
122+
// If the Sort contains a function
123+
if (order.getProperty().contains("(")) {
124+
return false;
125+
}
126+
127+
// If the Sort references an alias
128+
if (projectionAliases.contains(order.getProperty())) {
129+
return false;
130+
}
131+
132+
return true;
133+
}
134+
}

0 commit comments

Comments
 (0)