Skip to content

Commit af7c5d9

Browse files
committed
Better parse 'order by' clauses to handle subqueries.
By properly parsing "order by" clauses, Spring Data JPA can apply sorting parameters to the end of queries in the event of complex queries that involve nested subselects. Closes ##2496, #2522, #2537, #2045.
1 parent 2b6715a commit af7c5d9

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ public abstract class QueryUtils {
113113

114114
private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s";
115115
private static final Pattern ORDER_BY = Pattern.compile("(order\\s+by\\s+)", CASE_INSENSITIVE);
116-
private static final Pattern ORDER_BY_IN_WINDOW_OR_SUBSELECT = Pattern
117-
.compile("(\\(\\s*[a-z0-9 ,.*]*order\\s+by\\s+[a-z0-9 ,.]*\\s*\\))", CASE_INSENSITIVE);
116+
private static final Pattern ORDER_BY_IN_WINDOW_OR_SUBSELECT = Pattern.compile("(\\(.*order\\s+by\\s.*\\)) ",
117+
CASE_INSENSITIVE);
118118

119119
private static final Pattern NAMED_PARAMETER = Pattern.compile(COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER,
120120
CASE_INSENSITIVE);

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

+62
Original file line numberDiff line numberDiff line change
@@ -733,4 +733,66 @@ void countQueryUsesCorrectVariable() {
733733
assertThat(countQueryFor)
734734
.isEqualTo("select count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)");
735735
}
736+
737+
@Test // GH-2496, GH-2522, GH-2537, GH-2045
738+
void orderByShouldWorkWithSubSelectStatements() {
739+
740+
Sort sort = Sort.by(Order.desc("age"));
741+
742+
assertThat(QueryUtils.applySorting("SELECT\n" //
743+
+ " foo_bar.*\n" //
744+
+ "FROM\n" //
745+
+ " foo foo\n" //
746+
+ "INNER JOIN\n" //
747+
+ " foo_bar_dnrmv foo_bar ON\n" //
748+
+ " foo_bar.foo_id = foo.foo_id\n" //
749+
+ "INNER JOIN\n" //
750+
+ " (\n" //
751+
+ " SELECT\n" //
752+
+ " foo_bar_action.*,\n" //
753+
+ " RANK() OVER (PARTITION BY \"foo_bar_action\".attributes->>'baz' ORDER BY \"foo_bar_action\".attributes->>'qux' DESC) AS ranking\n" //
754+
+ " FROM\n" //
755+
+ " foo_bar_action\n" //
756+
+ " WHERE\n" //
757+
+ " foo_bar_action.deleted_ts IS NULL)\n" //
758+
+ " foo_bar_action ON\n" //
759+
+ " foo_bar.foo_bar_id = foo_bar_action.foo_bar_id\n" //
760+
+ " AND ranking = 1\n" //
761+
+ "INNER JOIN\n" //
762+
+ " bar bar ON\n" //
763+
+ " foo_bar.bar_id = bar.bar_id\n" //
764+
+ "INNER JOIN\n" //
765+
+ " bar_metadata bar_metadata ON\n" //
766+
+ " bar.bar_metadata_key = bar_metadata.bar_metadata_key\n" //
767+
+ "WHERE\n" //
768+
+ " foo.tenant_id =:tenantId\n" //
769+
+ "AND (foo.attributes ->> :serialNum IN (:serialNumValue))", sort)).endsWith("order by foo.age desc");
770+
771+
assertThat(QueryUtils.applySorting("select r " //
772+
+ "From DataRecord r " //
773+
+ "where " //
774+
+ " ( " //
775+
+ " r.adusrId = :userId " //
776+
+ " or EXISTS( select 1 FROM DataRecordDvsRight dr WHERE dr.adusrId = :userId AND dr.dataRecord = r ) " //
777+
+ ")", sort)).endsWith("order by r.age desc");
778+
779+
assertThat(QueryUtils.applySorting("select distinct u " //
780+
+ "from FooBar u " //
781+
+ "where [REDACTED] " //
782+
+ "and (" //
783+
+ " not exists (" //
784+
+ " from FooBarGroup group " //
785+
+ " where group in :excludedGroups " //
786+
+ " and group in elements(u.groups)" //
787+
+ " )" //
788+
+ ")", sort)).endsWith("order by u.age desc");
789+
790+
assertThat(QueryUtils.applySorting("SELECT i " //
791+
+ "FROM Item i " //
792+
+ "FETCH ALL PROPERTIES \" " //
793+
+ "+ \"WHERE i.id IN (\" " //
794+
+ "+ \"SELECT max(i2.id) FROM Item i2 \" " //
795+
+ "+ \"WHERE i2.field.id = :fieldId \" " //
796+
+ "+ \"GROUP BY i2.field.id, i2.version)", sort)).endsWith("order by i.age desc");
797+
}
736798
}

0 commit comments

Comments
 (0)