From c0e9211ae5d292ff505c9b1ce353e85ef61417b7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Jan 2023 11:59:37 +0100 Subject: [PATCH 1/3] Prepare issue branch. --- pom.xml | 2 +- spring-data-envers/pom.xml | 4 ++-- spring-data-jpa-distribution/pom.xml | 2 +- spring-data-jpa/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index c94ae35cd5..6e62fb99c6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index db915d7c3b..6e24605d3c 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index a5cb2f09b5..52cf3c12a0 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index 95a83aac44..bd8aa8d5cb 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.1.0-SNAPSHOT + 3.1.0-GH-2773-SNAPSHOT ../pom.xml From 9732cf374f462e5713aa8cf8ad96590b2411f02f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Jan 2023 12:08:19 +0100 Subject: [PATCH 2/3] 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. --- .../query/DefaultQueryEnhancer.java | 2 +- .../query/JSqlParserQueryEnhancer.java | 23 +++-- .../data/jpa/repository/query/QueryUtils.java | 43 +++++++-- .../query/DefaultQueryEnhancerUnitTests.java | 30 +++++++ .../JSqlParserQueryEnhancerUnitTests.java | 30 +++++++ .../query/QueryEnhancerTckTests.java | 87 +++++++++++++++++++ .../query/QueryEnhancerUnitTests.java | 26 +++--- 7 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java index 53a07bf6f9..92387c973e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java @@ -46,7 +46,7 @@ public String detectAlias() { @Override public String createCountQueryFor(@Nullable String countProjection) { - return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection); + return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, this.query.isNativeQuery()); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 7a44873ac4..cd76876ac7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -15,9 +15,8 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlCount; -import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlLower; -import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression; +import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*; +import static org.springframework.data.jpa.repository.query.QueryUtils.*; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; @@ -29,11 +28,23 @@ import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.merge.Merge; -import net.sf.jsqlparser.statement.select.*; +import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SetOperationList; +import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.values.ValuesStatement; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import org.springframework.data.domain.Sort; @@ -400,7 +411,7 @@ public String createCountQueryFor(@Nullable String countProjection) { return selectBody.toString(); } - String countProp = tableAlias == null ? "*" : tableAlias; + String countProp = query.isNativeQuery() ? (distinct ? "*" : "1") : tableAlias == null ? "*" : tableAlias; Function jSqlCount = getJSqlCount(Collections.singletonList(countProp), distinct); selectBody.setSelectItems(Collections.singletonList(new SelectExpressionItem(jSqlCount))); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 5707caec26..785dde580d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -18,10 +18,23 @@ import static jakarta.persistence.metamodel.Attribute.PersistentAttributeType.*; import static java.util.regex.Pattern.*; -import jakarta.persistence.*; -import jakarta.persistence.criteria.*; -import jakarta.persistence.metamodel.*; +import jakarta.persistence.EntityManager; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Parameter; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Fetch; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; +import jakarta.persistence.metamodel.Bindable; +import jakarta.persistence.metamodel.ManagedType; +import jakarta.persistence.metamodel.PluralAttribute; +import jakarta.persistence.metamodel.SingularAttribute; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; @@ -570,6 +583,19 @@ public static String createCountQueryFor(String originalQuery) { */ @Deprecated public static String createCountQueryFor(String originalQuery, @Nullable String countProjection) { + return createCountQueryFor(originalQuery, countProjection, false); + } + + /** + * Creates a count projected query from the given original query. + * + * @param originalQuery must not be {@literal null}. + * @param countProjection may be {@literal null}. + * @param nativeQuery whether the underlying query is a native query. + * @return a query String to be used a count query for pagination. Guaranteed to be not {@literal null}. + * @since 2.7.8 + */ + static String createCountQueryFor(String originalQuery, @Nullable String countProjection, boolean nativeQuery) { Assert.hasText(originalQuery, "OriginalQuery must not be null or empty"); @@ -591,9 +617,14 @@ public static String createCountQueryFor(String originalQuery, @Nullable String String replacement = useVariable ? SIMPLE_COUNT_VALUE : complexCountValue; - String alias = QueryUtils.detectAlias(originalQuery); - if ("*".equals(variable) && alias != null) { - replacement = alias; + if (nativeQuery && (variable.contains(",") || "*".equals(variable))) { + replacement = "1"; + } else { + + String alias = QueryUtils.detectAlias(originalQuery); + if (("*".equals(variable) && alias != null)) { + replacement = alias; + } } countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, replacement)); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java new file mode 100644 index 0000000000..f113586fc9 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +/** + * TCK Tests for {@link DefaultQueryEnhancer}. + * + * @author Mark Paluch + */ +public class DefaultQueryEnhancerUnitTests extends QueryEnhancerTckTests { + + @Override + QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) { + return new DefaultQueryEnhancer(declaredQuery); + } + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java new file mode 100644 index 0000000000..940af91477 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +/** + * TCK Tests for {@link JSqlParserQueryEnhancer}. + * + * @author Mark Paluch + */ +public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { + + @Override + QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) { + return new JSqlParserQueryEnhancer(declaredQuery); + } + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java new file mode 100644 index 0000000000..cc8bb6155b --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * TCK Tests for {@link QueryEnhancer}. + * + * @author Mark Paluch + */ +abstract class QueryEnhancerTckTests { + + @ParameterizedTest + @MethodSource("nativeCountQueries") // GH-2773 + void shouldDeriveNativeCountQuery(String query, String expected) { + + DeclaredQuery declaredQuery = DeclaredQuery.of(query, true); + QueryEnhancer enhancer = createQueryEnhancer(declaredQuery); + String countQueryFor = enhancer.createCountQueryFor(null); + + assertThat(countQueryFor).isEqualToIgnoringCase(expected); + } + + static Stream nativeCountQueries() { + + return Stream.of(Arguments.of( // + "SELECT * FROM table_name some_alias", // + "select count(1) FROM table_name some_alias"), // + + Arguments.of( // + "SELECT name FROM table_name some_alias", // + "select count(name) FROM table_name some_alias"), // + + Arguments.of( // + "SELECT DISTINCT name FROM table_name some_alias", // + "select count(DISTINCT name) FROM table_name some_alias")); + } + + @ParameterizedTest // GH-2773 + @MethodSource("jpqlCountQueries") + void shouldDeriveJpqlCountQuery(String query, String expected) { + + DeclaredQuery declaredQuery = DeclaredQuery.of(query, false); + QueryEnhancer enhancer = createQueryEnhancer(declaredQuery); + String countQueryFor = enhancer.createCountQueryFor(null); + + assertThat(countQueryFor).isEqualToIgnoringCase(expected); + } + + static Stream jpqlCountQueries() { + + return Stream.of(Arguments.of( // + "SELECT some_alias FROM table_name some_alias", // + "select count(some_alias) FROM table_name some_alias"), // + + Arguments.of( // + "SELECT name FROM table_name some_alias", // + "select count(name) FROM table_name some_alias"), // + + Arguments.of( // + "SELECT DISTINCT name FROM table_name some_alias", // + "select count(DISTINCT name) FROM table_name some_alias")); + } + + abstract QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery); + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index 5c1137aea2..8cfe0e0a3f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -15,9 +15,7 @@ */ package org.springframework.data.jpa.repository.query; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import java.util.Arrays; import java.util.Collections; @@ -229,7 +227,12 @@ void createsCountQueryForNestedReferenceCorrectly() { @Test // DATAJPA-420 void createsCountQueryForScalarSelects() { - assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", true); + assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", false); + } + + @Test // DATAJPA-420 + void createsCountQueryForNativeScalarSelects() { + assertCountQuery("select p.lastname,p.firstname from Person p", "select count(1) from Person p", true); } @Test // DATAJPA-456 @@ -490,7 +493,7 @@ void createCountQuerySupportsWhitespaceCharacters() { " order by user.name\n ", true); assertThat(getEnhancer(query).createCountQueryFor()) - .isEqualToIgnoringCase("select count(user) from User user where user.age = 18"); + .isEqualToIgnoringCase("select count(1) from User user where user.age = 18"); } @Test @@ -503,7 +506,7 @@ void createCountQuerySupportsLineBreaksInSelectClause() { " order\nby\nuser.name\n ", true); assertThat(getEnhancer(query).createCountQueryFor()) - .isEqualToIgnoringCase("select count(user) from User user where user.age = 18"); + .isEqualToIgnoringCase("select count(1) from User user where user.age = 18"); } @Test // DATAJPA-1061 @@ -724,17 +727,17 @@ void countQueryUsesCorrectVariable() { QueryEnhancer queryEnhancer = getEnhancer(nativeQuery); String countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(*) FROM User WHERE created_at > $1"); + assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM User WHERE created_at > $1"); nativeQuery = new StringQuery("SELECT * FROM (select * from test) ", true); queryEnhancer = getEnhancer(nativeQuery); countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(*) FROM (SELECT * FROM test)"); + assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test)"); nativeQuery = new StringQuery("SELECT * FROM (select * from test) as test", true); queryEnhancer = getEnhancer(nativeQuery); countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(test) FROM (SELECT * FROM test) AS test"); + assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test) AS test"); } @Test // GH-2555 @@ -864,7 +867,7 @@ void withStatementsWorksWithJSQLParser() { assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( "with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)))\n" - + "SELECT count(a) FROM sample_data AS a"); + + "SELECT count(1) FROM sample_data AS a"); assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); assertThat(queryEnhancer.getJoinAliases()).isEmpty(); assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); @@ -887,7 +890,7 @@ void multipleWithStatementsWorksWithJSQLParser() { assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( "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" - + "SELECT count(a) FROM sample_data AS a"); + + "SELECT count(1) FROM sample_data AS a"); assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); assertThat(queryEnhancer.getJoinAliases()).isEmpty(); assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); @@ -985,4 +988,5 @@ private static void assertCountQuery(StringQuery originalQuery, String countQuer private static QueryEnhancer getEnhancer(DeclaredQuery query) { return QueryEnhancerFactory.forQuery(query); } + } From fff3ae8fd9ec2414ef764132317fce39d1ea91fe Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Jan 2023 15:41:40 +0100 Subject: [PATCH 3/3] 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. --- .../query/DefaultQueryEnhancerUnitTests.java | 7 + .../JSqlParserQueryEnhancerUnitTests.java | 190 +++++++++++ .../query/QueryEnhancerTckTests.java | 127 ++++++- .../query/QueryEnhancerUnitTests.java | 316 ------------------ 4 files changed, 320 insertions(+), 320 deletions(-) diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java index f113586fc9..8b38061bf1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java @@ -15,6 +15,9 @@ */ package org.springframework.data.jpa.repository.query; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + /** * TCK Tests for {@link DefaultQueryEnhancer}. * @@ -27,4 +30,8 @@ QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) { return new DefaultQueryEnhancer(declaredQuery); } + @Override + @Test // GH-2511, GH-2773 + @Disabled("Not properly supported by QueryUtils") + void shouldDeriveNativeCountQueryWithVariable(String query, String expected) {} } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java index 940af91477..5d97802439 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java @@ -15,10 +15,23 @@ */ package org.springframework.data.jpa.repository.query; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assumptions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.data.domain.Sort; + /** * TCK Tests for {@link JSqlParserQueryEnhancer}. * * @author Mark Paluch + * @author Diego Krupitza + * @author Geoffrey Deremetz */ public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @@ -27,4 +40,181 @@ QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) { return new JSqlParserQueryEnhancer(declaredQuery); } + @Override + @ParameterizedTest // GH-2773 + @MethodSource("jpqlCountQueries") + void shouldDeriveJpqlCountQuery(String query, String expected) { + + assumeThat(query).as("JSQLParser does not support simple JPQL syntax").doesNotStartWithIgnoringCase("FROM"); + + assumeThat(query).as("JSQLParser does not support constructor JPQL syntax").doesNotContain(" new "); + + super.shouldDeriveJpqlCountQuery(query, expected); + } + + @Test + // GH-2578 + void setOperationListWorks() { + + String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // + + "except \n" // + + "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isNullOrEmpty(); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); + assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN"))).endsWith("ORDER BY SOME_COLUMN ASC"); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @Test // GH-2578 + void complexSetOperationListWorks() { + + String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // + + "except \n" // + + "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // + + "union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isNullOrEmpty(); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); + assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN").ascending())).endsWith("ORDER BY SOME_COLUMN ASC"); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @Test // GH-2578 + void deeplyNestedcomplexSetOperationListWorks() { + + String setQuery = "SELECT CustomerID FROM (\n" // + + "\t\t\tselect * from Customers\n" // + + "\t\t\texcept\n"// + + "\t\t\tselect * from Customers where country = 'Austria'\n"// + + "\t)\n" // + + "\texcept\n"// + + "\tselect CustomerID from customers where country = 'Germany'\n"// + + "\t;"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isNullOrEmpty(); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("CustomerID"); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); + assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).endsWith("ORDER BY CustomerID DESC"); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("CustomerID"); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @Test // GH-2578 + void valuesStatementsWorks() { + + String setQuery = "VALUES (1, 2, 'test')"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isNullOrEmpty(); + assertThat(stringQuery.getProjection()).isNullOrEmpty(); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); + assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).isEqualTo(setQuery); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); + assertThat(queryEnhancer.getProjection()).isNullOrEmpty(); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @Test // GH-2578 + void withStatementsWorks() { + + String setQuery = "with sample_data(day, value) as (values ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))) \n" + + "select day, value from sample_data as a"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a"); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value"); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( + "with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)))\n" + + "SELECT count(1) FROM sample_data AS a"); + assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value"); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @Test // GH-2578 + void multipleWithStatementsWorks() { + + 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" + + "select day, value from sample_data as a"; + + StringQuery stringQuery = new StringQuery(setQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a"); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value"); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( + "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" + + "SELECT count(1) FROM sample_data AS a"); + assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value"); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + @ParameterizedTest // GH-2641 + @MethodSource("mergeStatementWorksSource") + void mergeStatementWorksWithJSqlParser(String query, String alias) { + + StringQuery stringQuery = new StringQuery(query, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(queryEnhancer.detectAlias()).isEqualTo(alias); + assertThat(QueryUtils.detectAlias(query)).isNull(); + + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isEqualTo(alias); + assertThat(queryEnhancer.getProjection()).isEmpty(); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + static Stream mergeStatementWorksSource() { + + return Stream.of( // + Arguments.of( + "merge into a using (select id, value from b) query on (a.id = query.id) when matched then update set a.value = value", + "query"), + Arguments.of( + "merge into a using (select id2, value from b) on (id = id2) when matched then update set a.value = value", + null)); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java index cc8bb6155b..4bbeb008fb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java @@ -19,6 +19,7 @@ import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -36,9 +37,13 @@ void shouldDeriveNativeCountQuery(String query, String expected) { DeclaredQuery declaredQuery = DeclaredQuery.of(query, true); QueryEnhancer enhancer = createQueryEnhancer(declaredQuery); - String countQueryFor = enhancer.createCountQueryFor(null); + String countQueryFor = enhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualToIgnoringCase(expected); + // lenient cleanup to allow for rendering variance + String sanitized = countQueryFor.replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" {2}", " ") + .replaceAll(" {2}", " ").trim(); + + assertThat(sanitized).isEqualToIgnoringCase(expected); } static Stream nativeCountQueries() { @@ -53,7 +58,58 @@ static Stream nativeCountQueries() { Arguments.of( // "SELECT DISTINCT name FROM table_name some_alias", // - "select count(DISTINCT name) FROM table_name some_alias")); + "select count(DISTINCT name) FROM table_name some_alias"), // + + Arguments.of( // + "select distinct u from User u where u.foo = ?", // + "select count(distinct u) from User u where u.foo = ?"), + + Arguments.of( // + "select u from User as u", // + "select count(u) from User as u"), + + Arguments.of( // + "SELECT u FROM User u where u.foo.bar = ?", // + "select count(u) FROM User u where u.foo.bar = ?"), + + Arguments.of( // + "select p.lastname,p.firstname from Person p", // + "select count(1) from Person p"), + + // whitespace quirks + Arguments.of( // + """ + select user.age, + user.name + from User user + where user.age = 18 + order + by + user.name + \s""", // + "select count(1) from User user where user.age = 18"), + + Arguments.of( // + "select * from User user\n" + // + " where user.age = 18\n" + // + " order by user.name\n ", // + "select count(1) from User user where user.age = 18"), + + Arguments.of( // + "SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", // + "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"), + + Arguments.of( // + "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", // + "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"), + + Arguments.of( // + "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799", // + "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799"), + + Arguments.of( // + "select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC", // + "select count(distinct m.genre) from Media m where m.user = ?1")); } @ParameterizedTest // GH-2773 @@ -79,7 +135,70 @@ static Stream jpqlCountQueries() { Arguments.of( // "SELECT DISTINCT name FROM table_name some_alias", // - "select count(DISTINCT name) FROM table_name some_alias")); + "select count(DISTINCT name) FROM table_name some_alias"), + + Arguments.of( // + "select distinct new User(u.name) from User u where u.foo = ?", // + "select count(distinct u) from User u where u.foo = ?"), + + Arguments.of( // + "FROM User u WHERE u.foo.bar = ?", // + "select count(u) FROM User u WHERE u.foo.bar = ?"), + + Arguments.of( // + "from User u", // + "select count(u) FROM User u"), + + Arguments.of( // + "select u from User as u", // + "select count(u) from User as u"), + + Arguments.of( // + "select p.lastname,p.firstname from Person p", // + "select count(p) from Person p"), + + Arguments.of( // + "select a.b from A a", // + "select count(a.b) from A a"), + + Arguments.of( // + "select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", // + "select count(distinct m.genre) from Media m where m.user = ?1")); + } + + @ParameterizedTest // GH-2511, GH-2773 + @MethodSource("nativeQueriesWithVariables") + void shouldDeriveNativeCountQueryWithVariable(String query, String expected) { + + DeclaredQuery declaredQuery = DeclaredQuery.of(query, true); + QueryEnhancer enhancer = createQueryEnhancer(declaredQuery); + String countQueryFor = enhancer.createCountQueryFor(); + + assertThat(countQueryFor).isEqualToIgnoringCase(expected); + } + + static Stream nativeQueriesWithVariables() { + + return Stream.of(Arguments.of( // + "SELECT * FROM User WHERE created_at > $1", // + "SELECT count(1) FROM User WHERE created_at > $1"), // + + Arguments.of( // + "SELECT * FROM (select * from test) ", // + "SELECT count(1) FROM (SELECT * FROM test)"), // + + Arguments.of( // + "SELECT * FROM (select * from test) as test", // + "SELECT count(1) FROM (SELECT * FROM test) AS test")); + } + + @Test + // DATAJPA-1696 + void findProjectionClauseWithIncludedFrom() { + + StringQuery query = new StringQuery("select x, frommage, y from t", true); + + assertThat(createQueryEnhancer(query).getProjection()).isEqualTo("x, frommage, y"); } abstract QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index 8cfe0e0a3f..549528160f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -45,37 +45,8 @@ class QueryEnhancerUnitTests { private static final String FQ_QUERY = "select u from org.acme.domain.User$Foo_Bar u"; private static final String SIMPLE_QUERY = "from User u"; private static final String COUNT_QUERY = "select count(u) from User u"; - private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?"; - @Test - void createsCountQueryCorrectly() { - assertCountQuery(QUERY, COUNT_QUERY, true); - } - - @Test - void createsCountQueriesCorrectlyForCapitalLetterJPQL() { - - assertCountQuery("FROM User u WHERE u.foo.bar = ?", "select count(u) FROM User u WHERE u.foo.bar = ?", false); - - assertCountQuery("SELECT u FROM User u where u.foo.bar = ?", "select count(u) FROM User u where u.foo.bar = ?", - true); - } - - @Test - void createsCountQueryForDistinctQueries() { - - assertCountQuery("select distinct u from User u where u.foo = ?", - "select count(distinct u) from User u where u.foo = ?", true); - } - - @Test - void createsCountQueryForConstructorQueries() { - - assertCountQuery("select distinct new User(u.name) from User u where u.foo = ?", - "select count(distinct u) from User u where u.foo = ?", false); - } - @Test void createsCountQueryForJoinsNoneNative() { @@ -97,11 +68,6 @@ void createsCountQueryForQueriesWithSubSelects() { "select count(u) from User u left outer join u.roles r where r in (select r from Role)", true); } - @Test - void createsCountQueryForAliasesCorrectly() { - assertCountQuery("select u from User as u", "select count(u) from User as u", true); - } - @Test void allowsShortJpaSyntax() { assertCountQuery(SIMPLE_QUERY, COUNT_QUERY, false); @@ -177,13 +143,6 @@ void appendsIgnoreCaseOrderingCorrectly() { .endsWithIgnoringCase("order by p.lastname asc, lower(p.firstname) asc"); } - @Test // DATAJPA-342 - void usesReturnedVariableInCountProjectionIfSet() { - - assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", - "select count(distinct m.genre) from Media m where m.user = ?1", true); - } - @Test // DATAJPA-343 void projectsCountQueriesForQueriesWithSubSelects() { @@ -203,13 +162,6 @@ void doesNotPrefixSortsIfFunction() { .isInstanceOf(InvalidDataAccessApiUsageException.class); } - @Test // DATAJPA-377 - void removesOrderByInGeneratedCountQueryFromOriginalQueryIfPresent() { - - assertCountQuery("select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC", - "select count(distinct m.genre) from Media m where m.user = ?1", true); - } - @Test // DATAJPA-375 void findsExistingOrderByIndependentOfCase() { @@ -220,21 +172,6 @@ void findsExistingOrderByIndependentOfCase() { assertThat(query).endsWithIgnoringCase("ORDER BY p.firstname, p.lastname asc"); } - @Test // DATAJPA-409 - void createsCountQueryForNestedReferenceCorrectly() { - assertCountQuery("select a.b from A a", "select count(a.b) from A a", true); - } - - @Test // DATAJPA-420 - void createsCountQueryForScalarSelects() { - assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", false); - } - - @Test // DATAJPA-420 - void createsCountQueryForNativeScalarSelects() { - assertCountQuery("select p.lastname,p.firstname from Person p", "select count(1) from Person p", true); - } - @Test // DATAJPA-456 void createCountQueryFromTheGivenCountProjection() { @@ -485,30 +422,6 @@ void detectsAliasWithGroupAndOrderBy() { assertThat(getEnhancer(queryWithOrderAlias).detectAlias()).isEqualTo("u"); } - @Test // DATAJPA-1500 - void createCountQuerySupportsWhitespaceCharacters() { - - StringQuery query = new StringQuery("select * from User user\n" + // - " where user.age = 18\n" + // - " order by user.name\n ", true); - - assertThat(getEnhancer(query).createCountQueryFor()) - .isEqualToIgnoringCase("select count(1) from User user where user.age = 18"); - } - - @Test - void createCountQuerySupportsLineBreaksInSelectClause() { - - StringQuery query = new StringQuery("select user.age,\n" + // - " user.name\n" + // - " from User user\n" + // - " where user.age = 18\n" + // - " order\nby\nuser.name\n ", true); - - assertThat(getEnhancer(query).createCountQueryFor()) - .isEqualToIgnoringCase("select count(1) from User user where user.age = 18"); - } - @Test // DATAJPA-1061 void appliesSortCorrectlyForFieldAliases() { @@ -630,52 +543,6 @@ void findProjectionClauseWithSubselectNative() { assertThat(getEnhancer(query).getProjection()).isEqualTo("*"); } - @Test // DATAJPA-1696 - void findProjectionClauseWithIncludedFrom() { - - StringQuery query = new StringQuery("select x, frommage, y from t", true); - - assertThat(getEnhancer(query).getProjection()).isEqualTo("x, frommage, y"); - } - - @Test - void countProjectionDistinctQueryIncludesNewLineAfterFromAndBeforeJoin() { - - StringQuery originalQuery = new StringQuery( - "SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true); - - assertCountQuery(originalQuery, - "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"); - } - - @Test - void countProjectionDistinctQueryIncludesNewLineAfterEntity() { - - StringQuery originalQuery = new StringQuery( - "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true); - - assertCountQuery(originalQuery, - "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"); - } - - @Test - void countProjectionDistinctQueryIncludesNewLineAfterEntityAndBeforeWhere() { - - StringQuery originalQuery = new StringQuery( - "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799", - true); - - assertCountQuery(originalQuery, - "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799"); - } - - @Test - void createsCountQueriesCorrectlyForCapitalLetter() { - - assertCountQuery("SELECT u FROM User u where u.foo.bar = ?", "select count(u) FROM User u where u.foo.bar = ?", - true); - } - @ParameterizedTest // DATAJPA-252 @MethodSource("detectsJoinAliasesCorrectlySource") void detectsJoinAliasesCorrectly(String queryString, List aliases) { @@ -689,7 +556,6 @@ void detectsJoinAliasesCorrectly(String queryString, List aliases) { assertThat(nonNativeJoinAliases).containsAll(nativeJoinAliases); assertThat(nativeJoinAliases).hasSameSizeAs(aliases) // .containsAll(aliases); - } @Test // GH-2441 @@ -720,26 +586,6 @@ void correctApplySortOnComplexNestedFunctionQuery() { assertThat(result).containsIgnoringCase("order by dd.institutesIds"); } - @Test // GH-2511 - void countQueryUsesCorrectVariable() { - - StringQuery nativeQuery = new StringQuery("SELECT * FROM User WHERE created_at > $1", true); - - QueryEnhancer queryEnhancer = getEnhancer(nativeQuery); - String countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM User WHERE created_at > $1"); - - nativeQuery = new StringQuery("SELECT * FROM (select * from test) ", true); - queryEnhancer = getEnhancer(nativeQuery); - countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test)"); - - nativeQuery = new StringQuery("SELECT * FROM (select * from test) as test", true); - queryEnhancer = getEnhancer(nativeQuery); - countQueryFor = queryEnhancer.createCountQueryFor(); - assertThat(countQueryFor).isEqualTo("SELECT count(1) FROM (SELECT * FROM test) AS test"); - } - @Test // GH-2555 void modifyingQueriesAreDetectedCorrectly() { @@ -760,143 +606,6 @@ void modifyingQueriesAreDetectedCorrectly() { assertThat(QueryEnhancerFactory.forQuery(modiQuery).createCountQueryFor()).isEqualToIgnoringCase(modifyingQuery); } - @Test // GH-2578 - void setOperationListWorksWithJSQLParser() { - - String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // - + "except \n" // - + "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isNullOrEmpty(); - assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); - assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN"))).endsWith("ORDER BY SOME_COLUMN ASC"); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); - assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } - - @Test // GH-2578 - void complexSetOperationListWorksWithJSQLParser() { - - String setQuery = "select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // - + "except \n" // - + "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE \n" // - + "union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isNullOrEmpty(); - assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); - assertThat(queryEnhancer.applySorting(Sort.by("SOME_COLUMN").ascending())).endsWith("ORDER BY SOME_COLUMN ASC"); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); - assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("SOME_COLUMN"); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } - - @Test // GH-2578 - void deeplyNestedcomplexSetOperationListWorksWithJSQLParser() { - - String setQuery = "SELECT CustomerID FROM (\n" // - + "\t\t\tselect * from Customers\n" // - + "\t\t\texcept\n"// - + "\t\t\tselect * from Customers where country = 'Austria'\n"// - + "\t)\n" // - + "\texcept\n"// - + "\tselect CustomerID from customers where country = 'Germany'\n"// - + "\t;"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isNullOrEmpty(); - assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("CustomerID"); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); - assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).endsWith("ORDER BY CustomerID DESC"); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); - assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("CustomerID"); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } - - @Test // GH-2578 - void valuesStatementsWorksWithJSQLParser() { - - String setQuery = "VALUES (1, 2, 'test')"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isNullOrEmpty(); - assertThat(stringQuery.getProjection()).isNullOrEmpty(); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(setQuery); - assertThat(queryEnhancer.applySorting(Sort.by("CustomerID").descending())).isEqualTo(setQuery); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isNullOrEmpty(); - assertThat(queryEnhancer.getProjection()).isNullOrEmpty(); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } - - @Test // GH-2578 - void withStatementsWorksWithJSQLParser() { - - String setQuery = "with sample_data(day, value) as (values ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))) \n" - + "select day, value from sample_data as a"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a"); - assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value"); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( - "with sample_data (day, value) AS (VALUES ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)))\n" - + "SELECT count(1) FROM sample_data AS a"); - assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); - assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value"); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } - - @Test // GH-2578 - void multipleWithStatementsWorksWithJSQLParser() { - - 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" - + "select day, value from sample_data as a"; - - StringQuery stringQuery = new StringQuery(setQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a"); - assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value"); - assertThat(stringQuery.hasConstructorExpression()).isFalse(); - - assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase( - "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" - + "SELECT count(1) FROM sample_data AS a"); - assertThat(queryEnhancer.applySorting(Sort.by("day").descending())).endsWith("ORDER BY a.day DESC"); - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase("a"); - assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase("day, value"); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } @ParameterizedTest // GH-2593 @MethodSource("insertStatementIsProcessedSameAsDefaultSource") @@ -929,21 +638,7 @@ void insertStatementIsProcessedSameAsDefault(String insertQuery) { assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); } - @ParameterizedTest // GH-2641 - @MethodSource("mergeStatementWorksWithJSqlParserSource") - void mergeStatementWorksWithJSqlParser(String query, String alias) { - StringQuery stringQuery = new StringQuery(query, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); - - assertThat(queryEnhancer.detectAlias()).isEqualTo(alias); - assertThat(QueryUtils.detectAlias(query)).isNull(); - - assertThat(queryEnhancer.getJoinAliases()).isEmpty(); - assertThat(queryEnhancer.detectAlias()).isEqualTo(alias); - assertThat(queryEnhancer.getProjection()).isEmpty(); - assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); - } public static Stream insertStatementIsProcessedSameAsDefaultSource() { @@ -953,17 +648,6 @@ public static Stream insertStatementIsProcessedSameAsDefaultSource() ); } - public static Stream mergeStatementWorksWithJSqlParserSource() { - - return Stream.of( // - Arguments.of( - "merge into a using (select id, value from b) query on (a.id = query.id) when matched then update set a.value = value", - "query"), - Arguments.of( - "merge into a using (select id2, value from b) on (id = id2) when matched then update set a.value = value", - null)); - } - public static Stream detectsJoinAliasesCorrectlySource() { return Stream.of( //