Skip to content

Commit 284b97b

Browse files
committedJun 10, 2025··
Fix QueryUtils regex parsing field and function aliases.
Remove leading space requirement, simplify group nesting and replace character class with non-capturing group to avoid a, s and | (pipe) matching. Closes #3911
·
3.4.113.4.7
1 parent 140f2dc commit 284b97b

File tree

2 files changed

+47
-22
lines changed

2 files changed

+47
-22
lines changed
 

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,15 @@ public abstract class QueryUtils {
202202
// any function call including parameters within the brackets
203203
builder.append("\\w+\\s*\\([\\w\\.,\\s'=:;\\\\?]+\\)");
204204
// the potential alias
205-
builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))");
205+
builder.append("\\s+(?:as|AS)+\\s+([\\w\\.]+)");
206206

207207
FUNCTION_PATTERN = compile(builder.toString());
208208

209209
builder = new StringBuilder();
210-
builder.append("\\s+"); // at least one space
211210
builder.append("[^\\s\\(\\)]+"); // No white char no bracket
212-
builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); // the potential alias
211+
builder.append("\\s+(?:as)+\\s+([\\w\\.]+)"); // the potential alias
213212

214213
FIELD_ALIAS_PATTERN = compile(builder.toString());
215-
216214
}
217215

