diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 6a2e8e84f..d91e6ea38 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -77,6 +77,7 @@ import org.springframework.util.StringUtils; * @author Nils Borrmann * @author Reda.Housni-Alaoui * @author Florian Lüdiger + * @author Grégoire Druant */ public abstract class QueryUtils { @@ -121,6 +122,7 @@ public abstract class QueryUtils { private static final Pattern PUNCTATION_PATTERN = Pattern.compile(".*((?![\\._])[\\p{Punct}|\\s])"); private static final Pattern FUNCTION_PATTERN; + private static final Pattern FIELD_ALIAS_PATTERN; private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or " + "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 { builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); FUNCTION_PATTERN = compile(builder.toString()); + + builder = new StringBuilder(); + builder.append("\\s+"); // at least one space + builder.append("[^\\s\\(\\)]+"); // No white char no bracket + builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); // the potential alias + + FIELD_ALIAS_PATTERN = compile(builder.toString()); + } /** @@ -253,10 +263,11 @@ public abstract class QueryUtils { } Set aliases = getOuterJoinAliases(query); - Set functionAliases = getFunctionAliases(query); + Set fieldAliases = getFunctionAliases(query); + fieldAliases.addAll(getFieldAliases(query)); for (Order order : sort) { - builder.append(getOrderClause(aliases, functionAliases, alias, order)).append(", "); + builder.append(getOrderClause(aliases, fieldAliases, alias, order)).append(", "); } builder.delete(builder.length() - 2, builder.length()); @@ -273,14 +284,14 @@ public abstract class QueryUtils { * @param order the order object to build the clause for. Must not be {@literal null}. * @return a String containing a order clause. Guaranteed to be not {@literal null}. */ - private static String getOrderClause(Set joinAliases, Set functionAlias, @Nullable String alias, + private static String getOrderClause(Set joinAliases, Set fieldAlias, @Nullable String alias, Order order) { String property = order.getProperty(); checkSortExpression(order); - if (functionAlias.contains(property)) { + if (fieldAlias.contains(property)) { return String.format("%s %s", property, toJpaDirection(order)); } @@ -322,6 +333,26 @@ public abstract class QueryUtils { return result; } + /** + * Returns the aliases used for fields in the query. + * + * @param query a {@literal String} containing a query. Must not be {@literal null}. + * @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}. + */ + private static Set getFieldAliases(String query) { + Set result = new HashSet<>(); + Matcher matcher = FIELD_ALIAS_PATTERN.matcher(query); + + while (matcher.find()) { + String alias = matcher.group(1); + + if (StringUtils.hasText(alias)) { + result.add(alias); + } + } + return result; + } + /** * Returns the aliases used for aggregate functions like {@code SUM, COUNT, ...}. * diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java index 67cac60c0..52c81fdc5 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java @@ -39,6 +39,7 @@ import org.springframework.data.jpa.domain.JpaSort; * @author Christoph Strobl * @author Jens Schauder * @author Florian Lüdiger + * @author Grégoire Druant */ public class QueryUtilsUnitTests { @@ -423,6 +424,30 @@ public class QueryUtilsUnitTests { " where user.age = 18\n ")); } + @Test // DATAJPA-1061 + public void appliesSortCorrectlyForFieldAliases() { + String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a"; + Sort sort = Sort.by("authorName"); + String fullQuery = applySorting(query, sort); + assertThat(fullQuery, endsWith("order by authorName asc")); + } + + @Test // DATAJPA-1061 + public void appliesSortCorrectlyForFunctionAliases() { + String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a"; + Sort sort = Sort.by("title"); + String fullQuery = applySorting(query, sort); + assertThat(fullQuery, endsWith("order by title asc")); + } + + @Test // DATAJPA-1061 + public void appliesSortCorrectlyForSimpleField() { + String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a"; + Sort sort = Sort.by("price"); + String fullQuery = applySorting(query, sort); + assertThat(fullQuery, endsWith("order by m.price asc")); + } + private static void assertCountQuery(String originalQuery, String countQuery) { assertThat(createCountQueryFor(originalQuery), is(countQuery)); }