Skip to content

Commit e33f31e

Browse files
mp911deschauder
authored andcommitted
Refactor QueryEnhancer Tests into TCK-style.
De-duplicate code, use parametrized tests instead of test methods to verify individual fixtures. Ensure all variants are tested with JSQLparser and the default enhancer. See #2773 Original pull request #2777
1 parent 65f18f3 commit e33f31e

File tree

4 files changed

+320
-269
lines changed

4 files changed

+320
-269
lines changed

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java

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

18+
import org.junit.jupiter.api.Disabled;
19+
import org.junit.jupiter.api.Test;
20+
1821
/**
1922
* TCK Tests for {@link DefaultQueryEnhancer}.
2023
*
@@ -27,4 +30,8 @@ QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) {
2730
return new DefaultQueryEnhancer(declaredQuery);
2831
}
2932

33+
@Override
34+
@Test // GH-2511, GH-2773
35+
@Disabled("Not properly supported by QueryUtils")
36+
void shouldDeriveNativeCountQueryWithVariable(String query, String expected) {}
3037
}

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java

+190
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,23 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.assertj.core.api.Assumptions.*;
20+
21+
import java.util.stream.Stream;
22+
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.springframework.data.domain.Sort;
28+
1829
/**
1930
* TCK Tests for {@link JSqlParserQueryEnhancer}.
2031
*
2132
* @author Mark Paluch
33+
* @author Diego Krupitza
34+
* @author Geoffrey Deremetz
2235
*/
2336
public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
2437

@@ -27,4 +40,181 @@ QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) {
2740
return new JSqlParserQueryEnhancer(declaredQuery);
2841
}
2942