218216
/**
@@ -399,7 +397,7 @@ static Set<String> getOuterJoinAliases(String query) {
399397
* @param query a {@literal String} containing a query. Must not be {@literal null}.
400398
* @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
401399
*/
402-
private static Set<String> getFieldAliases(String query) {
400+
static Set<String> getFieldAliases(String query) {
403401

404402
Set<String> result = new HashSet<>();
405403
Matcher matcher = FIELD_ALIAS_PATTERN.matcher(query);

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

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
* @author Erik Pellizzon
5252
* @author Pranav HS
5353
* @author Eduard Dudar
54+
* @author Mark Paluch
5455
*/
5556
class QueryUtilsUnitTests {
5657

@@ -130,13 +131,13 @@ void detectsAliasCorrectly() {
130131
.isEqualTo("u");
131132
assertThat(detectAlias(
132133
"from Foo f left join f.bar b with type(b) = BarChild where (f.id = (select max(f.id) from Foo f2 where type(f2) = FooChild) or 1 <> 1) and 1=1"))
133-
.isEqualTo("f");
134+
.isEqualTo("f");
134135
assertThat(detectAlias(
135136
"(from Foo f max(f) ((((select * from Foo f2 (from Foo f3) max(*)) (from Foo f4)) max(f5)) (f6)) (from Foo f7))"))
136-
.isEqualTo("f");
137+
.isEqualTo("f");
137138
assertThat(detectAlias(
138139
"SELECT e FROM DbEvent e WHERE (CAST(:modifiedFrom AS date) IS NULL OR e.modificationDate >= :modifiedFrom)"))
139-
.isEqualTo("e");
140+
.isEqualTo("e");
140141
assertThat(detectAlias("from User u where (cast(:effective as date) is null) OR :effective >= u.createdAt"))
141142
.isEqualTo("u");
142143
assertThat(detectAlias("from User u where (cast(:effectiveDate as date) is null) OR :effectiveDate >= u.createdAt"))
@@ -145,7 +146,7 @@ void detectsAliasCorrectly() {
145146
.isEqualTo("u");
146147
assertThat(
147148
detectAlias("from User u where (cast(:e1f2f3ectiveFrom as date) is null) OR :effectiveFrom >= u.createdAt"))
148-
.isEqualTo("u");
149+
.isEqualTo("u");
149150
}
150151

151152
@Test // GH-2260
@@ -175,13 +176,13 @@ void testRemoveSubqueries() throws Exception {
175176
.isEqualTo("(select u from User u where not exists )");
176177
assertThat(normalizeWhitespace(
177178
removeSubqueries("select u from User u where not exists (from User u2 where not exists (from User u3))")))
178-
.isEqualTo("select u from User u where not exists");
179+
.isEqualTo("select u from User u where not exists");
179180
assertThat(normalizeWhitespace(
180181
removeSubqueries("select u from User u where not exists ((from User u2 where not exists (from User u3)))")))
181-
.isEqualTo("select u from User u where not exists ( )");
182+
.isEqualTo("select u from User u where not exists ( )");
182183
assertThat(normalizeWhitespace(
183184
removeSubqueries("(select u from User u where not exists ((from User u2 where not exists (from User u3))))")))
184-
.isEqualTo("(select u from User u where not exists ( ))");
185+
.isEqualTo("(select u from User u where not exists ( ))");
185186
}
186187

187188
@Test // GH-2581
@@ -543,6 +544,32 @@ void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpa
543544
assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc");
544545
}
545546

547+
@Test // GH-3911
548+
void discoversFunctionAliasesCorrectly() {
549+
550+
assertThat(getFunctionAliases("SELECT COUNT(1) a alias1,2 s alias2")).isEmpty();
551+
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,2 as alias2")).containsExactly("alias1");
552+
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
553+
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1");
554+
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, COUNT(2) as alias2")).contains("alias1", "alias2");
555+
assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
556+
assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
557+
assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2");
558+
assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2");
559+
assertThat(getFunctionAliases("COUNT(1) as alias1,2 as alias2")).containsExactly("alias1");
560+
assertThat(getFunctionAliases("COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1");
561+
}
562+
563+
@Test // GH-3911
564+
void discoversFieldAliasesCorrectly() {
565+
566+
assertThat(getFieldAliases("SELECT 1 a alias1,2 s alias2")).isEmpty();
567+
assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2");
568+
assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2");
569+
assertThat(getFieldAliases("1 as alias1,2 as alias2")).contains("alias1", "alias2");
570+
assertThat(getFieldAliases("1 as alias1, 2 as alias2")).contains("alias1", "alias2");
571+
}
572+
546573
@Test // DATAJPA-1000
547574
void discoversCorrectAliasForJoinFetch() {
548575

@@ -564,7 +591,7 @@ void discoversAliasWithComplexFunction() {
564591

565592
assertThat(
566593
QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
567-
.contains("myAlias");
594+
.contains("myAlias");
568595
}
569596

570597
@Test // DATAJPA-1506
@@ -784,18 +811,19 @@ void applySortingAccountsForNativeWindowFunction() {
784811
// order by in over clause + at the end
785812
assertThat(
786813
QueryUtils.applySorting("select dense_rank() over (order by lastname) from user u order by u.lastname", sort))
787-
.isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc");
814+
.isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc");
788815

789816
// partition by + order by in over clause
790-
assertThat(QueryUtils.applySorting(
791-
"select dense_rank() over (partition by active, age order by lastname) from user u", sort)).isEqualTo(
817+
assertThat(QueryUtils
818+
.applySorting("select dense_rank() over (partition by active, age order by lastname) from user u", sort))
819+
.isEqualTo(
792820
"select dense_rank() over (partition by active, age order by lastname) from user u order by u.age desc");
793821

794822
// partition by + order by in over clause + order by at the end
795823
assertThat(QueryUtils.applySorting(
796824
"select dense_rank() over (partition by active, age order by lastname) from user u order by active", sort))
797-
.isEqualTo(
798-
"select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc");
825+
.isEqualTo(
826+
"select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc");
799827

800828
// partition by + order by in over clause + frame clause
801829
assertThat(QueryUtils.applySorting(
@@ -812,8 +840,7 @@ void applySortingAccountsForNativeWindowFunction() {
812840
// order by in subselect (select expression)
813841
assertThat(
814842
QueryUtils.applySorting("select lastname, (select i.id from item i order by i.id limit 1) from user u", sort))
815-
.isEqualTo(
816-
"select lastname, (select i.id from item i order by i.id limit 1) from user u order by u.age desc");
843+
.isEqualTo("select lastname, (select i.id from item i order by i.id limit 1) from user u order by u.age desc");
817844

818845
// order by in subselect (select expression) + at the end
819846
assertThat(QueryUtils.applySorting(
@@ -949,7 +976,7 @@ select q.specialist_id, listagg(q.points, '%s') as points
949976

950977
@Test // GH-3324
951978
void createCountQueryForSimpleQuery() {
952-
assertCountQuery("select * from User","select count(*) from User");
953-
assertCountQuery("select * from User u","select count(u) from User u");
979+
assertCountQuery("select * from User", "select count(*) from User");
980+
assertCountQuery("select * from User u", "select count(u) from User u");
954981
}
955982
}

0 commit comments

Comments
 (0)
Please sign in to comment.