Skip to content

Commit a83c0bf

Browse files
GregoireDruantschauder
authored andcommitted
DATAJPA-1061 - Sorting now works with field aliases.
Extract field aliases in QueryUtils and add them in the set of possible sort criteria. Original pull request: #276.
1 parent b538591 commit a83c0bf

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java

+35-4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* @author Nils Borrmann
7878
* @author Reda.Housni-Alaoui
7979
* @author Florian Lüdiger
80+
* @author Grégoire Druant
8081
*/
8182
public abstract class QueryUtils {
8283

@@ -121,6 +122,7 @@ public abstract class QueryUtils {
121122

122123
private static final Pattern PUNCTATION_PATTERN = Pattern.compile(".*((?![\\._])[\\p{Punct}|\\s])");
123124
private static final Pattern FUNCTION_PATTERN;
125+
private static final Pattern FIELD_ALIAS_PATTERN;
124126

125127
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
126128
+ "aliases used in the select clause. If you really want to use something other than that for sorting, please use "
@@ -177,6 +179,14 @@ public abstract class QueryUtils {
177179
builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))");
178180

179181
FUNCTION_PATTERN = compile(builder.toString());
182+
183+
builder = new StringBuilder();
184+
builder.append("\\s+"); // at least one space
185+
builder.append("[^\\s\\(\\)]+"); // No white char no bracket
186+
builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); // the potential alias
187+
188+
FIELD_ALIAS_PATTERN = compile(builder.toString());
189+
180190
}
181191

182192
/**
@@ -253,10 +263,11 @@ public static String applySorting(String query, Sort sort, @Nullable String alia
253263
}
254264

255265
Set<String> aliases = getOuterJoinAliases(query);
256-
Set<String> functionAliases = getFunctionAliases(query);
266+
Set<String> fieldAliases = getFunctionAliases(query);
267+
fieldAliases.addAll(getFieldAliases(query));
257268

258269
for (Order order : sort) {
259-
builder.append(getOrderClause(aliases, functionAliases, alias, order)).append(", ");
270+
builder.append(getOrderClause(aliases, fieldAliases, alias, order)).append(", ");
260271
}
261272

262273
builder.delete(builder.length() - 2, builder.length());
@@ -273,14 +284,14 @@ public static String applySorting(String query, Sort sort, @Nullable String alia
273284
* @param order the order object to build the clause for. Must not be {@literal null}.
274285
* @return a String containing a order clause. Guaranteed to be not {@literal null}.
275286
*/
276-
private static String getOrderClause(Set<String> joinAliases, Set<String> functionAlias, @Nullable String alias,
287+
private static String getOrderClause(Set<String> joinAliases, Set<String> fieldAlias, @Nullable String alias,
277288
Order order) {
278289

279290
String property = order.getProperty();
280291

281292
checkSortExpression(order);
282293

283-
if (functionAlias.contains(property)) {
294+
if (fieldAlias.contains(property)) {
284295
return String.format("%s %s", property, toJpaDirection(order));
285296
}
286297

@@ -322,6 +333,26 @@ static Set<String> getOuterJoinAliases(String query) {
322333
return result;
323334
}
324335

336+
/**
337+
* Returns the aliases used for fields in the query.
338+
*
339+
* @param query a {@literal String} containing a query. Must not be {@literal null}.
340+
* @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
341+
*/
342+
private static Set<String> getFieldAliases(String query) {
343+
Set<String> result = new HashSet<>();
344+
Matcher matcher = FIELD_ALIAS_PATTERN.matcher(query);
345+
346+
while (matcher.find()) {
347+
String alias = matcher.group(1);
348+
349+
if (StringUtils.hasText(alias)) {
350+
result.add(alias);
351+
}
352+
}
353+
return result;
354+
}
355+
325356
/**
326357
* Returns the aliases used for aggregate functions like {@code SUM, COUNT, ...}.
327358
*

src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @author Christoph Strobl
4040
* @author Jens Schauder
4141
* @author Florian Lüdiger
42+
* @author Grégoire Druant
4243
*/
4344
public class QueryUtilsUnitTests {
4445

@@ -423,6 +424,30 @@ public void createCountQuerySupportsWhitespaceCharacters() {
423424
" where user.age = 18\n "));
424425
}
425426

427+
@Test // DATAJPA-1061
428+
public void appliesSortCorrectlyForFieldAliases() {
429+
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
430+
Sort sort = Sort.by("authorName");
431+
String fullQuery = applySorting(query, sort);
432+
assertThat(fullQuery, endsWith("order by authorName asc"));
433+
}
434+
435+
@Test // DATAJPA-1061
436+
public void appliesSortCorrectlyForFunctionAliases() {
437+
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
438+
Sort sort = Sort.by("title");
439+
String fullQuery = applySorting(query, sort);
440+
assertThat(fullQuery, endsWith("order by title asc"));
441+
}
442+
443+
@Test // DATAJPA-1061
444+
public void appliesSortCorrectlyForSimpleField() {
445+
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
446+
Sort sort = Sort.by("price");
447+
String fullQuery = applySorting(query, sort);
448+
assertThat(fullQuery, endsWith("order by m.price asc"));
449+
}
450+
426451
private static void assertCountQuery(String originalQuery, String countQuery) {
427452
assertThat(createCountQueryFor(originalQuery), is(countQuery));
428453
}

0 commit comments

Comments
 (0)