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
This commit is contained in:
Mark Paluch
2025-06-10 10:27:13 +02:00
parent 98e801cfd5
commit 3d28143f22
2 changed files with 47 additions and 22 deletions

View File

@@ -195,17 +195,15 @@ public abstract class QueryUtils {
// any function call including parameters within the brackets
builder.append("\\w+\\s*\\([\\w\\.,\\s'=:;\\\\?]+\\)");
// the potential alias
builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))");
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
builder.append("\\s+(?:as)+\\s+([\\w\\.]+)"); // the potential alias
FIELD_ALIAS_PATTERN = compile(builder.toString());
}
/**
@@ -391,7 +389,7 @@ public abstract class QueryUtils {
* @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<String> getFieldAliases(String query) {
static Set<String> getFieldAliases(String query) {
Set<String> result = new HashSet<>();
Matcher matcher = FIELD_ALIAS_PATTERN.matcher(query);

View File

@@ -51,6 +51,7 @@ import org.springframework.data.jpa.domain.JpaSort;
* @author Erik Pellizzon
* @author Pranav HS
* @author Eduard Dudar
* @author Mark Paluch
*/
class QueryUtilsUnitTests {
@@ -130,13 +131,13 @@ class QueryUtilsUnitTests {
.isEqualTo("u");
assertThat(detectAlias(
"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"))
.isEqualTo("f");
.isEqualTo("f");
assertThat(detectAlias(
"(from Foo f max(f) ((((select * from Foo f2 (from Foo f3) max(*)) (from Foo f4)) max(f5)) (f6)) (from Foo f7))"))
.isEqualTo("f");
.isEqualTo("f");
assertThat(detectAlias(
"SELECT e FROM DbEvent e WHERE (CAST(:modifiedFrom AS date) IS NULL OR e.modificationDate >= :modifiedFrom)"))
.isEqualTo("e");
.isEqualTo("e");
assertThat(detectAlias("from User u where (cast(:effective as date) is null) OR :effective >= u.createdAt"))
.isEqualTo("u");
assertThat(detectAlias("from User u where (cast(:effectiveDate as date) is null) OR :effectiveDate >= u.createdAt"))
@@ -145,7 +146,7 @@ class QueryUtilsUnitTests {
.isEqualTo("u");
assertThat(
detectAlias("from User u where (cast(:e1f2f3ectiveFrom as date) is null) OR :effectiveFrom >= u.createdAt"))
.isEqualTo("u");
.isEqualTo("u");
}
@Test // GH-2260
@@ -175,13 +176,13 @@ class QueryUtilsUnitTests {
.isEqualTo("(select u from User u where not exists )");
assertThat(normalizeWhitespace(
removeSubqueries("select u from User u where not exists (from User u2 where not exists (from User u3))")))
.isEqualTo("select u from User u where not exists");
.isEqualTo("select u from User u where not exists");
assertThat(normalizeWhitespace(
removeSubqueries("select u from User u where not exists ((from User u2 where not exists (from User u3)))")))
.isEqualTo("select u from User u where not exists ( )");
.isEqualTo("select u from User u where not exists ( )");
assertThat(normalizeWhitespace(
removeSubqueries("(select u from User u where not exists ((from User u2 where not exists (from User u3))))")))
.isEqualTo("(select u from User u where not exists ( ))");
.isEqualTo("(select u from User u where not exists ( ))");
}
@Test // GH-2581
@@ -543,6 +544,32 @@ class QueryUtilsUnitTests {
assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc");
}
@Test // GH-3911
void discoversFunctionAliasesCorrectly() {
assertThat(getFunctionAliases("SELECT COUNT(1) a alias1,2 s alias2")).isEmpty();
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,2 as alias2")).containsExactly("alias1");
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1");
assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, COUNT(2) as alias2")).contains("alias1", "alias2");
assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2");
assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2");
assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2");
assertThat(getFunctionAliases("COUNT(1) as alias1,2 as alias2")).containsExactly("alias1");
assertThat(getFunctionAliases("COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1");
}
@Test // GH-3911
void discoversFieldAliasesCorrectly() {
assertThat(getFieldAliases("SELECT 1 a alias1,2 s alias2")).isEmpty();
assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2");
assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2");
assertThat(getFieldAliases("1 as alias1,2 as alias2")).contains("alias1", "alias2");
assertThat(getFieldAliases("1 as alias1, 2 as alias2")).contains("alias1", "alias2");
}
@Test // DATAJPA-1000
void discoversCorrectAliasForJoinFetch() {
@@ -564,7 +591,7 @@ class QueryUtilsUnitTests {
assertThat(
QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
.contains("myAlias");
.contains("myAlias");
}
@Test // DATAJPA-1506
@@ -784,18 +811,19 @@ class QueryUtilsUnitTests {
// 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");
.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(
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");
.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(
@@ -812,8 +840,7 @@ class QueryUtilsUnitTests {
// 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");
.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(
@@ -949,7 +976,7 @@ class QueryUtilsUnitTests {
@Test // GH-3324
void createCountQueryForSimpleQuery() {
assertCountQuery("select * from User","select count(*) from User");
assertCountQuery("select * from User u","select count(u) from User u");
assertCountQuery("select * from User", "select count(*) from User");
assertCountQuery("select * from User u", "select count(u) from User u");
}
}