43+
@Override
44+
@ParameterizedTest // GH-2773
45+
@MethodSource("jpqlCountQueries")
46+
void shouldDeriveJpqlCountQuery(String query, String expected) {
47+
48+
assumeThat(query).as("JSQLParser does not support simple JPQL syntax").doesNotStartWithIgnoringCase("FROM");
49+
50+
assumeThat(query).as("JSQLParser does not support constructor JPQL syntax").doesNotContain(" new ");
51+
52+
super.shouldDeriveJpqlCountQuery(query, expected);
53+
}
54+
55+
@Test
56+
// GH-2578
57+
void setOperationListWorks() {
58+
59+
String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" //
60+
+ "except \n" //
61+
+ "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE";
62+
63+
StringQuery stringQuery = new StringQuery(setQuery, true);
64+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
65+
66+
assertThat(stringQuery.getAlias()).isNullOrEmpty();
67+
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
68+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
69+
70+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery);
71+
assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN"))).endsWith("ORDER BY SOME_COLUMN ASC");
72+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
73+
assertThat(queryEnhancer.detectAlias()).isNullOrEmpty();
74+
assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
75+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
76+
}
77+
78+
@Test // GH-2578
79+
void complexSetOperationListWorks() {
80+
81+
String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" //
82+
+ "except \n" //
83+
+ "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE \n" //
84+
+ "union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE";
85+
86+
StringQuery stringQuery = new StringQuery(setQuery, true);
87+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
88+
89+
assertThat(stringQuery.getAlias()).isNullOrEmpty();
90+
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
91+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
92+
93+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery);
94+
assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN").ascending())).endsWith("ORDER BY SOME_COLUMN ASC");
95+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
96+
assertThat(queryEnhancer.detectAlias()).isNullOrEmpty();
97+
assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
98+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
99+
}
100+
101+
@Test // GH-2578
102+
void deeplyNestedcomplexSetOperationListWorks() {
103+
104+
String setQuery = "SELECT CustomerID FROM (\n" //
105+
+ "\t\t\tselect * from Customers\n" //
106+
+ "\t\t\texcept\n"//
107+
+ "\t\t\tselect * from Customers where country = 'Austria'\n"//
108+
+ "\t)\n" //
109+
+ "\texcept\n"//
110+
+ "\tselect CustomerID from customers where country = 'Germany'\n"//
111+
+ "\t;";
112+
113+
StringQuery stringQuery = new StringQuery(setQuery, true);
114+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
115+
116+
assertThat(stringQuery.getAlias()).isNullOrEmpty();
117+
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("CustomerID");
118+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
119+
120+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery);
121+
assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).endsWith("ORDER BY CustomerID DESC");
122+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
123+
assertThat(queryEnhancer.detectAlias()).isNullOrEmpty();
124+
assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("CustomerID");
125+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
126+
}
127+
128+
@Test // GH-2578
129+
void valuesStatementsWorks() {
130+
131+
String setQuery = "VALUES (1, 2, 'test')";
132+
133+
StringQuery stringQuery = new StringQuery(setQuery, true);
134+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
135+
136+
assertThat(stringQuery.getAlias()).isNullOrEmpty();
137+
assertThat(stringQuery.getProjection()).isNullOrEmpty();
138+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
139+
140+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery);
141+
assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).isEqualTo(setQuery);
142+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
143+
assertThat(queryEnhancer.detectAlias()).isNullOrEmpty();
144+
assertThat(queryEnhancer.getProjection()).isNullOrEmpty();
145+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
146+
}
147+
148+
@Test // GH-2578
149+
void withStatementsWorks() {
150+
151+
String setQuery = "with sample_data(day, value) as (values ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))) \n"
152+
+ "select day, value from sample_data as a";
153+
154+
StringQuery stringQuery = new StringQuery(setQuery, true);
155+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
156+
157+
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
158+
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
159+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
160+
161+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(
162+
"with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)))\n"
163+
+ "SELECT count(1) FROM sample_data AS a");
164+
assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC");
165+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
166+
assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a");
167+
assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value");
168+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
169+
}
170+
171+
@Test // GH-2578
172+
void multipleWithStatementsWorks() {
173+
174+
String setQuery = "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"
175+
+ "select day, value from sample_data as a";
176+
177+
StringQuery stringQuery = new StringQuery(setQuery, true);
178+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
179+
180+
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
181+
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
182+
assertThat(stringQuery.hasConstructorExpression()).isFalse();
183+
184+
assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(
185+
"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"
186+
+ "SELECT count(1) FROM sample_data AS a");
187+
assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC");
188+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
189+
assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a");
190+
assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value");
191+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
192+
}
193+
194+
@ParameterizedTest // GH-2641
195+
@MethodSource("mergeStatementWorksSource")
196+
void mergeStatementWorksWithJSqlParser(String query, String alias) {
197+
198+
StringQuery stringQuery = new StringQuery(query, true);
199+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
200+
201+
assertThat(queryEnhancer.detectAlias()).isEqualTo(alias);
202+
assertThat(QueryUtils.detectAlias(query)).isNull();
203+
204+
assertThat(queryEnhancer.getJoinAliases()).isEmpty();
205+
assertThat(queryEnhancer.detectAlias()).isEqualTo(alias);
206+
assertThat(queryEnhancer.getProjection()).isEmpty();
207+
assertThat(queryEnhancer.hasConstructorExpression()).isFalse();
208+
}
209+
210+
static Stream<Arguments> mergeStatementWorksSource() {
211+
212+
return Stream.of( //
213+
Arguments.of(
214+
"merge into a using (select id, value from b) query on (a.id = query.id) when matched then update set a.value = value",
215+
"query"),
216+
Arguments.of(
217+
"merge into a using (select id2, value from b) on (id = id2) when matched then update set a.value = value",
218+
null));
219+
}
30220
}

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java

+123-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.stream.Stream;
2121

22+
import org.junit.jupiter.api.Test;
2223
import org.junit.jupiter.params.ParameterizedTest;
2324
import org.junit.jupiter.params.provider.Arguments;
2425
import org.junit.jupiter.params.provider.MethodSource;
@@ -36,9 +37,13 @@ void shouldDeriveNativeCountQuery(String query, String expected) {
3637

3738
DeclaredQuery declaredQuery = DeclaredQuery.of(query, true);
3839
QueryEnhancer enhancer = createQueryEnhancer(declaredQuery);
39-
String countQueryFor = enhancer.createCountQueryFor(null);
40+
String countQueryFor = enhancer.createCountQueryFor();
4041

41-
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
42+
// lenient cleanup to allow for rendering variance
43+
String sanitized = countQueryFor.replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" {2}", " ")
44+
.replaceAll(" {2}", " ").trim();
45+
46+
assertThat(sanitized).isEqualToIgnoringCase(expected);
4247
}
4348

