Skip to content

Commit a851022

Browse files
committed
Differentiate between JPQL and native queries in count query derivation.
We now consider whether a query is a native one when deriving a count query for pagination. Previously, the generated queries used JPQL syntax that doesn't comply with native SQL syntax rules. Closes #2773 Original pull request #2777
1 parent 9db5541 commit a851022

File tree

4 files changed

+39
-16
lines changed

4 files changed

+39
-16
lines changed

src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public String detectAlias() {
4646

4747
@Override
4848
public String createCountQueryFor(@Nullable String countProjection) {
49-
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
49+
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, this.query.isNativeQuery());
5050
}
5151

5252
@Override

src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlCount;
19-
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlLower;
20-
import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression;
18+
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
19+
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
2120

2221
import net.sf.jsqlparser.JSQLParserException;
2322
import net.sf.jsqlparser.expression.Alias;
@@ -414,7 +413,7 @@ public String createCountQueryFor(@Nullable String countProjection) {
414413
return selectBody.toString();
415414
}
416415

417-
String countProp = tableAlias == null ? "*" : tableAlias;
416+
String countProp = query.isNativeQuery() ? (distinct ? "*" : "1") : tableAlias == null ? "*" : tableAlias;
418417

419418
Function jSqlCount = getJSqlCount(Collections.singletonList(countProp), distinct);
420419
selectBody.setSelectItems(Collections.singletonList(new SelectExpressionItem(jSqlCount)));

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

+21-3
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,19 @@ public static String createCountQueryFor(String originalQuery) {
570570
*/
571571
@Deprecated
572572
public static String createCountQueryFor(String originalQuery, @Nullable String countProjection) {
573+
return createCountQueryFor(originalQuery, countProjection, false);
574+
}
575+
576+
/**
577+
* Creates a count projected query from the given original query.
578+
*
579+
* @param originalQuery must not be {@literal null}.
580+
* @param countProjection may be {@literal null}.
581+
* @param nativeQuery whether the underlying query is a native query.
582+
* @return a query String to be used a count query for pagination. Guaranteed to be not {@literal null}.
583+
* @since 2.7.8
584+
*/
585+
static String createCountQueryFor(String originalQuery, @Nullable String countProjection, boolean nativeQuery) {
573586

574587
Assert.hasText(originalQuery, "OriginalQuery must not be null or empty!");
575588

@@ -591,9 +604,14 @@ public static String createCountQueryFor(String originalQuery, @Nullable String
591604

592605
String replacement = useVariable ? SIMPLE_COUNT_VALUE : complexCountValue;
593606

594-
String alias = QueryUtils.detectAlias(originalQuery);
595-
if ("*".equals(variable) && alias != null) {
596-
replacement = alias;
607+
if (nativeQuery && (variable.contains(",") || "*".equals(variable))) {
608+
replacement = "1";
609+
} else {
610+
611+
String alias = QueryUtils.detectAlias(originalQuery);
612+
if (("*".equals(variable) && alias != null)) {
613+
replacement = alias;
614+
}
597615
}
598616

599617
countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, replacement));

src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java

+14-8
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,12 @@ void createsCountQueryForNestedReferenceCorrectly() {
228228

229229
@Test // DATAJPA-420
230230
void createsCountQueryForScalarSelects() {
231-
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", true);
231+
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", false);
232+
}
233+
234+
@Test // DATAJPA-420
235+
void createsCountQueryForNativeScalarSelects() {
236+
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(1) from Person p", true);
232237
}
233238

234239
@Test // DATAJPA-456
@@ -487,7 +492,7 @@ void createCountQuerySupportsWhitespaceCharacters() {
487492
" order by user.name\n ", true);
488493

489494
assertThat(getEnhancer(query).createCountQueryFor())
490-
.isEqualToIgnoringCase("select count(user) from User user where user.age = 18");
495+
.isEqualToIgnoringCase("select count(1) from User user where user.age = 18");
491496
}
492497

493498
@Test
@@ -500,7 +505,7 @@ void createCountQuerySupportsLineBreaksInSelectClause() {
500505
" order\nby\nuser.name\n ", true);
501506

502507
assertThat(getEnhancer(query).createCountQueryFor())
503-
.isEqualToIgnoringCase("select count(user) from User user where user.age = 18");
508+
.isEqualToIgnoringCase("select count(1) from User user where user.age = 18");
504509
}
505510

506511
@Test // DATAJPA-1061
@@ -721,17 +726,17 @@ void countQueryUsesCorrectVariable() {
721726

722727
QueryEnhancer queryEnhancer = getEnhancer(nativeQuery);
723728
String countQueryFor = queryEnhancer.createCountQueryFor();
724-
assertThat(countQueryFor).isEqualTo("SELECT count(*) FROM User WHERE created_at > $1");
729+
assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM User WHERE created_at > $1");
725730

726731
nativeQuery = new StringQuery("SELECT * FROM (select * from test) ", true);
727732
queryEnhancer = getEnhancer(nativeQuery);
728733
countQueryFor = queryEnhancer.createCountQueryFor();
729-
assertThat(countQueryFor).isEqualTo("SELECT count(*) FROM (SELECT * FROM test)");
734+
assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test)");
730735

731736
nativeQuery = new StringQuery("SELECT * FROM (select * from test) as test", true);
732737
queryEnhancer = getEnhancer(nativeQuery);
733738
countQueryFor = queryEnhancer.createCountQueryFor();
734-
assertThat(countQueryFor).isEqualTo("SELECT count(test) FROM (SELECT * FROM test) AS test");
739+
assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test) AS test");
735740
}
736741

737742
@Test // GH-2555
@@ -861,7 +866,7 @@ void withStatementsWorksWithJSQLParser() {
861866

862867
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(
863868
"with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)))\n"
864-
+ "SELECT count(a) FROM sample_data AS a");
869+
+ "SELECT count(1) FROM sample_data AS a");
865870
assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC");
866871
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
867872
assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a");
@@ -884,7 +889,7 @@ void multipleWithStatementsWorksWithJSQLParser() {
884889

885890
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(
886891
"with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))),test2 AS (VALUES (1, 2, 3))\n"
887-
+ "SELECT count(a) FROM sample_data AS a");
892+
+ "SELECT count(1) FROM sample_data AS a");
888893
assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC");
889894
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
890895
assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a");
@@ -989,4 +994,5 @@ private static void endsIgnoringCase(String original, String endWithIgnoreCase)
989994
private static QueryEnhancer getEnhancer(DeclaredQuery query) {
990995
return QueryEnhancerFactory.forQuery(query);
991996
}
997+
992998
}

0 commit comments

Comments
 (0)