From b9ef4306d79b12bafaf10dd1210ee997e5af8de2 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 25 Nov 2024 09:10:16 +0100 Subject: [PATCH 01/16] 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 ade6f22b06..38a9a14796 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 0bdf2c8e7e..b5fa9ea78d 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index af5244a230..4d826679e0 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 - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index ae5a4738dc..5dd4b50cbb 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml From cfb5d2dc4dcbe476fc967ef28e7a53f4c0660010 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 25 Nov 2024 09:10:28 +0100 Subject: [PATCH 02/16] Upgrade to JPA 3.2. Closes #3673 --- pom.xml | 2 +- .../data/jpa/util/TestMetaModel.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 38a9a14796..c90ec22407 100755 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 7.0.0-SNAPSHOT 2.7.4

2.3.232

- 3.1.0 + 3.2.0 5.0 9.1.0 42.7.4 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java index a755ba222b..822365b65a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java @@ -43,13 +43,13 @@ public class TestMetaModel implements Metamodel { private final Set> managedTypes; private final Lazy entityManagerFactory = Lazy.of(this::init); private final Lazy metamodel = Lazy.of(() -> entityManagerFactory.get().getMetamodel()); - private Lazy enityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager()); + private final Lazy entityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager()); - TestMetaModel(Set> managedTypes) { + private TestMetaModel(Set> managedTypes) { this("dynamic-tests", managedTypes); } - TestMetaModel(String persistenceUnit, Set> managedTypes) { + private TestMetaModel(String persistenceUnit, Set> managedTypes) { this.persistenceUnit = persistenceUnit; this.managedTypes = managedTypes; } @@ -66,6 +66,11 @@ public EntityType entity(Class cls) { return metamodel.get().entity(cls); } + @Override + public EntityType entity(String s) { + return metamodel.get().entity(s); + } + public ManagedType managedType(Class cls) { return metamodel.get().managedType(cls); } @@ -87,7 +92,7 @@ public Set> getEmbeddables() { } public EntityManager entityManager() { - return enityManager.get(); + return entityManager.get(); } EntityManagerFactory init() { From 43244ee2bc28e4b304c36f9f673adc3838c1cad5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 25 Nov 2024 09:10:53 +0100 Subject: [PATCH 03/16] Upgrade to Hibernate 7.0 Beta1. Closes #3671 --- Jenkinsfile | 44 -------------------------------------------- pom.xml | 43 +------------------------------------------ 2 files changed, 1 insertion(+), 86 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f87caaa0aa..a088fae34e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,50 +58,6 @@ pipeline { } parallel { - stage("test: hibernate 6.2 (LTS)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES')} - environment { - ARTIFACTORY = credentials("${p['artifactory.credentials']}") - DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") - TESTCONTAINERS_IMAGE_SUBSTITUTOR = 'org.springframework.data.jpa.support.ProxyImageNameSubstitutor' - } - steps { - script { - docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { - docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) { - sh "PROFILE=all-dbs,hibernate-62 " + - "JENKINS_USER_NAME=${p['jenkins.user.name']} " + - "ci/test.sh" - } - } - } - } - } - stage("test: baseline (hibernate 6.6 snapshots)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES')} - environment { - ARTIFACTORY = credentials("${p['artifactory.credentials']}") - DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") - TESTCONTAINERS_IMAGE_SUBSTITUTOR = 'org.springframework.data.jpa.support.ProxyImageNameSubstitutor' - } - steps { - script { - docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { - docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) { - sh "PROFILE=all-dbs,hibernate-66-snapshots " + - "JENKINS_USER_NAME=${p['jenkins.user.name']} " + - "ci/test.sh" - } - } - } - } - } stage("test: java.next (next)") { agent { label 'data' diff --git a/pom.xml b/pom.xml index c90ec22407..786b4ab235 100755 --- a/pom.xml +++ b/pom.xml @@ -30,10 +30,7 @@ 4.13.0 4.0.4 4.0.5-SNAPSHOT - 6.6.2.Final - 6.2.31.Final - 6.6.3-SNAPSHOT - 7.0.0.Beta1 + 7.0.0.Beta1 7.0.0-SNAPSHOT 2.7.4

2.3.232

@@ -47,7 +44,6 @@ org.hibernate reuseReports - @@ -58,43 +54,6 @@ - - hibernate-62 - - ${hibernate-62} - - - - hibernate-66-snapshots - - ${hibernate-66-snapshots} - - - - sonatype-oss - https://oss.sonatype.org/content/repositories/snapshots - - false - - - - - - hibernate-70 - - ${hibernate-70} - 3.2.0-M2 - - - - sonatype-oss - https://oss.sonatype.org/content/repositories/snapshots - - false - - - - hibernate-70-snapshots From c9f23cf21ee7841c51b6318c6b3d602e39696c01 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 25 Nov 2024 09:17:58 +0100 Subject: [PATCH 04/16] Upgrade to Eclipselink 5.0.0-B04. Closes #3672 --- pom.xml | 4 ++-- .../data/jpa/repository/sample/UserRepository.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 786b4ab235..4fc11f8452 100755 --- a/pom.xml +++ b/pom.xml @@ -28,8 +28,8 @@ 4.13.0 - 4.0.4 - 4.0.5-SNAPSHOT + 5.0.0-B04 + 5.0.0-SNAPSHOT 7.0.0.Beta1 7.0.0-SNAPSHOT 2.7.4 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 76896ece7a..042713219d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -544,7 +544,7 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity List findRolesAndFirstnameBy(); - @Query(value = "FROM User u") + @Query(value = "SELECT u FROM User u") List findIdOnly(); // DATAJPA-1172 @@ -643,13 +643,13 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity List findAllInterfaceProjectedBy(); // GH-2045, GH-425 - @Query("select concat(?1,u.id,?2) as id from #{#entityName} u") + @Query("select concat(?1,u.id,?2) as identifier from #{#entityName} u") List findAllAndSortByFunctionResultPositionalParameter( @Param("positionalParameter1") String positionalParameter1, @Param("positionalParameter2") String positionalParameter2, Sort sort); // GH-2045, GH-425 - @Query("select concat(:namedParameter1,u.id,:namedParameter2) as id from #{#entityName} u") + @Query("select concat(:namedParameter1,u.id,:namedParameter2) as identifier from #{#entityName} u") List findAllAndSortByFunctionResultNamedParameter(@Param("namedParameter1") String namedParameter1, @Param("namedParameter2") String namedParameter2, Sort sort); From e41c1118b34244081f83ea98978232c6cc79d271 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 25 Nov 2024 09:26:55 +0100 Subject: [PATCH 05/16] Consider `NULLS` precedence using `Sort` for Criteria Queries. Closes #3587 --- .../data/jpa/repository/query/QueryUtils.java | 23 ++++++++++++------- .../query/QueryUtilsIntegrationTests.java | 11 +++++---- 2 files changed, 22 insertions(+), 12 deletions(-) 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 e625c9460f..302da71f18 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 @@ -29,6 +29,7 @@ import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Nulls; import jakarta.persistence.criteria.Path; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; @@ -292,9 +293,8 @@ public static String applySorting(String query, Sort sort, @Nullable String alia Set selectionAliases = getFunctionAliases(query); selectionAliases.addAll(getFieldAliases(query)); - String orderClauses = sort.stream() - .map(order -> getOrderClause(joinAliases, selectionAliases, alias, order)) - .collect(Collectors.joining(", ")); + String orderClauses = sort.stream().map(order -> getOrderClause(joinAliases, selectionAliases, alias, order)) + .collect(Collectors.joining(", ")); builder.append(orderClauses); @@ -753,18 +753,25 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From expression = toExpressionRecursively(from, property); - if (order.getNullHandling() != Sort.NullHandling.NATIVE) { - throw new UnsupportedOperationException("Applying Null Precedence using Criteria Queries is not yet supported."); - } + Nulls nulls = toNulls(order.getNullHandling()); if (order.isIgnoreCase() && String.class.equals(expression.getJavaType())) { Expression upper = cb.lower((Expression) expression); - return order.isAscending() ? cb.asc(upper) : cb.desc(upper); + return order.isAscending() ? cb.asc(upper, nulls) : cb.desc(upper, nulls); } else { - return order.isAscending() ? cb.asc(expression) : cb.desc(expression); + return order.isAscending() ? cb.asc(expression, nulls) : cb.desc(expression, nulls); } } + private static Nulls toNulls(Sort.NullHandling nullHandling) { + + return switch (nullHandling) { + case NULLS_LAST -> Nulls.LAST; + case NULLS_FIRST -> Nulls.FIRST; + case NATIVE -> Nulls.NONE; + }; + } + static Expression toExpressionRecursively(From from, PropertyPath property) { return toExpressionRecursively(from, property, false); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java index 59eb5f8667..1c7adc781d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java @@ -32,6 +32,7 @@ import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Nulls; import jakarta.persistence.criteria.Root; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceProviderResolver; @@ -314,8 +315,8 @@ void toOrdersCanSortByJoinColumn() { assertThat(orders).hasSize(1); } - @Test // GH-3529 - void nullPrecedenceThroughCriteriaApiNotYetSupported() { + @Test // GH-3529, GH-3587 + void queryUtilsConsidersNullPrecedence() { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); @@ -324,8 +325,10 @@ void nullPrecedenceThroughCriteriaApiNotYetSupported() { Sort sort = Sort.by(Sort.Order.desc("manager").nullsFirst()); - assertThatExceptionOfType(UnsupportedOperationException.class) - .isThrownBy(() -> QueryUtils.toOrders(sort, join, builder)); + List orders = QueryUtils.toOrders(sort, join, builder); + for (jakarta.persistence.criteria.Order order : orders) { + assertThat(order.getNullPrecedence()).isEqualTo(Nulls.FIRST); + } } /** From cfde8cd1a7a51d1f9c68f46a0a509dd43d66624b Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Fri, 1 Sep 2023 13:48:34 -0500 Subject: [PATCH 06/16] Add support for JPA 3.2 additions to JPQL. See #3136. --- .../data/jpa/repository/query/Jpql.g4 | 12 ++- .../repository/query/JpqlQueryRenderer.java | 47 ++++++++++ .../query/HqlQueryRendererTests.java | 90 ++++++++++++------- .../query/JpqlQueryRendererTests.java | 53 +++++++++++ 4 files changed, 171 insertions(+), 31 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 index e87d523b12..b91748b91d 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 @@ -43,7 +43,13 @@ ql_statement ; select_statement - : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? + : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator_with_select_statement)* + ; + +setOperator_with_select_statement + : INTERSECT select_statement + | UNION select_statement + | EXCEPT select_statement ; update_statement @@ -434,6 +440,7 @@ string_expression | aggregate_expression | case_expression | function_invocation + | string_expression op='||' string_expression | '(' subquery ')' ; @@ -878,6 +885,7 @@ ELSE : E L S E; EMPTY : E M P T Y; ENTRY : E N T R Y; ESCAPE : E S C A P E; +EXCEPT : E X C E P T; EXISTS : E X I S T S; EXP : E X P; EXTRACT : E X T R A C T; @@ -892,6 +900,7 @@ HAVING : H A V I N G; IN : I N; INDEX : I N D E X; INNER : I N N E R; +INTERSECT : I N T E R S E C T; IS : I S; JOIN : J O I N; KEY : K E Y; @@ -936,6 +945,7 @@ TREAT : T R E A T; TRIM : T R I M; TRUE : T R U E; TYPE : T Y P E; +UNION : U N I O N; UPDATE : U P D A T E; UPPER : U P P E R; VALUE : V A L U E; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index f743d50669..5fb061f476 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -79,6 +79,29 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext builder.appendExpression(visit(ctx.orderby_clause())); } + ctx.setOperator_with_select_statement().forEach(setOperatorWithSelectStatementContext -> { + tokens.addAll(visit(setOperatorWithSelectStatementContext)); + }); + + return tokens; + } + + @Override + public List visitSetOperator_with_select_statement( + JpqlParser.SetOperator_with_select_statementContext ctx) { + + List tokens = new ArrayList<>(); + + if (ctx.INTERSECT() != null) { + tokens.add(new JpaQueryParsingToken(ctx.INTERSECT())); + } else if (ctx.UNION() != null) { + tokens.add(new JpaQueryParsingToken(ctx.UNION())); + } else if (ctx.EXCEPT() != null) { + tokens.add(new JpaQueryParsingToken(ctx.EXCEPT())); + } + + tokens.addAll(visit(ctx.select_statement())); + return builder; } @@ -799,6 +822,25 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { if (ctx.nullsPrecedence() != null) { builder.append(visit(ctx.nullsPrecedence())); } + if (ctx.nullsPrecedence() != null) { + tokens.addAll(visit(ctx.nullsPrecedence())); + } + + return tokens; + } + + @Override + public List visitNullsPrecedence(JpqlParser.NullsPrecedenceContext ctx) { + + List tokens = new ArrayList<>(); + + tokens.add(new JpaQueryParsingToken(ctx.NULLS())); + + if (ctx.FIRST() != null) { + tokens.add(new JpaQueryParsingToken(ctx.FIRST())); + } else if (ctx.LAST() != null) { + tokens.add(new JpaQueryParsingToken(ctx.LAST())); + } return builder; } @@ -1441,6 +1483,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); + } else if (ctx.op != null) { + + tokens.addAll(visit(ctx.string_expression(0))); + tokens.add(new JpaQueryParsingToken(ctx.op)); + tokens.addAll(visit(ctx.string_expression(1))); } else if (ctx.subquery() != null) { builder.append(TOKEN_OPEN_PAREN); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 067a3adbe4..57c0b43b67 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1646,42 +1646,36 @@ void hqlQueries() { @Test // GH-2962 void orderByWithNullsFirstOrLastShouldWork() { - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select a, - case - when a.geaendertAm is null then a.erstelltAm - else a.geaendertAm end as mutationAm - from Element a - where a.erstelltDurch = :variable - order by mutationAm desc nulls first - """); - }); - - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select a, - case - when a.geaendertAm is null then a.erstelltAm - else a.geaendertAm end as mutationAm - from Element a - where a.erstelltDurch = :variable - order by mutationAm desc nulls last + assertQuery(""" + select a, + case + when a.geaendertAm is null then a.erstelltAm + else a.geaendertAm end as mutationAm + from Element a + where a.erstelltDurch = :variable + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a, + case + when a.geaendertAm is null then a.erstelltAm + else a.geaendertAm end as mutationAm + from Element a + where a.erstelltDurch = :variable + order by mutationAm desc nulls last """); - }); } @Test // GH-2964 void roundFunctionShouldWorkLikeAnyOtherFunction() { - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc - from StockOrderItem oi - right join StockReceiptItem ri - on ri.article = oi.article - """); - }); + assertQuery(""" + select round(count(ri)*100/max(ri.receipt.positions), 0) as perc + from StockOrderItem oi + right join StockReceiptItem ri + on ri.article = oi.article + """); } @Test // GH-3711 @@ -1831,6 +1825,42 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + @Test // GH-3219 void extractFunctionShouldSupportAdditionalExtensions() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java index c50f07c596..3ec9ab7c9b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java @@ -1010,6 +1010,59 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + + @Disabled + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void orderByWithNullsFirstOrLastShouldWork() { + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls last + """); + } + @ParameterizedTest // GH-3342 @ValueSource(strings = { "select 1 as value from User u", "select -1 as value from User u", "select +1 as value from User u", "select +1 * -100 as value from User u", From baae49960653d54b7155561fcf1eee0bef38a8ca Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 21 Jun 2024 18:14:07 +0200 Subject: [PATCH 07/16] Add support for JPA 3.2 additions to EQL. See #3136 --- .../data/jpa/repository/query/Eql.g4 | 28 ++++- .../data/jpa/repository/query/Jpql.g4 | 50 ++++++++- .../repository/query/EqlQueryRenderer.java | 52 ++++++++- .../query/JpqlCountQueryTransformer.java | 12 +- .../repository/query/JpqlQueryRenderer.java | 103 ++++++++++++++++-- .../query/JpqlSortedQueryTransformer.java | 12 +- .../repository/query/EqlComplianceTests.java | 51 +++++++++ .../query/EqlQueryRendererTests.java | 7 -- .../query/HqlQueryRendererTests.java | 12 ++ .../repository/query/JpqlComplianceTests.java | 52 +++++++++ 10 files changed, 352 insertions(+), 27 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 index 3ed025efb5..b41427a202 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 @@ -309,6 +309,7 @@ scalar_expression | datetime_expression | boolean_expression | case_expression + | cast_function | entity_type_expression ; @@ -450,6 +451,7 @@ string_expression | case_expression | function_invocation | '(' subquery ')' + | string_expression '||' string_expression ; datetime_expression @@ -534,6 +536,9 @@ functions_returning_strings | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')' | LOWER '(' string_expression ')' | UPPER '(' string_expression ')' + | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' + | LEFT '(' string_expression ',' arithmetic_expression ')' + | RIGHT '(' string_expression ',' arithmetic_expression ')' ; trim_specification @@ -543,7 +548,7 @@ trim_specification ; cast_function - : CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + : CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' ; function_invocation @@ -609,6 +614,14 @@ nullif_expression : NULLIF '(' scalar_expression ',' scalar_expression ')' ; +type_literal + : STRING + | INTEGER + | LONG + | FLOAT + | DOUBLE + ; + /******************* Gaps in the spec. *******************/ @@ -621,6 +634,7 @@ trim_character identification_variable : IDENTIFICATION_VARIABLE | f=(COUNT + | AS | DATE | FROM | INNER @@ -630,11 +644,13 @@ identification_variable | ORDER | OUTER | POWER + | RIGHT | FLOOR | SIGN | TIME | TYPE | VALUE) + | type_literal ; constructor_name @@ -811,6 +827,8 @@ reserved_word |OR |ORDER |OUTER + |REPLACE + |RIGHT |POWER |ROUND |SELECT @@ -894,6 +912,7 @@ DATETIME : D A T E T I M E ; DELETE : D E L E T E; DESC : D E S C; DISTINCT : D I S T I N C T; +DOUBLE : D O U B L E; END : E N D; ELSE : E L S E; EMPTY : E M P T Y; @@ -906,6 +925,7 @@ EXTRACT : E X T R A C T; FALSE : F A L S E; FETCH : F E T C H; FIRST : F I R S T; +FLOAT : F L O A T; FLOOR : F L O O R; FROM : F R O M; FUNCTION : F U N C T I O N; @@ -914,6 +934,7 @@ HAVING : H A V I N G; IN : I N; INDEX : I N D E X; INNER : I N N E R; +INTEGER : I N T E G E R; INTERSECT : I N T E R S E C T; IS : I S; JOIN : J O I N; @@ -926,6 +947,7 @@ LIKE : L I K E; LN : L N; LOCAL : L O C A L; LOCATE : L O C A T E; +LONG : L O N G; LOWER : L O W E R; MAX : M A X; MEMBER : M E M B E R; @@ -944,6 +966,8 @@ ORDER : O R D E R; OUTER : O U T E R; POWER : P O W E R; REGEXP : R E G E X P; +REPLACE : R E P L A C E; +RIGHT : R I G H T; ROUND : R O U N D; SELECT : S E L E C T; SET : S E T; @@ -951,6 +975,7 @@ SIGN : S I G N; SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; +STRING : S T R I N G; SUBSTRING : S U B S T R I N G; SUM : S U M; THEN : T H E N; @@ -970,7 +995,6 @@ WHERE : W H E R E; EQUAL : '=' ; NOT_EQUAL : '<>' | '!=' ; - CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ; diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 index b91748b91d..28b47d81a1 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 @@ -43,13 +43,25 @@ ql_statement ; select_statement - : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator_with_select_statement)* + : select_query ; -setOperator_with_select_statement - : INTERSECT select_statement - | UNION select_statement - | EXCEPT select_statement +select_query + : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? + ; + +setOperator + : UNION ALL? + | INTERSECT ALL? + | EXCEPT ALL? + ; + +set_fuction + : setOperator set_function_select + ; + +set_function_select + : select_query ; update_statement @@ -303,6 +315,7 @@ scalar_expression | datetime_expression | boolean_expression | case_expression + | cast_expression | entity_type_expression ; @@ -442,6 +455,7 @@ string_expression | function_invocation | string_expression op='||' string_expression | '(' subquery ')' + | string_expression '||' string_expression ; datetime_expression @@ -525,7 +539,10 @@ functions_returning_strings | SUBSTRING '(' string_expression ',' arithmetic_expression (',' arithmetic_expression)? ')' | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')' | LOWER '(' string_expression ')' + | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' | UPPER '(' string_expression ')' + | LEFT '(' string_expression ',' arithmetic_expression ')' + | RIGHT '(' string_expression ',' arithmetic_expression ')' ; trim_specification @@ -598,6 +615,10 @@ nullif_expression : NULLIF '(' scalar_expression ',' scalar_expression ')' ; +cast_expression + : CAST '(' string_expression AS type_literal ')' + ; + /******************* Gaps in the spec. *******************/ @@ -619,6 +640,7 @@ identification_variable | ORDER | OUTER | POWER + | RIGHT | FLOOR | SIGN | TIME @@ -668,6 +690,14 @@ numeric_literal | LONGLITERAL ; +type_literal + : STRING + | INTEGER + | LONG + | FLOAT + | DOUBLE + ; + boolean_literal : TRUE | FALSE @@ -799,6 +829,8 @@ reserved_word |ORDER |OUTER |POWER + |REPLACE + |RIGHT |ROUND |SELECT |SET @@ -868,6 +900,7 @@ BETWEEN : B E T W E E N; BOTH : B O T H; BY : B Y; CASE : C A S E; +CAST : C A S T; CEILING : C E I L I N G; COALESCE : C O A L E S C E; CONCAT : C O N C A T; @@ -880,6 +913,7 @@ DATETIME : D A T E T I M E ; DELETE : D E L E T E; DESC : D E S C; DISTINCT : D I S T I N C T; +DOUBLE : D O U B L E; END : E N D; ELSE : E L S E; EMPTY : E M P T Y; @@ -892,6 +926,7 @@ EXTRACT : E X T R A C T; FALSE : F A L S E; FETCH : F E T C H; FIRST : F I R S T; +FLOAT : F L O A T; FLOOR : F L O O R; FROM : F R O M; FUNCTION : F U N C T I O N; @@ -900,6 +935,7 @@ HAVING : H A V I N G; IN : I N; INDEX : I N D E X; INNER : I N N E R; +INTEGER : I N T E G E R; INTERSECT : I N T E R S E C T; IS : I S; JOIN : J O I N; @@ -912,6 +948,7 @@ LIKE : L I K E; LN : L N; LOCAL : L O C A L; LOCATE : L O C A T E; +LONG : L O N G; LOWER : L O W E R; MAX : M A X; MEMBER : M E M B E R; @@ -928,6 +965,8 @@ ON : O N; OR : O R; ORDER : O R D E R; OUTER : O U T E R; +REPLACE : R E P L A C E; +RIGHT : R I G H T; POWER : P O W E R; ROUND : R O U N D; SELECT : S E L E C T; @@ -936,6 +975,7 @@ SIGN : S I G N; SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; +STRING : S T R I N G; SUBSTRING : S U B S T R I N G; SUM : S U M; THEN : T H E N; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index 17e6e51a55..34e085ba47 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -23,6 +23,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes. @@ -1008,6 +1009,8 @@ public QueryTokenStream visitScalar_expression(EqlParser.Scalar_expressionContex builder.append(visit(ctx.case_expression())); } else if (ctx.entity_type_expression() != null) { builder.append(visit(ctx.entity_type_expression())); + } else if (ctx.cast_function() != null) { + return (visit(ctx.cast_function())); } return builder; @@ -1595,6 +1598,11 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.subquery())); builder.append(TOKEN_CLOSE_PAREN); + } else if (!ObjectUtils.isEmpty(ctx.string_expression())) { + + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.appendExpression(visit(ctx.string_expression(1))); } return builder; @@ -1926,6 +1934,32 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.string_expression(0))); builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.LEFT() != null) { + + builder.append(QueryTokens.token(ctx.LEFT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.RIGHT() != null) { + + builder.append(QueryTokens.token(ctx.RIGHT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.REPLACE() != null) { + + builder.append(QueryTokens.token(ctx.REPLACE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(2))); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -1952,9 +1986,9 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.single_valued_path_expression())); builder.append(TOKEN_SPACE); - builder.appendInline(visit(ctx.identification_variable())); + builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE)); - if (ctx.numeric_literal() != null) { + if (!ObjectUtils.isEmpty(ctx.numeric_literal())) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); @@ -2055,6 +2089,14 @@ public QueryTokenStream visitCase_expression(EqlParser.Case_expressionContext ct } } + @Override + public QueryRendererBuilder visitType_literal(EqlParser.Type_literalContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText()))); + return builder; + } + @Override public QueryTokenStream visitGeneral_case_expression(EqlParser.General_case_expressionContext ctx) { @@ -2175,9 +2217,11 @@ public QueryTokenStream visitIdentification_variable(EqlParser.Identification_va return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { return QueryRendererBuilder.from(QueryTokens.expression(ctx.f)); - } else { - return QueryRenderer.builder(); + } else if (ctx.type_literal() != null) { + return visit(ctx.type_literal()); } + + return QueryRenderer.builder(); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java index 2adac83e9b..0ba682fb70 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -42,7 +42,17 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer { } @Override - public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { + + if(ctx.select_query() != null) { + return visitSelect_query(ctx.select_query()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index 5fb061f476..cbf32caa3d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -15,16 +15,33 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.QueryTokens.*; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COLON; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOT; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_EQUALS; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_QUESTION_MARK; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOUBLE_PIPE; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_SPACE; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN; +import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN; import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.tree.ParseTree; +import org.springframework.data.jpa.repository.query.JpqlParser.Except_clauseContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Intersect_clauseContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Relation_fuctions_selectContext; import org.springframework.data.jpa.repository.query.JpqlParser.NullsPrecedenceContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Cast_expressionContext; import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Set_fuctionContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Type_literalContext; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes. @@ -55,8 +72,17 @@ public QueryTokenStream visitQl_statement(JpqlParser.Ql_statementContext ctx) { } } - @Override - public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { + @Override + public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { + + if(ctx.select_query() != null) { + return visitSelect_query(ctx.select_query()); + } + + return QueryTokenStream.empty(); + } + + public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -79,11 +105,11 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext builder.appendExpression(visit(ctx.orderby_clause())); } - ctx.setOperator_with_select_statement().forEach(setOperatorWithSelectStatementContext -> { - tokens.addAll(visit(setOperatorWithSelectStatementContext)); - }); + if(ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } - return tokens; + return builder; } @Override @@ -799,6 +825,19 @@ public QueryTokenStream visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx return builder; } + @Override + public QueryTokenStream visitSet_fuction(Set_fuctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.setOperator().getStart())); + if(ctx.setOperator().ALL() != null) { + builder.append(QueryTokens.expression(ctx.setOperator().ALL())); + } + builder.appendExpression(visit(ctx.set_function_select().select_query())); + return builder; + } + @Override public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { @@ -973,6 +1012,8 @@ public QueryTokenStream visitScalar_expression(JpqlParser.Scalar_expressionConte return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { return visit(ctx.entity_type_expression()); + } else if (ctx.cast_expression() != null) { + return (visit(ctx.cast_expression())); } return QueryTokenStream.empty(); @@ -1493,6 +1534,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.subquery())); builder.append(TOKEN_CLOSE_PAREN); + } else if (!ObjectUtils.isEmpty(ctx.string_expression())) { + + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.appendExpression(visit(ctx.string_expression(1))); } return builder; @@ -1813,6 +1859,29 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re builder.append(TOKEN_OPEN_PAREN); builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.LEFT() != null) { + builder.append(QueryTokens.token(ctx.LEFT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.RIGHT() != null) { + builder.append(QueryTokens.token(ctx.RIGHT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.REPLACE() != null) { + builder.append(QueryTokens.token(ctx.REPLACE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(2))); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -1915,6 +1984,26 @@ public QueryTokenStream visitCase_expression(JpqlParser.Case_expressionContext c } } + @Override + public QueryRendererBuilder visitCast_expression(Cast_expressionContext ctx) { + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.appendInline(visit(ctx.type_literal())); + builder.append(TOKEN_CLOSE_PAREN); + return builder; + } + + @Override + public QueryRendererBuilder visitType_literal(Type_literalContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText()))); + return builder; + } + @Override public QueryTokenStream visitGeneral_case_expression(JpqlParser.General_case_expressionContext ctx) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index a545171bbf..9cd43176de 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -50,6 +50,16 @@ class JpqlSortedQueryTransformer extends JpqlQueryRenderer { @Override public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { + if(ctx.select_query() != null) { + return visitSelect_query(ctx.select_query()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { + QueryRendererBuilder builder = QueryRenderer.builder(); builder.appendExpression(visit(ctx.select_clause())); @@ -72,7 +82,7 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext return builder; } - private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_statementContext ctx) { + private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_queryContext ctx) { if (ctx.orderby_clause() != null) { QueryTokenStream existingOrder = visit(ctx.orderby_clause()); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index bbfbffe1ab..a0c556ee99 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -20,6 +20,8 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -414,4 +416,53 @@ void isNullAndIsNotNull() { assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT null OR e.active = true)"); assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)"); } + + + @Test // GH-3496 + void lateralShouldBeAValidParameter() { + + assertQuery("select e from Employee e where e.lateral = :_lateral"); + assertQuery("select te from TestEntity te where te.lateral = :lateral"); + } + + @Test // GH-3136 + void intersect() { + + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + } + + @Test // GH-3136 + void except() { + + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"}) + void jpqlCast(String targetType) { + assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"LEFT", "RIGHT"}) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } + + @Test // GH-3136 + void stringConcatWithPipes() { + assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e"); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java index 91a4bb761e..242ec81b83 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java @@ -1039,11 +1039,4 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord); assertQuery(source); } - - @Test // GH-3496 - void lateralShouldBeAValidParameter() { - - assertQuery("select e from Employee e where e.lateral = :_lateral"); - assertQuery("select te from TestEntity te where te.lateral = :lateral"); - } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 57c0b43b67..a3430901a1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1913,4 +1913,16 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord); assertQuery(source); } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"LEFT", "RIGHT"}) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index aadf2c2589..58bb071648 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -20,6 +20,8 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Test to verify compliance of {@link JpqlParser} with standard SQL. Other than {@link JpqlSpecificationTests} tests in @@ -70,4 +72,54 @@ void newWithStrings() { assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); } + @Test // GH-3136 + void union() { + + assertQuery(""" + SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city1 + UNION SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city2 + """); + } + + @Test // GH-3136 + void intersect() { + + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + } + + @Test // GH-3136 + void except() { + + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"}) + void cast(String targetType) { + assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"LEFT", "RIGHT"}) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } + + @Test // GH-3136 + void stringConcatWithPipes() { + assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e"); + } + } From 47825cc769ecf69167e506a91bce8a28cd788f12 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 28 Jun 2024 11:46:42 +0200 Subject: [PATCH 08/16] Make sure sorting is rendered correctly for JPQL query using set operator. --- .../repository/query/JpqlCountQueryTransformer.java | 3 +++ .../repository/query/JpqlSortedQueryTransformer.java | 6 +++++- .../data/jpa/repository/query/EqlComplianceTests.java | 11 +++++------ .../repository/query/JpqlQueryTransformerTests.java | 9 +++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java index 0ba682fb70..fe3e9e4940 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -68,6 +68,9 @@ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { if (ctx.having_clause() != null) { builder.appendExpression(visit(ctx.having_clause())); } + if(ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } return builder; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index 9cd43176de..f182ad2039 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -77,7 +77,11 @@ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { builder.appendExpression(visit(ctx.having_clause())); } - doVisitOrderBy(builder, ctx); + if(ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } else { + doVisitOrderBy(builder, ctx); + } return builder; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index a0c556ee99..0f0d2aecc0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -27,9 +27,9 @@ /** * Tests built around examples of EQL found in the EclipseLink's docs at * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
- * With the exception of {@literal MOD} which is defined as {@literal MOD(arithmetic_expression , arithmetic_expression)}, - * but shown in tests as {@literal MOD(arithmetic_expression ? arithmetic_expression)}. - *
+ * With the exception of {@literal MOD} which is defined as + * {@literal MOD(arithmetic_expression , arithmetic_expression)}, but shown in tests as + * {@literal MOD(arithmetic_expression ? arithmetic_expression)}.
* IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist @@ -417,7 +417,6 @@ void isNullAndIsNotNull() { assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)"); } - @Test // GH-3496 void lateralShouldBeAValidParameter() { @@ -444,13 +443,13 @@ void except() { } @ParameterizedTest // GH-3136 - @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"}) + @ValueSource(strings = { "STRING", "INTEGER", "FLOAT", "DOUBLE" }) void jpqlCast(String targetType) { assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); } @ParameterizedTest // GH-3136 - @ValueSource(strings = {"LEFT", "RIGHT"}) + @ValueSource(strings = { "LEFT", "RIGHT" }) void leftRightStringFunctions(String keyword) { assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java index c2de2ca015..8cbb80df43 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java @@ -784,6 +784,15 @@ void sortingRecognizesJoinAliases() { """); } + @Test // GH-3427 + void sortShouldBeAppendedToFullSelectOnlyInCaseOfSetOperator() { + + String source = "SELECT tb FROM Test tb WHERE (tb.type='A') UNION SELECT tb FROM Test tb WHERE (tb.type='B')"; + String target = createQueryFor(source, Sort.by("Type").ascending()); + + assertThat(target).isEqualTo("SELECT tb FROM Test tb WHERE (tb.type = 'A') UNION SELECT tb FROM Test tb WHERE (tb.type = 'B') order by tb.Type asc"); + } + static Stream queriesWithReservedWordsAsIdentifiers() { return Stream.of( // From 6e2984bee877d974dcc21856ed2fde9e3b74d008 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 27 Nov 2024 08:25:32 +0100 Subject: [PATCH 09/16] Polishing. Inline select_query into select_statement, simplify set_function resolution. Align JPQL and EQL grammars. --- .../data/jpa/repository/query/Eql.g4 | 10 +- .../data/jpa/repository/query/Jpql.g4 | 39 +- .../query/EqlCountQueryTransformer.java | 4 +- .../repository/query/EqlQueryRenderer.java | 456 +++++++++--------- .../query/EqlSortedQueryTransformer.java | 15 +- .../query/JpqlCountQueryTransformer.java | 12 +- .../repository/query/JpqlQueryRenderer.java | 268 +++++----- .../query/JpqlSortedQueryTransformer.java | 13 +- .../repository/query/EqlComplianceTests.java | 14 +- .../query/EqlQueryRendererTests.java | 54 +++ .../query/EqlSpecificationTests.java | 52 +- .../query/HqlQueryRendererTests.java | 4 +- .../repository/query/JpqlComplianceTests.java | 207 +++++++- .../query/JpqlSpecificationTests.java | 33 ++ 14 files changed, 747 insertions(+), 434 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 index b41427a202..b2a45b24d5 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 @@ -43,7 +43,7 @@ ql_statement ; select_statement - : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator select_statement)* + : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? ; setOperator @@ -52,6 +52,10 @@ setOperator | EXCEPT ALL? ; +set_fuction + : setOperator select_statement + ; + update_statement : update_clause (where_clause)? ; @@ -659,6 +663,7 @@ constructor_name literal : STRINGLITERAL + | JAVASTRINGLITERAL | INTLITERAL | FLOATLITERAL | LONGLITERAL @@ -827,9 +832,9 @@ reserved_word |OR |ORDER |OUTER + |POWER |REPLACE |RIGHT - |POWER |ROUND |SELECT |SET @@ -997,6 +1002,7 @@ NOT_EQUAL : '<>' | '!=' ; CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; +JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('0' .. '9')+ ; diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 index 28b47d81a1..87c93f1394 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 @@ -43,10 +43,6 @@ ql_statement ; select_statement - : select_query - ; - -select_query : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? ; @@ -57,11 +53,7 @@ setOperator ; set_fuction - : setOperator set_function_select - ; - -set_function_select - : select_query + : setOperator select_statement ; update_statement @@ -95,7 +87,7 @@ join ; fetch_join - : join_spec FETCH join_association_path_expression + : join_spec FETCH join_association_path_expression AS? identification_variable? join_condition? ; join_spec @@ -315,7 +307,7 @@ scalar_expression | datetime_expression | boolean_expression | case_expression - | cast_expression + | cast_function | entity_type_expression ; @@ -441,6 +433,7 @@ arithmetic_primary | functions_returning_numerics | aggregate_expression | case_expression + | cast_function | function_invocation | '(' subquery ')' ; @@ -453,7 +446,6 @@ string_expression | aggregate_expression | case_expression | function_invocation - | string_expression op='||' string_expression | '(' subquery ')' | string_expression '||' string_expression ; @@ -539,8 +531,8 @@ functions_returning_strings | SUBSTRING '(' string_expression ',' arithmetic_expression (',' arithmetic_expression)? ')' | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')' | LOWER '(' string_expression ')' - | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' | UPPER '(' string_expression ')' + | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' | LEFT '(' string_expression ',' arithmetic_expression ')' | RIGHT '(' string_expression ',' arithmetic_expression ')' ; @@ -551,6 +543,9 @@ trim_specification | BOTH ; +cast_function + : CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + ; function_invocation : FUNCTION '(' function_name (',' function_arg)* ')' @@ -615,9 +610,6 @@ nullif_expression : NULLIF '(' scalar_expression ',' scalar_expression ')' ; -cast_expression - : CAST '(' string_expression AS type_literal ')' - ; /******************* Gaps in the spec. @@ -631,6 +623,7 @@ trim_character identification_variable : IDENTIFICATION_VARIABLE | f=(COUNT + | AS | DATE | FROM | INNER @@ -646,6 +639,7 @@ identification_variable | TIME | TYPE | VALUE) + | type_literal ; constructor_name @@ -673,6 +667,9 @@ pattern_value date_time_timestamp_literal : STRINGLITERAL + | DATELITERAL + | TIMELITERAL + | TIMESTAMPLITERAL ; entity_type_literal @@ -965,9 +962,10 @@ ON : O N; OR : O R; ORDER : O R D E R; OUTER : O U T E R; +POWER : P O W E R; +REGEXP : R E G E X P; REPLACE : R E P L A C E; RIGHT : R I G H T; -POWER : P O W E R; ROUND : R O U N D; SELECT : S E L E C T; SET : S E T; @@ -997,8 +995,11 @@ NOT_EQUAL : '<>' | '!=' ; CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; -STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ; +STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ; JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('0' .. '9')+ ; -LONGLITERAL : ('0' .. '9')+L ; +LONGLITERAL : ('0' .. '9')+ L; +DATELITERAL : '{' D STRINGLITERAL '}'; +TIMELITERAL : '{' T STRINGLITERAL '}'; +TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}'; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java index aec9763fa0..2d458b91fa 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java @@ -43,7 +43,7 @@ class EqlCountQueryTransformer extends EqlQueryRenderer { } @Override - public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -93,7 +93,7 @@ public QueryTokenStream visitSelect_clause(EqlParser.Select_clauseContext ctx) { return builder; } - private QueryRendererBuilder getDistinctCountSelection(QueryTokenStream selectionListbuilder) { + private QueryTokenStream getDistinctCountSelection(QueryTokenStream selectionListbuilder) { QueryRendererBuilder nested = new QueryRendererBuilder(); CountSelectionTokenStream countSelection = CountSelectionTokenStream.create(selectionListbuilder); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index 34e085ba47..321c9eda28 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -78,30 +78,8 @@ public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext builder.appendExpression(visit(ctx.orderby_clause())); } - for (int i = 0; i < ctx.setOperator().size(); i++) { - - builder.appendExpression(visit(ctx.setOperator(i))); - builder.appendExpression(visit(ctx.select_statement(i))); - } - - return builder; - } - - @Override - public QueryTokenStream visitSetOperator(EqlParser.SetOperatorContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - if (ctx.UNION() != null) { - builder.append(QueryTokens.expression(ctx.UNION())); - } else if (ctx.INTERSECT() != null) { - builder.append(QueryTokens.expression(ctx.INTERSECT())); - } else if (ctx.EXCEPT() != null) { - builder.append(QueryTokens.expression(ctx.EXCEPT())); - } - - if (ctx.ALL() != null) { - builder.append(QueryTokens.expression(ctx.ALL())); + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); } return builder; @@ -227,9 +205,11 @@ public QueryTokenStream visitJoin(EqlParser.JoinContext ctx) { if (ctx.AS() != null) { builder.append(QueryTokens.expression(ctx.AS())); } + if (ctx.identification_variable() != null) { builder.appendExpression(visit(ctx.identification_variable())); } + if (ctx.join_condition() != null) { builder.appendExpression(visit(ctx.join_condition())); } @@ -292,8 +272,7 @@ public QueryTokenStream visitJoin_condition(EqlParser.Join_conditionContext ctx) } @Override - public QueryTokenStream visitJoin_association_path_expression( - EqlParser.Join_association_path_expressionContext ctx) { + public QueryTokenStream visitJoin_association_path_expression(EqlParser.Join_association_path_expressionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -305,31 +284,25 @@ public QueryTokenStream visitJoin_association_path_expression( builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { - if (ctx.join_collection_valued_path_expression() != null) { + QueryRendererBuilder nested = QueryRenderer.builder(); - QueryRendererBuilder nested = QueryRenderer.builder(); + if (ctx.join_collection_valued_path_expression() != null) { - nested.append(QueryTokens.token(ctx.TREAT())); - nested.append(TOKEN_OPEN_PAREN); - nested.appendInline(visit(ctx.join_collection_valued_path_expression())); + nested.appendExpression(visit(ctx.join_collection_valued_path_expression())); nested.append(QueryTokens.expression(ctx.AS())); - nested.appendInline(visit(ctx.subtype())); - nested.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.subtype())); - builder.appendExpression(nested); } else if (ctx.join_single_valued_path_expression() != null) { - QueryRendererBuilder nested = QueryRenderer.builder(); - - nested.append(QueryTokens.token(ctx.TREAT())); - nested.append(TOKEN_OPEN_PAREN); - nested.appendInline(visit(ctx.join_single_valued_path_expression())); + nested.appendExpression(visit(ctx.join_single_valued_path_expression())); nested.append(QueryTokens.expression(ctx.AS())); - nested.appendInline(visit(ctx.subtype())); - nested.append(TOKEN_CLOSE_PAREN); - - builder.appendExpression(nested); + nested.appendExpression(visit(ctx.subtype())); } + + builder.append(QueryTokens.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -451,8 +424,7 @@ public QueryTokenStream visitSingle_valued_path_expression(EqlParser.Single_valu } @Override - public QueryTokenStream visitGeneral_identification_variable( - EqlParser.General_identification_variableContext ctx) { + public QueryTokenStream visitGeneral_identification_variable(EqlParser.General_identification_variableContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -495,12 +467,15 @@ public QueryTokenStream visitSimple_subpath(EqlParser.Simple_subpathContext ctx) public QueryTokenStream visitTreated_subpath(EqlParser.Treated_subpathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.general_subpath())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); builder.append(QueryTokens.token(ctx.TREAT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.general_subpath())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -853,15 +828,15 @@ public QueryTokenStream visitOrderby_item(EqlParser.Orderby_itemContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_field_path_expression() != null) { - builder.append(visit(ctx.state_field_path_expression())); + builder.appendExpression(visit(ctx.state_field_path_expression())); } else if (ctx.general_identification_variable() != null) { - builder.append(visit(ctx.general_identification_variable())); + builder.appendExpression(visit(ctx.general_identification_variable())); } else if (ctx.result_variable() != null) { - builder.append(visit(ctx.result_variable())); + builder.appendExpression(visit(ctx.result_variable())); } else if (ctx.string_expression() != null) { - builder.append(visit(ctx.string_expression())); + builder.appendExpression(visit(ctx.string_expression())); } else if (ctx.scalar_expression() != null) { - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); } if (ctx.ASC() != null) { @@ -883,12 +858,44 @@ public QueryTokenStream visitNullsPrecedence(EqlParser.NullsPrecedenceContext ct QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_NULLS); + builder.append(QueryTokens.expression(ctx.NULLS())); if (ctx.FIRST() != null) { - builder.append(TOKEN_FIRST); + builder.append(QueryTokens.expression(ctx.FIRST())); } else if (ctx.LAST() != null) { - builder.append(TOKEN_LAST); + builder.append(QueryTokens.expression(ctx.LAST())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSet_fuction(EqlParser.Set_fuctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.setOperator() != null) { + builder.append(visit(ctx.setOperator())); + } + + builder.appendExpression(visit(ctx.select_statement())); + + return builder; + } + + @Override + public QueryTokenStream visitSetOperator(EqlParser.SetOperatorContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.INTERSECT() != null) { + builder.append(QueryTokens.expression(ctx.INTERSECT())); + } else if (ctx.UNION() != null) { + builder.append(QueryTokens.expression(ctx.UNION())); + } else if (ctx.EXCEPT() != null) { + builder.append(QueryTokens.expression(ctx.EXCEPT())); + } else if (ctx.ALL() != null) { + builder.append(QueryTokens.expression(ctx.ALL())); } return builder; @@ -993,83 +1000,82 @@ public QueryTokenStream visitSimple_select_expression(EqlParser.Simple_select_ex @Override public QueryTokenStream visitScalar_expression(EqlParser.Scalar_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_expression() != null) { - builder.append(visit(ctx.arithmetic_expression())); + return visit(ctx.arithmetic_expression()); } else if (ctx.string_expression() != null) { - builder.append(visit(ctx.string_expression())); + return visit(ctx.string_expression()); } else if (ctx.enum_expression() != null) { - builder.append(visit(ctx.enum_expression())); + return visit(ctx.enum_expression()); } else if (ctx.datetime_expression() != null) { - builder.append(visit(ctx.datetime_expression())); + return visit(ctx.datetime_expression()); } else if (ctx.boolean_expression() != null) { - builder.append(visit(ctx.boolean_expression())); + return visit(ctx.boolean_expression()); } else if (ctx.case_expression() != null) { - builder.append(visit(ctx.case_expression())); + return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { - builder.append(visit(ctx.entity_type_expression())); + return visit(ctx.entity_type_expression()); } else if (ctx.cast_function() != null) { return (visit(ctx.cast_function())); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitConditional_expression(EqlParser.Conditional_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.conditional_expression() != null) { - builder.append(visit(ctx.conditional_expression())); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.conditional_expression())); builder.append(QueryTokens.expression(ctx.OR())); - builder.append(visit(ctx.conditional_term())); + builder.appendExpression(visit(ctx.conditional_term())); + + return builder; } else { - builder.append(visit(ctx.conditional_term())); + return visit(ctx.conditional_term()); } - - return builder; } @Override public QueryTokenStream visitConditional_term(EqlParser.Conditional_termContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.conditional_term() != null) { - builder.append(visit(ctx.conditional_term())); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.conditional_term())); builder.append(QueryTokens.expression(ctx.AND())); - builder.append(visit(ctx.conditional_factor())); + builder.appendExpression(visit(ctx.conditional_factor())); + + return builder; } else { - builder.append(visit(ctx.conditional_factor())); + return visit(ctx.conditional_factor()); } - - return builder; } @Override public QueryTokenStream visitConditional_factor(EqlParser.Conditional_factorContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.NOT() != null) { + QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.NOT())); + builder.appendExpression(visit(ctx.conditional_primary())); + return builder; } - builder.append(visit(ctx.conditional_primary())); - - return builder; + return visit(ctx.conditional_primary()); } @Override public QueryTokenStream visitConditional_primary(EqlParser.Conditional_primaryContext ctx) { + if (ctx.simple_cond_expression() != null) { + return visit(ctx.simple_cond_expression()); + } + QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.simple_cond_expression() != null) { - builder.append(visit(ctx.simple_cond_expression())); - } else if (ctx.conditional_expression() != null) { + if (ctx.conditional_expression() != null) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.conditional_expression())); @@ -1082,27 +1088,25 @@ public QueryTokenStream visitConditional_primary(EqlParser.Conditional_primaryCo @Override public QueryTokenStream visitSimple_cond_expression(EqlParser.Simple_cond_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.comparison_expression() != null) { - builder.append(visit(ctx.comparison_expression())); + return visit(ctx.comparison_expression()); } else if (ctx.between_expression() != null) { - builder.append(visit(ctx.between_expression())); + return visit(ctx.between_expression()); } else if (ctx.in_expression() != null) { - builder.append(visit(ctx.in_expression())); + return visit(ctx.in_expression()); } else if (ctx.like_expression() != null) { - builder.append(visit(ctx.like_expression())); + return visit(ctx.like_expression()); } else if (ctx.null_comparison_expression() != null) { - builder.append(visit(ctx.null_comparison_expression())); + return visit(ctx.null_comparison_expression()); } else if (ctx.empty_collection_comparison_expression() != null) { - builder.append(visit(ctx.empty_collection_comparison_expression())); + return visit(ctx.empty_collection_comparison_expression()); } else if (ctx.collection_member_expression() != null) { - builder.append(visit(ctx.collection_member_expression())); + return visit(ctx.collection_member_expression()); } else if (ctx.exists_expression() != null) { - builder.append(visit(ctx.exists_expression())); + return visit(ctx.exists_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1112,7 +1116,7 @@ public QueryTokenStream visitBetween_expression(EqlParser.Between_expressionCont if (ctx.arithmetic_expression(0) != null) { - builder.append(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1138,7 +1142,7 @@ public QueryTokenStream visitBetween_expression(EqlParser.Between_expressionCont } else if (ctx.datetime_expression(0) != null) { - builder.append(visit(ctx.datetime_expression(0))); + builder.appendExpression(visit(ctx.datetime_expression(0))); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1159,10 +1163,10 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - builder.append(visit(ctx.state_valued_path_expression())); + builder.appendExpression(visit(ctx.state_valued_path_expression())); } if (ctx.type_discriminator() != null) { - builder.append(visit(ctx.type_discriminator())); + builder.appendExpression(visit(ctx.type_discriminator())); } if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1175,7 +1179,6 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(QueryTokenStream.concat(ctx.in_item(), this::visit, TOKEN_COMMA)); - builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.subquery() != null) { @@ -1192,15 +1195,13 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { @Override public QueryTokenStream visitIn_item(EqlParser.In_itemContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.literal() != null) { - builder.append(visit(ctx.literal())); + return visit(ctx.literal()); } else if (ctx.single_valued_input_parameter() != null) { - builder.append(visit(ctx.single_valued_input_parameter())); + return visit(ctx.single_valued_input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1208,7 +1209,8 @@ public QueryTokenStream visitLike_expression(EqlParser.Like_expressionContext ct QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.string_expression())); + builder.appendExpression(visit(ctx.string_expression())); + if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } @@ -1230,11 +1232,11 @@ public QueryTokenStream visitNull_comparison_expression(EqlParser.Null_compariso QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - builder.append(visit(ctx.single_valued_path_expression())); + builder.appendExpression(visit(ctx.single_valued_path_expression())); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + builder.appendExpression(visit(ctx.input_parameter())); } else if (ctx.nullif_expression() != null) { - builder.append(visit(ctx.nullif_expression())); + builder.appendExpression(visit(ctx.nullif_expression())); } if (ctx.op != null) { @@ -1256,7 +1258,7 @@ public QueryTokenStream visitEmpty_collection_comparison_expression( QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.collection_valued_path_expression())); + builder.appendExpression(visit(ctx.collection_valued_path_expression())); builder.append(QueryTokens.expression(ctx.IS())); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1271,7 +1273,7 @@ public QueryTokenStream visitCollection_member_expression(EqlParser.Collection_m QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.entity_or_value_expression())); + builder.appendExpression(visit(ctx.entity_or_value_expression())); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } @@ -1287,34 +1289,30 @@ public QueryTokenStream visitCollection_member_expression(EqlParser.Collection_m @Override public QueryTokenStream visitEntity_or_value_expression(EqlParser.Entity_or_value_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_object_path_expression() != null) { - builder.append(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.state_field_path_expression() != null) { - builder.append(visit(ctx.state_field_path_expression())); + return visit(ctx.state_field_path_expression()); } else if (ctx.simple_entity_or_value_expression() != null) { - builder.append(visit(ctx.simple_entity_or_value_expression())); + return visit(ctx.simple_entity_or_value_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitSimple_entity_or_value_expression( EqlParser.Simple_entity_or_value_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } else if (ctx.literal() != null) { - builder.append(visit(ctx.literal())); + return visit(ctx.literal()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1359,13 +1357,13 @@ public QueryTokenStream visitStringComparison(EqlParser.StringComparisonContext QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendInline(visit(ctx.string_expression(0))); - builder.append(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.string_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.string_expression(1) != null) { - builder.append(visit(ctx.string_expression(1))); + builder.appendExpression(visit(ctx.string_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1380,9 +1378,9 @@ public QueryTokenStream visitBooleanComparison(EqlParser.BooleanComparisonContex builder.append(QueryTokens.ventilated(ctx.op)); if (ctx.boolean_expression(1) != null) { - builder.append(visit(ctx.boolean_expression(1))); + builder.appendExpression(visit(ctx.boolean_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1402,9 +1400,9 @@ public QueryTokenStream visitEnumComparison(EqlParser.EnumComparisonContext ctx) builder.append(QueryTokens.ventilated(ctx.op)); if (ctx.enum_expression(1) != null) { - builder.append(visit(ctx.enum_expression(1))); + builder.appendExpression(visit(ctx.enum_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1419,9 +1417,9 @@ public QueryTokenStream visitDatetimeComparison(EqlParser.DatetimeComparisonCont builder.append(QueryTokens.ventilated(ctx.comparison_operator().op)); if (ctx.datetime_expression(1) != null) { - builder.append(visit(ctx.datetime_expression(1))); + builder.appendExpression(visit(ctx.datetime_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1436,9 +1434,9 @@ public QueryTokenStream visitEntityComparison(EqlParser.EntityComparisonContext builder.append(QueryTokens.expression(ctx.op)); if (ctx.entity_expression(1) != null) { - builder.append(visit(ctx.entity_expression(1))); + builder.appendExpression(visit(ctx.entity_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1449,13 +1447,13 @@ public QueryTokenStream visitArithmeticComparison(EqlParser.ArithmeticComparison QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.arithmetic_expression(0))); - builder.append(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.arithmetic_expression(1) != null) { - builder.append(visit(ctx.arithmetic_expression(1))); + builder.appendExpression(visit(ctx.arithmetic_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1468,7 +1466,7 @@ public QueryTokenStream visitEntityTypeComparison(EqlParser.EntityTypeComparison builder.appendInline(visit(ctx.entity_type_expression(0))); builder.append(QueryTokens.ventilated(ctx.op)); - builder.append(visit(ctx.entity_type_expression(1))); + builder.appendExpression(visit(ctx.entity_type_expression(1))); return builder; } @@ -1487,42 +1485,37 @@ public QueryTokenStream visitRegexpComparison(EqlParser.RegexpComparisonContext @Override public QueryTokenStream visitComparison_operator(EqlParser.Comparison_operatorContext ctx) { - return QueryRendererBuilder.from(QueryTokens.ventilated(ctx.op)); + return QueryRenderer.from(QueryTokens.token(ctx.op)); } @Override public QueryTokenStream visitArithmetic_expression(EqlParser.Arithmetic_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_expression() != null) { + QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.arithmetic_expression())); - builder.append(QueryTokens.expression(ctx.op)); + builder.append(QueryTokens.ventilated(ctx.op)); builder.append(visit(ctx.arithmetic_term())); + return builder; } else { - builder.append(visit(ctx.arithmetic_term())); + return visit(ctx.arithmetic_term()); } - - return builder; } @Override public QueryTokenStream visitArithmetic_term(EqlParser.Arithmetic_termContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_term() != null) { - + QueryRendererBuilder builder = QueryRenderer.builder(); builder.appendInline(visit(ctx.arithmetic_term())); builder.append(QueryTokens.ventilated(ctx.op)); builder.append(visit(ctx.arithmetic_factor())); + return builder; } else { - builder.append(visit(ctx.arithmetic_factor())); + return visit(ctx.arithmetic_factor()); } - - return builder; } @Override @@ -1533,7 +1526,8 @@ public QueryTokenStream visitArithmetic_factor(EqlParser.Arithmetic_factorContex if (ctx.op != null) { builder.append(QueryTokens.token(ctx.op)); } - builder.appendInline(visit(ctx.arithmetic_primary())); + + builder.append(visit(ctx.arithmetic_primary())); return builder; } @@ -1690,45 +1684,39 @@ public QueryTokenStream visitEnum_expression(EqlParser.Enum_expressionContext ct @Override public QueryTokenStream visitEntity_expression(EqlParser.Entity_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_object_path_expression() != null) { - builder.append(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.simple_entity_expression() != null) { - builder.append(visit(ctx.simple_entity_expression())); + return visit(ctx.simple_entity_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitSimple_entity_expression(EqlParser.Simple_entity_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitEntity_type_expression(EqlParser.Entity_type_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.type_discriminator() != null) { - builder.append(visit(ctx.type_discriminator())); + return visit(ctx.type_discriminator()); } else if (ctx.entity_type_literal() != null) { - builder.append(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1903,7 +1891,7 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.SUBSTRING())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.string_expression(0))); + builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_COMMA); builder.appendInline(QueryTokenStream.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_PAREN); @@ -1920,7 +1908,7 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret if (ctx.FROM() != null) { builder.append(QueryTokens.expression(ctx.FROM())); } - builder.appendInline(visit(ctx.string_expression(0))); + builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { @@ -1932,10 +1920,9 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.UPPER())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.string_expression(0))); + builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LEFT() != null) { - builder.append(QueryTokens.token(ctx.LEFT())); builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.string_expression(0))); @@ -1943,7 +1930,6 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.appendInline(visit(ctx.arithmetic_expression(0))); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.RIGHT() != null) { - builder.append(QueryTokens.token(ctx.RIGHT())); builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.string_expression(0))); @@ -1951,7 +1937,6 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.appendInline(visit(ctx.arithmetic_expression(0))); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.REPLACE() != null) { - builder.append(QueryTokens.token(ctx.REPLACE())); builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.string_expression(0))); @@ -1969,11 +1954,11 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.LEADING())); + return QueryRenderer.from(QueryTokens.expression(ctx.LEADING())); } else if (ctx.TRAILING() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TRAILING())); + return QueryRenderer.from(QueryTokens.expression(ctx.TRAILING())); } else { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.BOTH())); + return QueryRenderer.from(QueryTokens.expression(ctx.BOTH())); } } @@ -2025,12 +2010,15 @@ public QueryTokenStream visitFunction_invocation(EqlParser.Function_invocationCo public QueryTokenStream visitExtract_datetime_field(EqlParser.Extract_datetime_fieldContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_field())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_field())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -2045,12 +2033,15 @@ public QueryTokenStream visitDatetime_field(EqlParser.Datetime_fieldContext ctx) public QueryTokenStream visitExtract_datetime_part(EqlParser.Extract_datetime_partContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_part())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_part())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -2103,10 +2094,7 @@ public QueryTokenStream visitGeneral_case_expression(EqlParser.General_case_expr QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.CASE())); - - ctx.when_clause().forEach(whenClauseContext -> { - builder.appendExpression(visit(whenClauseContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.when_clause(), this::visit, TOKEN_SPACE)); builder.append(QueryTokens.expression(ctx.ELSE())); builder.appendExpression(visit(ctx.scalar_expression())); @@ -2121,9 +2109,9 @@ public QueryTokenStream visitWhen_clause(EqlParser.When_clauseContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.WHEN())); - builder.append(visit(ctx.conditional_expression())); + builder.appendExpression(visit(ctx.conditional_expression())); builder.append(QueryTokens.expression(ctx.THEN())); - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); return builder; } @@ -2134,14 +2122,11 @@ public QueryTokenStream visitSimple_case_expression(EqlParser.Simple_case_expres QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.CASE())); - builder.append(visit(ctx.case_operand())); - - ctx.simple_when_clause().forEach(simpleWhenClauseContext -> { - builder.append(visit(simpleWhenClauseContext)); - }); + builder.appendExpression(visit(ctx.case_operand())); + builder.appendExpression(QueryTokenStream.concat(ctx.simple_when_clause(), this::visit, TOKEN_SPACE)); builder.append(QueryTokens.expression(ctx.ELSE())); - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); builder.append(QueryTokens.expression(ctx.END())); return builder; @@ -2202,11 +2187,11 @@ public QueryTokenStream visitNullif_expression(EqlParser.Nullif_expressionContex public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2214,14 +2199,14 @@ public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) public QueryTokenStream visitIdentification_variable(EqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); - } else if (ctx.f != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.f)); + return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.type_literal() != null) { return visit(ctx.type_literal()); + } else if (ctx.f != null) { + return QueryRenderer.from(QueryTokens.token(ctx.f)); + } else { + return QueryTokenStream.empty(); } - - return QueryRenderer.builder(); } @Override @@ -2232,23 +2217,23 @@ public QueryTokenStream visitConstructor_name(EqlParser.Constructor_nameContext @Override public QueryTokenStream visitLiteral(EqlParser.LiteralContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.STRINGLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + } else if (ctx.JAVASTRINGLITERAL() != null) { + return QueryRenderer.from(QueryTokens.expression(ctx.JAVASTRINGLITERAL())); } else if (ctx.INTLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.INTLITERAL())); + return QueryRenderer.from(QueryTokens.expression(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.FLOATLITERAL())); + return QueryRenderer.from(QueryTokens.expression(ctx.FLOATLITERAL())); } else if (ctx.LONGLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.LONGLITERAL())); + return QueryRenderer.from(QueryTokens.expression(ctx.LONGLITERAL())); } else if (ctx.boolean_literal() != null) { - builder.append(visit(ctx.boolean_literal())); + return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { - builder.append(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -2302,20 +2287,20 @@ public QueryTokenStream visitEntity_type_literal(EqlParser.Entity_type_literalCo @Override public QueryTokenStream visitEscape_character(EqlParser.Escape_characterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.CHARACTER())); + return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); } @Override public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTLITERAL())); + return QueryRenderer.from(QueryTokens.token(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOATLITERAL())); + return QueryRenderer.from(QueryTokens.token(ctx.FLOATLITERAL())); } else if (ctx.LONGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LONGLITERAL())); + return QueryRenderer.from(QueryTokens.token(ctx.LONGLITERAL())); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2323,11 +2308,11 @@ public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ct public QueryTokenStream visitBoolean_literal(EqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.TRUE())); + return QueryRenderer.from(QueryTokens.expression(ctx.TRUE())); } else if (ctx.FALSE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FALSE())); + return QueryRenderer.from(QueryTokens.expression(ctx.FALSE())); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2340,11 +2325,11 @@ public QueryTokenStream visitEnum_literal(EqlParser.Enum_literalContext ctx) { public QueryTokenStream visitString_literal(EqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); } else if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2381,7 +2366,7 @@ public QueryTokenStream visitCollection_value_field(EqlParser.Collection_value_f @Override public QueryTokenStream visitEntity_name(EqlParser.Entity_nameContext ctx) { - return QueryTokenStream.concat(ctx.reserved_word(), this::visit, QueryRenderer::inline, TOKEN_DOT); + return QueryTokenStream.concat(ctx.reserved_word(), this::visit, TOKEN_DOT); } @Override @@ -2412,26 +2397,25 @@ public QueryTokenStream visitFunction_name(EqlParser.Function_nameContext ctx) { } @Override - public QueryTokenStream visitCharacter_valued_input_parameter( - EqlParser.Character_valued_input_parameterContext ctx) { + public QueryTokenStream visitCharacter_valued_input_parameter(EqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @Override public QueryTokenStream visitReserved_word(EqlParser.Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); + return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.f)); + return QueryRenderer.from(QueryTokens.token(ctx.f)); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java index ed14e9afdf..dc5aa35951 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -23,7 +23,6 @@ import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query by applying @@ -50,7 +49,7 @@ class EqlSortedQueryTransformer extends EqlQueryRenderer { } @Override - public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -69,18 +68,16 @@ public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementCont builder.appendExpression(visit(ctx.having_clause())); } - doVisitOrderBy(builder, ctx, ObjectUtils.isEmpty(ctx.setOperator()) ? this.sort : Sort.unsorted()); - - for (int i = 0; i < ctx.setOperator().size(); i++) { - - builder.appendExpression(visit(ctx.setOperator(i))); - builder.appendExpression(visit(ctx.select_statement(i))); + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } else { + doVisitOrderBy(builder, ctx); } return builder; } - private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx, Sort sort) { + private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx) { if (ctx.orderby_clause() != null) { QueryTokenStream existingOrder = visit(ctx.orderby_clause()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java index fe3e9e4940..faad242941 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -44,16 +44,6 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer { @Override public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { - if(ctx.select_query() != null) { - return visitSelect_query(ctx.select_query()); - } - - return QueryTokenStream.empty(); - } - - @Override - public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); builder.appendExpression(visit(ctx.select_clause())); @@ -68,7 +58,7 @@ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { if (ctx.having_clause() != null) { builder.appendExpression(visit(ctx.having_clause())); } - if(ctx.set_fuction() != null) { + if (ctx.set_fuction() != null) { builder.appendExpression(visit(ctx.set_fuction())); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index cbf32caa3d..79391fa677 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -15,28 +15,14 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COLON; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOT; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_EQUALS; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_QUESTION_MARK; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOUBLE_PIPE; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_SPACE; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN; +import static org.springframework.data.jpa.repository.query.QueryTokens.*; import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.tree.ParseTree; -import org.springframework.data.jpa.repository.query.JpqlParser.Except_clauseContext; -import org.springframework.data.jpa.repository.query.JpqlParser.Intersect_clauseContext; -import org.springframework.data.jpa.repository.query.JpqlParser.Relation_fuctions_selectContext; import org.springframework.data.jpa.repository.query.JpqlParser.NullsPrecedenceContext; -import org.springframework.data.jpa.repository.query.JpqlParser.Cast_expressionContext; import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext; import org.springframework.data.jpa.repository.query.JpqlParser.Set_fuctionContext; import org.springframework.data.jpa.repository.query.JpqlParser.Type_literalContext; @@ -72,17 +58,8 @@ public QueryTokenStream visitQl_statement(JpqlParser.Ql_statementContext ctx) { } } - @Override - public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { - - if(ctx.select_query() != null) { - return visitSelect_query(ctx.select_query()); - } - - return QueryTokenStream.empty(); - } - - public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { + @Override + public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -105,32 +82,13 @@ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { builder.appendExpression(visit(ctx.orderby_clause())); } - if(ctx.set_fuction() != null) { - builder.appendExpression(visit(ctx.set_fuction())); + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); } return builder; } - @Override - public List visitSetOperator_with_select_statement( - JpqlParser.SetOperator_with_select_statementContext ctx) { - - List tokens = new ArrayList<>(); - - if (ctx.INTERSECT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTERSECT())); - } else if (ctx.UNION() != null) { - tokens.add(new JpaQueryParsingToken(ctx.UNION())); - } else if (ctx.EXCEPT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXCEPT())); - } - - tokens.addAll(visit(ctx.select_statement())); - - return builder; - } - @Override public QueryTokenStream visitUpdate_statement(JpqlParser.Update_statementContext ctx) { @@ -229,14 +187,19 @@ public QueryTokenStream visitJoin(JpqlParser.JoinContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.join_spec())); - builder.append(visit(ctx.join_association_path_expression())); + builder.appendExpression(visit(ctx.join_spec())); + builder.appendExpression(visit(ctx.join_association_path_expression())); + if (ctx.AS() != null) { builder.append(QueryTokens.expression(ctx.AS())); } - builder.append(visit(ctx.identification_variable())); + + if (ctx.identification_variable() != null) { + builder.appendExpression(visit(ctx.identification_variable())); + } + if (ctx.join_condition() != null) { - builder.append(visit(ctx.join_condition())); + builder.appendExpression(visit(ctx.join_condition())); } return builder; @@ -247,9 +210,19 @@ public QueryTokenStream visitFetch_join(JpqlParser.Fetch_joinContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.join_spec())); + builder.appendExpression(visit(ctx.join_spec())); builder.append(QueryTokens.expression(ctx.FETCH())); - builder.append(visit(ctx.join_association_path_expression())); + builder.appendExpression(visit(ctx.join_association_path_expression())); + + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + if (ctx.identification_variable() != null) { + builder.appendExpression(visit(ctx.identification_variable())); + } + if (ctx.join_condition() != null) { + builder.appendExpression(visit(ctx.join_condition())); + } return builder; } @@ -300,23 +273,25 @@ public QueryTokenStream visitJoin_association_path_expression( builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { + QueryRendererBuilder nested = QueryRenderer.builder(); + if (ctx.join_collection_valued_path_expression() != null) { - builder.append(QueryTokens.token(ctx.TREAT())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.join_collection_valued_path_expression())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); - builder.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.join_collection_valued_path_expression())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); + } else if (ctx.join_single_valued_path_expression() != null) { - builder.append(QueryTokens.token(ctx.TREAT())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.join_single_valued_path_expression())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); - builder.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.join_single_valued_path_expression())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); } + + builder.append(QueryTokens.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -476,12 +451,15 @@ public QueryTokenStream visitSimple_subpath(JpqlParser.Simple_subpathContext ctx public QueryTokenStream visitTreated_subpath(JpqlParser.Treated_subpathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.general_subpath())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); builder.append(QueryTokens.token(ctx.TREAT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.general_subpath())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -825,19 +803,6 @@ public QueryTokenStream visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx return builder; } - @Override - public QueryTokenStream visitSet_fuction(Set_fuctionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.setOperator().getStart())); - if(ctx.setOperator().ALL() != null) { - builder.append(QueryTokens.expression(ctx.setOperator().ALL())); - } - builder.appendExpression(visit(ctx.set_function_select().select_query())); - return builder; - } - @Override public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { @@ -853,48 +818,60 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { if (ctx.ASC() != null) { builder.append(QueryTokens.expression(ctx.ASC())); - } - if (ctx.DESC() != null) { + } else if (ctx.DESC() != null) { builder.append(QueryTokens.expression(ctx.DESC())); } if (ctx.nullsPrecedence() != null) { builder.append(visit(ctx.nullsPrecedence())); } - if (ctx.nullsPrecedence() != null) { - tokens.addAll(visit(ctx.nullsPrecedence())); - } - return tokens; + return builder; } @Override - public List visitNullsPrecedence(JpqlParser.NullsPrecedenceContext ctx) { + public QueryTokenStream visitNullsPrecedence(NullsPrecedenceContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NULLS())); + builder.append(QueryTokens.expression(ctx.NULLS())); if (ctx.FIRST() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FIRST())); + builder.append(QueryTokens.expression(ctx.FIRST())); } else if (ctx.LAST() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LAST())); + builder.append(QueryTokens.expression(ctx.LAST())); } return builder; } @Override - public QueryTokenStream visitNullsPrecedence(NullsPrecedenceContext ctx) { + public QueryTokenStream visitSet_fuction(Set_fuctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_NULLS); + if (ctx.setOperator() != null) { + builder.append(visit(ctx.setOperator())); + } - if (ctx.FIRST() != null) { - builder.append(TOKEN_FIRST); - } else if (ctx.LAST() != null) { - builder.append(TOKEN_LAST); + builder.appendExpression(visit(ctx.select_statement())); + + return builder; + } + + @Override + public QueryTokenStream visitSetOperator(JpqlParser.SetOperatorContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.INTERSECT() != null) { + builder.append(QueryTokens.expression(ctx.INTERSECT())); + } else if (ctx.UNION() != null) { + builder.append(QueryTokens.expression(ctx.UNION())); + } else if (ctx.EXCEPT() != null) { + builder.append(QueryTokens.expression(ctx.EXCEPT())); + } else if (ctx.ALL() != null) { + builder.append(QueryTokens.expression(ctx.ALL())); } return builder; @@ -1012,8 +989,8 @@ public QueryTokenStream visitScalar_expression(JpqlParser.Scalar_expressionConte return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { return visit(ctx.entity_type_expression()); - } else if (ctx.cast_expression() != null) { - return (visit(ctx.cast_expression())); + } else if (ctx.cast_function() != null) { + return (visit(ctx.cast_function())); } return QueryTokenStream.empty(); @@ -1237,9 +1214,11 @@ public QueryTokenStream visitNull_comparison_expression(JpqlParser.Null_comparis } builder.append(QueryTokens.expression(ctx.IS())); + if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } + builder.append(QueryTokens.expression(ctx.NULL())); return builder; @@ -1493,6 +1472,8 @@ public QueryTokenStream visitArithmetic_primary(JpqlParser.Arithmetic_primaryCon builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { builder.append(visit(ctx.case_expression())); + } else if (ctx.cast_function() != null) { + builder.append(visit(ctx.cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1524,11 +1505,6 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); - } else if (ctx.op != null) { - - tokens.addAll(visit(ctx.string_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.string_expression(1))); } else if (ctx.subquery() != null) { builder.append(TOKEN_OPEN_PAREN); @@ -1782,6 +1758,8 @@ public QueryTokenStream visitFunctions_returning_numerics(JpqlParser.Functions_r builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.identification_variable())); builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.extract_datetime_field() != null) { + builder.append(visit(ctx.extract_datetime_field())); } return builder; @@ -1809,6 +1787,8 @@ public QueryTokenStream visitFunctions_returning_datetime(JpqlParser.Functions_r } else if (ctx.DATETIME() != null) { builder.append(QueryTokens.expression(ctx.DATETIME())); } + } else if (ctx.extract_datetime_part() != null) { + builder.append(visit(ctx.extract_datetime_part())); } return builder; @@ -1830,6 +1810,7 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re builder.append(QueryTokens.token(ctx.SUBSTRING())); builder.append(TOKEN_OPEN_PAREN); builder.append(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); builder.appendInline(QueryTokenStream.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.TRIM() != null) { @@ -1899,6 +1880,28 @@ public QueryTokenStream visitTrim_specification(JpqlParser.Trim_specificationCon } } + @Override + public QueryTokenStream visitCast_function(JpqlParser.Cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.single_valued_path_expression())); + builder.append(TOKEN_SPACE); + builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE)); + + if (!ObjectUtils.isEmpty(ctx.numeric_literal())) { + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + @Override public QueryTokenStream visitFunction_invocation(JpqlParser.Function_invocationContext ctx) { @@ -1920,12 +1923,15 @@ public QueryTokenStream visitFunction_invocation(JpqlParser.Function_invocationC public QueryTokenStream visitExtract_datetime_field(JpqlParser.Extract_datetime_fieldContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.EXTRACT())); + nested.appendExpression(visit(ctx.datetime_field())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); + + builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_field())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1940,12 +1946,15 @@ public QueryTokenStream visitDatetime_field(JpqlParser.Datetime_fieldContext ctx public QueryTokenStream visitExtract_datetime_part(JpqlParser.Extract_datetime_partContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_part())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); - builder.append(QueryTokens.expression(ctx.EXTRACT())); + builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_part())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.append(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1984,18 +1993,6 @@ public QueryTokenStream visitCase_expression(JpqlParser.Case_expressionContext c } } - @Override - public QueryRendererBuilder visitCast_expression(Cast_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.token(ctx.CAST())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.string_expression())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.type_literal())); - builder.append(TOKEN_CLOSE_PAREN); - return builder; - } - @Override public QueryRendererBuilder visitType_literal(Type_literalContext ctx) { @@ -2089,7 +2086,7 @@ public QueryTokenStream visitNullif_expression(JpqlParser.Nullif_expressionConte QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.NULLIF())); + builder.append(QueryTokens.token(ctx.NULLIF())); builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.scalar_expression(0))); builder.append(TOKEN_COMMA); @@ -2115,7 +2112,9 @@ public QueryTokenStream visitTrim_character(JpqlParser.Trim_characterContext ctx public QueryTokenStream visitIdentification_variable(JpqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); + return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + } else if (ctx.type_literal() != null) { + return visit(ctx.type_literal()); } else if (ctx.f != null) { return QueryRenderer.from(QueryTokens.token(ctx.f)); } else { @@ -2175,7 +2174,18 @@ public QueryTokenStream visitPattern_value(JpqlParser.Pattern_valueContext ctx) @Override public QueryTokenStream visitDate_time_timestamp_literal(JpqlParser.Date_time_timestamp_literalContext ctx) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + + if (ctx.STRINGLITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + } else if (ctx.DATELITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATELITERAL())); + } else if (ctx.TIMELITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMELITERAL())); + } else if (ctx.TIMESTAMPLITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMESTAMPLITERAL())); + } else { + return QueryRenderer.builder(); + } } @Override @@ -2264,7 +2274,7 @@ public QueryTokenStream visitCollection_value_field(JpqlParser.Collection_value_ @Override public QueryTokenStream visitEntity_name(JpqlParser.Entity_nameContext ctx) { - return QueryTokenStream.concat(ctx.reserved_word(), this::visitReserved_word, TOKEN_DOT); + return QueryTokenStream.concat(ctx.reserved_word(), this::visit, TOKEN_DOT); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index f182ad2039..fc229016a5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -50,16 +50,6 @@ class JpqlSortedQueryTransformer extends JpqlQueryRenderer { @Override public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { - if(ctx.select_query() != null) { - return visitSelect_query(ctx.select_query()); - } - - return QueryTokenStream.empty(); - } - - @Override - public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); builder.appendExpression(visit(ctx.select_clause())); @@ -86,7 +76,7 @@ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) { return builder; } - private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_queryContext ctx) { + private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_statementContext ctx) { if (ctx.orderby_clause() != null) { QueryTokenStream existingOrder = visit(ctx.orderby_clause()); @@ -136,4 +126,5 @@ public QueryTokenStream visitJoin(JpqlParser.JoinContext ctx) { return tokens; } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index 0f0d2aecc0..0f744d5587 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -103,6 +103,7 @@ void joinFetch() { assertQuery("SELECT e FROM Employee e JOIN FETCH e.address"); assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address AS a ORDER BY a.city"); } @Test @@ -123,6 +124,16 @@ void subselectsInFromClause() { "SELECT e, c.city FROM Employee e, (SELECT DISTINCT a.city FROM Address a) c WHERE e.address.city = c.city"); } + @Test // GH-3277 + void numericLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + } + @Test void orderByClause() { @@ -444,7 +455,7 @@ void except() { @ParameterizedTest // GH-3136 @ValueSource(strings = { "STRING", "INTEGER", "FLOAT", "DOUBLE" }) - void jpqlCast(String targetType) { + void cast(String targetType) { assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); } @@ -464,4 +475,5 @@ void replaceStringFunctions() { void stringConcatWithPipes() { assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e"); } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java index 242ec81b83..7588619f23 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java @@ -1017,6 +1017,59 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + + @Disabled + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void orderByWithNullsFirstOrLastShouldWork() { + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls last + """); + } + @ParameterizedTest // GH-3342 @ValueSource(strings = { "select 1 from User u", "select -1 from User u", "select +1 from User u", "select +1 * -100 from User u", "select count(u) * -0.7f from User u", @@ -1039,4 +1092,5 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord); assertQuery(source); } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java index b6ed4fc8b8..81a104c953 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java @@ -281,7 +281,7 @@ void fromClauseDowncastingExample1() { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -290,7 +290,7 @@ void fromClauseDowncastingExample2() { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -305,7 +305,7 @@ void fromClauseDowncastingExample3_SPEC_BUG() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test @@ -316,7 +316,7 @@ void fromClauseDowncastingExample3fixed() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test @@ -326,7 +326,39 @@ void fromClauseDowncastingExample4() { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); + } + + @Test // GH-3136 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + } + + @Test // GH-3136 + void currentDateFunctions() { + + assertQuery("select CURRENT_DATE " + // + "from Call c "); + + assertQuery("select CURRENT_TIME " + // + "from Call c "); + + assertQuery("select CURRENT_TIMESTAMP " + // + "from Call c "); + + assertQuery("select LOCAL_DATE " + // + "from Call c "); + + assertQuery("select LOCAL_TIME " + // + "from Call c "); + + assertQuery("select LOCAL_DATETIME " + // + "from Call c "); } @Test @@ -390,7 +422,7 @@ void allExample() { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -402,7 +434,7 @@ void existsSubSelectExample2() { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -470,7 +502,7 @@ void updateCaseExample1() { WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -483,7 +515,7 @@ void updateCaseExample2() { WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -523,7 +555,7 @@ void theRest() { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index a3430901a1..5eb578b498 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1664,14 +1664,14 @@ void orderByWithNullsFirstOrLastShouldWork() { from Element a where a.erstelltDurch = :variable order by mutationAm desc nulls last - """); + """); } @Test // GH-2964 void roundFunctionShouldWorkLikeAnyOtherFunction() { assertQuery(""" - select round(count(ri)*100/max(ri.receipt.positions), 0) as perc + select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc from StockOrderItem oi right join StockReceiptItem ri on ri.article = oi.article diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index 58bb071648..b0ec7cbb1a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -29,6 +29,7 @@ * suffix. * * @author Christoph Strobl + * @author Mark Paluch */ class JpqlComplianceTests { @@ -57,6 +58,52 @@ private String reduceWhitespace(String original) { .trim(); } + @Test + void selectQueries() { + + assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); + assertQuery("Select e FROM Employee e WHERE e.id = :id"); + assertQuery("Select MAX(e.salary) FROM Employee e"); + assertQuery("Select e.firstName FROM Employee e"); + assertQuery("Select e.firstName, e.lastName FROM Employee e"); + } + + @Test + void selectClause() { + + assertQuery("SELECT COUNT(e) FROM Employee e"); + assertQuery("SELECT MAX(e.salary) FROM Employee e"); + assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); + } + + @Test + void fromClause() { + + assertQuery("SELECT e FROM Employee e"); + assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); + assertQuery("SELECT e FROM com.acme.Employee e"); + } + + @Test + void join() { + + assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); + assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); + } + + @Test + void joinFetch() { + + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address AS a ORDER BY a.city"); + } + + @Test + void leftJoin() { + assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); + } + @Test // GH-3277 void numericLiterals() { @@ -72,6 +119,27 @@ void newWithStrings() { assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); } + @Test + void orderByClause() { + + assertQuery("SELECT e FROM Employee e ORDER BY e.lastName ASC, e.firstName ASC"); // Typo in EQL document + assertQuery("SELECT e FROM Employee e LEFT JOIN e.manager m ORDER BY m.lastName NULLS FIRST"); + assertQuery("SELECT e FROM Employee e ORDER BY e.address"); + } + + @Test + void groupByClause() { + + assertQuery("SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city"); + assertQuery("SELECT e, COUNT(p) FROM Employee e LEFT JOIN e.projects p GROUP BY e"); + } + + @Test + void havingClause() { + assertQuery( + "SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city HAVING AVG(e.salary) > 100000"); + } + @Test // GH-3136 void union() { @@ -79,6 +147,141 @@ void union() { SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city1 UNION SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city2 """); + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @Test + void whereClause() { + // TBD + } + + @Test + void updateQueries() { + assertQuery("UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 50000"); + } + + @Test + void deleteQueries() { + assertQuery("DELETE FROM Employee e WHERE e.department IS NULL"); + } + + @Test + void literals() { + + assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + assertQuery("SELECT e FROM Employee e WHERE e.active = TRUE"); + assertQuery("SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}"); + assertQuery("SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00.000000001'}"); + assertQuery("SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE"); + assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager"); + } + + @Test + void functionsInSelect() { + + assertQuery("SELECT e.salary - 1000 FROM Employee e"); + assertQuery("SELECT e.salary + 1000 FROM Employee e"); + assertQuery("SELECT e.salary * 2 FROM Employee e"); + assertQuery("SELECT e.salary * 2.0 FROM Employee e"); + assertQuery("SELECT e.salary / 2 FROM Employee e"); + assertQuery("SELECT e.salary / 2.0 FROM Employee e"); + assertQuery("SELECT ABS(e.salary - e.manager.salary) FROM Employee e"); + assertQuery( + "select e from Employee e where case e.firstName when 'Bob' then 'Robert' when 'Jill' then 'Gillian' else '' end = 'Robert'"); + assertQuery( + "select case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end from Employee e where e.firstName = 'Bob' or e.firstName = 'Jill'"); + assertQuery( + "select e from Employee e where case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end = 'Robert'"); + assertQuery("SELECT COALESCE(e.salary, 0) FROM Employee e"); + assertQuery("SELECT CONCAT(e.firstName, ' ', e.lastName) FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_DATE FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_TIME FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_TIMESTAMP FROM Employee e"); + assertQuery("SELECT LENGTH(e.lastName) FROM Employee e"); + assertQuery("SELECT LOWER(e.lastName) FROM Employee e"); + assertQuery("SELECT MOD(e.hoursWorked, 8) FROM Employee e"); + assertQuery("SELECT NULLIF(e.salary, 0) FROM Employee e"); + assertQuery("SELECT SQRT(o.RESULT) FROM Output o"); + assertQuery("SELECT SUBSTRING(e.lastName, 0, 2) FROM Employee e"); + assertQuery( + "SELECT TRIM(TRAILING FROM e.lastName), TRIM(e.lastName), TRIM(LEADING '-' FROM e.lastName) FROM Employee e"); + assertQuery("SELECT UPPER(e.lastName) FROM Employee e"); + assertQuery("SELECT CAST(e.salary NUMERIC(10, 2)) FROM Employee e"); + assertQuery("SELECT EXTRACT(YEAR FROM e.startDate) FROM Employee e"); + } + + @Test + void functionsInWhere() { + + assertQuery("SELECT e FROM Employee e WHERE e.salary - 1000 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary + 1000 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary * 2 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary * 2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary / 2 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary / 2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE ABS(e.salary - e.manager.salary) > 0"); + assertQuery("SELECT e FROM Employee e WHERE COALESCE(e.salary, 0) > 0"); + assertQuery("SELECT e FROM Employee e WHERE CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE CURRENT_DATE > CURRENT_TIME"); + assertQuery("SELECT e FROM Employee e WHERE CURRENT_TIME > CURRENT_TIMESTAMP"); + assertQuery("SELECT e FROM Employee e WHERE LENGTH(e.lastName) > 0"); + assertQuery("SELECT e FROM Employee e WHERE LOWER(e.lastName) = 'bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked, 8) > 0"); + assertQuery("SELECT e FROM Employee e WHERE SQRT(o.RESULT) > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE SUBSTRING(e.lastName, 0, 2) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(TRAILING FROM e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(LEADING '-' FROM e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE UPPER(e.lastName) = 'BILBO'"); + assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE EXTRACT(YEAR FROM e.startDate) = '2023'"); + } + + @Test + void specialOperators() { + + assertQuery("SELECT toDo FROM Employee e JOIN e.toDoList toDo WHERE INDEX(toDo) = 1"); + assertQuery("SELECT p FROM Employee e JOIN e.priorities p WHERE KEY(p) = 'high'"); + assertQuery("SELECT e FROM Employee e WHERE SIZE(e.managedEmployees) < 2"); + assertQuery("SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY"); + assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities"); + assertQuery("SELECT p FROM Project p WHERE TYPE(p) = LargeProject"); + + /** + * NOTE: The following query has been altered to properly align with EclipseLink test code despite NOT matching + * their ref docs. See https://github.com/eclipse-ee4j/eclipselink/issues/1949 for more details. + */ + assertQuery("SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) p WHERE p.budget > 1000000"); + + assertQuery("SELECT p FROM Phone p WHERE FUNCTION('TO_NUMBER', p.areaCode) > 613"); + } + + @Test // GH-3314 + void isNullAndIsNotNull() { + + assertQuery("SELECT e FROM Employee e WHERE (e.active IS null OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NULL OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT null OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)"); + } + + @Test // GH-3496 + void lateralShouldBeAValidParameter() { + + assertQuery("select e from Employee e where e.lateral = :_lateral"); + assertQuery("select te from TestEntity te where te.lateral = :lateral"); } @Test // GH-3136 @@ -100,13 +303,13 @@ void except() { } @ParameterizedTest // GH-3136 - @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"}) + @ValueSource(strings = { "STRING", "INTEGER", "FLOAT", "DOUBLE" }) void cast(String targetType) { assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); } @ParameterizedTest // GH-3136 - @ValueSource(strings = {"LEFT", "RIGHT"}) + @ValueSource(strings = { "LEFT", "RIGHT" }) void leftRightStringFunctions(String keyword) { assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java index f32a9d1c75..7fae0bf044 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java @@ -21,6 +21,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -333,6 +334,38 @@ OR TREAT(e AS Contractor).hours > 100 """); } + @Test // GH-3136 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + } + + @Test // GH-3136 + void currentDateFunctions() { + + assertQuery("select CURRENT_DATE " + // + "from Call c "); + + assertQuery("select CURRENT_TIME " + // + "from Call c "); + + assertQuery("select CURRENT_TIMESTAMP " + // + "from Call c "); + + assertQuery("select LOCAL_DATE " + // + "from Call c "); + + assertQuery("select LOCAL_TIME " + // + "from Call c "); + + assertQuery("select LOCAL_DATETIME " + // + "from Call c "); + } + @Test void pathExpressionsNamedParametersExample() { From 6b7bb61219b9731b51fb50e7bea20188cc5623e6 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 27 Nov 2024 11:12:06 +0100 Subject: [PATCH 10/16] Fix EQL grammar to accept literals in constructor expressions. --- .../data/jpa/repository/query/Eql.g4 | 1 + .../jpa/repository/query/EqlQueryRenderer.java | 14 +++++++------- .../jpa/repository/query/EqlComplianceTests.java | 5 +++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 index b2a45b24d5..ede622152e 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 @@ -215,6 +215,7 @@ constructor_item | scalar_expression | aggregate_expression | identification_variable + | literal ; aggregate_expression diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index 321c9eda28..c4d81c69d3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -695,19 +695,19 @@ public QueryTokenStream visitConstructor_expression(EqlParser.Constructor_expres @Override public QueryTokenStream visitConstructor_item(EqlParser.Constructor_itemContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_path_expression() != null) { - builder.append(visit(ctx.single_valued_path_expression())); + return visit(ctx.single_valued_path_expression()); } else if (ctx.scalar_expression() != null) { - builder.append(visit(ctx.scalar_expression())); + return visit(ctx.scalar_expression()); } else if (ctx.aggregate_expression() != null) { - builder.append(visit(ctx.aggregate_expression())); + return visit(ctx.aggregate_expression()); } else if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); + } else if (ctx.literal() != null) { + return visit(ctx.literal()); } - return builder; + return QueryTokenStream.empty(); } @Override diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index 0f744d5587..02ac17b66d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -134,6 +134,11 @@ void numericLiterals() { assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); } + @Test // GH-3308 + void newWithStrings() { + assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); + } + @Test void orderByClause() { From 4d487973bd64d5377797f6a83c0eccf491141c11 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 27 Nov 2024 14:55:47 +0100 Subject: [PATCH 11/16] Cleanup QueryTokenStream methods. Remove unused methods. Introduce QueryTokenStream.from and QueryTokenStream.ofToken() factory methods. Migrate JPQL visitors to consistently return token streams instead of mixing expression streams when obtaining values from nodes/terminal nodes. Remove also unused concat methods for consistency. We now instead decide on the composition (calling) site whether a token (stream) should be inlined, an expression or used as-is. --- .../repository/query/EqlQueryRenderer.java | 76 +++++----- .../query/EqlSortedQueryTransformer.java | 4 +- .../repository/query/HqlQueryRenderer.java | 132 +++++++++--------- .../query/HqlSortedQueryTransformer.java | 6 +- .../repository/query/JpqlQueryRenderer.java | 78 +++++------ .../query/JpqlSortedQueryTransformer.java | 4 +- .../jpa/repository/query/QueryRenderer.java | 119 +++------------- .../repository/query/QueryTokenStream.java | 63 +++++++-- .../jpa/repository/query/QueryTokens.java | 25 ---- 9 files changed, 220 insertions(+), 287 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index c4d81c69d3..43f66ee2cb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -163,13 +163,8 @@ public QueryTokenStream visitIdentification_variable_declaration( QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.range_variable_declaration())); - - ctx.join().forEach(joinContext -> { - builder.append(visit(joinContext)); - }); - ctx.fetch_join().forEach(fetchJoinContext -> { - builder.append(visit(fetchJoinContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.join(), this::visit, TOKEN_SPACE)); + builder.appendExpression(QueryTokenStream.concat(ctx.fetch_join(), this::visit, TOKEN_SPACE)); return builder; } @@ -588,7 +583,7 @@ public QueryTokenStream visitNew_value(EqlParser.New_valueContext ctx) { } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else { return QueryRenderer.builder(); } @@ -1485,7 +1480,7 @@ public QueryTokenStream visitRegexpComparison(EqlParser.RegexpComparisonContext @Override public QueryTokenStream visitComparison_operator(EqlParser.Comparison_operatorContext ctx) { - return QueryRenderer.from(QueryTokens.token(ctx.op)); + return QueryTokenStream.ofToken(ctx.op); } @Override @@ -1899,16 +1894,19 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); if (ctx.trim_specification() != null) { - builder.appendExpression(visit(ctx.trim_specification())); + nested.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - builder.appendExpression(visit(ctx.trim_character())); + nested.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - builder.append(QueryTokens.expression(ctx.FROM())); + nested.append(QueryTokens.expression(ctx.FROM())); } - builder.append(visit(ctx.string_expression(0))); + nested.append(visit(ctx.string_expression(0))); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { @@ -1954,11 +1952,11 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LEADING())); + return QueryTokenStream.ofToken(ctx.LEADING()); } else if (ctx.TRAILING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.TRAILING()); } else { - return QueryRenderer.from(QueryTokens.expression(ctx.BOTH())); + return QueryTokenStream.ofToken(ctx.BOTH()); } } @@ -2187,7 +2185,7 @@ public QueryTokenStream visitNullif_expression(EqlParser.Nullif_expressionContex public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { @@ -2199,11 +2197,11 @@ public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) public QueryTokenStream visitIdentification_variable(EqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.type_literal() != null) { return visit(ctx.type_literal()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } @@ -2218,15 +2216,15 @@ public QueryTokenStream visitConstructor_name(EqlParser.Constructor_nameContext public QueryTokenStream visitLiteral(EqlParser.LiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.JAVASTRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.JAVASTRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.JAVASTRINGLITERAL()); } else if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else if (ctx.boolean_literal() != null) { return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { @@ -2268,13 +2266,13 @@ public QueryTokenStream visitPattern_value(EqlParser.Pattern_valueContext ctx) { public QueryTokenStream visitDate_time_timestamp_literal(EqlParser.Date_time_timestamp_literalContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.DATELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATELITERAL())); + return QueryTokenStream.ofToken(ctx.DATELITERAL()); } else if (ctx.TIMELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMELITERAL())); + return QueryTokenStream.ofToken(ctx.TIMELITERAL()); } else if (ctx.TIMESTAMPLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMESTAMPLITERAL())); + return QueryTokenStream.ofToken(ctx.TIMESTAMPLITERAL()); } else { return QueryRenderer.builder(); } @@ -2287,18 +2285,18 @@ public QueryTokenStream visitEntity_type_literal(EqlParser.Entity_type_literalCo @Override public QueryTokenStream visitEscape_character(EqlParser.Escape_characterContext ctx) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } @Override public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2308,9 +2306,9 @@ public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ct public QueryTokenStream visitBoolean_literal(EqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { return QueryTokenStream.empty(); } @@ -2325,9 +2323,9 @@ public QueryTokenStream visitEnum_literal(EqlParser.Enum_literalContext ctx) { public QueryTokenStream visitString_literal(EqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2400,7 +2398,7 @@ public QueryTokenStream visitFunction_name(EqlParser.Function_nameContext ctx) { public QueryTokenStream visitCharacter_valued_input_parameter(EqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { @@ -2411,9 +2409,9 @@ public QueryTokenStream visitCharacter_valued_input_parameter(EqlParser.Characte @Override public QueryTokenStream visitReserved_word(EqlParser.Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java index dc5aa35951..23e75a20ee 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -110,7 +110,7 @@ public QueryTokenStream visitSelect_item(EqlParser.Select_itemContext ctx) { QueryTokenStream tokens = super.visitSelect_item(ctx); if (ctx.result_variable() != null && !tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -122,7 +122,7 @@ public QueryTokenStream visitJoin(EqlParser.JoinContext ctx) { QueryTokenStream tokens = super.visitJoin(ctx); if (!tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java index 311fdce1d3..3d6ef20a95 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java @@ -107,7 +107,7 @@ public QueryTokenStream visitQueryExpression(HqlParser.QueryExpressionContext ct @Override public QueryTokenStream visitWithClause(HqlParser.WithClauseContext ctx) { - QueryRendererBuilder builder = QueryRendererBuilder.from(TOKEN_WITH); + QueryRendererBuilder builder = QueryRendererBuilder.builder(TOKEN_WITH); builder.append(QueryTokenStream.concatExpressions(ctx.cte(), this::visit, TOKEN_COMMA)); return builder; @@ -668,7 +668,7 @@ public QueryTokenStream visitGroupedItem(HqlParser.GroupedItemContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { @@ -700,7 +700,7 @@ public QueryTokenStream visitSortExpression(HqlParser.SortExpressionContext ctx) if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { @@ -712,9 +712,9 @@ public QueryTokenStream visitSortExpression(HqlParser.SortExpressionContext ctx) public QueryTokenStream visitSortDirection(HqlParser.SortDirectionContext ctx) { if (ctx.ASC() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.ASC())); + return QueryTokenStream.ofToken(ctx.ASC()); } else if (ctx.DESC() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DESC())); + return QueryTokenStream.ofToken(ctx.DESC()); } else { return QueryTokenStream.empty(); } @@ -778,9 +778,9 @@ public QueryTokenStream visitFetchClause(HqlParser.FetchClauseContext ctx) { } if (ctx.parameterOrIntegerLiteral() != null) { - builder.append(visit(ctx.parameterOrIntegerLiteral())); + builder.appendExpression(visit(ctx.parameterOrIntegerLiteral())); } else if (ctx.parameterOrNumberLiteral() != null) { - builder.append(visit(ctx.parameterOrNumberLiteral())); + builder.appendExpression(visit(ctx.parameterOrNumberLiteral())); } if (ctx.ROW() != null) { @@ -1029,13 +1029,13 @@ public QueryTokenStream visitSetOperator(HqlParser.SetOperatorContext ctx) { public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { if (ctx.NULL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else if (ctx.booleanLiteral() != null) { return visit(ctx.booleanLiteral()); } else if (ctx.JAVA_STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.JAVA_STRING_LITERAL()); } else if (ctx.STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); } else if (ctx.dateTimeLiteral() != null) { @@ -1051,9 +1051,9 @@ public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) { if (ctx.TRUE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { return QueryTokenStream.empty(); } @@ -1063,19 +1063,19 @@ public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.LONG_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LONG_LITERAL())); + return QueryTokenStream.ofToken(ctx.LONG_LITERAL()); } else if (ctx.BIG_INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.BIG_INTEGER_LITERAL()); } else if (ctx.FLOAT_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOAT_LITERAL())); + return QueryTokenStream.ofToken(ctx.FLOAT_LITERAL()); } else if (ctx.DOUBLE_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.DOUBLE_LITERAL())); + return QueryTokenStream.ofToken(ctx.DOUBLE_LITERAL()); } else if (ctx.BIG_DECIMAL_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_DECIMAL_LITERAL())); + return QueryTokenStream.ofToken(ctx.BIG_DECIMAL_LITERAL()); } else if (ctx.HEX_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HEX_LITERAL())); + return QueryTokenStream.ofToken(ctx.HEX_LITERAL()); } else { return QueryTokenStream.empty(); } @@ -1130,25 +1130,25 @@ public QueryTokenStream visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ct public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { if (ctx.YEAR() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.YEAR())); + return QueryTokenStream.ofToken(ctx.YEAR()); } else if (ctx.MONTH() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MONTH())); + return QueryTokenStream.ofToken(ctx.MONTH()); } else if (ctx.DAY() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.DAY())); + return QueryTokenStream.ofToken(ctx.DAY()); } else if (ctx.WEEK() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.WEEK())); + return QueryTokenStream.ofToken(ctx.WEEK()); } else if (ctx.QUARTER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.QUARTER())); + return QueryTokenStream.ofToken(ctx.QUARTER()); } else if (ctx.HOUR() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HOUR())); + return QueryTokenStream.ofToken(ctx.HOUR()); } else if (ctx.MINUTE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MINUTE())); + return QueryTokenStream.ofToken(ctx.MINUTE()); } else if (ctx.SECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.SECOND())); + return QueryTokenStream.ofToken(ctx.SECOND()); } else if (ctx.NANOSECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.NANOSECOND())); + return QueryTokenStream.ofToken(ctx.NANOSECOND()); } else if (ctx.EPOCH() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.EPOCH())); + return QueryTokenStream.ofToken(ctx.EPOCH()); } else { return QueryTokenStream.empty(); } @@ -1226,7 +1226,7 @@ public QueryTokenStream visitTimeZoneField(HqlParser.TimeZoneFieldContext ctx) { @Override public QueryTokenStream visitDateOrTimeField(HqlParser.DateOrTimeFieldContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATE() != null ? ctx.DATE() : ctx.TIME())); + return QueryTokenStream.ofToken(ctx.DATE() != null ? ctx.DATE() : ctx.TIME()); } @Override @@ -1239,11 +1239,7 @@ public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { } else if (ctx.HEX_LITERAL() != null) { builder.append(TOKEN_OPEN_BRACE); - - builder.append(QueryTokenStream.concat(ctx.HEX_LITERAL(), it -> { - return QueryRendererBuilder.from(QueryTokens.token(it)); - }, TOKEN_COMMA)); - + builder.append(QueryTokenStream.concat(ctx.HEX_LITERAL(), QueryTokenStream::ofToken, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_BRACE); } @@ -1390,7 +1386,7 @@ public QueryTokenStream visitToDurationExpression(HqlParser.ToDurationExpression QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); builder.appendExpression(visit(ctx.datetimeField())); return builder; @@ -1401,7 +1397,7 @@ public QueryTokenStream visitFromDurationExpression(HqlParser.FromDurationExpres QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); builder.append(QueryTokens.expression(ctx.BY())); builder.appendExpression(visit(ctx.datetimeField())); @@ -1812,12 +1808,12 @@ public QueryTokenStream visitPadFunction(HqlParser.PadFunctionContext ctx) { @Override public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING()); } @Override public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -1829,12 +1825,16 @@ public QueryTokenStream visitPadLength(HqlParser.PadLengthContext ctx) { public QueryTokenStream visitPositionFunction(HqlParser.PositionFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); builder.append(QueryTokens.token(ctx.POSITION())); builder.append(TOKEN_OPEN_PAREN); - builder.append(visit(ctx.positionFunctionPatternArgument())); - builder.append(QueryTokens.expression(ctx.IN())); - builder.appendInline(visit(ctx.positionFunctionStringArgument())); + + nested.appendExpression(visit(ctx.positionFunctionPatternArgument())); + nested.append(QueryTokens.expression(ctx.IN())); + nested.append(visit(ctx.positionFunctionStringArgument())); + + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1857,7 +1857,7 @@ public QueryTokenStream visitOverlayFunction(HqlParser.OverlayFunctionContext ct builder.append(QueryTokens.token(ctx.OVERLAY())); builder.append(TOKEN_OPEN_PAREN); - builder.append(visit(ctx.overlayFunctionStringArgument())); + builder.appendExpression(visit(ctx.overlayFunctionStringArgument())); builder.append(QueryTokens.expression(ctx.PLACING())); builder.append(visit(ctx.overlayFunctionReplacementArgument())); builder.append(QueryTokens.expression(ctx.FROM())); @@ -2091,7 +2091,7 @@ public QueryTokenStream visitRollup(HqlParser.RollupContext ctx) { @Override public QueryTokenStream visitFormat(HqlParser.FormatContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -2153,7 +2153,7 @@ public QueryTokenStream visitJpaNonstandardFunctionName(HqlParser.JpaNonstandard return visit(ctx.identifier()); } - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -2652,10 +2652,7 @@ public QueryTokenStream visitSimpleCaseExpression(HqlParser.SimpleCaseExpression builder.append(QueryTokens.expression(ctx.CASE())); builder.append(visit(ctx.expressionOrPredicate(0))); - - ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> { - builder.append(visit(caseWhenExpressionClauseContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.caseWhenExpressionClause(), this::visit, TOKEN_SPACE)); if (ctx.ELSE() != null) { @@ -2898,7 +2895,7 @@ public QueryTokenStream visitCastTarget(HqlParser.CastTargetContext ctx) { @Override public QueryTokenStream visitCastTargetType(HqlParser.CastTargetTypeContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.fullTargetName)); + return QueryTokenStream.from(QueryTokens.token(ctx.fullTargetName)); } @Override @@ -2915,7 +2912,7 @@ public QueryTokenStream visitExtractFunction(HqlParser.ExtractFunctionContext ct nested.appendExpression(visit(ctx.extractField())); nested.append(QueryTokens.expression(ctx.FROM())); - nested.append(visit(ctx.expression())); + nested.appendExpression(visit(ctx.expression())); builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); @@ -3005,7 +3002,7 @@ public QueryTokenStream visitTrimSpecification(HqlParser.TrimSpecificationContex public QueryTokenStream visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { if (ctx.STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } return visit(ctx.parameter()); @@ -3155,11 +3152,11 @@ public QueryTokenStream visitToOneFkReference(HqlParser.ToOneFkReferenceContext public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuantifierContext ctx) { if (ctx.ELEMENT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.ELEMENT())); + return QueryTokenStream.ofToken(ctx.ELEMENT()); } if (ctx.VALUE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.VALUE())); + return QueryTokenStream.ofToken(ctx.VALUE()); } return QueryTokenStream.empty(); @@ -3169,11 +3166,11 @@ public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuanti public QueryTokenStream visitIndexKeyQuantifier(HqlParser.IndexKeyQuantifierContext ctx) { if (ctx.INDEX() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INDEX())); + return QueryTokenStream.ofToken(ctx.INDEX()); } if (ctx.KEY() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.KEY())); + return QueryTokenStream.ofToken(ctx.KEY()); } return QueryTokenStream.empty(); @@ -3517,11 +3514,10 @@ public QueryTokenStream visitExistsExpression(HqlParser.ExistsExpressionContext public QueryTokenStream visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) { if (ctx.LIST() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LIST())); + return QueryTokenStream.ofToken(ctx.LIST()); } else if (ctx.MAP() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MAP())); + return QueryTokenStream.ofToken(ctx.MAP()); } else if (ctx.simplePath() != null) { - return visit(ctx.simplePath()); } else { return QueryTokenStream.empty(); @@ -3557,7 +3553,7 @@ public QueryTokenStream visitParameterOrIntegerLiteral(HqlParser.ParameterOrInte if (ctx.parameter() != null) { return visit(ctx.parameter()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else { return QueryTokenStream.empty(); } @@ -3623,15 +3619,15 @@ public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { if (ctx.nakedIdentifier() != null) { return visit(ctx.nakedIdentifier()); } else if (ctx.FULL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FULL())); + return QueryTokenStream.ofToken(ctx.FULL()); } else if (ctx.LEFT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LEFT())); + return QueryTokenStream.ofToken(ctx.LEFT()); } else if (ctx.INNER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INNER())); + return QueryTokenStream.ofToken(ctx.INNER()); } else if (ctx.OUTER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.OUTER())); + return QueryTokenStream.ofToken(ctx.OUTER()); } else if (ctx.RIGHT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.RIGHT())); + return QueryTokenStream.ofToken(ctx.RIGHT()); } return QueryTokenStream.empty(); @@ -3641,11 +3637,11 @@ public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { public QueryTokenStream visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { if (ctx.IDENTIFIER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFIER())); + return QueryTokenStream.ofToken(ctx.IDENTIFIER()); } else if (ctx.QUOTED_IDENTIFIER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.QUOTED_IDENTIFIER())); + return QueryTokenStream.ofToken(ctx.QUOTED_IDENTIFIER()); } else { - return QueryRendererBuilder.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java index b6b8853f93..256e4a443e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java @@ -87,7 +87,7 @@ public QueryTokenStream visitJoinPath(HqlParser.JoinPathContext ctx) { QueryTokenStream tokens = super.visitJoinPath(ctx); if (ctx.variable() != null && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -99,7 +99,7 @@ public QueryTokenStream visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { QueryTokenStream tokens = super.visitJoinSubquery(ctx); if (ctx.variable() != null && !tokens.isEmpty() && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -111,7 +111,7 @@ public QueryTokenStream visitVariable(HqlParser.VariableContext ctx) { QueryTokenStream tokens = super.visitVariable(ctx); if (ctx.identifier() != null && !tokens.isEmpty() && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index 79391fa677..593e25e579 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -153,15 +153,10 @@ public QueryTokenStream visitIdentification_variable_declaration( JpqlParser.Identification_variable_declarationContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendExpression(visit(ctx.range_variable_declaration())); - ctx.join().forEach(joinContext -> { - builder.append(visit(joinContext)); - }); - - ctx.fetch_join().forEach(fetchJoinContext -> { - builder.append(visit(fetchJoinContext)); - }); + builder.append(visit(ctx.range_variable_declaration())); + builder.appendExpression(QueryTokenStream.concat(ctx.join(), this::visit, TOKEN_SPACE)); + builder.appendExpression(QueryTokenStream.concat(ctx.fetch_join(), this::visit, TOKEN_SPACE)); return builder; } @@ -571,7 +566,7 @@ public QueryTokenStream visitNew_value(JpqlParser.New_valueContext ctx) { } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else { return QueryTokenStream.empty(); } @@ -1401,7 +1396,7 @@ public QueryTokenStream visitComparison_expression(JpqlParser.Comparison_express @Override public QueryTokenStream visitComparison_operator(JpqlParser.Comparison_operatorContext ctx) { - return QueryRenderer.from(QueryTokens.token(ctx.op)); + return QueryTokenStream.ofToken(ctx.op); } @Override @@ -1817,16 +1812,19 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); if (ctx.trim_specification() != null) { - builder.appendExpression(visit(ctx.trim_specification())); + nested.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - builder.appendExpression(visit(ctx.trim_character())); + nested.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - builder.append(QueryTokens.expression(ctx.FROM())); + nested.append(QueryTokens.expression(ctx.FROM())); } - builder.append(visit(ctx.string_expression(0))); + nested.append(visit(ctx.string_expression(0))); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { @@ -1872,11 +1870,11 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re public QueryTokenStream visitTrim_specification(JpqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LEADING())); + return QueryTokenStream.ofToken(ctx.LEADING()); } else if (ctx.TRAILING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.TRAILING()); } else { - return QueryRenderer.from(QueryTokens.expression(ctx.BOTH())); + return QueryTokenStream.ofToken(ctx.BOTH()); } } @@ -2100,7 +2098,7 @@ public QueryTokenStream visitNullif_expression(JpqlParser.Nullif_expressionConte public QueryTokenStream visitTrim_character(JpqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { @@ -2112,11 +2110,11 @@ public QueryTokenStream visitTrim_character(JpqlParser.Trim_characterContext ctx public QueryTokenStream visitIdentification_variable(JpqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.type_literal() != null) { return visit(ctx.type_literal()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } @@ -2131,15 +2129,15 @@ public QueryTokenStream visitConstructor_name(JpqlParser.Constructor_nameContext public QueryTokenStream visitLiteral(JpqlParser.LiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.JAVASTRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.JAVASTRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.JAVASTRINGLITERAL()); } else if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else if (ctx.boolean_literal() != null) { return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { @@ -2176,13 +2174,13 @@ public QueryTokenStream visitPattern_value(JpqlParser.Pattern_valueContext ctx) public QueryTokenStream visitDate_time_timestamp_literal(JpqlParser.Date_time_timestamp_literalContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.DATELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATELITERAL())); + return QueryTokenStream.ofToken(ctx.DATELITERAL()); } else if (ctx.TIMELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMELITERAL())); + return QueryTokenStream.ofToken(ctx.TIMELITERAL()); } else if (ctx.TIMESTAMPLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMESTAMPLITERAL())); + return QueryTokenStream.ofToken(ctx.TIMESTAMPLITERAL()); } else { return QueryRenderer.builder(); } @@ -2195,18 +2193,18 @@ public QueryTokenStream visitEntity_type_literal(JpqlParser.Entity_type_literalC @Override public QueryTokenStream visitEscape_character(JpqlParser.Escape_characterContext ctx) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } @Override public QueryTokenStream visitNumeric_literal(JpqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2216,9 +2214,9 @@ public QueryTokenStream visitNumeric_literal(JpqlParser.Numeric_literalContext c public QueryTokenStream visitBoolean_literal(JpqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { return QueryTokenStream.empty(); } @@ -2233,9 +2231,9 @@ public QueryTokenStream visitEnum_literal(JpqlParser.Enum_literalContext ctx) { public QueryTokenStream visitString_literal(JpqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2309,7 +2307,7 @@ public QueryTokenStream visitCharacter_valued_input_parameter( JpqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { @@ -2320,9 +2318,9 @@ public QueryTokenStream visitCharacter_valued_input_parameter( @Override public QueryTokenStream visitReserved_word(Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index fc229016a5..196fbcff1e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -109,7 +109,7 @@ public QueryTokenStream visitSelect_item(JpqlParser.Select_itemContext ctx) { QueryTokenStream tokens = super.visitSelect_item(ctx); if (ctx.result_variable() != null && !tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -121,7 +121,7 @@ public QueryTokenStream visitJoin(JpqlParser.JoinContext ctx) { QueryTokenStream tokens = super.visitJoin(ctx); if (!tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java index 87fe53e050..6d116a33c9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java @@ -20,9 +20,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.function.Function; import java.util.stream.Stream; +import org.springframework.lang.Nullable; import org.springframework.util.CompositeIterator; /** @@ -44,9 +44,6 @@ abstract class QueryRenderer implements QueryTokenStream { /** * Creates a QueryRenderer from a {@link QueryToken}. - * - * @param token - * @return */ static QueryRenderer from(QueryToken token) { return QueryRenderer.from(Collections.singletonList(token)); @@ -54,9 +51,6 @@ static QueryRenderer from(QueryToken token) { /** * Creates a QueryRenderer from a collection of {@link QueryToken}. - * - * @param tokens - * @return */ static QueryRenderer from(Collection tokens) { List tokensToUse = new ArrayList<>(Math.max(tokens.size(), 32)); @@ -66,9 +60,6 @@ static QueryRenderer from(Collection tokens) { /** * Creates a QueryRenderer from a {@link QueryTokenStream}. - * - * @param tokens - * @return */ static QueryRenderer from(QueryTokenStream tokens) { @@ -85,8 +76,6 @@ static QueryRenderer from(QueryTokenStream tokens) { /** * Creates a new empty {@link QueryRenderer}. - * - * @return */ public static QueryRenderer empty() { return EmptyQueryRenderer.INSTANCE; @@ -94,8 +83,6 @@ public static QueryRenderer empty() { /** * Creates a new {@link QueryRendererBuilder}. - * - * @return */ static QueryRendererBuilder builder() { return new QueryRendererBuilder(); @@ -144,14 +131,11 @@ static String render(Iterable tokenStream) { results.append(token.value()); } - return results.toString(); + return results != null ? results.toString() : ""; } /** * Append a {@link QueryRenderer} to create a composed renderer. - * - * @param tokens - * @return */ QueryRenderer append(QueryTokenStream tokens) { @@ -180,7 +164,7 @@ public String toString() { return render(); } - public static QueryRenderer expression(QueryTokenStream tokenStream) { + public static QueryRenderer ofExpression(QueryTokenStream tokenStream) { if (tokenStream instanceof QueryRendererBuilder builder) { tokenStream = builder.current; @@ -258,9 +242,6 @@ String render() { /** * Append a {@link QueryRenderer} to create a composed renderer. - * - * @param tokens - * @return */ QueryRenderer append(QueryTokenStream tokens) { @@ -290,6 +271,7 @@ QueryRenderer append(QueryTokenStream tokens) { } @Override + @Nullable public QueryToken getLast() { for (int i = nested.size() - 1; i > -1; i--) { @@ -386,11 +368,13 @@ public List toList() { } @Override + @Nullable public QueryToken getFirst() { return tokens.isEmpty() ? null : tokens.get(0); } @Override + @Nullable public QueryToken getLast() { return tokens.isEmpty() ? null : tokens.get(tokens.size() - 1); } @@ -407,7 +391,7 @@ public boolean isEmpty() { @Override public boolean isExpression() { - return !tokens.isEmpty() && getLast().isExpression(); + return !tokens.isEmpty() && getRequiredLast().isExpression(); } /** @@ -418,7 +402,7 @@ public boolean isExpression() { */ static String render(Object tokens) { - if (tokens instanceof Collection tpr) { + if (tokens instanceof Collection tpr) { return render(tpr); } @@ -454,11 +438,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return tokens.getFirst(); } @Override + @Nullable public QueryToken getLast() { return tokens.getLast(); } @@ -475,7 +461,7 @@ public boolean isEmpty() { @Override public boolean isExpression() { - return !tokens.isEmpty() && tokens.getLast().isExpression(); + return !tokens.isEmpty() && tokens.getRequiredLast().isExpression(); } } @@ -486,68 +472,13 @@ static class QueryRendererBuilder implements QueryTokenStream { protected QueryRenderer current = QueryRenderer.empty(); - /** - * Compose a {@link QueryRendererBuilder} from a collection of inline elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concat(Collection elements, Function visitor, - QueryToken separator) { - return concat(elements, visitor, QueryRendererBuilder::toInline, separator); - } - - /** - * Compose a {@link QueryRendererBuilder} from a collection of expression elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concatExpressions(Collection elements, - Function visitor, QueryToken separator) { - return concat(elements, visitor, QueryRendererBuilder::toExpression, separator); - } - - /** - * Compose a {@link QueryRendererBuilder} from a collection of elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param postProcess post-processing function to convert {@link QueryRendererBuilder} into {@link QueryRenderer}. - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concat(Collection elements, Function visitor, - Function postProcess, QueryToken separator) { - - QueryRendererBuilder builder = new QueryRendererBuilder(); - for (T element : elements) { - if (!builder.isEmpty()) { - builder.append(separator); - } - builder.append(postProcess.apply(visitor.apply(element))); - } - - return builder; - } - /** * Create and initialize a QueryRendererBuilder from a {@link QueryTokens.SimpleQueryToken}. * * @param token * @return */ - public static QueryRendererBuilder from(QueryToken token) { + public static QueryRendererBuilder builder(QueryToken token) { return new QueryRendererBuilder().append(token); } @@ -627,7 +558,7 @@ QueryRendererBuilder appendExpression(QueryTokenStream tokens) { return this; } - current = current.append(QueryRenderer.expression(tokens)); + current = current.append(QueryRenderer.ofExpression(tokens)); return this; } @@ -643,11 +574,13 @@ public Stream stream() { } @Override + @Nullable public QueryToken getFirst() { return current.getFirst(); } @Override + @Nullable public QueryToken getLast() { return current.getLast(); } @@ -657,11 +590,6 @@ public boolean isExpression() { return current.isExpression(); } - /** - * Return whet the builder is empty. - * - * @return - */ @Override public boolean isEmpty() { return current.isEmpty(); @@ -686,19 +614,6 @@ public QueryRenderer build() { return current; } - QueryRenderer toExpression() { - - if (current instanceof ExpressionRenderer) { - return current; - } - - return QueryRenderer.expression(current); - } - - public QueryRenderer toInline() { - return new InlineRenderer(current); - } - } private static class InlineRenderer extends QueryRenderer { @@ -730,11 +645,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return delegate.getFirst(); } @Override + @Nullable public QueryToken getLast() { return delegate.getLast(); } @@ -784,11 +701,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return delegate.getFirst(); } @Override + @Nullable public QueryToken getLast() { return delegate.getLast(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java index 233711d797..b31f48939c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java @@ -16,10 +16,14 @@ package org.springframework.data.jpa.repository.query; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.function.Function; -import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.TerminalNode; + import org.springframework.data.util.Streamable; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; @@ -35,13 +39,32 @@ interface QueryTokenStream extends Streamable { /** * Creates an empty stream. - * - * @return */ static QueryTokenStream empty() { return EmptyQueryTokenStream.INSTANCE; } + /** + * Creates a QueryTokenStream from a {@link QueryToken}. + */ + static QueryTokenStream from(QueryToken token) { + return QueryRenderer.from(Collections.singletonList(token)); + } + + /** + * Creates an token QueryRenderer from an AST {@link TerminalNode}. + */ + static QueryTokenStream ofToken(TerminalNode node) { + return from(QueryTokens.token(node)); + } + + /** + * Creates an token QueryRenderer from an AST {@link Token}. + */ + static QueryTokenStream ofToken(Token node) { + return from(QueryTokens.token(node)); + } + /** * Compose a {@link QueryTokenStream} from a collection of inline elements. * @@ -55,10 +78,6 @@ static QueryTokenStream concat(Collection elements, Function QueryTokenStream justAs(Collection elements, Function converter) { - return concat(elements, it-> QueryRendererBuilder.from(converter.apply(it)), QueryRenderer::inline, QueryTokens.TOKEN_SPACE); - } - /** * Compose a {@link QueryTokenStream} from a collection of expression elements. * @@ -69,7 +88,7 @@ static QueryTokenStream justAs(Collection elements, Function QueryTokenStream concatExpressions(Collection elements, Function visitor, QueryToken separator) { - return concat(elements, visitor, QueryRenderer::expression, separator); + return concat(elements, visitor, QueryRenderer::ofExpression, separator); } /** @@ -127,6 +146,20 @@ default QueryToken getFirst() { return it.hasNext() ? it.next() : null; } + /** + * @return the required first query token or throw {@link java.util.NoSuchElementException} if empty. + */ + default QueryToken getRequiredFirst() { + + QueryToken first = getFirst(); + + if (first == null) { + throw new NoSuchElementException("No token in the stream"); + } + + return first; + } + /** * @return the last query token or {@code null} if empty. */ @@ -135,6 +168,20 @@ default QueryToken getLast() { return CollectionUtils.lastElement(toList()); } + /** + * @return the required last query token or throw {@link java.util.NoSuchElementException} if empty. + */ + default QueryToken getRequiredLast() { + + QueryToken last = getLast(); + + if (last == null) { + throw new NoSuchElementException("No token in the stream"); + } + + return last; + } + /** * @return {@code true} if this stream represents a query expression. */ diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java index 80df0b3300..cfbaf20ba8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java @@ -31,7 +31,6 @@ class QueryTokens { /** * Commonly use tokens. */ - static final QueryToken TOKEN_NONE = token(""); static final QueryToken TOKEN_COMMA = token(", "); static final QueryToken TOKEN_SPACE = token(" "); static final QueryToken TOKEN_DOT = token("."); @@ -56,15 +55,9 @@ class QueryTokens { static final QueryToken TOKEN_WITH = expression("WITH"); static final QueryToken TOKEN_NOT = expression("NOT"); static final QueryToken TOKEN_MATERIALIZED = expression("materialized"); - static final QueryToken TOKEN_NULLS = expression("NULLS"); - static final QueryToken TOKEN_FIRST = expression("FIRST"); - static final QueryToken TOKEN_LAST = expression("LAST"); /** * Creates a {@link QueryToken token} from an ANTLR {@link TerminalNode}. - * - * @param node - * @return */ static QueryToken token(TerminalNode node) { return token(node.getText()); @@ -72,9 +65,6 @@ static QueryToken token(TerminalNode node) { /** * Creates a {@link QueryToken token} from an ANTLR {@link Token}. - * - * @param token - * @return */ static QueryToken token(Token token) { return token(token.getText()); @@ -82,9 +72,6 @@ static QueryToken token(Token token) { /** * Creates a {@link QueryToken token} from a string {@code token}. - * - * @param token - * @return */ static QueryToken token(String token) { return new SimpleQueryToken(token); @@ -92,9 +79,6 @@ static QueryToken token(String token) { /** * Creates a ventilated token that is embedded in spaces. - * - * @param token - * @return */ static QueryToken ventilated(Token token) { return new SimpleQueryToken(" " + token.getText() + " "); @@ -102,9 +86,6 @@ static QueryToken ventilated(Token token) { /** * Creates a {@link QueryToken expression} from an ANTLR {@link TerminalNode}. - * - * @param node - * @return */ static QueryToken expression(TerminalNode node) { return expression(node.getText()); @@ -112,9 +93,6 @@ static QueryToken expression(TerminalNode node) { /** * Creates a {@link QueryToken expression} from an ANTLR {@link Token}. - * - * @param token - * @return */ static QueryToken expression(Token token) { return expression(token.getText()); @@ -122,9 +100,6 @@ static QueryToken expression(Token token) { /** * Creates a {@link QueryToken token} from a string {@code expression}. - * - * @param expression - * @return */ static QueryToken expression(String expression) { return new ExpressionToken(expression); From 58264a5669ad29c26b34da30801aa76681262522 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 29 Nov 2024 11:16:31 +0100 Subject: [PATCH 12/16] Adopt Hibernate version guards in tests. --- .../procedures/PostgresStoredProcedureIntegrationTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java index b3c76d25b1..40f3c87379 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java @@ -106,7 +106,8 @@ void testNamedOutputParameter() { new Employee(4, "Gabriel")); } - @DisabledOnHibernate("6") + @DisabledOnHibernate(value = "7", + disabledReason = "class org.hibernate.metamodel.model.domain.internal.EntityTypeImpl cannot be cast to class org.hibernate.query.OutputableType (org.hibernate.metamodel.model.domain.internal.EntityTypeImpl and org.hibernate.query.OutputableType are in unnamed module of loader 'app')") @Test // 2256 void testSingleEntityFromResultSet() { From 9f3bac87201146c0b9e7575da385a9751bc8a782 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 9 Dec 2024 14:34:01 +0100 Subject: [PATCH 13/16] Switch to `Query.getSingleResultOrNull()`. We now use getSingleResultOrNull() to avoid NoResultException handling. Closes #3701 --- .../repository/query/JpaQueryExecution.java | 11 ++--------- .../support/SimpleJpaRepository.java | 19 +++++-------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 82482cd99c..b6dd20cd47 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.query; import jakarta.persistence.EntityManager; -import jakarta.persistence.NoResultException; import jakarta.persistence.Query; import jakarta.persistence.StoredProcedureQuery; @@ -87,13 +86,7 @@ public Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor acc Assert.notNull(query, "AbstractJpaQuery must not be null"); Assert.notNull(accessor, "JpaParametersParameterAccessor must not be null"); - Object result; - - try { - result = doExecute(query, accessor); - } catch (NoResultException e) { - return null; - } + Object result = doExecute(query, accessor); if (result == null) { return null; @@ -221,7 +214,7 @@ static class SingleEntityExecution extends JpaQueryExecution { @Override protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { - return query.createQuery(accessor).getSingleResult(); + return query.createQuery(accessor).getSingleResultOrNull(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 1036ce24dd..457f4a1104 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -19,7 +19,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.LockModeType; -import jakarta.persistence.NoResultException; import jakarta.persistence.Parameter; import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; @@ -433,12 +432,7 @@ public Page findAll(Pageable pageable) { @Override public Optional findOne(Specification spec) { - - try { - return Optional.of(getQuery(spec, Sort.unsorted()).setMaxResults(2).getSingleResult()); - } catch (NoResultException e) { - return Optional.empty(); - } + return Optional.ofNullable(getQuery(spec, Sort.unsorted()).setMaxResults(2).getSingleResultOrNull()); } @Override @@ -540,13 +534,10 @@ private R doFindBy(Specification spec, Class domainClass, @Override public Optional findOne(Example example) { - try { - return Optional - .of(getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted()) - .setMaxResults(2).getSingleResult()); - } catch (NoResultException e) { - return Optional.empty(); - } + TypedQuery query = getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), + Sort.unsorted()).setMaxResults(2); + + return Optional.ofNullable(query.getSingleResultOrNull()); } @Override From ad9e866e21f3e20926e4f0b1c2de49421a31dc76 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 19 Dec 2024 08:46:59 +0100 Subject: [PATCH 14/16] Switch XML persistence & orm files to version 3.2 --- spring-data-jpa/src/test/resources/META-INF/orm.xml | 8 ++++---- .../src/test/resources/META-INF/persistence-jmh.xml | 7 ++++--- .../src/test/resources/META-INF/persistence.xml | 5 ++++- .../src/test/resources/META-INF/persistence2.xml | 7 ++++--- .../org/springframework/data/jpa/support/mapping.xml | 5 ++++- .../data/jpa/support/module1/module1-orm.xml | 6 ++++-- .../data/jpa/support/module2/module2-orm.xml | 6 ++++-- .../org/springframework/data/jpa/support/persistence.xml | 5 ++++- .../org/springframework/data/jpa/support/persistence2.xml | 5 ++++- .../resources/simple-persistence/simple-persistence.xml | 5 ++++- 10 files changed, 40 insertions(+), 19 deletions(-) diff --git a/spring-data-jpa/src/test/resources/META-INF/orm.xml b/spring-data-jpa/src/test/resources/META-INF/orm.xml index 820a9cced2..65f0ef28fe 100644 --- a/spring-data-jpa/src/test/resources/META-INF/orm.xml +++ b/spring-data-jpa/src/test/resources/META-INF/orm.xml @@ -1,8 +1,8 @@ - + diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml b/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml index 60c6b5c97a..a78eb59468 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml @@ -14,9 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + org.hibernate.jpa.HibernatePersistenceProvider org.springframework.data.jpa.domain.AbstractPersistable diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence.xml b/spring-data-jpa/src/test/resources/META-INF/persistence.xml index 4f904373c3..35a8715991 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence.xml @@ -1,5 +1,8 @@ - + org.springframework.data.jpa.domain.AbstractPersistable org.springframework.data.jpa.domain.AbstractAuditable diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml index f4f7adb6b2..a93617de58 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml @@ -1,7 +1,8 @@ - + org.springframework.data.jpa.domain.sample.AnnotatedAuditableUser org.springframework.data.jpa.domain.sample.AuditableRole diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml index 87f3460858..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml @@ -1,2 +1,5 @@ - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml index da1ce9a7d4..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml @@ -1,3 +1,5 @@ - - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml index da1ce9a7d4..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml @@ -1,3 +1,5 @@ - - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml index ad1460bad7..f75fea5ba3 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml @@ -1,5 +1,8 @@ - + foo.xml org.springframework.data.jpa.domain.sample.User diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml index 962748440b..1666022d07 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml @@ -1,5 +1,8 @@ - + bar.xml org.springframework.data.jpa.domain.sample.Role diff --git a/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml b/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml index 9caa71259a..706d5fb919 100644 --- a/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml +++ b/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml @@ -1,5 +1,8 @@ - + true From a2ce35f666658176b4b199bac031ce5a4fc128b7 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 19 Dec 2024 09:39:33 +0100 Subject: [PATCH 15/16] Move off deprecated TemporalType.TIMESTAMP Favor java.time types for auditing switching from Date to Instant. --- .../data/jpa/domain/AbstractAuditable.java | 18 +++++++----------- .../data/jpa/repository/Temporal.java | 2 ++ .../jpa/repository/query/JpaParameters.java | 2 ++ ...AuditingViaJavaConfigRepositoriesTests.java | 7 ++++--- .../data/jpa/util/FixedDate.java | 11 ++++++----- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java index 6cc365619f..058da4c4f5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java @@ -17,13 +17,11 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.Temporal; -import jakarta.persistence.TemporalType; import java.io.Serializable; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Date; import java.util.Optional; import org.springframework.data.domain.Auditable; @@ -45,14 +43,12 @@ public abstract class AbstractAuditable extends Abst @ManyToOne // private @Nullable U createdBy; - @Temporal(TemporalType.TIMESTAMP) // - private @Nullable Date createdDate; + private @Nullable Instant createdDate; @ManyToOne // private @Nullable U lastModifiedBy; - @Temporal(TemporalType.TIMESTAMP) // - private @Nullable Date lastModifiedDate; + private @Nullable Instant lastModifiedDate; @Override public Optional getCreatedBy() { @@ -67,12 +63,12 @@ public void setCreatedBy(U createdBy) { @Override public Optional getCreatedDate() { return null == createdDate ? Optional.empty() - : Optional.of(LocalDateTime.ofInstant(createdDate.toInstant(), ZoneId.systemDefault())); + : Optional.of(LocalDateTime.ofInstant(createdDate, ZoneId.systemDefault())); } @Override public void setCreatedDate(LocalDateTime createdDate) { - this.createdDate = Date.from(createdDate.atZone(ZoneId.systemDefault()).toInstant()); + this.createdDate = createdDate.atZone(ZoneId.systemDefault()).toInstant(); } @Override @@ -88,11 +84,11 @@ public void setLastModifiedBy(U lastModifiedBy) { @Override public Optional getLastModifiedDate() { return null == lastModifiedDate ? Optional.empty() - : Optional.of(LocalDateTime.ofInstant(lastModifiedDate.toInstant(), ZoneId.systemDefault())); + : Optional.of(LocalDateTime.ofInstant(lastModifiedDate, ZoneId.systemDefault())); } @Override public void setLastModifiedDate(LocalDateTime lastModifiedDate) { - this.lastModifiedDate = Date.from(lastModifiedDate.atZone(ZoneId.systemDefault()).toInstant()); + this.lastModifiedDate = lastModifiedDate.atZone(ZoneId.systemDefault()).toInstant(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java index 7668ccd33f..245983b766 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java @@ -30,10 +30,12 @@ * * @author Thomas Darimont * @author Oliver Gierke + * @deprecated since 4.0. Please use {@literal java.time} types instead. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented +@Deprecated(since = "4.0", forRemoval = true) public @interface Temporal { /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index d40895ab78..9f42ebf970 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -88,6 +88,8 @@ public boolean hasLimitingParameters() { public static class JpaParameter extends Parameter { private final @Nullable Temporal annotation; + + @SuppressWarnings("deprecation") private @Nullable TemporalType temporalType; /** diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java index 7a5fb05c0d..a8cec7ab1e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java @@ -20,9 +20,9 @@ import jakarta.persistence.EntityManager; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -54,6 +54,7 @@ * @author Oliver Gierke * @author Jens Schauder * @author Krzysztof Krason + * @author Christoph Strobl */ @ExtendWith(SpringExtension.class) @Transactional @@ -111,13 +112,13 @@ void shouldAllowUseOfDynamicSpelParametersInUpdateQueries() { em.detach(thomas); em.detach(auditor); - FixedDate.INSTANCE.setDate(new Date()); + FixedDate.INSTANCE.setDate(Instant.now()); SampleSecurityContextHolder.getCurrent().setPrincipal(thomas); auditableUserRepository.updateAllNamesToUpperCase(); // DateTime now = new DateTime(FixedDate.INSTANCE.getDate()); - LocalDateTime now = LocalDateTime.ofInstant(FixedDate.INSTANCE.getDate().toInstant(), ZoneId.systemDefault()); + LocalDateTime now = LocalDateTime.ofInstant(FixedDate.INSTANCE.getDate(), ZoneId.systemDefault()); List users = auditableUserRepository.findAll(); for (AuditableUser user : users) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java index 377874e48c..fc930b94d5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java @@ -15,24 +15,25 @@ */ package org.springframework.data.jpa.util; -import java.util.Date; +import java.time.Instant; /** - * Holds a fixed {@link Date} value to use in components that have no direct connection. + * Holds a fixed {@link Instant} value to use in components that have no direct connection. * * @author Thomas Darimont + * @author Christoph Strobl */ public enum FixedDate { INSTANCE; - private Date fixedDate; + private Instant fixedDate; - public void setDate(Date date) { + public void setDate(Instant date) { this.fixedDate = date; } - public Date getDate() { + public Instant getDate() { return fixedDate; } } From cef096cb300ffc9a62ac67725c5b6ca5b0dad0e6 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 19 Dec 2024 14:07:53 +0100 Subject: [PATCH 16/16] Polishing. --- .../data/jpa/domain/JpaSort.java | 4 +-- .../mapping/JpaPersistentPropertyImpl.java | 10 +++---- .../data/jpa/projection/package-info.java | 5 ++++ .../query/AbstractStringBasedJpaQuery.java | 4 +-- .../query/JSqlParserQueryEnhancer.java | 5 ++-- .../query/JpaQueryLookupStrategy.java | 3 ++- .../repository/query/JpqlQueryBuilder.java | 4 +-- .../query/KeysetScrollDelegate.java | 2 +- .../repository/query/ParameterBinding.java | 2 +- .../query/ParameterMetadataProvider.java | 17 +++++------- .../query/QueryParameterSetterFactory.java | 2 -- .../repository/query/QueryTokenStream.java | 5 ++++ .../jpa/repository/query/SimpleJpaQuery.java | 26 ++++++------------- .../jpa/repository/query/StringQuery.java | 26 +++++++------------ ...hScanningPersistenceUnitPostProcessor.java | 2 +- 15 files changed, 48 insertions(+), 69 deletions(-) create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java index a28bf8a390..99b29715ec 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java @@ -281,14 +281,14 @@ public , U> Path dot(A attribute) { * @return */ public

