Properly handle nested ORDER BY clauses.
In several ways, it's possible to have various ORDER BY clauses, including SQL OVER (windowing) clauses. Need to improve handling of ORDER BY. See #2260.
This commit is contained in:
committed by
Greg L. Turnquist
parent
390e154a65
commit
c93aa25fec
@@ -74,6 +74,8 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Andriy Redko
|
* @author Andriy Redko
|
||||||
* @author Peter Großmann
|
* @author Peter Großmann
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Diego Krupitza
|
||||||
|
* @author Jędrzej Biedrzycki
|
||||||
*/
|
*/
|
||||||
public abstract class QueryUtils {
|
public abstract class QueryUtils {
|
||||||
|
|
||||||
@@ -107,7 +109,8 @@ public abstract class QueryUtils {
|
|||||||
private static final Pattern JOIN_PATTERN = Pattern.compile(JOIN, Pattern.CASE_INSENSITIVE);
|
private static final Pattern JOIN_PATTERN = Pattern.compile(JOIN, Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s";
|
private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s";
|
||||||
private static final Pattern ORDER_BY = Pattern.compile(".*order\\s+by\\s+.*", CASE_INSENSITIVE);
|
private static final Pattern ORDER_BY = Pattern.compile("(order\\s+by\\s+)", CASE_INSENSITIVE);
|
||||||
|
private static final Pattern ORDER_BY_IN_WINDOW_OR_SUBSELECT = Pattern.compile("(\\(\\s*[a-z0-9 ,.*]*order\\s+by\\s+[a-z0-9 ,.]*\\s*\\))", CASE_INSENSITIVE);
|
||||||
|
|
||||||
private static final Pattern NAMED_PARAMETER = Pattern.compile(COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER,
|
private static final Pattern NAMED_PARAMETER = Pattern.compile(COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER,
|
||||||
CASE_INSENSITIVE);
|
CASE_INSENSITIVE);
|
||||||
@@ -256,10 +259,10 @@ public abstract class QueryUtils {
|
|||||||
|
|
||||||
StringBuilder builder = new StringBuilder(query);
|
StringBuilder builder = new StringBuilder(query);
|
||||||
|
|
||||||
if (!ORDER_BY.matcher(query).matches()) {
|
if (hasOrderByClause(query)) {
|
||||||
builder.append(" order by ");
|
|
||||||
} else {
|
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
|
} else {
|
||||||
|
builder.append(" order by ");
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> joinAliases = getOuterJoinAliases(query);
|
Set<String> joinAliases = getOuterJoinAliases(query);
|
||||||
@@ -275,6 +278,31 @@ public abstract class QueryUtils {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the query has {@code order by} clause.
|
||||||
|
* The query has {@code order by} clause if there is an {@code order by} which is not part of window clause.
|
||||||
|
*
|
||||||
|
* @param query the analysed query string
|
||||||
|
* @return {@code true} if the query has {@code order by} clause, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
private static boolean hasOrderByClause(String query) {
|
||||||
|
return countOccurences(ORDER_BY, query) > countOccurences(ORDER_BY_IN_WINDOW_OR_SUBSELECT, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of occurrences of the pattern in the string
|
||||||
|
*
|
||||||
|
* @param pattern regex with a group to match
|
||||||
|
* @param string analysed string
|
||||||
|
* @return the number of occurences of the pattern in the string
|
||||||
|
*/
|
||||||
|
private static int countOccurences(Pattern pattern, String string) {
|
||||||
|
Matcher matcher = pattern.matcher(string);
|
||||||
|
int occurences = 0;
|
||||||
|
while (matcher.find()) occurences++;
|
||||||
|
return occurences;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the order clause for the given {@link Order}. Will prefix the clause with the given alias if the referenced
|
* Returns the order clause for the given {@link Order}. Will prefix the clause with the given alias if the referenced
|
||||||
* property refers to a join alias, i.e. starts with {@code $alias.}.
|
* property refers to a join alias, i.e. starts with {@code $alias.}.
|
||||||
@@ -396,10 +424,12 @@ public abstract class QueryUtils {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static String detectAlias(String query) {
|
public static String detectAlias(String query) {
|
||||||
|
String alias = null;
|
||||||
Matcher matcher = ALIAS_MATCH.matcher(query);
|
Matcher matcher = ALIAS_MATCH.matcher(query);
|
||||||
|
while (matcher.find()) {
|
||||||
return matcher.find() ? matcher.group(2) : null;
|
alias = matcher.group(2);
|
||||||
|
}
|
||||||
|
return alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.springframework.data.jpa.domain.JpaSort;
|
|||||||
* @author Grégoire Druant
|
* @author Grégoire Druant
|
||||||
* @author Mohammad Hewedy
|
* @author Mohammad Hewedy
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Jędrzej Biedrzycki
|
||||||
*/
|
*/
|
||||||
class QueryUtilsUnitTests {
|
class QueryUtilsUnitTests {
|
||||||
|
|
||||||
@@ -554,4 +555,79 @@ class QueryUtilsUnitTests {
|
|||||||
private static void assertCountQuery(String originalQuery, String countQuery) {
|
private static void assertCountQuery(String originalQuery, String countQuery) {
|
||||||
assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery);
|
assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-2260
|
||||||
|
void applySortingAccountsForNativeWindowFunction() {
|
||||||
|
|
||||||
|
Sort sort = Sort.by(Order.desc("age"));
|
||||||
|
|
||||||
|
// order by absent
|
||||||
|
assertThat(QueryUtils.applySorting("select * from user u", sort))
|
||||||
|
.isEqualTo("select * from user u order by u.age desc");
|
||||||
|
|
||||||
|
// order by present
|
||||||
|
assertThat(QueryUtils.applySorting("select * from user u order by u.lastname", sort))
|
||||||
|
.isEqualTo("select * from user u order by u.lastname, u.age desc");
|
||||||
|
|
||||||
|
// partition by
|
||||||
|
assertThat(QueryUtils.applySorting("select dense_rank() over (partition by age) from user u", sort))
|
||||||
|
.isEqualTo("select dense_rank() over (partition by age) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// order by in over clause
|
||||||
|
assertThat(QueryUtils.applySorting("select dense_rank() over (order by lastname) from user u", sort))
|
||||||
|
.isEqualTo("select dense_rank() over (order by lastname) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// order by in over clause (additional spaces)
|
||||||
|
assertThat(QueryUtils.applySorting("select dense_rank() over ( order by lastname ) from user u", sort))
|
||||||
|
.isEqualTo("select dense_rank() over ( order by lastname ) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// order by in over clause + at the end
|
||||||
|
assertThat(
|
||||||
|
QueryUtils.applySorting("select dense_rank() over (order by lastname) from user u order by u.lastname", sort))
|
||||||
|
.isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc");
|
||||||
|
|
||||||
|
// partition by + order by in over clause
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select dense_rank() over (partition by active, age order by lastname) from user u", sort)).isEqualTo(
|
||||||
|
"select dense_rank() over (partition by active, age order by lastname) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// partition by + order by in over clause + order by at the end
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select dense_rank() over (partition by active, age order by lastname) from user u order by active", sort))
|
||||||
|
.isEqualTo(
|
||||||
|
"select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc");
|
||||||
|
|
||||||
|
// partition by + order by in over clause + frame clause
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select dense_rank() over ( partition by active, age order by username rows between current row and unbounded following ) from user u",
|
||||||
|
sort)).isEqualTo(
|
||||||
|
"select dense_rank() over ( partition by active, age order by username rows between current row and unbounded following ) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// partition by + order by in over clause + frame clause + order by at the end
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select dense_rank() over ( partition by active, age order by username rows between current row and unbounded following ) from user u order by active",
|
||||||
|
sort)).isEqualTo(
|
||||||
|
"select dense_rank() over ( partition by active, age order by username rows between current row and unbounded following ) from user u order by active, u.age desc");
|
||||||
|
|
||||||
|
// order by in subselect (select expression)
|
||||||
|
assertThat(
|
||||||
|
QueryUtils.applySorting("select lastname, (select i.id from item i order by i.id limit 1) from user u", sort))
|
||||||
|
.isEqualTo(
|
||||||
|
"select lastname, (select i.id from item i order by i.id limit 1) from user u order by u.age desc");
|
||||||
|
|
||||||
|
// order by in subselect (select expression) + at the end
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select lastname, (select i.id from item i order by 1 limit 1) from user u order by active", sort)).isEqualTo(
|
||||||
|
"select lastname, (select i.id from item i order by 1 limit 1) from user u order by active, u.age desc");
|
||||||
|
|
||||||
|
// order by in subselect (from expression)
|
||||||
|
assertThat(QueryUtils.applySorting("select * from (select * from user order by age desc limit 10) u", sort))
|
||||||
|
.isEqualTo("select * from (select * from user order by age desc limit 10) u order by age desc");
|
||||||
|
|
||||||
|
// order by in subselect (from expression) + at the end
|
||||||
|
assertThat(QueryUtils.applySorting(
|
||||||
|
"select * from (select * from user order by 1, 2, 3 desc limit 10) u order by u.active asc", sort)).isEqualTo(
|
||||||
|
"select * from (select * from user order by 1, 2, 3 desc limit 10) u order by u.active asc, age desc");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user