Skip to content

Commit c35c914

Browse files
committed
Introduce flyweights for re-used tokens.
There are many tokens that are reusable such as "(", ",", etc. By turning them into constant flyweights, this reduces churn. Consequence is that their "space" attribute must be made immutible to avoid side effects, so I introduced a utility function.
1 parent 07b75a8 commit c35c914

File tree

8 files changed

+705
-637
lines changed

8 files changed

+705
-637
lines changed

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

+297-295
Large diffs are not rendered by default.

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

+313-319
Large diffs are not rendered by default.

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
*/
3636
abstract class QueryParser {
3737

38+
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile(".*((?![._])[\\p{Punct}|\\s])");
39+
40+
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
41+
+ "aliases used in the select clause; If you really want to use something other than that for sorting, please use "
42+
+ "JpaSort.unsafe(…)";
43+
3844
private final DeclaredQuery declaredQuery;
3945

4046
QueryParser(DeclaredQuery declaredQuery) {
@@ -56,7 +62,7 @@ String getQuery() {
5662
/**
5763
* Parse the JPA query using its corresponding ANTLR parser.
5864
*/
59-
abstract ParserRuleContext parse();
65+
abstract ParserRuleContext parse(); // TODO move details inside QueryParser
6066

6167
/**
6268
* Generate a query using the original query with an @literal order by} clause added (or amended) based upon the
@@ -133,12 +139,6 @@ String projection(ParserRuleContext parsedQuery) {
133139
*/
134140
abstract boolean hasConstructor(ParserRuleContext parsedQuery);
135141

136-
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile(".*((?![._])[\\p{Punct}|\\s])");
137-
138-
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
139-
+ "aliases used in the select clause; If you really want to use something other than that for sorting, please use "
140-
+ "JpaSort.unsafe(…)";
141-
142142
/**
143143
* Check any given {@link JpaSort.JpaOrder#isUnsafe()} order for presence of at least one property offending the
144144
* {@link #PUNCTUATION_PATTERN} and throw an {@link Exception} indicating potential unsafe order by expression.

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
* Implementation of {@link QueryEnhancer} using a {@link QueryParser}.<br/>
2929
* <br/>
3030
* NOTE: The parser can find everything it needs to created sorted and count queries. Thus, looking up the alias or the
31-
* projection isn't needed for its primary function, and are simply implemented for test purposes.
31+
* projection isn't needed for its primary function, and are simply implemented for test purposes. TODO: Don't LOG
32+
* warning messages in the middle of actions.
3233
*
3334
* @author Greg Turnquist
3435
* @since 3.1
@@ -72,7 +73,6 @@ public String applySorting(Sort sort) {
7273

7374
return queryParser.createQuery(parsedQuery, sort);
7475
} catch (QueryParsingSyntaxError e) {
75-
LOG.warn(e);
7676
throw new IllegalArgumentException(e);
7777
}
7878
}
@@ -107,7 +107,6 @@ public String detectAlias() {
107107

108108
return queryParser.findAlias(parsedQuery);
109109
} catch (QueryParsingSyntaxError e) {
110-
LOG.warn(e);
111110
return null;
112111
}
113112
}
@@ -139,7 +138,6 @@ public String createCountQueryFor(@Nullable String countProjection) {
139138

140139
return queryParser.createCountQuery(parsedQuery, countProjection);
141140
} catch (QueryParsingSyntaxError e) {
142-
LOG.warn(e);
143141
throw new IllegalArgumentException(e);
144142
}
145143
}
@@ -161,7 +159,6 @@ public boolean hasConstructorExpression() {
161159

162160
return queryParser.hasConstructor(parsedQuery);
163161
} catch (QueryParsingSyntaxError e) {
164-
LOG.warn(e);
165162
return false;
166163
}
167164
}
@@ -182,7 +179,6 @@ public String getProjection() {
182179

183180
return queryParser.projection(parsedQuery);
184181
} catch (QueryParsingSyntaxError e) {
185-
LOG.debug(e);
186182
return "";
187183
}
188184
}

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

+82-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
import java.util.List;
1919
import java.util.function.Supplier;
20+
import java.util.stream.Collectors;
21+
22+
import org.antlr.v4.runtime.Token;
23+
import org.antlr.v4.runtime.tree.TerminalNode;
2024

2125
/**
2226
* A value type used to represent a JPA query token. NOTE: Sometimes the token's value is based upon a value found later
@@ -27,6 +31,31 @@
2731
*/
2832
class QueryParsingToken {
2933

34+
/**
35+
* Commonly use tokens.
36+
*/
37+
public static final QueryParsingToken TOKEN_COMMA = new QueryParsingToken(",");
38+
public static final QueryParsingToken TOKEN_DOT = new QueryParsingToken(".", false);
39+
public static final QueryParsingToken TOKEN_EQUALS = new QueryParsingToken("=");
40+
public static final QueryParsingToken TOKEN_OPEN_PAREN = new QueryParsingToken("(", false);
41+
public static final QueryParsingToken TOKEN_CLOSE_PAREN = new QueryParsingToken(")");
42+
public static final QueryParsingToken TOKEN_ORDER_BY = new QueryParsingToken("order by");
43+
public static final QueryParsingToken TOKEN_LOWER_FUNC = new QueryParsingToken("lower(", false);
44+
public static final QueryParsingToken TOKEN_SELECT_COUNT = new QueryParsingToken("select count(", false);
45+
public static final QueryParsingToken TOKEN_PERCENT = new QueryParsingToken("%");
46+
public static final QueryParsingToken TOKEN_COUNT_FUNC = new QueryParsingToken("count(", false);
47+
public static final QueryParsingToken TOKEN_DOUBLE_PIPE = new QueryParsingToken("||");
48+
public static final QueryParsingToken TOKEN_OPEN_SQUARE_BRACKET = new QueryParsingToken("[", false);
49+
public static final QueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET = new QueryParsingToken("]");
50+
public static final QueryParsingToken TOKEN_COLON = new QueryParsingToken(":", false);
51+
public static final QueryParsingToken TOKEN_QUESTION_MARK = new QueryParsingToken("?", false);
52+
public static final QueryParsingToken TOKEN_CLOSE_BRACE = new QueryParsingToken("}");
53+
public static final QueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET_BRACE = new QueryParsingToken("]}");
54+
public static final QueryParsingToken TOKEN_CLOSE_PAREN_BRACE = new QueryParsingToken(")}");
55+
56+
public static final QueryParsingToken TOKEN_DESC = new QueryParsingToken("desc", false);
57+
58+
public static final QueryParsingToken TOKEN_ASC = new QueryParsingToken("asc", false);
3059
/**
3160
* The text value of the token.
3261
*/
@@ -35,7 +64,7 @@ class QueryParsingToken {
3564
/**
3665
* Space|NoSpace after token is rendered?
3766
*/
38-
private boolean space;
67+
private final boolean space;
3968

4069
QueryParsingToken(Supplier<String> token, boolean space) {
4170

@@ -55,6 +84,18 @@ class QueryParsingToken {
5584
this(() -> token, true);
5685
}
5786

87+
QueryParsingToken(TerminalNode node, boolean space) {
88+
this(node.getText(), space);
89+
}
90+
91+
QueryParsingToken(TerminalNode node) {
92+
this(node.getText());
93+
}
94+
95+
QueryParsingToken(Token token) {
96+
this(token.getText());
97+
}
98+
5899
/**
59100
* Extract the token's value from it's {@link Supplier}.
60101
*/
@@ -69,17 +110,18 @@ boolean getSpace() {
69110
return this.space;
70111
}
71112

72-
void setSpace(boolean space) {
73-
this.space = space;
74-
}
75-
76113
/**
77114
* Switch the last {@link QueryParsingToken}'s spacing to {@literal true}.
78115
*/
79116
static void SPACE(List<QueryParsingToken> tokens) {
80117

81118
if (!tokens.isEmpty()) {
82-
tokens.get(tokens.size() - 1).setSpace(true);
119+
120+
int index = tokens.size() - 1;
121+
122+
QueryParsingToken lastTokenWithSpacing = new QueryParsingToken(tokens.get(index).token);
123+
tokens.remove(index);
124+
tokens.add(lastTokenWithSpacing);
83125
}
84126
}
85127

@@ -89,10 +131,43 @@ static void SPACE(List<QueryParsingToken> tokens) {
89131
static void NOSPACE(List<QueryParsingToken> tokens) {
90132

91133
if (!tokens.isEmpty()) {
92-
tokens.get(tokens.size() - 1).setSpace(false);
134+
135+
int index = tokens.size() - 1;
136+
137+
QueryParsingToken lastTokenWithNoSpacing = new QueryParsingToken(tokens.get(index).token, false);
138+
tokens.remove(index);
139+
tokens.add(lastTokenWithNoSpacing);
93140
}
94141
}
95142

143+
/**
144+
* Take a list of {@link QueryParsingToken}s and convert them ALL to {@code space = false} (except possibly the last
145+
* one).
146+
*
147+
* @param tokens
148+
* @param spacelastElement
149+
*/
150+
static List<QueryParsingToken> NOSPACE_ALL_BUT_LAST_ELEMENT(List<QueryParsingToken> tokens,
151+
boolean spacelastElement) {
152+
153+
List<QueryParsingToken> respacedTokens = tokens.stream() //
154+
.map(queryParsingToken -> {
155+
156+
if (queryParsingToken.space == true) {
157+
return new QueryParsingToken(queryParsingToken.token, false);
158+
} else {
159+
return queryParsingToken;
160+
}
161+
}) //
162+
.collect(Collectors.toList());
163+
164+
if (spacelastElement) {
165+
SPACE(respacedTokens);
166+
}
167+
168+
return respacedTokens;
169+
}
170+
96171
/**
97172
* Drop the last entry from the list of {@link QueryParsingToken}s.
98173
*/

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

-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ class StringQuery implements DeclaredQuery {
6767
*
6868
* @param query must not be {@literal null} or empty.
6969
*/
70-
@Deprecated
7170
@SuppressWarnings("deprecation")
7271
StringQuery(String query, boolean isNative) {
7372

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ void appendsIgnoreCaseOrderingCorrectly() {
265265
String query = "select p from Person p order by p.lastname asc";
266266
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
267267

268-
assertThat(createQueryFor(query, sort)).endsWith("order by p.lastname asc, lower(p.firstname) asc");
268+
assertThat(createQueryFor(query, sort))
269+
.isEqualTo("select p from Person p order by p.lastname asc, lower(p.firstname) asc");
269270
}
270271

271272
@Test // DATAJPA-342

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ void appendsIgnoreCaseOrderingCorrectly() {
254254
String query = "select p from Person p order by p.lastname asc";
255255
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
256256

257-
assertThat(createQueryFor(query, sort)).endsWith("order by p.lastname asc, lower(p.firstname) asc");
257+
assertThat(createQueryFor(query, sort))
258+
.isEqualTo("select p from Person p order by p.lastname asc, lower(p.firstname) asc");
258259
}
259260

260261
@Test // DATAJPA-342

0 commit comments

Comments
 (0)