, U> Path dot(P attribute) { - return new Path(add(attribute)); + return new Path<>(add(attribute)); } private List> add(Attribute attribute) { Assert.notNull(attribute, "Attribute must not be null"); - List> newAttributes = new ArrayList>(attributes.size() + 1); + List> newAttributes = new ArrayList<>(attributes.size() + 1); newAttributes.addAll(attributes); newAttributes.add(attribute); return newAttributes; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java index 501d3a6444..d2bd3039ad 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -57,13 +57,9 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty> annotations = new HashSet<>(); - annotations.add(OneToMany.class); - annotations.add(OneToOne.class); - annotations.add(ManyToMany.class); - annotations.add(ManyToOne.class); + Set> annotations; - ASSOCIATION_ANNOTATIONS = Collections.unmodifiableSet(annotations); + ASSOCIATION_ANNOTATIONS = Set.of(OneToMany.class, OneToOne.class, ManyToMany.class, ManyToOne.class); annotations = new HashSet<>(); annotations.add(Id.class); @@ -107,7 +103,7 @@ public JpaPersistentPropertyImpl(JpaMetamodel metamodel, Property property, this.associationTargetType = detectAssociationTargetType(); this.updateable = detectUpdatability(); - this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) // + this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(this::isAnnotationPresent) // || metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType())); this.isEntity = Lazy.of(() -> metamodel.isMappedType(getActualType())); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java new file mode 100644 index 0000000000..4f85f48a62 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java @@ -0,0 +1,5 @@ +/** + * JPA specific support projection support. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.jpa.projection; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 9c83985546..657778b260 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -93,9 +93,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri return query.deriveCountQuery(method.getCountQueryProjection()); }); - this.countParameterBinder = Lazy.of(() -> { - return this.createBinder(this.countQuery.get()); - }); + this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get())); this.queryRewriter = queryRewriter; 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 1686148603..c7a76502f9 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 @@ -98,7 +98,8 @@ public JSqlParserQueryEnhancer(DeclaredQuery query) { /** * Parses a query string with JSqlParser. * - * @param query the query to parse + * @param sql the query to parse + * @param classOfT the query to parse * @return the parsed query */ static T parseStatement(String sql, Class classOfT) { @@ -502,7 +503,7 @@ private static boolean onlyASingleColumnProjection(List> projectio * */ enum ParsedType { - DELETE, UPDATE, SELECT, INSERT, MERGE, OTHER; + DELETE, UPDATE, SELECT, INSERT, MERGE, OTHER } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 8ca7a007df..affcee14c8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -143,7 +143,8 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra * * @param em must not be {@literal null}. * @param queryMethodFactory must not be {@literal null}. - * @param evaluationContextProvider must not be {@literal null}. + * @param delegate must not be {@literal null}. + * @param queryRewriterProvider must not be {@literal null}. */ public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java index 287b397384..6c1817946b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java @@ -848,9 +848,7 @@ public String getAlias(Origin source) { */ public String getAlias(Origin source) { - return aliases.computeIfAbsent(source, it -> JpqlQueryBuilder.getAlias(source.getName(), s -> { - return !aliases.containsValue(s); - }, () -> "join_" + (counter++))); + return aliases.computeIfAbsent(source, it -> JpqlQueryBuilder.getAlias(source.getName(), s -> !aliases.containsValue(s), () -> "join_" + (counter++))); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java index 5d4d8acb5f..a9f38dcebc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java @@ -138,7 +138,7 @@ public Sort createSort(Sort sort, JpaEntityInformation entity) { } /** - * Reverse scrolling variant applying {@link Direction#Backward}. In reverse scrolling, we need to flip directions for + * Reverse scrolling variant applying {@link Direction#BACKWARD}. In reverse scrolling, we need to flip directions for * the actual query so that we do not get everything from the top position and apply the limit but rather flip the * sort direction, apply the limit and then reverse the result to restore the actual sort order. */ diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java index 259343e9c7..ecd4d63ee0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java @@ -653,7 +653,7 @@ static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Int /** * Creates a {@link MethodInvocationArgument} object for {@code position}. * - * @param position the parameter position (1-based) from the method invocation. + * @param parameter the parameter from the method invocation. * @return {@link MethodInvocationArgument} object for {@code position}. */ static MethodInvocationArgument ofParameter(Parameter parameter) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index de3256baa3..9fd7817a7e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -273,17 +273,12 @@ public Object prepare(@Nullable Object value) { if (String.class.equals(parameterType) && !noWildcards) { - switch (type) { - case STARTING_WITH: - return String.format("%s%%", escape.escape(value.toString())); - case ENDING_WITH: - return String.format("%%%s", escape.escape(value.toString())); - case CONTAINING: - case NOT_CONTAINING: - return String.format("%%%s%%", escape.escape(value.toString())); - default: - return value; - } + return switch (type) { + case STARTING_WITH -> String.format("%s%%", escape.escape(value.toString())); + case ENDING_WITH -> String.format("%%%s", escape.escape(value.toString())); + case CONTAINING, NOT_CONTAINING -> String.format("%%%s%%", escape.escape(value.toString())); + default -> value; + }; } return Collection.class.isAssignableFrom(parameterType) // diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index 4844060aa3..4b5f530d0b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -93,7 +93,6 @@ static QueryParameterSetterFactory forSynthetic() { * * @param parser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. - * @param parameters must not be {@literal null}. * @return a {@link QueryParameterSetterFactory} that can handle * {@link org.springframework.expression.spel.standard.SpelExpression}s. */ @@ -170,7 +169,6 @@ private static class ExpressionBasedQueryParameterSetterFactory extends QueryPar /** * @param parser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. - * @param parameters must not be {@literal null}. */ ExpressionBasedQueryParameterSetterFactory(ValueExpressionParser parser, ValueEvaluationContextProvider evaluationContextProvider) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java index b31f48939c..9b97836ab9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java @@ -46,6 +46,7 @@ static QueryTokenStream empty() { /** * Creates a QueryTokenStream from a {@link QueryToken}. + * @since 4.0 */ static QueryTokenStream from(QueryToken token) { return QueryRenderer.from(Collections.singletonList(token)); @@ -53,6 +54,7 @@ static QueryTokenStream from(QueryToken token) { /** * Creates an token QueryRenderer from an AST {@link TerminalNode}. + * @since 4.0 */ static QueryTokenStream ofToken(TerminalNode node) { return from(QueryTokens.token(node)); @@ -60,6 +62,7 @@ static QueryTokenStream ofToken(TerminalNode node) { /** * Creates an token QueryRenderer from an AST {@link Token}. + * @since 4.0 */ static QueryTokenStream ofToken(Token node) { return from(QueryTokens.token(node)); @@ -148,6 +151,7 @@ default QueryToken getFirst() { /** * @return the required first query token or throw {@link java.util.NoSuchElementException} if empty. + * @since 4.0 */ default QueryToken getRequiredFirst() { @@ -170,6 +174,7 @@ default QueryToken getLast() { /** * @return the required last query token or throw {@link java.util.NoSuchElementException} if empty. + * @since 4.0 */ default QueryToken getRequiredLast() { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java index 16fa3c30e0..b044191b54 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java @@ -84,23 +84,13 @@ private void validateQuery(String query, String errorMessage, Object... argument return; } - EntityManager validatingEm = null; - - try { - validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager(); - validatingEm.createQuery(query); - - } catch (RuntimeException e) { - - // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider - // https://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17 - throw new IllegalArgumentException(String.format(errorMessage, arguments), e); - - } finally { - - if (validatingEm != null) { - validatingEm.close(); - } - } + try (EntityManager validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager()) { + validatingEm.createQuery(query); + } catch (RuntimeException e) { + + // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider + // https://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17 + throw new IllegalArgumentException(String.format(errorMessage, arguments), e); + } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 471ede0403..e531262ce1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -304,25 +304,17 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que : ParameterOrigin.ofExpression(expression); BindingIdentifier targetBinding = queryParameter; - Function bindingFactory; - switch (ParameterBindingType.of(typeSource)) { + Function bindingFactory = switch (ParameterBindingType.of(typeSource)) { + case LIKE -> { - case LIKE: + Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); + yield (identifier) -> new LikeParameterBinding(identifier, origin, likeType); + } + case IN -> (identifier) -> new InParameterBinding(identifier, origin); // fall-through we don't need a special parameter queryParameter for the given parameter. + default -> (identifier) -> new ParameterBinding(identifier, origin); + }; - Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); - bindingFactory = (identifier) -> new LikeParameterBinding(identifier, origin, likeType); - break; - - case IN: - bindingFactory = (identifier) -> new InParameterBinding(identifier, origin); - break; - - case AS_IS: // fall-through we don't need a special parameter queryParameter for the given parameter. - default: - bindingFactory = (identifier) -> new ParameterBinding(identifier, origin); - } - - if (origin.isExpression()) { + if (origin.isExpression()) { parameterBindings.register(bindingFactory.apply(queryParameter)); } else { targetBinding = parameterBindings.register(queryParameter, origin, bindingFactory); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java index 8415d67959..06f454e24c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java @@ -193,7 +193,7 @@ private Set scanForMappingFileLocations() { * @param uri * @return */ - private static String getResourcePath(URI uri) throws IOException { + private static String getResourcePath(URI uri) { if (uri.isOpaque()) { // e.g. jar:file:/foo/lib/somelib.jar!/com/acme/orm.xml