4449
static Stream<Arguments> nativeCountQueries() {
@@ -53,7 +58,58 @@ static Stream<Arguments> nativeCountQueries() {
5358

5459
Arguments.of( //
5560
"SELECT DISTINCT name FROM table_name some_alias", //
56-
"select count(DISTINCT name) FROM table_name some_alias"));
61+
"select count(DISTINCT name) FROM table_name some_alias"), //
62+
63+
Arguments.of( //
64+
"select distinct u from User u where u.foo = ?", //
65+
"select count(distinct u) from User u where u.foo = ?"),
66+
67+
Arguments.of( //
68+
"select u from User as u", //
69+
"select count(u) from User as u"),
70+
71+
Arguments.of( //
72+
"SELECT u FROM User u where u.foo.bar = ?", //
73+
"select count(u) FROM User u where u.foo.bar = ?"),
74+
75+
Arguments.of( //
76+
"select p.lastname,p.firstname from Person p", //
77+
"select count(1) from Person p"),
78+
79+
// whitespace quirks
80+
Arguments.of( //
81+
"""
82+
select user.age,
83+
user.name
84+
from User user
85+
where user.age = 18
86+
order
87+
by
88+
user.name
89+
\s""", //
90+
"select count(1) from User user where user.age = 18"),
91+
92+
Arguments.of( //
93+
"select * from User user\n" + //
94+
" where user.age = 18\n" + //
95+
" order by user.name\n ", //
96+
"select count(1) from User user where user.age = 18"),
97+
98+
Arguments.of( //
99+
"SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", //
100+
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"),
101+
102+
Arguments.of( //
103+
"SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", //
104+
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"),
105+
106+
Arguments.of( //
107+
"SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799", //
108+
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799"),
109+
110+
Arguments.of( //
111+
"select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC", //
112+
"select count(distinct m.genre) from Media m where m.user = ?1"));
57113
}
58114

59115
@ParameterizedTest // GH-2773
@@ -79,7 +135,70 @@ static Stream<Arguments> jpqlCountQueries() {
79135

80136
Arguments.of( //
81137
"SELECT DISTINCT name FROM table_name some_alias", //
82-
"select count(DISTINCT name) FROM table_name some_alias"));
138+
"select count(DISTINCT name) FROM table_name some_alias"),
139+
140+
Arguments.of( //
141+
"select distinct new User(u.name) from User u where u.foo = ?", //
142+
"select count(distinct u) from User u where u.foo = ?"),
143+
144+
Arguments.of( //
145+
"FROM User u WHERE u.foo.bar = ?", //
146+
"select count(u) FROM User u WHERE u.foo.bar = ?"),
147+
148+
Arguments.of( //
149+
"from User u", //
150+
"select count(u) FROM User u"),
151+
152+
Arguments.of( //
153+
"select u from User as u", //
154+
"select count(u) from User as u"),
155+
156+
Arguments.of( //
157+
"select p.lastname,p.firstname from Person p", //
158+
"select count(p) from Person p"),
159+
160+
Arguments.of( //
161+
"select a.b from A a", //
162+
"select count(a.b) from A a"),
163+
164+
Arguments.of( //
165+
"select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", //
166+
"select count(distinct m.genre) from Media m where m.user = ?1"));
167+
}
168+
169+
@ParameterizedTest // GH-2511, GH-2773
170+
@MethodSource("nativeQueriesWithVariables")
171+
void shouldDeriveNativeCountQueryWithVariable(String query, String expected) {
172+
173+
DeclaredQuery declaredQuery = DeclaredQuery.of(query, true);
174+
QueryEnhancer enhancer = createQueryEnhancer(declaredQuery);
175+
String countQueryFor = enhancer.createCountQueryFor();
176+
177+
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
178+
}
179+
180+
static Stream<Arguments> nativeQueriesWithVariables() {
181+
182+
return Stream.of(Arguments.of( //
183+
"SELECT * FROM User WHERE created_at > $1", //
184+
"SELECT count(1) FROM User WHERE created_at > $1"), //
185+
186+
Arguments.of( //
187+
"SELECT * FROM (select * from test) ", //
188+
"SELECT count(1) FROM (SELECT * FROM test)"), //
189+
190+
Arguments.of( //
191+
"SELECT * FROM (select * from test) as test", //
192+
"SELECT count(1) FROM (SELECT * FROM test) AS test"));
193+
}
194+
195+
@Test
196+
// DATAJPA-1696
197+
void findProjectionClauseWithIncludedFrom() {
198+
199+
StringQuery query = new StringQuery("select x, frommage, y from t", true);
200+
201+
assertThat(createQueryEnhancer(query).getProjection()).isEqualTo("x, frommage, y");
83202
}
84203

85204
abstract QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery);

0 commit comments

Comments
 (0)