diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..411d4a9338 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,21 @@ +# GitHub Actions for CodeQL Scanning + +name: "CodeQL Advanced" + +on: + push: + pull_request: + workflow_dispatch: + schedule: + # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule + - cron: '0 5 * * *' + +permissions: read-all + +jobs: + codeql-analysis-call: + permissions: + actions: read + contents: read + security-events: write + uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1 diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml index a5f764579a..4c8108d353 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -10,6 +10,11 @@ on: pull_request_target: types: [opened, edited, reopened] +permissions: + contents: read + issues: write + pull-requests: write + jobs: Inbox: runs-on: ubuntu-latest diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 1e3bb355f5..e0857eaa25 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -3,6 +3,6 @@ io.spring.develocity.conventions develocity-conventions-maven-extension - 0.0.19 + 0.0.22 diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..e27f6e8f5e --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,14 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED +--add-opens=java.base/java.util=ALL-UNNAMED +--add-opens=java.base/java.lang.reflect=ALL-UNNAMED +--add-opens=java.base/java.text=ALL-UNNAMED +--add-opens=java.desktop/java.awt.font=ALL-UNNAMED diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8eb4cb3b3d..c0870ea66f 100755 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,2 @@ -#Thu Nov 07 09:47:17 CET 2024 -distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +#Thu Jul 17 14:04:38 CEST 2025 +distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/Jenkinsfile b/Jenkinsfile index 915e46ddb7..119aa608fd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,7 @@ pipeline { triggers { pollSCM 'H/10 * * * *' - upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS) + upstream(upstreamProjects: "spring-data-commons/3.4.x", threshold: hudson.model.Result.SUCCESS) } options { diff --git a/README.adoc b/README.adoc index a8421d9d54..d968519203 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -= Spring Data JPA image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-jpa%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-jpa/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring%20Data%20JPA%20Parent"] += Spring Data JPA image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring%20Data%20JPA%20Parent"] Spring Data JPA, part of the larger https://projects.spring.io/spring-data[Spring Data] family, makes it easy to implement JPA-based repositories. This module deals with enhanced support for JPA-based data access layers. @@ -144,7 +144,7 @@ https://github.com/spring-projects/spring-data-jpa/issues[issue tracker] to see * If the issue doesn’t exist already, https://github.com/spring-projects/spring-data-jpa/issues[create a new issue]. * Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using and JVM version, complete stack traces and any relevant configuration information. * If you need to paste code, or include a stack trace format it as code using triple backtick. -* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code. Use an in-memory datatabase if possible or set the database up using https://github.com/testcontainers[Testcontainers]. +* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code. Use an in-memory database if possible or set the database up using https://github.com/testcontainers[Testcontainers]. == Building from Source @@ -158,7 +158,9 @@ You also need JDK 17 or above. If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.8.0 or above]. -_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._ +_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests._ +All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. +For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. === Building reference documentation @@ -169,7 +171,7 @@ Building the documentation builds also the project without running tests. $ ./mvnw clean install -Pantora ---- -The generated documentation is available from `target/antora/site/index.html`. +The generated documentation is available from `target/antora/index.html`. == Guides diff --git a/ci/pipeline.properties b/ci/pipeline.properties index cd2fcf7fbe..7b01602c38 100644 --- a/ci/pipeline.properties +++ b/ci/pipeline.properties @@ -1,6 +1,6 @@ # Java versions -java.main.tag=17.0.13_11-jdk-focal -java.next.tag=23.0.1_11-jdk-noble +java.main.tag=17.0.15_6-jdk-focal +java.next.tag=24.0.1_9-jdk-noble # Docker container images - standard docker.java.main.image=library/eclipse-temurin:${java.main.tag} diff --git a/pom.xml b/pom.xml index c8e8a9ae06..57b77d5da0 100755 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ - + 4.0.0 org.springframework.data spring-data-jpa-parent - 3.4.0 + 3.4.12-SNAPSHOT pom Spring Data JPA Parent @@ -23,31 +23,30 @@ org.springframework.data.build spring-data-parent - 3.4.0 + 3.4.12-SNAPSHOT 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 + 4.0.8 + 4.0.8-SNAPSHOT + 6.6.33.Final + 6.2.38.Final + 6.6.26-SNAPSHOT + 7.0.0.Beta5 7.0.0-SNAPSHOT 2.7.4

2.3.232

3.1.0 5.0 - 9.1.0 - 42.7.4 - 3.4.0 + 9.2.0 + 42.7.7 + 3.4.12-SNAPSHOT 0.10.3 org.hibernate reuseReports -
@@ -84,6 +83,7 @@ ${hibernate-70} 3.2.0-M2 + 4.13.2 @@ -111,43 +111,6 @@ - - all-dbs - - - - org.apache.maven.plugins - maven-surefire-plugin - - - mysql-test - test - - test - - - - **/MySql*IntegrationTests.java - - - - - postgres-test - test - - test - - - - **/Postgres*IntegrationTests.java - - - - - - - - eclipselink-next @@ -188,85 +151,6 @@ - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.springframework - spring-instrument - ${spring} - runtime - - - - - - default-test - - - **/* - - - - - unit-test - - test - - test - - - **/*UnitTests.java - - - - - integration-test - - test - - test - - - **/*IntegrationTests.java - **/*Tests.java - - - **/*UnitTests.java - **/OpenJpa* - **/EclipseLink* - **/MySql* - **/Postgres* - - - -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar - - - - - eclipselink-test - - test - - test - - - **/EclipseLink*Tests.java - - - -javaagent:${settings.localRepository}/org/eclipse/persistence/org.eclipse.persistence.jpa/${eclipselink}/org.eclipse.persistence.jpa-${eclipselink}.jar - -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar - - - - - - - - @@ -289,8 +173,20 @@ - - + + spring-snapshot + https://repo.spring.io/snapshot + + true + + + false + + + + spring-milestone + https://repo.spring.io/milestone + diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index d83c8dd464..e452acd7a5 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.4.0 + 3.4.12-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.4.0 + 3.4.12-SNAPSHOT ../pom.xml diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/EnableEnversRepositories.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/EnableEnversRepositories.java index 7ec6ec4b70..feb8782229 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/EnableEnversRepositories.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/EnableEnversRepositories.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java index cf461e8d9f..d8b96b28d9 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java index 6b92607b1f..5c7672d10d 100755 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java index 2d0ba8bea5..f1483304f3 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java index dd7a6b4768..9f40559ca4 100755 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java index 7a6c1b62a3..aeb84f6455 100755 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ * @author Donghun Shin * @author Greg Turnquist * @author Aref Behboodi + * @author Ngoc Nhan */ @Transactional(readOnly = true) public class EnversRevisionRepositoryImpl> @@ -77,12 +78,12 @@ public class EnversRevisionRepositoryImpl entityInformation, - RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) { + RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) { Assert.notNull(entityInformation, "JpaEntityInformation must not be null!"); Assert.notNull(entityManager, "EntityManager must not be null!"); @@ -92,6 +93,7 @@ public EnversRevisionRepositoryImpl(JpaEntityInformation entityInformation this.entityManager = entityManager; } + @Override @SuppressWarnings("unchecked") public Optional> findLastChangeRevision(ID id) { @@ -117,7 +119,7 @@ public Optional> findRevision(ID id, N revisionNumber) { Assert.notNull(id, "Identifier must not be null!"); Assert.notNull(revisionNumber, "Revision number must not be null!"); - List singleResult = (List) createBaseQuery(id) // + List singleResult = createBaseQuery(id) // .add(AuditEntity.revisionNumber().eq(revisionNumber)) // .getResultList(); @@ -130,6 +132,7 @@ public Optional> findRevision(ID id, N revisionNumber) { return Optional.of(createRevision(new QueryResult<>(singleResult.get(0)))); } + @Override @SuppressWarnings("unchecked") public Revisions findRevisions(ID id) { @@ -170,6 +173,7 @@ private List mapPropertySort(Sort sort) { return result; } + @Override @SuppressWarnings("unchecked") public Page> findRevisions(ID id, Pageable pageable) { @@ -181,9 +185,12 @@ public Page> findRevisions(ID id, Pageable pageable) { orderMapped.forEach(baseQuery::addOrder); + if (pageable.isPaged()) { + baseQuery.setFirstResult((int) pageable.getOffset()) // + .setMaxResults(pageable.getPageSize()); + } + List resultList = baseQuery // - .setFirstResult((int) pageable.getOffset()) // - .setMaxResults(pageable.getPageSize()) // .getResultList(); Long count = (Long) createBaseQuery(id) // @@ -240,7 +247,7 @@ RevisionMetadata createRevisionMetadata() { return metadata instanceof DefaultRevisionEntity defaultRevisionEntity // ? new DefaultRevisionMetadata(defaultRevisionEntity, revisionType) // : new AnnotationRevisionMetadata<>(Hibernate.unproxy(metadata), RevisionNumber.class, RevisionTimestamp.class, - revisionType); + revisionType); } private static RevisionMetadata.RevisionType convertRevisionType(RevisionType datum) { diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java index ea629fcdbe..631dbca9f8 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/Config.java b/spring-data-envers/src/test/java/org/springframework/data/envers/Config.java index 5f636e65e5..c8c1aa963e 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/Config.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/Config.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadataUnitTests.java b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadataUnitTests.java index 930b20be20..f5e50b0dcc 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadataUnitTests.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadataUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java index 81db782d28..625e099d5a 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/QueryDslRepositoryIntegrationTests.java b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/QueryDslRepositoryIntegrationTests.java index 7aed270c92..7009f219e5 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/QueryDslRepositoryIntegrationTests.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/QueryDslRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTests.java b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTests.java index 1a4ce785ea..bf04d06e28 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTests.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,23 @@ */ package org.springframework.data.envers.repository.support; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.history.RevisionMetadata.RevisionType.*; + +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.envers.Config; import org.springframework.data.envers.sample.Country; @@ -33,21 +44,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.time.Instant; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.history.RevisionMetadata.RevisionType.*; - /** * Integration tests for repositories. * * @author Oliver Gierke * @author Jens Schauder * @author Niklas Loechte + * @author Mark Paluch */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = Config.class) @@ -107,6 +110,40 @@ void testLifeCycle() { }); } + @Test // GH-3999 + void shouldReturnUnpagedResults() { + + License license = new License(); + license.name = "Schnitzel"; + + licenseRepository.save(license); + + Country de = new Country(); + de.code = "de"; + de.name = "Deutschland"; + + countryRepository.save(de); + + Country se = new Country(); + se.code = "se"; + se.name = "Schweden"; + + countryRepository.save(se); + + license.laender = new HashSet<>(); + license.laender.addAll(Arrays.asList(de, se)); + + licenseRepository.save(license); + + de.name = "Daenemark"; + + countryRepository.save(de); + + Page> revisions = licenseRepository.findRevisions(license.id, Pageable.unpaged()); + + assertThat(revisions).hasSize(2); + } + @Test // #1 void returnsEmptyLastRevisionForUnrevisionedEntity() { assertThat(countryRepository.findLastChangeRevision(100L)).isEmpty(); diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java index 29cc905a15..1120fded1b 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/Country.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/Country.java index 67a2fed9d4..5973ecaa78 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/Country.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/Country.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryQueryDslRepository.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryQueryDslRepository.java index 741b2c117f..b7bc3e1c5a 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryQueryDslRepository.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryQueryDslRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryRepository.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryRepository.java index 046de77f23..4f5cc1f2f5 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryRepository.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CountryRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntity.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntity.java index be2dfd0fe3..d69410894d 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntity.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionListener.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionListener.java index 3bda04361d..80b45588db 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionListener.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/License.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/License.java index b6542eaa48..9ba1070f0a 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/License.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/License.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java index 87b38d5b88..2d6b7737df 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/QCountry.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/QCountry.java index 66a3d509b9..4d1f03c430 100755 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/QCountry.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/QCountry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 9b0bc40b31..8a59ed4431 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.4.0 + 3.4.12-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index 3711d26d84..32d0ae3d4a 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -1,12 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-jpa - 3.4.0 + 3.4.12-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +16,7 @@ org.springframework.data spring-data-jpa-parent - 3.4.0 + 3.4.12-SNAPSHOT ../pom.xml @@ -301,6 +302,7 @@ **/Postgres* + -Xmx4G -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar @@ -316,6 +318,7 @@ **/EclipseLink*Tests.java + -Xmx4G -javaagent:${settings.localRepository}/org/eclipse/persistence/org.eclipse.persistence.jpa/${eclipselink}/org.eclipse.persistence.jpa-${eclipselink}.jar -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar @@ -336,7 +339,8 @@ generate-sources true - ${project.basedir}/src/main/antlr4 + ${project.basedir}/src/main/antlr4 + @@ -427,4 +431,45 @@ + + + all-dbs + + + + org.apache.maven.plugins + maven-surefire-plugin + + + mysql-test + test + + test + + + + **/MySql*IntegrationTests.java + + + + + postgres-test + test + + test + + + + **/Postgres*IntegrationTests.java + + + + + + + + + + + diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryFinderBenchmarks.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryFinderBenchmarks.java index 05f66f1203..209dc55318 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryFinderBenchmarks.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryFinderBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/IPersonProjection.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/IPersonProjection.java index 6934c0ae67..442385ca32 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/IPersonProjection.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/IPersonProjection.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Person.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Person.java index 3996c0b611..aa830371aa 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Person.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Profile.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Profile.java index 816554553e..acd3f39b97 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Profile.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/model/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/repository/PersonRepository.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/repository/PersonRepository.java index 3ba5fc00c9..491ab736a8 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/repository/PersonRepository.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/repository/PersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java index 482c4454fa..fd46a3f6c2 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java index 724a417353..845282e319 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..1baf31e7a7 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 @@ -214,8 +214,8 @@ constructor_item ; aggregate_expression - : (AVG | MAX | MIN | SUM) '(' (DISTINCT)? state_valued_path_expression ')' - | COUNT '(' (DISTINCT)? (identification_variable | state_valued_path_expression | single_valued_object_path_expression) ')' + : (AVG | MAX | MIN | SUM) '(' (DISTINCT)? simple_select_expression ')' + | COUNT '(' (DISTINCT)? simple_select_expression ')' | function_invocation ; @@ -349,12 +349,17 @@ between_expression ; in_expression - : (state_valued_path_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter) + : (string_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter) ; in_item : literal + | string_expression + | boolean_literal + | numeric_literal + | date_time_timestamp_literal | single_valued_input_parameter + | conditional_expression ; like_expression @@ -436,7 +441,8 @@ arithmetic_primary | functions_returning_numerics | aggregate_expression | case_expression - | cast_function + | arithmetic_cast_function + | type_cast_function | function_invocation | '(' subquery ')' ; @@ -449,6 +455,8 @@ string_expression | aggregate_expression | case_expression | function_invocation + | string_cast_function + | type_cast_function | '(' subquery ')' ; @@ -542,8 +550,16 @@ trim_specification | BOTH ; -cast_function - : CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' +arithmetic_cast_function + : CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')' + ; + +type_cast_function + : CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + ; + +string_cast_function + : CAST '(' scalar_expression (AS)? STRING ')' ; function_invocation @@ -567,10 +583,7 @@ datetime_part ; function_arg - : literal - | state_valued_path_expression - | input_parameter - | scalar_expression + : simple_select_expression ; case_expression @@ -672,7 +685,8 @@ entity_type_literal escape_character : CHARACTER - | character_valued_input_parameter // + | string_literal + | character_valued_input_parameter ; numeric_literal @@ -705,18 +719,22 @@ subtype collection_valued_field : identification_variable + | reserved_word ; single_valued_object_field : identification_variable + | reserved_word ; state_field : identification_variable + | reserved_word ; collection_value_field : identification_variable + | reserved_word ; entity_name @@ -837,7 +855,8 @@ reserved_word */ -WS : [ \t\r\n] -> skip ; +WS : [ \t\r\n] -> channel(HIDDEN) ; +COMMENT : '/*' (~'*' | '*' ~'/' )* '*/' -> skip; // Build up case-insentive tokens @@ -894,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; @@ -907,6 +927,7 @@ FALSE : F A L S E; FETCH : F E T C H; FIRST : F I R S T; FLOOR : F L O O R; +FLOAT : F L O A T; FROM : F R O M; FUNCTION : F U N C T I O N; GROUP : G R O U P; @@ -916,6 +937,7 @@ 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; +INTEGER : I N T E G E R; JOIN : J O I N; KEY : K E Y; LAST : L A S T; @@ -926,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; @@ -952,6 +975,7 @@ SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; SUBSTRING : S U B S T R I N G; +STRING : S T R I N G; SUM : S U M; THEN : T H E N; TIME : T I M E; diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 index 54a93e9ebf..b8e06ee253 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 @@ -26,6 +26,8 @@ grammar Hql; * to simplify the processing. * * @author Greg Turnquist + * @author Mark Paluch + * @author Yannick Brandt * @since 3.1 */ } @@ -83,16 +85,16 @@ cteAttributes ; orderedQuery - : (query | '(' queryExpression ')') queryOrder? + : (query | '(' queryExpression ')') queryOrder? limitClause? offsetClause? fetchClause? ; query - : selectClause fromClause? whereClause? (groupByClause havingClause?)? # SelectQuery - | fromClause whereClause? (groupByClause havingClause?)? selectClause? # FromQuery + : selectClause fromClause? whereClause? groupByClause? havingClause? # SelectQuery + | fromClause whereClause? groupByClause? havingClause? selectClause? # FromQuery ; queryOrder - : orderByClause limitClause? offsetClause? fetchClause? + : orderByClause ; fromClause @@ -147,7 +149,7 @@ deleteStatement // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert insertStatement - : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) + : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) conflictClause? ; // Already defined underneath updateStatement @@ -167,12 +169,25 @@ values : '(' expression (',' expression)* ')' ; -instantiation - : NEW instantiationTarget '(' instantiationArguments ')' +/** + * a 'conflict' clause in an 'insert' statement + */ +conflictClause + : ON CONFLICT conflictTarget? DO conflictAction + ; + +conflictTarget + : ON CONSTRAINT identifier + | '(' simplePath (',' simplePath)* ')' + ; + +conflictAction + : NOTHING + | UPDATE setClause whereClause? ; -alias - : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word +instantiation + : NEW instantiationTarget '(' instantiationArguments ')' ; groupedItem @@ -294,12 +309,15 @@ setOperator // Literals // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-literals literal - : NULL + : STRING_LITERAL + | JAVA_STRING_LITERAL + | NULL | booleanLiteral - | stringLiteral | numericLiteral - | dateTimeLiteral | binaryLiteral + | temporalLiteral + | arrayLiteral + | generalizedLiteral ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals @@ -308,33 +326,168 @@ booleanLiteral | FALSE ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-string-literals -stringLiteral - : STRINGLITERAL - | JAVASTRINGLITERAL - | CHARACTER - ; - // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-literals numericLiteral : INTEGER_LITERAL + | LONG_LITERAL + | BIG_INTEGER_LITERAL | FLOAT_LITERAL - | HEXLITERAL + | DOUBLE_LITERAL + | BIG_DECIMAL_LITERAL + | HEX_LITERAL ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals +/** + * A literal datetime, in braces, or with the 'datetime' keyword + */ dateTimeLiteral - : LOCAL_DATE - | LOCAL_TIME - | LOCAL_DATETIME - | CURRENT_DATE - | CURRENT_TIME - | CURRENT_TIMESTAMP - | OFFSET_DATETIME - | (LOCAL | CURRENT) DATE - | (LOCAL | CURRENT) TIME - | (LOCAL | CURRENT | OFFSET) DATETIME - | INSTANT + : localDateTimeLiteral + | zonedDateTimeLiteral + | offsetDateTimeLiteral + ; + +localDateTimeLiteral + : '(' localDateTime ')' + | LOCAL? DATETIME localDateTime + ; + +zonedDateTimeLiteral + : '(' zonedDateTime ')' + | ZONED? DATETIME zonedDateTime + ; + +offsetDateTimeLiteral + : '(' offsetDateTime ')' + | OFFSET? DATETIME offsetDateTimeWithMinutes + ; +/** + * A literal date, in braces, or with the 'date' keyword + */ +dateLiteral + : '(' date ')' + | LOCAL? DATE date + ; + +/** + * A literal time, in braces, or with the 'time' keyword + */ +timeLiteral + : '(' time ')' + | LOCAL? TIME time + ; + +/** + * A literal datetime + */ + dateTime + : localDateTime + | zonedDateTime + | offsetDateTime + ; + +localDateTime + : date time + ; + +zonedDateTime + : date time zoneId + ; + +offsetDateTime + : date time offset + ; + +offsetDateTimeWithMinutes + : date time offsetWithMinutes + ; + +/** + * A JDBC-style timestamp escape, as required by JPQL + */ +jdbcTimestampLiteral + : TIMESTAMP_ESCAPE_START (dateTime | genericTemporalLiteralText) '}' + ; + +/** + * A JDBC-style date escape, as required by JPQL + */ +jdbcDateLiteral + : DATE_ESCAPE_START (date | genericTemporalLiteralText) '}' + ; + +/** + * A JDBC-style time escape, as required by JPQL + */ +jdbcTimeLiteral + : TIME_ESCAPE_START (time | genericTemporalLiteralText) '}' + ; + +genericTemporalLiteralText + : STRING_LITERAL + ; + +/** + * A generic format for specifying literal values of arbitary types + */ +arrayLiteral + : '[' (expression (',' expression)*)? ']' + ; + +/** + * A generic format for specifying literal values of arbitary types + */ +generalizedLiteral + : '(' generalizedLiteralType ':' generalizedLiteralText ')' + ; + +generalizedLiteralType : STRING_LITERAL; +generalizedLiteralText : STRING_LITERAL; + +/** + * A literal date + */ +date + : year '-' month '-' day + ; + +/** + * A literal time + */ +time + : hour ':' minute (':' second)? + ; + +/** + * A literal offset + */ +offset + : (PLUS | MINUS) hour (':' minute)? + ; + +offsetWithMinutes + : (PLUS | MINUS) hour ':' minute + ; + +year: INTEGER_LITERAL; +month: INTEGER_LITERAL; +day: INTEGER_LITERAL; +hour: INTEGER_LITERAL; +minute: INTEGER_LITERAL; +second: INTEGER_LITERAL | DOUBLE_LITERAL; +zoneId + : IDENTIFIER ('/' IDENTIFIER)? + | STRING_LITERAL; + +/** + * A field that may be extracted from a date, time, or datetime + */ +extractField + : datetimeField + | dayField + | weekField + | timeZoneField + | dateOrTimeField ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals @@ -351,14 +504,44 @@ datetimeField | EPOCH ; +dayField + : DAY OF MONTH + | DAY OF WEEK + | DAY OF YEAR + ; + +weekField + : WEEK OF MONTH + | WEEK OF YEAR + ; + +timeZoneField + : OFFSET (HOUR | MINUTE)? + | TIMEZONE_HOUR | TIMEZONE_MINUTE + ; + +dateOrTimeField + : DATE + | TIME + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals binaryLiteral : BINARY_LITERAL - | '{' HEXLITERAL (',' HEXLITERAL)* '}' + | '{' HEX_LITERAL (',' HEX_LITERAL)* '}' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals -// TBD +/** + * A literal date, time, or datetime, in HQL syntax, or as a JDBC-style "escape" syntax + */ +temporalLiteral + : dateTimeLiteral + | dateLiteral + | timeLiteral + | jdbcTimestampLiteral + | jdbcDateLiteral + | jdbcTimeLiteral + ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-java-constants // TBD @@ -387,184 +570,672 @@ expression | WEEK OF YEAR # WeekOfYearExpression ; -primaryExpression - : caseList # CaseExpression - | literal # LiteralExpression - | parameter # ParameterExpression - | function # FunctionExpression - | generalPathFragment # GeneralPathExpression +primaryExpression + : caseList # CaseExpression + | literal # LiteralExpression + | parameter # ParameterExpression + | entityTypeReference # EntityTypeExpression + | entityIdReference # EntityIdExpression + | entityVersionReference # EntityVersionExpression + | entityNaturalIdReference # EntityNaturalIdExpression + | syntacticDomainPath pathContinuation? # SyntacticPathExpression + | function # FunctionExpression + | generalPathFragment # GeneralPathExpression + ; + +/** + * A much more complicated path expression involving operators and functions + * + * A path which needs to be resolved semantically. This recognizes + * any path-like structure. Generally, the path is semantically + * interpreted by the consumer of the parse-tree. However, there + * are certain cases where we can syntactically recognize a navigable + * path; see 'syntacticNavigablePath' rule + */ +path + : syntacticDomainPath pathContinuation? + | generalPathFragment + ; + +generalPathFragment + : simplePath indexedPathAccessFragment? + ; + +indexedPathAccessFragment + : '[' expression ']' ('.' generalPathFragment)? + ; + +/** + * A simple path expression + * + * - a reference to an identification variable (not case-sensitive), + * - followed by a list of period-separated identifiers (case-sensitive) + */ +simplePath + : identifier simplePathElement* + ; + +/** + * An element of a simple path expression: a period, and an identifier (case-sensitive) + */ +simplePathElement + : '.' identifier + ; + +/** + * A continuation of a path expression "broken" by an operator or function + */ +pathContinuation + : '.' simplePath + ; + +/** + * The special function 'type()' + */ +entityTypeReference + : TYPE '(' (path | parameter) ')' + ; + +/** + * The special function 'id()' + */ +entityIdReference + : ID '(' path ')' pathContinuation? + ; + +/** + * The special function 'version()' + */ +entityVersionReference + : VERSION '(' path ')' + ; + +/** + * The special function 'naturalid()' + */ +entityNaturalIdReference + : NATURALID '(' path ')' pathContinuation? + ; + +/** + * An operator or function that may occur within a path expression + * + * Rule for cases where we syntactically know that the path is a + * "domain path" because it is one of these special cases: + * + * * TREAT( path ) + * * ELEMENTS( path ) + * * INDICES( path ) + * * VALUE( path ) + * * KEY( path ) + * * path[ selector ] + * * ARRAY_GET( embeddableArrayPath, index ).path + * * COALESCE( array1, array2 )[ selector ].path + */ +syntacticDomainPath + : treatedNavigablePath + | collectionValueNavigablePath + | mapKeyNavigablePath + | simplePath indexedPathAccessFragment + | simplePath slicedPathAccessFragment + | toOneFkReference + | function pathContinuation + | function indexedPathAccessFragment pathContinuation? + | function slicedPathAccessFragment + ; + +/** + * The slice operator to obtain elements between the lower and upper bound. + */ +slicedPathAccessFragment + : '[' expression ':' expression ']' + ; + +/** + * A 'treat()' function that "breaks" a path expression + */ +treatedNavigablePath + : TREAT '(' path AS simplePath ')' pathContinuation? + ; + +/** + * A 'value()' function that "breaks" a path expression + */ +collectionValueNavigablePath + : elementValueQuantifier '(' path ')' pathContinuation? + ; + +/** + * A 'key()' or 'index()' function that "breaks" a path expression + */ +mapKeyNavigablePath + : indexKeyQuantifier '(' path ')' pathContinuation? + ; + +/** + * The special function 'fk()' + */ +toOneFkReference + : FK '(' path ')' + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-case-expressions +caseList + : simpleCaseExpression + | searchedCaseExpression + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-simple-case-expressions +simpleCaseExpression + : CASE expressionOrPredicate caseWhenExpressionClause+ (ELSE expressionOrPredicate)? END + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-searched-case-expressions +searchedCaseExpression + : CASE caseWhenPredicateClause+ (ELSE expressionOrPredicate)? END + ; + +caseWhenExpressionClause + : WHEN expression THEN expressionOrPredicate + ; + +caseWhenPredicateClause + : WHEN predicate THEN expressionOrPredicate + ; + +// Functions +/** + * A function invocation that may occur in an arbitrary expression + */ +function + : standardFunction # StandardFunctionInvocation + | aggregateFunction # AggregateFunctionInvocation + | collectionSizeFunction # CollectionSizeFunctionInvocation + | collectionAggregateFunction # CollectionAggregateFunctionInvocation + | collectionFunctionMisuse # CollectionFunctionMisuseInvocation + | jpaNonstandardFunction # JpaNonstandardFunctionInvocation + | columnFunction # ColumnFunctionInvocation + | genericFunction # GenericFunctionInvocation + ; + +/** + * Any function with an irregular syntax for the argument list + * + * These are all inspired by the syntax of ANSI SQL + */ +standardFunction + : castFunction + | treatedNavigablePath + | extractFunction + | truncFunction + | formatFunction + | collateFunction + | substringFunction + | overlayFunction + | trimFunction + | padFunction + | positionFunction + | currentDateFunction + | currentTimeFunction + | currentTimestampFunction + | instantFunction + | localDateFunction + | localTimeFunction + | localDateTimeFunction + | offsetDateTimeFunction + | cube + | rollup + ; + +/** + * The 'cast()' function for typecasting + */ +castFunction + : CAST '(' expression AS castTarget ')' + ; + +/** + * The target type for a typecast: a typename, together with length or precision/scale + */ +castTarget + : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? + ; + +/** + * The name of the target type in a typecast + * + * Like the 'entityName' rule, we have a specialized dotIdentifierSequence rule + */ +castTargetType + returns [String fullTargetName] + : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* + ; + +/** + * The two formats for the 'substring() function: one defined by JPQL, the other by ANSI SQL + */ +substringFunction + : SUBSTRING '(' expression ',' substringFunctionStartArgument (',' substringFunctionLengthArgument)? ')' + | SUBSTRING '(' expression FROM substringFunctionStartArgument (FOR substringFunctionLengthArgument)? ')' + ; + +substringFunctionStartArgument + : expression + ; + +substringFunctionLengthArgument + : expression + ; + +/** + * The ANSI SQL-style 'trim()' function + */ +trimFunction + : TRIM '(' trimSpecification? trimCharacter? FROM? expression ')' + ; + +trimSpecification + : LEADING + | TRAILING + | BOTH + ; + +trimCharacter + : STRING_LITERAL + | parameter + ; + +/** + * A 'pad()' function inspired by 'trim()' + */ +padFunction + : PAD '(' expression WITH padLength padSpecification padCharacter? ')' + ; + +padSpecification + : LEADING + | TRAILING + ; + +padCharacter + : STRING_LITERAL + ; + +padLength + : expression + ; + +/** + * The ANSI SQL-style 'position()' function + */ +positionFunction + : POSITION '(' positionFunctionPatternArgument IN positionFunctionStringArgument ')' + ; + +positionFunctionPatternArgument + : expression + ; + +positionFunctionStringArgument + : expression + ; + +/** + * The ANSI SQL-style 'overlay()' function + */ +overlayFunction + : OVERLAY '(' overlayFunctionStringArgument PLACING overlayFunctionReplacementArgument FROM overlayFunctionStartArgument (FOR overlayFunctionLengthArgument)? ')' + ; + +overlayFunctionStringArgument + : expression + ; + +overlayFunctionReplacementArgument + : expression + ; + +overlayFunctionStartArgument + : expression + ; + +overlayFunctionLengthArgument + : expression + ; + +/** + * The deprecated current_date function required by JPQL + */ +currentDateFunction + : CURRENT_DATE ('(' ')')? + | CURRENT DATE + ; + +/** + * The deprecated current_time function required by JPQL + */ +currentTimeFunction + : CURRENT_TIME ('(' ')')? + | CURRENT TIME + ; + +/** + * The deprecated current_timestamp function required by JPQL + */ +currentTimestampFunction + : CURRENT_TIMESTAMP ('(' ')')? + | CURRENT TIMESTAMP + ; + +/** + * The instant function, and deprecated current_instant function + */ +instantFunction + : CURRENT_INSTANT ('(' ')')? //deprecated legacy syntax + | INSTANT + ; + +/** + * The 'local datetime' function (or literal if you prefer) + */ +localDateTimeFunction + : LOCAL_DATETIME ('(' ')')? + | LOCAL DATETIME + ; + +/** + * The 'offset datetime' function (or literal if you prefer) + */ +offsetDateTimeFunction + : OFFSET_DATETIME ('(' ')')? + | OFFSET DATETIME ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-Datetime-arithmetic -// TBD +/** + * The 'local date' function (or literal if you prefer) + */ +localDateFunction + : LOCAL_DATE ('(' ')')? + | LOCAL DATE + ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-path-expressions -identificationVariable - : identifier - | simplePath +/** + * The 'local time' function (or literal if you prefer) + */ +localTimeFunction + : LOCAL_TIME ('(' ')')? + | LOCAL TIME ; -path - : treatedPath pathContinutation? - | generalPathFragment +/** + * The 'format()' function for formatting dates and times according to a pattern + */ +formatFunction + : FORMAT '(' expression AS format ')' ; -generalPathFragment - : simplePath indexedPathAccessFragment? +/** + * The name of a database-defined collation + * + * Certain databases allow a period in a collation name + */ +collation + : simplePath ; -indexedPathAccessFragment - : '[' expression ']' ('.' generalPathFragment)? +/** + * The special 'collate()' functions + */ +collateFunction + : COLLATE '(' expression AS collation ')' ; -simplePath - : identifier simplePathElement* +/** + * The 'cube()' function specific to the 'group by' clause + */ +cube + : CUBE '(' expressionOrPredicate (',' expressionOrPredicate)* ')' ; -simplePathElement - : '.' identifier +/** + * The 'rollup()' function specific to the 'group by' clause + */ +rollup + : ROLLUP '(' expressionOrPredicate (',' expressionOrPredicate)* ')' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-case-expressions -caseList - : simpleCaseExpression - | searchedCaseExpression +/** + * A format pattern, with a syntax inspired by by java.time.format.DateTimeFormatter + * + * see 'Dialect.appendDatetimeFormat()' + */ +format + : STRING_LITERAL ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-simple-case-expressions -simpleCaseExpression - : CASE expressionOrPredicate caseWhenExpressionClause+ (ELSE expressionOrPredicate)? END +/** + * The 'extract()' function for extracting fields of dates, times, and datetimes + */ +extractFunction + : EXTRACT '(' extractField FROM expression ')' + | datetimeField '(' expression ')' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-searched-case-expressions -searchedCaseExpression - : CASE caseWhenPredicateClause+ (ELSE expressionOrPredicate)? END +/** + * The 'trunc()' function for truncating both numeric and datetime values + */ +truncFunction + : (TRUNC | TRUNCATE) '(' expression (',' (datetimeField | expression))? ')' ; -caseWhenExpressionClause - : WHEN expression THEN expressionOrPredicate +/** + * A syntax for calling user-defined or native database functions, required by JPQL + */ +jpaNonstandardFunction + : FUNCTION '(' jpaNonstandardFunctionName (AS castTarget)? (',' genericFunctionArguments)? ')' ; -caseWhenPredicateClause - : WHEN predicate THEN expressionOrPredicate +/** + * The name of a user-defined or native database function, given as a quoted string + */ +jpaNonstandardFunctionName + : STRING_LITERAL + | identifier ; -// Functions -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exp-functions -function - : functionName '(' (functionArguments | ASTERISK)? ')' pathContinutation? filterClause? withinGroup? overClause? # GenericFunction - | functionName '(' subquery ')' # FunctionWithSubquery - | castFunction # CastFunctionInvocation - | extractFunction # ExtractFunctionInvocation - | trimFunction # TrimFunctionInvocation - | everyFunction # EveryFunctionInvocation - | anyFunction # AnyFunctionInvocation - | treatedPath # TreatedPathInvocation +columnFunction + : COLUMN '(' path '.' jpaNonstandardFunctionName (AS castTarget)? ')' ; -functionArguments - : DISTINCT? expressionOrPredicate (',' expressionOrPredicate)* +/** + * Any function invocation that follows the regular syntax + * + * The function name, followed by a parenthesized list of ','-separated expressions + */ +genericFunction + : genericFunctionName '(' (genericFunctionArguments | ASTERISK)? ')' pathContinuation? + nthSideClause? nullsClause? withinGroupClause? filterClause? overClause? ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-filter -filterClause - : FILTER '(' whereClause ')' +/** + * The name of a generic function, which may contain periods and quoted identifiers + * + * Names of generic functions are resolved against the SqmFunctionRegistry + */ +genericFunctionName + : simplePath ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset -withinGroup - : WITHIN GROUP '(' orderByClause ')' +/** + * The arguments of a generic function + */ +genericFunctionArguments + : (DISTINCT | datetimeField ',')? expressionOrPredicate (',' expressionOrPredicate)* ; -overClause - : OVER '(' partitionClause? orderByClause? frameClause? ')' +/** + * The special 'size()' function defined by JPQL + */ +collectionSizeFunction + : SIZE '(' path ')' ; -partitionClause - : PARTITION BY expression (',' expression)* +/** + * Special rule for 'max(elements())`, 'avg(keys())', 'sum(indices())`, etc., as defined by HQL + * Also the deprecated 'maxindex()', 'maxelement()', 'minindex()', 'minelement()' functions from old HQL + */ +collectionAggregateFunction + : (MAX|MIN|SUM|AVG) '(' elementsValuesQuantifier '(' path ')' ')' # ElementAggregateFunction + | (MAX|MIN|SUM|AVG) '(' indicesKeysQuantifier '(' path ')' ')' # IndexAggregateFunction + | (MAXELEMENT|MINELEMENT) '(' path ')' # ElementAggregateFunction + | (MAXINDEX|MININDEX) '(' path ')' # IndexAggregateFunction ; -frameClause - : (RANGE|ROWS|GROUPS) frameStart frameExclusion? - | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? +/** + * To accommodate the misuse of elements() and indices() in the select clause + * + * (At some stage in the history of HQL, someone mixed them up with value() and index(), + * and so we have tests that insist they're interchangeable. Ugh.) + */ +collectionFunctionMisuse + : elementsValuesQuantifier '(' path ')' + | indicesKeysQuantifier '(' path ')' ; -frameStart - : UNBOUNDED PRECEDING # UnboundedPrecedingFrameStart - | expression PRECEDING # ExpressionPrecedingFrameStart - | CURRENT ROW # CurrentRowFrameStart - | expression FOLLOWING # ExpressionFollowingFrameStart +/** + * The special 'every()', 'all()', 'any()' and 'some()' functions defined by HQL + * + * May be applied to a subquery or collection reference, or may occur as an aggregate function in the 'select' clause + */ +aggregateFunction + : everyFunction + | anyFunction + | listaggFunction ; -frameExclusion - : EXCLUDE CURRENT ROW # CurrentRowFrameExclusion - | EXCLUDE GROUP # GroupFrameExclusion - | EXCLUDE TIES # TiesFrameExclusion - | EXCLUDE NO OTHERS # NoOthersFrameExclusion +/** + * The functions 'every()' and 'all()' are synonyms + */ +everyFunction + : everyAllQuantifier '(' predicate ')' filterClause? overClause? + | everyAllQuantifier '(' subquery ')' + | everyAllQuantifier collectionQuantifier '(' simplePath ')' ; -frameEnd - : expression PRECEDING # ExpressionPrecedingFrameEnd - | CURRENT ROW # CurrentRowFrameEnd - | expression FOLLOWING # ExpressionFollowingFrameEnd - | UNBOUNDED FOLLOWING # UnboundedFollowingFrameEnd +/** + * The functions 'any()' and 'some()' are synonyms + */ +anyFunction + : anySomeQuantifier '(' predicate ')' filterClause? overClause? + | anySomeQuantifier '(' subquery ')' + | anySomeQuantifier collectionQuantifier '(' simplePath ')' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions -castFunction - : CAST '(' expression AS castTarget ')' +everyAllQuantifier + : EVERY + | ALL ; -castTarget - : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? +anySomeQuantifier + : ANY + | SOME ; -castTargetType - returns [String fullTargetName] - : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* +/** + * The 'listagg()' ordered set-aggregate function + */ +listaggFunction + : LISTAGG '(' DISTINCT? expressionOrPredicate ',' expressionOrPredicate onOverflowClause? ')' + withinGroupClause? filterClause? overClause? ; -extractFunction - : EXTRACT '(' expression FROM expression ')' - | dateTimeFunction '(' expression ')' +/** + * A 'on overflow' clause: what to do when the text data type used for 'listagg' overflows + */ +onOverflowClause + : ON OVERFLOW (ERROR | TRUNCATE expression? (WITH|WITHOUT) COUNT) ; -trimFunction - : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')' +/** + * A 'within group' clause: defines the order in which the ordered set-aggregate function should work + */ +withinGroupClause + : WITHIN GROUP '(' orderByClause ')' ; -dateTimeFunction - : d=(YEAR - | MONTH - | DAY - | WEEK - | QUARTER - | HOUR - | MINUTE - | SECOND - | NANOSECOND - | EPOCH) +/** + * A 'filter' clause: a restriction applied to an aggregate function + */ +filterClause + : FILTER '(' whereClause ')' ; -everyFunction - : every=(EVERY | ALL) '(' predicate ')' - | every=(EVERY | ALL) '(' subquery ')' - | every=(EVERY | ALL) (ELEMENTS | INDICES) '(' simplePath ')' +/** + * A `nulls` clause: what should a value access window function do when encountering a `null` + */ +nullsClause + : RESPECT NULLS + | IGNORE NULLS ; -anyFunction - : any=(ANY | SOME) '(' predicate ')' - | any=(ANY | SOME) '(' subquery ')' - | any=(ANY | SOME) (ELEMENTS | INDICES) '(' simplePath ')' +/** + * A `nulls` clause: what should a value access window function do when encountering a `null` + */ +nthSideClause + : FROM FIRST + | FROM LAST ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-treat-type -treatedPath - : TREAT '(' path AS simplePath')' pathContinutation? +/** + * A 'over' clause: the specification of a window within which the function should act + */ +overClause + : OVER '(' partitionClause? orderByClause? frameClause? ')' ; -pathContinutation - : '.' simplePath +/** + * A 'partition' clause: the specification the group within which a function should act in a window + */ +partitionClause + : PARTITION BY expression (',' expression)* + ; + +/** + * A 'frame' clause: the specification the content of the window + */ +frameClause + : (RANGE|ROWS|GROUPS) frameStart frameExclusion? + | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? + ; + +/** + * The start of the window content + */ +frameStart + : CURRENT ROW + | UNBOUNDED PRECEDING + | expression PRECEDING + | expression FOLLOWING + ; + +/** + * The end of the window content + */ +frameEnd + : CURRENT ROW + | UNBOUNDED FOLLOWING + | expression PRECEDING + | expression FOLLOWING + ; + +/** + * A 'exclusion' clause: the specification what to exclude from the window content + */ +frameExclusion + : EXCLUDE CURRENT ROW + | EXCLUDE GROUP + | EXCLUDE TIES + | EXCLUDE NO OTHERS ; // Predicates @@ -591,6 +1262,31 @@ expressionOrPredicate | predicate ; +collectionQuantifier + : elementsValuesQuantifier + | indicesKeysQuantifier + ; + +elementsValuesQuantifier + : ELEMENTS + | VALUES + ; + +elementValueQuantifier + : ELEMENT + | VALUE + ; + +indexKeyQuantifier + : INDEX + | KEY + ; + +indicesKeysQuantifier + : INDICES + | KEYS + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-relational-comparisons // NOTE: The TIP shows that "!=" is also supported. Hibernate's source code shows that "^=" is another NOT_EQUALS option as well. relationalExpression @@ -604,7 +1300,7 @@ betweenExpression // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-like-predicate stringPatternMatching - : expression NOT? (LIKE | ILIKE) expression (ESCAPE (stringLiteral|parameter))? + : expression NOT? (LIKE | ILIKE) expression (ESCAPE (STRING_LITERAL | JAVA_STRING_LITERAL |parameter))? ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-elements-indices @@ -656,9 +1352,12 @@ parameterOrNumberLiteral | numericLiteral ; +/** + * An identification variable (an entity alias) + */ variable : AS identifier - | reservedWord + | nakedIdentifier ; parameter @@ -670,20 +1369,9 @@ entityName : identifier ('.' identifier)* ; -identifier - : reservedWord - ; - -character - : CHARACTER - ; - -functionName - : reservedWord ('.' reservedWord)* - ; - -reservedWord - : IDENTIFICATION_VARIABLE +nakedIdentifier + : IDENTIFIER + | QUOTED_IDENTIFIER | f=(ALL | AND | ANY @@ -697,6 +1385,9 @@ reservedWord | CASE | CAST | COLLATE + | COLUMN + | CONFLICT + | CONSTRAINT | CONTAINS | COUNT | CROSS @@ -715,6 +1406,7 @@ reservedWord | DEPTH | DESC | DISTINCT + | DO | ELEMENT | ELEMENTS | ELSE @@ -728,18 +1420,15 @@ reservedWord | EXCEPT | EXCLUDE | EXISTS - | EXP | EXTRACT - | FALSE | FETCH | FILTER | FIRST - | FLOOR + | FK | FOLLOWING | FOR | FORMAT | FROM - | FULL | FUNCTION | GROUP | GROUPS @@ -749,10 +1438,9 @@ reservedWord | IGNORE | ILIKE | IN - | INCLUDES | INDEX + | INCLUDES | INDICES - | INNER | INSERT | INSTANT | INTERSECT @@ -761,10 +1449,10 @@ reservedWord | IS | JOIN | KEY + | KEYS | LAST | LATERAL | LEADING - | LEFT | LIKE | LIMIT | LIST @@ -792,6 +1480,7 @@ reservedWord | NEXT | NO | NOT + | NOTHING | NULLS | OBJECT | OF @@ -802,7 +1491,6 @@ reservedWord | OR | ORDER | OTHERS - | OUTER | OVER | OVERFLOW | OVERLAY @@ -811,7 +1499,6 @@ reservedWord | PERCENT | PLACING | POSITION - | POWER | PRECEDING | QUARTER | RANGE @@ -828,7 +1515,6 @@ reservedWord | SOME | SUBSTRING | SUM - | TRUE | THEN | TIES | TIME @@ -856,7 +1542,17 @@ reservedWord | WITH | WITHIN | WITHOUT - | YEAR) + | YEAR + | ZONED) + ; + +identifier + : nakedIdentifier + | FULL + | INNER + | LEFT + | OUTER + | RIGHT ; /* @@ -864,7 +1560,8 @@ reservedWord */ -WS : [ \t\r\n] -> skip ; +WS : [ \t\r\n] -> channel(HIDDEN); +COMMENT : '/*' (~'*' | '*' ~'/' )* '*/' -> skip; // Build up case-insentive tokens @@ -895,14 +1592,20 @@ fragment X: 'x' | 'X'; fragment Y: 'y' | 'Y'; fragment Z: 'z' | 'Z'; +ASTERISK : '*'; + // The following are reserved identifiers: +ID : I D; +VERSION : V E R S I O N; +VERSIONED : V E R S I O N E D; +NATURALID : N A T U R A L I D; +FK : F K; ALL : A L L; AND : A N D; ANY : A N Y; AS : A S; ASC : A S C; -ASTERISK : '*'; AVG : A V G; BETWEEN : B E T W E E N; BOTH : B O T H; @@ -910,8 +1613,10 @@ BREADTH : B R E A D T H; BY : B Y; CASE : C A S E; CAST : C A S T; -CEILING : C E I L I N G; COLLATE : C O L L A T E; +COLUMN : C O L U M N; +CONFLICT : C O N F L I C T; +CONSTRAINT : C O N S T R A I N T; CONTAINS : C O N T A I N S; COUNT : C O U N T; CROSS : C R O S S; @@ -930,6 +1635,7 @@ DELETE : D E L E T E; DEPTH : D E P T H; DESC : D E S C; DISTINCT : D I S T I N C T; +DO : D O; ELEMENT : E L E M E N T; ELEMENTS : E L E M E N T S; ELSE : E L S E; @@ -943,14 +1649,10 @@ EVERY : E V E R Y; EXCEPT : E X C E P T; EXCLUDE : E X C L U D E; EXISTS : E X I S T S; -EXP : E X P; EXTRACT : E X T R A C T; -FALSE : F A L S E; FETCH : F E T C H; FILTER : F I L T E R; FIRST : F I R S T; -FK : F K; -FLOOR : F L O O R; FOLLOWING : F O L L O W I N G; FOR : F O R; FORMAT : F O R M A T; @@ -961,7 +1663,6 @@ GROUP : G R O U P; GROUPS : G R O U P S; HAVING : H A V I N G; HOUR : H O U R; -ID : I D; IGNORE : I G N O R E; ILIKE : I L I K E; IN : I N; @@ -977,6 +1678,7 @@ INTO : I N T O; IS : I S; JOIN : J O I N; KEY : K E Y; +KEYS : K E Y S; LAST : L A S T; LATERAL : L A T E R A L; LEADING : L E A D I N G; @@ -985,7 +1687,6 @@ LIKE : L I K E; LIMIT : L I M I T; LIST : L I S T; LISTAGG : L I S T A G G; -LN : L N; LOCAL : L O C A L; LOCAL_DATE : L O C A L '_' D A T E ; LOCAL_DATETIME : L O C A L '_' D A T E T I M E; @@ -1004,12 +1705,11 @@ MININDEX : M I N I N D E X; MINUTE : M I N U T E; MONTH : M O N T H; NANOSECOND : N A N O S E C O N D; -NATURALID : N A T U R A L I D; NEW : N E W; NEXT : N E X T; NO : N O; NOT : N O T; -NULL : N U L L; +NOTHING : N O T H I N G; NULLS : N U L L S; OBJECT : O B J E C T; OF : O F; @@ -1029,7 +1729,6 @@ PARTITION : P A R T I T I O N; PERCENT : P E R C E N T; PLACING : P L A C I N G; POSITION : P O S I T I O N; -POWER : P O W E R; PRECEDING : P R E C E D I N G; QUARTER : Q U A R T E R; RANGE : R A N G E; @@ -1056,7 +1755,6 @@ TO : T O; TRAILING : T R A I L I N G; TREAT : T R E A T; TRIM : T R I M; -TRUE : T R U E; TRUNC : T R U N C; TRUNCATE : T R U N C A T E; TYPE : T Y P E; @@ -1066,8 +1764,6 @@ UPDATE : U P D A T E; USING : U S I N G; VALUE : V A L U E; VALUES : V A L U E S; -VERSION : V E R S I O N; -VERSIONED : V E R S I O N E D; WEEK : W E E K; WHEN : W H E N; WHERE : W H E R E; @@ -1075,21 +1771,112 @@ WITH : W I T H; WITHIN : W I T H I N; WITHOUT : W I T H O U T; YEAR : Y E A R; +ZONED : Z O N E D; + +NULL : N U L L; +TRUE : T R U E; +FALSE : F A L S E; + +fragment +INTEGER_NUMBER + : DIGIT+ + ; + +fragment +FLOATING_POINT_NUMBER + : DIGIT+ '.' DIGIT* EXPONENT? + | '.' DIGIT+ EXPONENT? + | DIGIT+ EXPONENT + | DIGIT+ + ; + +fragment +EXPONENT : [eE] [+-]? DIGIT+; -fragment INTEGER_NUMBER : ('0' .. '9')+ ; -fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ; fragment HEX_DIGIT : [0-9a-fA-F]; +fragment SINGLE_QUOTE : '\''; +fragment DOUBLE_QUOTE : '"'; + +STRING_LITERAL : SINGLE_QUOTE ( SINGLE_QUOTE SINGLE_QUOTE | ~('\'') )* SINGLE_QUOTE; + +JAVA_STRING_LITERAL + : DOUBLE_QUOTE ( ESCAPE_SEQUENCE | ~('"') )* DOUBLE_QUOTE + | [jJ] SINGLE_QUOTE ( ESCAPE_SEQUENCE | ~('\'') )* SINGLE_QUOTE + | [jJ] DOUBLE_QUOTE ( ESCAPE_SEQUENCE | ~('\'') )* DOUBLE_QUOTE + ; + +INTEGER_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)*; + +LONG_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)* LONG_SUFFIX; + +FLOAT_LITERAL : FLOATING_POINT_NUMBER FLOAT_SUFFIX; + +DOUBLE_LITERAL : FLOATING_POINT_NUMBER DOUBLE_SUFFIX?; + +BIG_INTEGER_LITERAL : INTEGER_NUMBER BIG_INTEGER_SUFFIX; + +BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER BIG_DECIMAL_SUFFIX; + +HEX_LITERAL : '0' [xX] HEX_DIGIT+ LONG_SUFFIX?; -CHARACTER : '\'' (~ ('\'' | '\\' )) '\'' ; -STRINGLITERAL : '\'' ('\'' '\'' | ~('\''))* '\'' ; -JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; -INTEGER_LITERAL : INTEGER_NUMBER (L | B I)? ; -FLOAT_LITERAL : FLOAT_NUMBER (D | F | B D)?; -HEXLITERAL : '0' X HEX_DIGIT+ ; BINARY_LITERAL : [xX] '\'' HEX_DIGIT+ '\'' | [xX] '"' HEX_DIGIT+ '"' ; -IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; +// ESCAPE start tokens +TIMESTAMP_ESCAPE_START : '{ts'; +DATE_ESCAPE_START : '{d'; +TIME_ESCAPE_START : '{t'; + +PLUS : '+'; +MINUS : '-'; + + +fragment +LETTER : [a-zA-Z\u0080-\ufffe_$]; + +fragment +DIGIT : [0-9]; + +fragment +LONG_SUFFIX : [lL]; + +fragment +FLOAT_SUFFIX : [fF]; + +fragment +DOUBLE_SUFFIX : [dD]; + +fragment +BIG_DECIMAL_SUFFIX : [bB] [dD]; + +fragment +BIG_INTEGER_SUFFIX : [bB] [iI]; + +// Identifiers +IDENTIFIER + : LETTER (LETTER | DIGIT)* + ; + +fragment +BACKTICK : '`'; + +fragment BACKSLASH : '\\'; + +fragment +UNICODE_ESCAPE + : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +fragment +ESCAPE_SEQUENCE + : BACKSLASH [btnfr"'] + | BACKSLASH UNICODE_ESCAPE + | BACKSLASH BACKSLASH + ; + +QUOTED_IDENTIFIER + : BACKTICK ( ESCAPE_SEQUENCE | '\\' BACKTICK | ~([`]) )* BACKTICK + ; 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..96bfeb90e3 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 @@ -208,8 +208,8 @@ constructor_item ; aggregate_expression - : (AVG | MAX | MIN | SUM) '(' (DISTINCT)? state_valued_path_expression ')' - | COUNT '(' (DISTINCT)? (identification_variable | state_valued_path_expression | single_valued_object_path_expression) ')' + : (AVG | MAX | MIN | SUM) '(' (DISTINCT)? simple_select_expression ')' + | COUNT '(' (DISTINCT)? simple_select_expression ')' | function_invocation ; @@ -337,12 +337,17 @@ between_expression ; in_expression - : (state_valued_path_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter) + : (string_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter) ; in_item : literal + | string_expression + | boolean_literal + | numeric_literal + | date_time_timestamp_literal | single_valued_input_parameter + | conditional_expression ; like_expression @@ -382,13 +387,15 @@ all_or_any_expression ; comparison_expression - : string_expression comparison_operator (string_expression | all_or_any_expression) - | boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression) - | enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression) - | datetime_expression comparison_operator (datetime_expression | all_or_any_expression) - | entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression) - | arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression) - | entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression + : string_expression comparison_operator (string_expression | all_or_any_expression) #StringComparison + | boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression) #BooleanComparison + | boolean_expression #DirectBooleanCheck + | enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression) #EnumComparison + | datetime_expression comparison_operator (datetime_expression | all_or_any_expression) #DatetimeComparison + | entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression) #EntityComparison + | arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression) #ArithmeticComparison + | entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression #EntityTypeComparison + | string_expression REGEXP string_literal #RegexpComparison ; comparison_operator @@ -422,6 +429,8 @@ arithmetic_primary | functions_returning_numerics | aggregate_expression | case_expression + | arithmetic_cast_function + | type_cast_function | function_invocation | '(' subquery ')' ; @@ -434,6 +443,8 @@ string_expression | aggregate_expression | case_expression | function_invocation + | string_cast_function + | type_cast_function | '(' subquery ')' ; @@ -527,6 +538,17 @@ trim_specification | BOTH ; +arithmetic_cast_function + : CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')' + ; + +type_cast_function + : CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + ; + +string_cast_function + : CAST '(' scalar_expression (AS)? STRING ')' + ; function_invocation : FUNCTION '(' function_name (',' function_arg)* ')' @@ -549,10 +571,7 @@ datetime_part ; function_arg - : literal - | state_valued_path_expression - | input_parameter - | scalar_expression + : simple_select_expression ; case_expression @@ -652,7 +671,8 @@ entity_type_literal escape_character : CHARACTER - | character_valued_input_parameter // + | string_literal + | character_valued_input_parameter ; numeric_literal @@ -685,18 +705,22 @@ subtype collection_valued_field : identification_variable + | reserved_word ; single_valued_object_field : identification_variable + | reserved_word ; state_field : identification_variable + | reserved_word ; collection_value_field : identification_variable + | reserved_word ; entity_name @@ -741,6 +765,7 @@ reserved_word |BOTH |BY |CASE + |CAST |CEILING |COALESCE |CONCAT @@ -817,7 +842,8 @@ reserved_word */ -WS : [ \t\r\n] -> skip ; +WS : [ \t\r\n] -> channel(HIDDEN) ; +COMMENT : '/*' (~'*' | '*' ~'/' )* '*/' -> skip; // Build up case-insentive tokens @@ -861,6 +887,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; @@ -873,6 +900,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; @@ -885,6 +913,7 @@ FALSE : F A L S E; FETCH : F E T C H; FIRST : F I R S T; FLOOR : F L O O R; +FLOAT : F L O A T; FROM : F R O M; FUNCTION : F U N C T I O N; GROUP : G R O U P; @@ -892,7 +921,9 @@ 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; +INTEGER : I N T E G E R; JOIN : J O I N; KEY : K E Y; LAST : L A S T; @@ -903,6 +934,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; @@ -920,6 +952,7 @@ 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; ROUND : R O U N D; SELECT : S E L E C T; SET : S E T; @@ -928,6 +961,7 @@ SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; SUBSTRING : S U B S T R I N G; +STRING : S T R I N G; SUM : S U M; THEN : T H E N; TIME : T I M E; @@ -947,7 +981,7 @@ 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')+ ; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java index e24aeded0e..6b2314a2d0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -35,6 +36,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.ExampleMatcher.MatchMode; import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.support.ExampleMatcherAccessor; @@ -57,6 +59,7 @@ * @author Oliver Gierke * @author Jens Schauder * @author Greg Turnquist + * @author Arnaud Lecointre * @since 1.10 */ public class QueryByExamplePredicateBuilder { @@ -103,8 +106,8 @@ public static Predicate getPredicate(Root root, CriteriaBuilder cb, Examp ExampleMatcher matcher = example.getMatcher(); List predicates = getPredicates("", cb, root, root.getModel(), example.getProbe(), - example.getProbeType(), new ExampleMatcherAccessor(matcher), new PathNode("root", null, example.getProbe()), - escapeCharacter); + example.getProbeType(), matcher.getMatchMode(), new ExampleMatcherAccessor(matcher), + new PathNode("root", null, example.getProbe()), escapeCharacter); if (predicates.isEmpty()) { return null; @@ -121,7 +124,7 @@ public static Predicate getPredicate(Root root, CriteriaBuilder cb, Examp @SuppressWarnings({ "rawtypes", "unchecked" }) static List getPredicates(String path, CriteriaBuilder cb, Path from, ManagedType type, Object value, - Class probeType, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, + Class probeType, MatchMode matchMode, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, EscapeCharacter escapeCharacter) { List predicates = new ArrayList<>(); @@ -158,7 +161,7 @@ static List getPredicates(String path, CriteriaBuilder cb, Path fr predicates .addAll(getPredicates(currentPath, cb, from.get(attribute.getName()), (ManagedType) attribute.getType(), - attributeValue, probeType, exampleAccessor, currentNode, escapeCharacter)); + attributeValue, probeType, matchMode, exampleAccessor, currentNode, escapeCharacter)); continue; } @@ -171,8 +174,10 @@ static List getPredicates(String path, CriteriaBuilder cb, Path fr ClassUtils.getShortName(probeType), node)); } - predicates.addAll(getPredicates(currentPath, cb, ((From) from).join(attribute.getName()), - (ManagedType) attribute.getType(), attributeValue, probeType, exampleAccessor, node, escapeCharacter)); + JoinType joinType = matchMode.equals(MatchMode.ALL) ? JoinType.INNER : JoinType.LEFT; + predicates.addAll(getPredicates(currentPath, cb, ((From) from).join(attribute.getName(), joinType), + (ManagedType) attribute.getType(), attributeValue, probeType, matchMode, exampleAccessor, node, + escapeCharacter)); continue; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java index 8f19eb580f..87aeb9353e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..c2653a2e89 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java index 63fa8307e6..989eac2cff 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ * @author Thomas Darimont * @author Mark Paluch * @author Greg Turnquist + * @author Ngoc Nhan * @param the type of the identifier. */ @MappedSuperclass @@ -89,7 +90,7 @@ public boolean equals(Object obj) { AbstractPersistable that = (AbstractPersistable) obj; - return null == this.getId() ? false : this.getId().equals(that.getId()); + return this.getId() != null && this.getId().equals(that.getId()); } @Override 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..771b5361a6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ * @author Christoph Strobl * @author David Madden * @author Jens Schauder + * @author Ngoc Nhan */ public class JpaSort extends Sort { @@ -281,14 +282,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/domain/Specification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java index 32c84faae9..9b5476d213 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,13 @@ /** * Specification in the sense of Domain Driven Design. + *

+ * Specifications can be composed into higher order functions from other specifications using + * {@link #and(Specification)}, {@link #or(Specification)} or factory methods such as {@link #allOf(Iterable)}. + *

+ * Composition considers whether one or more specifications contribute to the overall predicate by returning a + * {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@code where(null)}, are + * considered to not contribute to the overall predicate, and their result is not considered in the final predicate. * * @author Oliver Gierke * @author Thomas Darimont @@ -38,7 +45,9 @@ * @author Jens Schauder * @author Daniel Shuy * @author Sergey Rukin + * @author Peter Aisher */ +@FunctionalInterface public interface Specification extends Serializable { @Serial long serialVersionUID = 1L; @@ -55,7 +64,11 @@ static Specification not(@Nullable Specification spec) { return spec == null // ? (root, query, builder) -> null // - : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder)); + : (root, query, builder) -> { + + Predicate predicate = spec.toPredicate(root, query, builder); + return predicate != null ? builder.not(predicate) : null; + }; } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java index 9b0654bd76..a246ccbade 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,8 @@ * @author Oliver Gierke * @author Jens Schauder * @author Mark Paluch - * @see Specification * @since 2.2 + * @see Specification */ class SpecificationComposition { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessor.java index 4d77cb4070..1b669e0854 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java index 84de19a961..9dc73af957 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java index ee663c687a..bc5a71c25c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java index daf8c5ab74..90ca19aa71 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java index fa8eebd38d..761a1600d0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java index 111e4cc0b6..63a9cf3515 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..da773247e1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java index eea31872ff..9b602fc65b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java index f99e6fdb0e..862cb5a1fb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java index c0e3324659..f00f4b849d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index 84ca80baeb..2b5e0abbeb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java index 3d1dcdb86d..d999d7490b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java index 8fdabf0959..aa39144da4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java index 2d84633bf1..6bd6f4bace 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java index 0a40faa247..a6fd6367c4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaContext.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaContext.java index 3b889e30ac..13eff2d386 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaContext.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java index 6be4294d94..a3541460f9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java index 0d38cc580d..4d18351a99 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.function.Function; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -122,11 +123,16 @@ public interface JpaSpecificationExecutor { /** * Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query * and its result type. + *

+ * The query object used with {@code queryFunction} is only valid inside the {@code findBy(…)} method call. This + * requires the query function to return a query result and not the {@link FluentQuery} object itself to ensure the + * query is executed inside the {@code findBy(…)} method. * * @param spec must not be null. * @param queryFunction the query function defining projection, sorting, and the result type - * @return all entities matching the given Example. + * @return all entities matching the given specification. * @since 3.0 + * @throws InvalidDataAccessApiUsageException if the query function returns the {@link FluentQuery} instance. */ R findBy(Specification spec, Function, R> queryFunction); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Lock.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Lock.java index 219756b6ef..a8d1fc7775 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Lock.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Lock.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Meta.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Meta.java index 55225de082..2ba8a0a9ea 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Meta.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Meta.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Modifying.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Modifying.java index 48ef4ce55b..612bb4092a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Modifying.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Modifying.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/NativeQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/NativeQuery.java index 47ed58c61a..d10c90b68c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/NativeQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/NativeQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java index e43157b40e..12ff41bb71 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryHints.java index 86b482db80..996b8a2933 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java index d55d9a31a6..619d6231a9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,10 @@ * and tools intends to do has been done. You can customize the query to apply final changes. Rewriting can only make * use of already existing contextual data. That is, adding or replacing query text or reuse of bound parameters. Query * rewriting must not add additional bindable parameters as these cannot be materialized. + *

+ * Query rewriting applies to the actual query and, when applicable, to count queries. Count queries are optimized and + * therefore, either not necessary or a count is obtained through other means, such as derived from a Hibernate + * {@code SelectionQuery}. * * @author Greg Turnquist * @author Mark Paluch @@ -71,4 +75,5 @@ public String rewrite(String query, Sort sort) { return query; } } + } 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..e7492ab305 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java index a2dcece1f2..80b67fd896 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,5 +93,23 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) hints.reflection().registerType(NamedEntityGraph.class, hint -> hint.onReachableType(EntityGraph.class).withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + + if (ClassUtils.isPresent("org.hibernate.Hibernate", classLoader)) { + + /* + Fetching a single results causes: + java.lang.IllegalArgumentException: Class org.hibernate.query.sqm.tree.select.SqmQueryPart[] is instantiated reflectively but was never registered.Register the class by adding "unsafeAllocated" for the class in reflect-config.json. + at org.graalvm.nativeimage.builder/com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.arrayHubErrorStub(SubstrateAllocationSnippets.java:345) + at org.hibernate.internal.util.collections.StandardStack.push(StandardStack.java:48) + at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:2073) + + both formats: + - org.hibernate.query.sqm.tree.select.SqmQueryPart[] + - [Lorg.hibernate.query.sqm.tree.select.SqmQueryPart; + seem to be supported via reflect-config. However TypeReference does not support [L... + */ + hints.reflection().registerType(TypeReference.of("org.hibernate.query.sqm.tree.select.SqmQueryPart[]"), + MemberCategory.UNSAFE_ALLOCATED); + } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/BeanManagerQueryRewriterProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/BeanManagerQueryRewriterProvider.java index e1ef5f475c..4aa304ec04 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/BeanManagerQueryRewriterProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/BeanManagerQueryRewriterProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java index e0bcb27c97..8cb39d7d49 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtension.java index 8b8088c0c9..a10d005a1b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java index b7a90e128e..8625119632 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/BeanDefinitionNames.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/BeanDefinitionNames.java index 2304e4b239..55cd78b091 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/BeanDefinitionNames.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/BeanDefinitionNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaAuditing.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaAuditing.java index 48304dbac1..6a6577fbbc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaAuditing.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaAuditing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java index 87bb9d5551..15467a84b5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,8 +56,20 @@ String[] value() default {}; /** - * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this - * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + * Base packages to scan for annotated components. + *

+ * {@link #value} is an alias for (and mutually exclusive with) this attribute. + *

+ * Supports {@code ${…}} placeholders which are resolved against the {@link org.springframework.core.env.Environment + * Environment} as well as Ant-style package patterns — for example, {@code "org.example.**"}. + *

+ * Multiple packages or patterns may be specified, either separately or within a single {@code String} — for + * example, {@code {"org.example.config", "org.example.service.**"}} or + * {@code "org.example.config, org.example.service.**"}. + *

+ * Use {@link #basePackageClasses} for a type-safe alternative to String-based package names. + * + * @see org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS */ String[] basePackages() default {}; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/InspectionClassLoader.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/InspectionClassLoader.java index 7611ba3986..a95eb1db86 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/InspectionClassLoader.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/InspectionClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java index bafc70f0aa..88e4dbbd0c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java index b0a7851d42..2bd8cd5ec8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrar.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrar.java index 9e8d640da9..6c6e803576 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrar.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index 7010542f45..1bcf8073a8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryNameSpaceHandler.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryNameSpaceHandler.java index a0de895722..951e4132cd 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryNameSpaceHandler.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryNameSpaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 5742a1ea4e..2696ef4cc5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -256,7 +256,7 @@ private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) JpaEntityGraph entityGraph = method.getEntityGraph(); if (entityGraph != null) { - QueryHints hints = Jpa21Utils.getFetchGraphHint(em, method.getEntityGraph(), + QueryHints hints = Jpa21Utils.getFetchGraphHint(em, entityGraph, getQueryMethod().getEntityInformation().getJavaType()); hints.forEach(query::setHint); 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 0d257fe5a2..256d8012e3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,15 +80,12 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri this.valueExpressionDelegate = valueExpressionDelegate; this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters()); - this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate, - method.isNativeQuery()); + this.query = new ExpressionBasedStringQuery(queryString, method, valueExpressionDelegate); this.countQuery = Lazy.of(() -> { if (StringUtils.hasText(countQueryString)) { - - return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate, - method.isNativeQuery()); + return new ExpressionBasedStringQuery(countQueryString, method, valueExpressionDelegate); } return query.deriveCountQuery(method.getCountQueryProjection()); @@ -148,9 +145,11 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) { String queryString = countQuery.get().getQueryString(); EntityManager em = getEntityManager(); + String queryStringToUse = potentiallyRewriteQuery(queryString, accessor.getSort(), accessor.getPageable()); + Query query = getQueryMethod().isNativeQuery() // - ? em.createNativeQuery(queryString) // - : em.createQuery(queryString, Long.class); + ? em.createNativeQuery(queryStringToUse) // + : em.createQuery(queryStringToUse, Long.class); QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryString, query); @@ -181,16 +180,17 @@ protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable ReturnedType returnedType) { EntityManager em = getEntityManager(); + String queryToUse = potentiallyRewriteQuery(queryString, sort, pageable); if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) { - return em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable)); + return em.createQuery(queryToUse); } Class typeToRead = getTypeToRead(returnedType); return typeToRead == null // - ? em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable)) // - : em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable), typeToRead); + ? em.createQuery(queryToUse) // + : em.createQuery(queryToUse, typeToRead); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarErrorListener.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarErrorListener.java index b629bd6179..dd3eab6412 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarErrorListener.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarErrorListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,17 @@ */ package org.springframework.data.jpa.repository.query; +import java.util.List; + import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CommonToken; +import org.antlr.v4.runtime.InputMismatchException; +import org.antlr.v4.runtime.NoViableAltException; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; +import org.springframework.util.ObjectUtils; + /** * A {@link BaseErrorListener} that will throw a {@link BadJpqlGrammarException} if the query is invalid. * @@ -29,14 +36,62 @@ class BadJpqlGrammarErrorListener extends BaseErrorListener { private final String query; + private final String grammar; + BadJpqlGrammarErrorListener(String query) { + this(query, "JPQL"); + } + + BadJpqlGrammarErrorListener(String query, String grammar) { this.query = query; + this.grammar = grammar; } @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { - throw new BadJpqlGrammarException("Line " + line + ":" + charPositionInLine + " " + msg, query, null); + throw new BadJpqlGrammarException(formatMessage(offendingSymbol, line, charPositionInLine, msg, e, query), grammar, + query, null); + } + + /** + * Rewrite the error message. + */ + private static String formatMessage(Object offendingSymbol, int line, int charPositionInLine, String message, + RecognitionException e, String query) { + + String errorText = "At " + line + ":" + charPositionInLine; + + if (offendingSymbol instanceof CommonToken ct) { + + String token = ct.getText(); + if (!ObjectUtils.isEmpty(token)) { + errorText += " and token '" + token + "'"; + } + } + errorText += ", "; + + if (e instanceof NoViableAltException) { + + errorText += message.substring(0, message.indexOf('\'')); + if (query.isEmpty()) { + errorText += "'*' (empty query string)"; + } else { + + List list = query.lines().toList(); + String lineText = list.get(line - 1); + String text = lineText.substring(0, charPositionInLine) + "*" + lineText.substring(charPositionInLine); + errorText += "'" + text + "'"; + } + + } else if (e instanceof InputMismatchException) { + errorText += message.substring(0, message.length() - 1).replace(" expecting {", + ", expecting one of the following tokens: "); + } else { + errorText += message; + } + + return errorText; } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java index 56dae97430..ab3b51b7b3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,11 @@ public class BadJpqlGrammarException extends InvalidDataAccessResourceUsageExcep private final String jpql; public BadJpqlGrammarException(String message, String jpql, @Nullable Throwable cause) { - super(message + "; Bad JPQL grammar [" + jpql + "]", cause); + this(message, jpql, "JPQL", cause); + } + + BadJpqlGrammarException(String message, String grammar, String jpql, @Nullable Throwable cause) { + super(message + "; Bad " + grammar + " grammar [" + jpql + "]", cause); this.jpql = jpql; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BeanFactoryQueryRewriterProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BeanFactoryQueryRewriterProvider.java index 552cca806c..ee50c94696 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BeanFactoryQueryRewriterProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BeanFactoryQueryRewriterProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/CollectionUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/CollectionUtils.java index 632125c566..6352836f98 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/CollectionUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java index 4e54424404..70bc5c829b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java index 78105e7eb0..daa30cbb87 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaQueryMethodFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaQueryMethodFactory.java index 03bcb52b83..80539f2fac 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaQueryMethodFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaQueryMethodFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java index d8c5bb4a50..2abd312a91 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DelegatingQueryRewriter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DelegatingQueryRewriter.java index e171a49413..8cba903a94 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DelegatingQueryRewriter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DelegatingQueryRewriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java index 67f9f9b3e6..850c0919a3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java index dd1dd6dc82..db498281fc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..55ae8b0bd6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,6 +100,10 @@ private QueryRendererBuilder getDistinctCountSelection(QueryTokenStream selectio if (countSelection.requiresPrimaryAlias()) { // constructor + if (primaryFromAlias == null) { + throw new IllegalStateException( + "Primary alias must be set for DISTINCT count selection using constructor expressions"); + } nested.append(QueryTokens.token(primaryFromAlias)); } else { // keep all the select items to distinct against diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java index 9a678b4511..d9a558540c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..72cac4a4bb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.CollectionUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes. @@ -49,7 +50,7 @@ public QueryTokenStream visitQl_statement(EqlParser.Ql_statementContext ctx) { } else if (ctx.delete_statement() != null) { return visit(ctx.delete_statement()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -760,7 +761,7 @@ public QueryTokenStream visitAggregate_expression(EqlParser.Aggregate_expression builder.append(QueryTokens.expression(ctx.DISTINCT())); } - builder.appendInline(visit(ctx.state_valued_path_expression())); + builder.appendInline(visit(ctx.simple_select_expression())); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.COUNT() != null) { @@ -769,13 +770,7 @@ public QueryTokenStream visitAggregate_expression(EqlParser.Aggregate_expression if (ctx.DISTINCT() != null) { builder.append(QueryTokens.expression(ctx.DISTINCT())); } - if (ctx.identification_variable() != null) { - builder.appendInline(visit(ctx.identification_variable())); - } else if (ctx.state_valued_path_expression() != null) { - builder.appendInline(visit(ctx.state_valued_path_expression())); - } else if (ctx.single_valued_object_path_expression() != null) { - builder.appendInline(visit(ctx.single_valued_object_path_expression())); - } + builder.appendInline(visit(ctx.simple_select_expression())); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); @@ -1155,8 +1150,8 @@ 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())); + if (ctx.string_expression() != null) { + builder.append(visit(ctx.string_expression())); } if (ctx.type_discriminator() != null) { builder.append(visit(ctx.type_discriminator())); @@ -1189,15 +1184,23 @@ 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.string_expression() != null) { + return visit(ctx.string_expression()); + } else if (ctx.boolean_literal() != null) { + return visit(ctx.boolean_literal()); + } else if (ctx.numeric_literal() != null) { + return visit(ctx.numeric_literal()); + } else if (ctx.date_time_timestamp_literal() != null) { + return visit(ctx.date_time_timestamp_literal()); } else if (ctx.single_valued_input_parameter() != null) { - builder.append(visit(ctx.single_valued_input_parameter())); + return visit(ctx.single_valued_input_parameter()); + } else if (ctx.conditional_expression() != null) { + return visit(ctx.conditional_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1304,14 +1307,14 @@ public QueryTokenStream visitSimple_entity_or_value_expression( 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 @@ -1557,8 +1560,10 @@ public QueryTokenStream visitArithmetic_primary(EqlParser.Arithmetic_primaryCont 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.arithmetic_cast_function() != null) { + builder.append(visit(ctx.arithmetic_cast_function())); + } else if (ctx.type_cast_function() != null) { + builder.append(visit(ctx.type_cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1588,6 +1593,10 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { builder.append(visit(ctx.case_expression())); + } else if (ctx.string_cast_function() != null) { + builder.append(visit(ctx.string_cast_function())); + } else if (ctx.type_cast_function() != null) { + builder.append(visit(ctx.type_cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1944,17 +1953,36 @@ public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationCont } @Override - public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) { + public QueryTokenStream visitArithmetic_cast_function(EqlParser.Arithmetic_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.appendExpression(visit(ctx.string_expression())); + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + builder.append(QueryTokens.token(ctx.f)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitType_cast_function(EqlParser.Type_cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.scalar_expression())); + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } builder.appendInline(visit(ctx.identification_variable())); - if (ctx.numeric_literal() != null) { + if (!CollectionUtils.isEmpty(ctx.numeric_literal())) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); @@ -1965,6 +1993,23 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) { return builder; } + @Override + public QueryTokenStream visitString_cast_function(EqlParser.String_cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.scalar_expression())); + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + builder.append(QueryTokens.token(ctx.STRING())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + @Override public QueryTokenStream visitFunction_invocation(EqlParser.Function_invocationContext ctx) { @@ -2029,16 +2074,7 @@ public QueryTokenStream visitDatetime_part(EqlParser.Datetime_partContext ctx) { @Override public QueryTokenStream visitFunction_arg(EqlParser.Function_argContext ctx) { - - if (ctx.literal() != null) { - return visit(ctx.literal()); - } else if (ctx.state_valued_path_expression() != null) { - return visit(ctx.state_valued_path_expression()); - } else if (ctx.input_parameter() != null) { - return visit(ctx.input_parameter()); - } else { - return visit(ctx.scalar_expression()); - } + return visit(ctx.simple_select_expression()); } @Override @@ -2258,7 +2294,16 @@ 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())); + + if (ctx.CHARACTER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.CHARACTER())); + } else if (ctx.character_valued_input_parameter() != null) { + return visit(ctx.character_valued_input_parameter()); + } else if (ctx.string_literal() != null) { + return visit(ctx.string_literal()); + } + + return QueryTokenStream.empty(); } @Override @@ -2317,21 +2362,41 @@ public QueryTokenStream visitSubtype(EqlParser.SubtypeContext ctx) { @Override public QueryTokenStream visitCollection_valued_field(EqlParser.Collection_valued_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } + return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitSingle_valued_object_field(EqlParser.Single_valued_object_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } + return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitState_field(EqlParser.State_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } + return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitCollection_value_field(EqlParser.Collection_value_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } + return visit(ctx.identification_variable()); } 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..1bd0635150 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java index b9897832f6..d73680ff62 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java index 8066504ca3..218e88b998 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,17 @@ class ExpressionBasedStringQuery extends StringQuery { private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME; private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE; + /** + * Creates a new {@link ExpressionBasedStringQuery} for the given query and {@link JpaQueryMethod}. + * + * @param query must not be {@literal null} or empty. + * @param queryMethod must not be {@literal null} or empty. + * @param parser must not be {@literal null}. + */ + public ExpressionBasedStringQuery(String query, JpaQueryMethod queryMethod, ValueExpressionParser parser) { + this(query, queryMethod.getEntityInformation(), parser, queryMethod.isNativeQuery()); + } + /** * Creates a new {@link ExpressionBasedStringQuery} for the given query and {@link EntityMetadata}. * @@ -62,7 +73,7 @@ class ExpressionBasedStringQuery extends StringQuery { */ public ExpressionBasedStringQuery(String query, JpaEntityMetadata metadata, ValueExpressionParser parser, boolean nativeQuery) { - super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query)); + super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java index 53291a0ea0..37b06f0744 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java index 735bdae29c..00417913ea 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ class HqlCountQueryTransformer extends HqlQueryRenderer { private final @Nullable String countProjection; private final @Nullable String primaryFromAlias; + private boolean containsCTE = false; HqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) { this.countProjection = countProjection; @@ -59,13 +60,27 @@ public QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx) builder.appendExpression(nested); } - if (ctx.queryOrder() != null) { - builder.append(visit(ctx.queryOrder())); + if (ctx.limitClause() != null) { + builder.appendExpression(visit(ctx.limitClause())); + } + + if (ctx.offsetClause() != null) { + builder.appendExpression(visit(ctx.offsetClause())); + } + + if (ctx.fetchClause() != null) { + builder.appendExpression(visit(ctx.fetchClause())); } return builder; } + @Override + public QueryTokenStream visitCte(HqlParser.CteContext ctx) { + this.containsCTE = true; + return super.visitCte(ctx); + } + @Override public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) { @@ -93,8 +108,13 @@ public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) { if (ctx.fromClause() != null) { builder.appendExpression(visit(ctx.fromClause())); + if(primaryFromAlias == null) { + builder.append(TOKEN_AS); + builder.append(TOKEN_DOUBLE_UNDERSCORE); + } } + if (ctx.whereClause() != null) { builder.appendExpression(visit(ctx.whereClause())); } @@ -125,11 +145,6 @@ public QueryRendererBuilder visitFromRoot(HqlParser.FromRootContext ctx) { if (ctx.variable() != null) { builder.appendExpression(visit(ctx.variable())); - - } else { - - builder.append(TOKEN_AS); - builder.append(TOKEN_DOUBLE_UNDERSCORE); } } else if (ctx.subquery() != null) { @@ -184,12 +199,26 @@ public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) { boolean usesDistinct = ctx.DISTINCT() != null; QueryRendererBuilder nested = QueryRenderer.builder(); if (countProjection == null) { + QueryTokenStream selection = visit(ctx.selectionList()); if (usesDistinct) { nested.append(QueryTokens.expression(ctx.DISTINCT())); - nested.append(getDistinctCountSelection(visit(ctx.selectionList()))); + nested.append(getDistinctCountSelection(selection)); } else { - nested.append(QueryTokens.token(primaryFromAlias)); + + // with CTE primary alias fails with hibernate (WITH entities AS (…) SELECT count(c) FROM entities c) + if (containsCTE) { + nested.append(QueryTokens.token("*")); + } else { + + if (selection.size() == 1) { + nested.append(selection); + } else if (primaryFromAlias != null) { + nested.append(QueryTokens.token(primaryFromAlias)); + } else { + nested.append(QueryTokens.token("*")); + } + } } } else { builder.append(QueryTokens.token(countProjection)); @@ -219,27 +248,8 @@ public QueryTokenStream visitSelection(HqlParser.SelectionContext ctx) { return builder; } - @Override - public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - if (ctx.limitClause() != null) { - builder.appendExpression(visit(ctx.limitClause())); - } - - if (ctx.offsetClause() != null) { - builder.appendExpression(visit(ctx.offsetClause())); - } - - if (ctx.fetchClause() != null) { - builder.appendExpression(visit(ctx.fetchClause())); - } - - return builder; - } - private QueryRendererBuilder visitSubQuerySelectClause(SelectClauseContext ctx, QueryRendererBuilder builder) { + if (ctx.DISTINCT() != null) { builder.append(QueryTokens.expression(ctx.DISTINCT())); } @@ -254,8 +264,13 @@ private QueryRendererBuilder getDistinctCountSelection(QueryTokenStream selectio CountSelectionTokenStream countSelection = CountSelectionTokenStream.create(selectionListbuilder); if (countSelection.requiresPrimaryAlias()) { - // constructor - nested.append(QueryTokens.token(primaryFromAlias)); + + if (primaryFromAlias != null) { + // constructor + nested.append(QueryTokens.token(primaryFromAlias)); + } else { + nested.append(countSelection.withoutConstructorExpression()); + } } else { // keep all the select items to distinct against nested.append(selectionListbuilder); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java index 0e6c5cab02..ebec68efb2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA; +import static org.springframework.data.jpa.repository.query.QueryTokens.*; import java.util.ArrayList; import java.util.Collections; @@ -84,7 +84,7 @@ public Void visitInstantiation(HqlParser.InstantiationContext ctx) { } private static String capturePrimaryAlias(VariableContext ctx) { - return ((ctx).reservedWord() != null ? ctx.reservedWord() : ctx.identifier().reservedWord()).getText(); + return ((ctx).nakedIdentifier() != null ? ctx.nakedIdentifier() : ctx.identifier()).getText(); } private static List captureSelectItems(List selections, 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 9976347f1d..5d4f770fc1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,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 HQL query without making any changes. @@ -194,22 +195,22 @@ public QueryTokenStream visitCycleClause(HqlParser.CycleClauseContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.CYCLE().getText())); - builder.append(visit(ctx.cteAttributes())); + builder.appendExpression(visit(ctx.cteAttributes())); builder.append(QueryTokens.expression(ctx.SET().getText())); - builder.append(visit(ctx.identifier(0))); + builder.appendExpression(visit(ctx.identifier(0))); if (ctx.TO() != null) { builder.append(QueryTokens.expression(ctx.TO().getText())); builder.append(visit(ctx.literal(0))); builder.append(QueryTokens.expression(ctx.DEFAULT().getText())); - builder.append(visit(ctx.literal(1))); + builder.appendExpression(visit(ctx.literal(1))); } if (ctx.USING() != null) { builder.append(QueryTokens.expression(ctx.USING().getText())); - builder.append(visit(ctx.identifier(1))); + builder.appendExpression(visit(ctx.identifier(1))); } return builder; @@ -238,6 +239,18 @@ public QueryTokenStream visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { builder.append(visit(ctx.queryOrder())); } + if (ctx.limitClause() != null) { + builder.appendExpression(visit(ctx.limitClause())); + } + + if (ctx.offsetClause() != null) { + builder.appendExpression(visit(ctx.offsetClause())); + } + + if (ctx.fetchClause() != null) { + builder.appendExpression(visit(ctx.fetchClause())); + } + return builder; } @@ -297,26 +310,7 @@ public QueryTokenStream visitFromQuery(HqlParser.FromQueryContext ctx) { @Override public QueryTokenStream visitQueryOrder(HqlParser.QueryOrderContext ctx) { - - if (ctx.limitClause() == null && ctx.offsetClause() == null && ctx.fetchClause() == null) { - return visit(ctx.orderByClause()); - } - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.orderByClause())); - - if (ctx.limitClause() != null) { - builder.appendExpression(visit(ctx.limitClause())); - } - if (ctx.offsetClause() != null) { - builder.appendExpression(visit(ctx.offsetClause())); - } - if (ctx.fetchClause() != null) { - builder.appendExpression(visit(ctx.fetchClause())); - } - - return builder; + return visit(ctx.orderByClause()); } @Override @@ -545,6 +539,10 @@ public QueryTokenStream visitInsertStatement(HqlParser.InsertStatementContext ct builder.appendExpression(visit(ctx.valuesList())); } + if (ctx.conflictClause() != null) { + builder.appendExpression(visit(ctx.conflictClause())); + } + return builder; } @@ -584,29 +582,75 @@ public QueryTokenStream visitValues(HqlParser.ValuesContext ctx) { } @Override - public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) { + public QueryTokenStream visitConflictClause(HqlParser.ConflictClauseContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.NEW())); - builder.append(visit(ctx.instantiationTarget())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.instantiationArguments())); - builder.append(TOKEN_CLOSE_PAREN); + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.CONFLICT())); + + if (ctx.conflictTarget() != null) { + builder.appendExpression(visit(ctx.conflictTarget())); + } + + builder.append(QueryTokens.expression(ctx.DO())); + builder.appendExpression(visit(ctx.conflictAction())); return builder; } @Override - public QueryTokenStream visitAlias(HqlParser.AliasContext ctx) { + public QueryTokenStream visitConflictTarget(HqlParser.ConflictTargetContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.AS() != null) { - builder.append(QueryTokens.expression(ctx.AS())); + if (ctx.identifier() != null) { + + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.CONSTRAINT())); + builder.appendExpression(visit(ctx.identifier())); } - builder.append(visit(ctx.identifier())); + if (!ObjectUtils.isEmpty(ctx.simplePath())) { + + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryTokenStream.concat(ctx.simplePath(), this::visit, TOKEN_COMMA)); + + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitConflictAction(HqlParser.ConflictActionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.NOTHING() != null) { + builder.append(QueryTokens.expression(ctx.NOTHING())); + } else { + builder.append(QueryTokens.expression(ctx.UPDATE())); + builder.appendExpression(visit(ctx.setClause())); + + if (ctx.whereClause() != null) { + builder.appendExpression(visit(ctx.whereClause())); + } + } + + return builder; + } + + @Override + public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.NEW())); + builder.append(visit(ctx.instantiationTarget())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.instantiationArguments())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } @@ -981,12 +1025,18 @@ public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); } else if (ctx.booleanLiteral() != null) { return visit(ctx.booleanLiteral()); - } else if (ctx.stringLiteral() != null) { - return visit(ctx.stringLiteral()); + } else if (ctx.JAVA_STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); + } else if (ctx.STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRING_LITERAL())); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); - } else if (ctx.dateTimeLiteral() != null) { - return visit(ctx.dateTimeLiteral()); + } else if (ctx.temporalLiteral() != null) { + return visit(ctx.temporalLiteral()); + } else if (ctx.arrayLiteral() != null) { + return visit(ctx.arrayLiteral()); + } else if (ctx.generalizedLiteral() != null) { + return visit(ctx.generalizedLiteral()); } else if (ctx.binaryLiteral() != null) { return visit(ctx.binaryLiteral()); } else { @@ -1006,27 +1056,23 @@ public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) } } - @Override - public QueryTokenStream visitStringLiteral(HqlParser.StringLiteralContext ctx) { - - if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); - } else if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); - } else { - return QueryTokenStream.empty(); - } - } - @Override public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { if (ctx.INTEGER_LITERAL() != null) { return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } else if (ctx.LONG_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LONG_LITERAL())); + } else if (ctx.BIG_INTEGER_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_INTEGER_LITERAL())); } else if (ctx.FLOAT_LITERAL() != null) { return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOAT_LITERAL())); - } else if (ctx.HEXLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HEXLITERAL())); + } else if (ctx.DOUBLE_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.DOUBLE_LITERAL())); + } else if (ctx.BIG_DECIMAL_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_DECIMAL_LITERAL())); + } else if (ctx.HEX_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.HEX_LITERAL())); } else { return QueryTokenStream.empty(); } @@ -1035,290 +1081,1823 @@ public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) @Override public QueryTokenStream visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.localDateTimeLiteral() != null) { + return visit(ctx.localDateTimeLiteral()); + } - if (ctx.LOCAL_DATE() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_DATE())); - } else if (ctx.LOCAL_TIME() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_TIME())); - } else if (ctx.LOCAL_DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_DATETIME())); - } else if (ctx.CURRENT_DATE() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_DATE())); - } else if (ctx.CURRENT_TIME() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_TIME())); - } else if (ctx.CURRENT_TIMESTAMP() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_TIMESTAMP())); - } else if (ctx.OFFSET_DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.OFFSET_DATETIME())); - } else { + if (ctx.offsetDateTimeLiteral() != null) { + return visit(ctx.offsetDateTimeLiteral()); + } + + if (ctx.zonedDateTimeLiteral() != null) { + return visit(ctx.zonedDateTimeLiteral()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitLocalDateTimeLiteral(HqlParser.LocalDateTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.DATETIME() != null) { if (ctx.LOCAL() != null) { builder.append(QueryTokens.expression(ctx.LOCAL())); - } else if (ctx.CURRENT() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT())); - } else if (ctx.OFFSET() != null) { - builder.append(QueryTokens.expression(ctx.OFFSET())); } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.localDateTime())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.localDateTime())); + builder.append(TOKEN_CLOSE_PAREN); + } - if (ctx.DATE() != null) { - builder.append(QueryTokens.expression(ctx.DATE())); - } else if (ctx.TIME() != null) { - builder.append(QueryTokens.expression(ctx.TIME())); - } else if (ctx.DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.DATETIME())); - } + return builder; + } + + @Override + public QueryTokenStream visitZonedDateTimeLiteral(HqlParser.ZonedDateTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.INSTANT() != null) { - builder.append(QueryTokens.expression(ctx.INSTANT())); + if (ctx.DATETIME() != null) { + if (ctx.ZONED() != null) { + builder.append(QueryTokens.expression(ctx.ZONED())); } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.zonedDateTime())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.zonedDateTime())); + builder.append(TOKEN_CLOSE_PAREN); } return builder; } @Override - public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { + public QueryTokenStream visitOffsetDateTimeLiteral(HqlParser.OffsetDateTimeLiteralContext ctx) { - if (ctx.YEAR() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.YEAR())); - } else if (ctx.MONTH() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.MONTH())); - } else if (ctx.DAY() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DAY())); - } else if (ctx.WEEK() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.WEEK())); - } else if (ctx.QUARTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.QUARTER())); - } else if (ctx.HOUR() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.HOUR())); - } else if (ctx.MINUTE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.MINUTE())); - } else if (ctx.SECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.SECOND())); - } else if (ctx.NANOSECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NANOSECOND())); - } else if (ctx.EPOCH() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.EPOCH())); + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DATETIME() != null) { + if (ctx.OFFSET() != null) { + builder.append(QueryTokens.expression(ctx.OFFSET())); + } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.offsetDateTimeWithMinutes())); } else { - return QueryTokenStream.empty(); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.offsetDateTime())); + builder.append(TOKEN_CLOSE_PAREN); } + + return builder; } @Override - public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { + public QueryTokenStream visitDateLiteral(HqlParser.DateLiteralContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.BINARY_LITERAL() != null) { - builder.append(QueryTokens.expression(ctx.BINARY_LITERAL())); - } else if (ctx.HEXLITERAL() != null) { + if (ctx.DATE() != null) { + if (ctx.LOCAL() != null) { + builder.append(QueryTokens.expression(ctx.LOCAL())); + } + builder.append(QueryTokens.expression(ctx.DATE())); + builder.append(visit(ctx.date())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.date())); + builder.append(TOKEN_CLOSE_PAREN); + } - builder.append(TOKEN_OPEN_BRACE); + return builder; + } - builder.append(QueryTokenStream.concat(ctx.HEXLITERAL(), it -> { - return QueryRendererBuilder.from(QueryTokens.token(it)); - }, TOKEN_COMMA)); + @Override + public QueryTokenStream visitTimeLiteral(HqlParser.TimeLiteralContext ctx) { - builder.append(TOKEN_CLOSE_BRACE); + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.TIME() != null) { + if (ctx.LOCAL() != null) { + builder.append(QueryTokens.expression(ctx.LOCAL())); + } + builder.append(QueryTokens.expression(ctx.TIME())); + builder.append(visit(ctx.time())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.time())); + builder.append(TOKEN_CLOSE_PAREN); } return builder; } @Override - public QueryTokenStream visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) { - return visit(ctx.primaryExpression()); + public QueryTokenStream visitDateTime(HqlParser.DateTimeContext ctx) { + + if (ctx.localDateTime() != null) { + return visit(ctx.localDateTime()); + } + + if (ctx.offsetDateTime() != null) { + return visit(ctx.offsetDateTime()); + } + + if (ctx.zonedDateTime() != null) { + return visit(ctx.zonedDateTime()); + } + + return QueryTokenStream.empty(); } @Override - public QueryTokenStream visitTupleExpression(HqlParser.TupleExpressionContext ctx) { + public QueryTokenStream visitLocalDateTime(HqlParser.LocalDateTimeContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_OPEN_PAREN); - builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); - builder.append(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.date())); + builder.appendExpression(visit(ctx.time())); return builder; } @Override - public QueryTokenStream visitHqlConcatenationExpression(HqlParser.HqlConcatenationExpressionContext ctx) { + public QueryTokenStream visitZonedDateTime(HqlParser.ZonedDateTimeContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendInline(visit(ctx.expression(0))); - builder.append(TOKEN_DOUBLE_PIPE); - builder.append(visit(ctx.expression(1))); + builder.appendExpression(visit(ctx.date())); + builder.appendExpression(visit(ctx.time())); + builder.appendExpression(visit(ctx.zoneId())); return builder; } @Override - public QueryTokenStream visitDayOfWeekExpression(HqlParser.DayOfWeekExpressionContext ctx) { + public QueryTokenStream visitOffsetDateTime(HqlParser.OffsetDateTimeContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.DAY())); - builder.append(QueryTokens.expression(ctx.OF())); - builder.append(QueryTokens.expression(ctx.WEEK())); + builder.appendExpression(visit(ctx.date())); + builder.appendInline(visit(ctx.time())); + builder.appendInline(visit(ctx.offset())); return builder; } @Override - public QueryTokenStream visitDayOfMonthExpression(HqlParser.DayOfMonthExpressionContext ctx) { + public QueryTokenStream visitOffsetDateTimeWithMinutes(HqlParser.OffsetDateTimeWithMinutesContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.DAY())); - builder.append(QueryTokens.expression(ctx.OF())); - builder.append(QueryTokens.expression(ctx.MONTH())); + builder.appendExpression(visit(ctx.date())); + builder.appendInline(visit(ctx.time())); + builder.appendInline(visit(ctx.offsetWithMinutes())); return builder; } @Override - public QueryTokenStream visitWeekOfYearExpression(HqlParser.WeekOfYearExpressionContext ctx) { + public QueryTokenStream visitJdbcTimestampLiteral(HqlParser.JdbcTimestampLiteralContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.WEEK())); - builder.append(QueryTokens.expression(ctx.OF())); - builder.append(QueryTokens.expression(ctx.YEAR())); + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("ts")); + builder.append(visit(ctx.dateTime() != null ? ctx.dateTime() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); return builder; } @Override - public QueryTokenStream visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) { + public QueryTokenStream visitJdbcDateLiteral(HqlParser.JdbcDateLiteralContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.expression())); - builder.append(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("d")); + builder.append(visit(ctx.date() != null ? ctx.date() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); return builder; } @Override - public QueryTokenStream visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) { + public QueryTokenStream visitJdbcTimeLiteral(HqlParser.JdbcTimeLiteralContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendInline(visit(ctx.expression(0))); - builder.append(QueryTokens.ventilated(ctx.op)); - builder.appendInline(visit(ctx.expression(1))); + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("t")); + builder.append(visit(ctx.time() != null ? ctx.time() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); return builder; } @Override - public QueryTokenStream visitSignedNumericLiteral(HqlParser.SignedNumericLiteralContext ctx) { + public QueryTokenStream visitGenericTemporalLiteralText(HqlParser.GenericTemporalLiteralTextContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + @Override + public QueryTokenStream visitArrayLiteral(HqlParser.ArrayLiteralContext ctx) { - builder.append(QueryTokens.token(ctx.op)); - builder.append(visit(ctx.numericLiteral())); + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.append(QueryTokenStream.concat(ctx.expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); return builder; } @Override - public QueryTokenStream visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) { + public QueryTokenStream visitGeneralizedLiteral(HqlParser.GeneralizedLiteralContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression(0))); - builder.append(QueryTokens.expression(ctx.op)); - builder.appendExpression(visit(ctx.expression(1))); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.generalizedLiteralType())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.generalizedLiteralText())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } @Override - public QueryTokenStream visitSubqueryExpression(HqlParser.SubqueryExpressionContext ctx) { + public QueryTokenStream visitGeneralizedLiteralType(HqlParser.GeneralizedLiteralTypeContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + @Override + public QueryTokenStream visitGeneralizedLiteralText(HqlParser.GeneralizedLiteralTextContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.subquery())); - builder.append(TOKEN_CLOSE_PAREN); + @Override + public QueryTokenStream visitDate(HqlParser.DateContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(visit(ctx.year())); + builder.append(TOKEN_DASH); + builder.append(visit(ctx.month())); + builder.append(TOKEN_DASH); + builder.append(visit(ctx.day())); return builder; } @Override - public QueryTokenStream visitSignedExpression(HqlParser.SignedExpressionContext ctx) { + public QueryTokenStream visitTime(HqlParser.TimeContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(visit(ctx.hour())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); - builder.append(QueryTokens.token(ctx.op)); - builder.appendInline(visit(ctx.expression())); + if (ctx.second() != null) { + builder.append(TOKEN_COLON); + builder.append(visit(ctx.second())); + } return builder; } @Override - public QueryTokenStream visitToDurationExpression(HqlParser.ToDurationExpressionContext ctx) { + public QueryTokenStream visitOffset(HqlParser.OffsetContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.MINUS() != null) { + builder.append(QueryTokens.token(ctx.MINUS())); + } else if (ctx.PLUS() != null) { + builder.append(QueryTokens.token(ctx.PLUS())); + } + builder.append(visit(ctx.hour())); - builder.append(visit(ctx.expression())); - builder.append(visit(ctx.datetimeField())); + if (ctx.minute() != null) { + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); + } return builder; } @Override - public QueryTokenStream visitFromDurationExpression(HqlParser.FromDurationExpressionContext ctx) { + public QueryTokenStream visitOffsetWithMinutes(HqlParser.OffsetWithMinutesContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.BY())); - builder.append(visit(ctx.datetimeField())); + if (ctx.MINUS() != null) { + builder.append(QueryTokens.token(ctx.MINUS())); + } else if (ctx.PLUS() != null) { + builder.append(QueryTokens.token(ctx.PLUS())); + } + + builder.append(visit(ctx.hour())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); return builder; } @Override - public QueryTokenStream visitCaseExpression(HqlParser.CaseExpressionContext ctx) { - return visit(ctx.caseList()); + public QueryTokenStream visitYear(HqlParser.YearContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); } @Override - public QueryTokenStream visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { - return visit(ctx.literal()); + public QueryTokenStream visitMonth(HqlParser.MonthContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); } @Override - public QueryTokenStream visitParameterExpression(HqlParser.ParameterExpressionContext ctx) { - return visit(ctx.parameter()); + public QueryTokenStream visitDay(HqlParser.DayContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); } @Override - public QueryTokenStream visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) { - return visit(ctx.function()); + public QueryTokenStream visitHour(HqlParser.HourContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); } @Override - public QueryTokenStream visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { - return visit(ctx.generalPathFragment()); + public QueryTokenStream visitMinute(HqlParser.MinuteContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); } @Override - public QueryTokenStream visitIdentificationVariable(HqlParser.IdentificationVariableContext ctx) { + public QueryTokenStream visitSecond(HqlParser.SecondContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } - if (ctx.identifier() != null) { - return visit(ctx.identifier()); - } else if (ctx.simplePath() != null) { - return visit(ctx.simplePath()); - } else { - return QueryTokenStream.empty(); - } + @Override + public QueryTokenStream visitZoneId(HqlParser.ZoneIdContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { + + if (ctx.YEAR() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.YEAR())); + } else if (ctx.MONTH() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.MONTH())); + } else if (ctx.DAY() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.DAY())); + } else if (ctx.WEEK() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.WEEK())); + } else if (ctx.QUARTER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.QUARTER())); + } else if (ctx.HOUR() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.HOUR())); + } else if (ctx.MINUTE() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.MINUTE())); + } else if (ctx.SECOND() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.SECOND())); + } else if (ctx.NANOSECOND() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.NANOSECOND())); + } else if (ctx.EPOCH() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.EPOCH())); + } else { + return QueryTokenStream.empty(); + } + } + + @Override + public QueryTokenStream visitDayField(HqlParser.DayFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.DAY())); + builder.append(QueryTokens.expression(ctx.OF())); + + if (ctx.MONTH() != null) { + builder.append(QueryTokens.expression(ctx.MONTH())); + } + + if (ctx.WEEK() != null) { + builder.append(QueryTokens.expression(ctx.WEEK())); + } + + if (ctx.YEAR() != null) { + builder.append(QueryTokens.expression(ctx.YEAR())); + } + + return builder; + } + + @Override + public QueryTokenStream visitWeekField(HqlParser.WeekFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WEEK())); + builder.append(QueryTokens.expression(ctx.OF())); + + if (ctx.MONTH() != null) { + builder.append(QueryTokens.expression(ctx.MONTH())); + } + + if (ctx.YEAR() != null) { + builder.append(QueryTokens.expression(ctx.YEAR())); + } + + return builder; + } + + @Override + public QueryTokenStream visitTimeZoneField(HqlParser.TimeZoneFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.OFFSET() != null) { + builder.append(QueryTokens.expression(ctx.OFFSET())); + + if (ctx.HOUR() != null) { + builder.append(QueryTokens.expression(ctx.HOUR())); + } + + if (ctx.MINUTE() != null) { + builder.append(QueryTokens.expression(ctx.MINUTE())); + } + } + + if (ctx.TIMEZONE_HOUR() != null) { + builder.append(QueryTokens.expression(ctx.TIMEZONE_HOUR())); + } + + if (ctx.TIMEZONE_HOUR() != null) { + builder.append(QueryTokens.expression(ctx.TIMEZONE_MINUTE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitDateOrTimeField(HqlParser.DateOrTimeFieldContext ctx) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATE() != null ? ctx.DATE() : ctx.TIME())); + } + + @Override + public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.BINARY_LITERAL() != null) { + builder.append(QueryTokens.expression(ctx.BINARY_LITERAL())); + } 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(TOKEN_CLOSE_BRACE); + } + + return builder; + } + + @Override + public QueryTokenStream visitTemporalLiteral(HqlParser.TemporalLiteralContext ctx) { + + if (ctx.dateTimeLiteral() != null) { + return visit(ctx.dateTimeLiteral()); + } + + if (ctx.dateLiteral() != null) { + return visit(ctx.dateLiteral()); + } + + if (ctx.timeLiteral() != null) { + return visit(ctx.timeLiteral()); + } + + if (ctx.jdbcTimestampLiteral() != null) { + return visit(ctx.jdbcTimestampLiteral()); + } + + if (ctx.jdbcDateLiteral() != null) { + return visit(ctx.jdbcDateLiteral()); + } + + if (ctx.jdbcTimeLiteral() != null) { + return visit(ctx.jdbcTimeLiteral()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) { + return visit(ctx.primaryExpression()); + } + + @Override + public QueryTokenStream visitTupleExpression(HqlParser.TupleExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitHqlConcatenationExpression(HqlParser.HqlConcatenationExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.append(visit(ctx.expression(1))); + + return builder; + } + + @Override + public QueryTokenStream visitDayOfWeekExpression(HqlParser.DayOfWeekExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.DAY())); + builder.append(QueryTokens.expression(ctx.OF())); + builder.append(QueryTokens.expression(ctx.WEEK())); + + return builder; + } + + @Override + public QueryTokenStream visitDayOfMonthExpression(HqlParser.DayOfMonthExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.DAY())); + builder.append(QueryTokens.expression(ctx.OF())); + builder.append(QueryTokens.expression(ctx.MONTH())); + + return builder; + } + + @Override + public QueryTokenStream visitWeekOfYearExpression(HqlParser.WeekOfYearExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WEEK())); + builder.append(QueryTokens.expression(ctx.OF())); + builder.append(QueryTokens.expression(ctx.YEAR())); + + return builder; + } + + @Override + public QueryTokenStream visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.expression())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.expression(0))); + builder.append(QueryTokens.ventilated(ctx.op)); + builder.appendInline(visit(ctx.expression(1))); + + return builder; + } + + @Override + public QueryTokenStream visitSignedNumericLiteral(HqlParser.SignedNumericLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.op)); + builder.append(visit(ctx.numericLiteral())); + + return builder; + } + + @Override + public QueryTokenStream visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.expression(0))); + builder.append(QueryTokens.expression(ctx.op)); + builder.appendExpression(visit(ctx.expression(1))); + + return builder; + } + + @Override + public QueryTokenStream visitSubqueryExpression(HqlParser.SubqueryExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitSignedExpression(HqlParser.SignedExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.op)); + builder.appendInline(visit(ctx.expression())); + + return builder; + } + + @Override + public QueryTokenStream visitToDurationExpression(HqlParser.ToDurationExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.expression())); + builder.appendExpression(visit(ctx.datetimeField())); + + return builder; + } + + @Override + public QueryTokenStream visitFromDurationExpression(HqlParser.FromDurationExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.BY())); + builder.appendExpression(visit(ctx.datetimeField())); + + return builder; + } + + @Override + public QueryTokenStream visitCaseExpression(HqlParser.CaseExpressionContext ctx) { + return visit(ctx.caseList()); + } + + @Override + public QueryTokenStream visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { + return visit(ctx.literal()); + } + + @Override + public QueryTokenStream visitParameterExpression(HqlParser.ParameterExpressionContext ctx) { + return visit(ctx.parameter()); + } + + @Override + public QueryTokenStream visitEntityTypeExpression(HqlParser.EntityTypeExpressionContext ctx) { + return visit(ctx.entityTypeReference()); + } + + @Override + public QueryTokenStream visitEntityIdExpression(HqlParser.EntityIdExpressionContext ctx) { + return visit(ctx.entityIdReference()); + } + + @Override + public QueryTokenStream visitEntityVersionExpression(HqlParser.EntityVersionExpressionContext ctx) { + return visit(ctx.entityVersionReference()); + } + + @Override + public QueryTokenStream visitEntityNaturalIdExpression(HqlParser.EntityNaturalIdExpressionContext ctx) { + return visit(ctx.entityNaturalIdReference()); + } + + @Override + public QueryTokenStream visitSyntacticPathExpression(HqlParser.SyntacticPathExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.syntacticDomainPath())); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitPathContinuation(HqlParser.PathContinuationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_DOT); + builder.append(visit(ctx.simplePath())); + + return builder; + } + + @Override + public QueryTokenStream visitEntityTypeReference(HqlParser.EntityTypeReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.TYPE())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.appendInline(visit(ctx.path())); + } + + if (ctx.parameter() != null) { + builder.appendInline(visit(ctx.parameter())); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitEntityIdReference(HqlParser.EntityIdReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.ID())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitEntityVersionReference(HqlParser.EntityVersionReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.VERSION())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitEntityNaturalIdReference(HqlParser.EntityNaturalIdReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.NATURALID())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSyntacticDomainPath(HqlParser.SyntacticDomainPathContext ctx) { + + if (ctx.treatedNavigablePath() != null) { + return visit(ctx.treatedNavigablePath()); + } + + if (ctx.collectionValueNavigablePath() != null) { + return visit(ctx.collectionValueNavigablePath()); + } + + if (ctx.mapKeyNavigablePath() != null) { + return visit(ctx.mapKeyNavigablePath()); + } + + if (ctx.toOneFkReference() != null) { + return visit(ctx.toOneFkReference()); + } + + if (ctx.function() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.function())); + + if (ctx.indexedPathAccessFragment() != null) { + builder.append(visit(ctx.indexedPathAccessFragment())); + } + + if (ctx.slicedPathAccessFragment() != null) { + builder.append(visit(ctx.slicedPathAccessFragment())); + } + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } + + return builder; + } + + if (ctx.indexedPathAccessFragment() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.indexedPathAccessFragment())); + + return builder; + } + + if (ctx.slicedPathAccessFragment() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.slicedPathAccessFragment())); + + return builder; + } + + return QueryRenderer.empty(); + } + + @Override + public QueryTokenStream visitSlicedPathAccessFragment(HqlParser.SlicedPathAccessFragmentContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.appendInline(visit(ctx.expression(0))); + builder.append(TOKEN_COLON); + builder.appendInline(visit(ctx.expression(1))); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); + + return builder; + } + + @Override + public QueryTokenStream visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) { + return visit(ctx.function()); + } + + @Override + public QueryTokenStream visitStandardFunctionInvocation(HqlParser.StandardFunctionInvocationContext ctx) { + return visit(ctx.standardFunction()); + } + + @Override + public QueryTokenStream visitAggregateFunctionInvocation(HqlParser.AggregateFunctionInvocationContext ctx) { + return visit(ctx.aggregateFunction()); + } + + @Override + public QueryTokenStream visitCollectionSizeFunctionInvocation(HqlParser.CollectionSizeFunctionInvocationContext ctx) { + return visit(ctx.collectionSizeFunction()); + } + + @Override + public QueryTokenStream visitCollectionAggregateFunctionInvocation( + HqlParser.CollectionAggregateFunctionInvocationContext ctx) { + return visit(ctx.collectionAggregateFunction()); + } + + @Override + public QueryTokenStream visitCollectionFunctionMisuseInvocation( + HqlParser.CollectionFunctionMisuseInvocationContext ctx) { + return visit(ctx.collectionFunctionMisuse()); + } + + @Override + public QueryTokenStream visitJpaNonstandardFunctionInvocation(HqlParser.JpaNonstandardFunctionInvocationContext ctx) { + return visit(ctx.jpaNonstandardFunction()); + } + + @Override + public QueryTokenStream visitColumnFunctionInvocation(HqlParser.ColumnFunctionInvocationContext ctx) { + return visit(ctx.columnFunction()); + } + + @Override + public QueryTokenStream visitGenericFunctionInvocation(HqlParser.GenericFunctionInvocationContext ctx) { + return visit(ctx.genericFunction()); + } + + @Override + public QueryTokenStream visitStandardFunction(HqlParser.StandardFunctionContext ctx) { + + if (ctx.castFunction() != null) { + return visit(ctx.castFunction()); + } + + if (ctx.extractFunction() != null) { + return visit(ctx.extractFunction()); + } + + if (ctx.truncFunction() != null) { + return visit(ctx.truncFunction()); + } + + if (ctx.formatFunction() != null) { + return visit(ctx.formatFunction()); + } + + if (ctx.collateFunction() != null) { + return visit(ctx.collateFunction()); + } + + if (ctx.substringFunction() != null) { + return visit(ctx.substringFunction()); + } + + if (ctx.overlayFunction() != null) { + return visit(ctx.overlayFunction()); + } + + if (ctx.trimFunction() != null) { + return visit(ctx.trimFunction()); + } + + if (ctx.padFunction() != null) { + return visit(ctx.padFunction()); + } + + if (ctx.positionFunction() != null) { + return visit(ctx.positionFunction()); + } + + if (ctx.currentDateFunction() != null) { + return visit(ctx.currentDateFunction()); + } + + if (ctx.currentTimeFunction() != null) { + return visit(ctx.currentTimeFunction()); + } + + if (ctx.currentTimestampFunction() != null) { + return visit(ctx.currentTimestampFunction()); + } + + if (ctx.instantFunction() != null) { + return visit(ctx.instantFunction()); + } + + if (ctx.localDateFunction() != null) { + return visit(ctx.localDateFunction()); + } + + if (ctx.localTimeFunction() != null) { + return visit(ctx.localTimeFunction()); + } + + if (ctx.localDateTimeFunction() != null) { + return visit(ctx.localDateTimeFunction()); + } + + if (ctx.offsetDateTimeFunction() != null) { + return visit(ctx.offsetDateTimeFunction()); + } + + if (ctx.cube() != null) { + return visit(ctx.cube()); + } + + if (ctx.rollup() != null) { + return visit(ctx.rollup()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitSubstringFunction(HqlParser.SubstringFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.SUBSTRING())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + + if (ctx.FROM() == null) { + builder.append(TOKEN_COMMA); + } else { + builder.append(QueryTokens.expression(ctx.FROM())); + } + + builder.append(visit(ctx.substringFunctionStartArgument())); + + if (ctx.substringFunctionLengthArgument() != null) { + if (ctx.FOR() == null) { + builder.append(TOKEN_COMMA); + } else { + builder.append(QueryTokens.expression(ctx.FOR())); + } + + builder.append(visit(ctx.substringFunctionLengthArgument())); + } + + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitSubstringFunctionStartArgument(HqlParser.SubstringFunctionStartArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitSubstringFunctionLengthArgument(HqlParser.SubstringFunctionLengthArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPadFunction(HqlParser.PadFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.PAD())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.WITH())); + builder.appendExpression(visit(ctx.padLength())); + + if (ctx.padCharacter() != null) { + builder.appendExpression(visit(ctx.padSpecification())); + builder.appendInline(visit(ctx.padCharacter())); + } else { + builder.append(visit(ctx.padSpecification())); + } + + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING())); + } + + @Override + public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitPadLength(HqlParser.PadLengthContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPositionFunction(HqlParser.PositionFunctionContext ctx) { + + QueryRendererBuilder builder = 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())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitPositionFunctionPatternArgument(HqlParser.PositionFunctionPatternArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPositionFunctionStringArgument(HqlParser.PositionFunctionStringArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunction(HqlParser.OverlayFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.OVERLAY())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.overlayFunctionStringArgument())); + builder.append(QueryTokens.expression(ctx.PLACING())); + builder.append(visit(ctx.overlayFunctionReplacementArgument())); + builder.append(QueryTokens.expression(ctx.FROM())); + builder.append(visit(ctx.overlayFunctionStartArgument())); + + if (ctx.overlayFunctionLengthArgument() != null) { + builder.append(QueryTokens.expression(ctx.FOR())); + builder.append(visit(ctx.overlayFunctionLengthArgument())); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitOverlayFunctionStringArgument(HqlParser.OverlayFunctionStringArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionReplacementArgument( + HqlParser.OverlayFunctionReplacementArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionStartArgument(HqlParser.OverlayFunctionStartArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionLengthArgument(HqlParser.OverlayFunctionLengthArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitCurrentDateFunction(HqlParser.CurrentDateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_DATE() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_DATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.DATE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCurrentTimeFunction(HqlParser.CurrentTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_TIME() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_TIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.TIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCurrentTimestampFunction(HqlParser.CurrentTimestampFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_TIMESTAMP() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_TIMESTAMP())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.TIMESTAMP())); + } + + return builder; + } + + @Override + public QueryTokenStream visitInstantFunction(HqlParser.InstantFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_INSTANT() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_INSTANT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.INSTANT())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalDateTimeFunction(HqlParser.LocalDateTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_DATETIME() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_DATETIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.DATETIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOffsetDateTimeFunction(HqlParser.OffsetDateTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.OFFSET_DATETIME() != null) { + builder.append(QueryTokens.token(ctx.OFFSET_DATETIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.OFFSET())); + builder.append(QueryTokens.expression(ctx.DATETIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalDateFunction(HqlParser.LocalDateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_DATE() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_DATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.DATE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalTimeFunction(HqlParser.LocalTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_TIME() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_TIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.TIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFormatFunction(HqlParser.FormatFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FORMAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.appendInline(visit(ctx.format())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitCollation(HqlParser.CollationContext ctx) { + return visit(ctx.simplePath()); + } + + @Override + public QueryTokenStream visitCollateFunction(HqlParser.CollateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.COLLATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.appendInline(visit(ctx.collation())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitCube(HqlParser.CubeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CUBE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitRollup(HqlParser.RollupContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.ROLLUP())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitFormat(HqlParser.FormatContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitTruncFunction(HqlParser.TruncFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.TRUNC() != null) { + builder.append(QueryTokens.token(ctx.TRUNC())); + } else { + builder.append(QueryTokens.token(ctx.TRUNCATE())); + } + + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.datetimeField() != null) { + builder.append(visit(ctx.expression(0))); + builder.append(TOKEN_COMMA); + builder.append(visit(ctx.datetimeField())); + } else { + builder.append(QueryTokenStream.concat(ctx.expression(), this::visit, TOKEN_COMMA)); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitJpaNonstandardFunction(HqlParser.JpaNonstandardFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FUNCTION())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendInline(visit(ctx.jpaNonstandardFunctionName())); + + if (ctx.castTarget() != null) { + nested.append(QueryTokens.expression(ctx.AS())); + nested.append(visit(ctx.castTarget())); + } + + if (ctx.genericFunctionArguments() != null) { + nested.append(TOKEN_COMMA); + nested.appendInline(visit(ctx.genericFunctionArguments())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitJpaNonstandardFunctionName(HqlParser.JpaNonstandardFunctionNameContext ctx) { + + if (ctx.identifier() != null) { + return visit(ctx.identifier()); + } + + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitColumnFunction(HqlParser.ColumnFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.COLUMN())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendInline(visit(ctx.path())); + nested.append(TOKEN_DOT); + nested.appendExpression(visit(ctx.jpaNonstandardFunctionName())); + + if (ctx.castTarget() != null) { + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.jpaNonstandardFunctionName())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitGenericFunctionName(HqlParser.GenericFunctionNameContext ctx) { + return visit(ctx.simplePath()); + } + + @Override + public QueryTokenStream visitGenericFunctionArguments(HqlParser.GenericFunctionArgumentsContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + builder.append(QueryTokens.expression(ctx.DISTINCT())); + } + + if (ctx.datetimeField() != null) { + builder.append(visit(ctx.datetimeField())); + builder.append(TOKEN_COMMA); + } + + builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + + return builder; + } + + @Override + public QueryTokenStream visitCollectionSizeFunction(HqlParser.CollectionSizeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.SIZE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitElementAggregateFunction(HqlParser.ElementAggregateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.MAXELEMENT() != null || ctx.MINELEMENT() != null) { + builder.append(QueryTokens.token(ctx.MAXELEMENT() != null ? ctx.MAXELEMENT() : ctx.MINELEMENT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + if (ctx.MAX() != null) { + builder.append(QueryTokens.token(ctx.MAX())); + } + if (ctx.MIN() != null) { + builder.append(QueryTokens.token(ctx.MIN())); + } + if (ctx.SUM() != null) { + builder.append(QueryTokens.token(ctx.SUM())); + } + if (ctx.AVG() != null) { + builder.append(QueryTokens.token(ctx.AVG())); + } + + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.elementsValuesQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.append(visit(ctx.path())); + } + + builder.append(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitIndexAggregateFunction(HqlParser.IndexAggregateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.MAXINDEX() != null || ctx.MININDEX() != null) { + builder.append(QueryTokens.token(ctx.MAXINDEX() != null ? ctx.MAXINDEX() : ctx.MININDEX())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + if (ctx.MAX() != null) { + builder.append(QueryTokens.token(ctx.MAX())); + } + if (ctx.MIN() != null) { + builder.append(QueryTokens.token(ctx.MIN())); + } + if (ctx.SUM() != null) { + builder.append(QueryTokens.token(ctx.SUM())); + } + if (ctx.AVG() != null) { + builder.append(QueryTokens.token(ctx.AVG())); + } + + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.indicesKeysQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.append(visit(ctx.path())); + } + + builder.append(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitCollectionFunctionMisuse(HqlParser.CollectionFunctionMisuseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append( + visit(ctx.elementsValuesQuantifier() != null ? ctx.elementsValuesQuantifier() : ctx.indicesKeysQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitAggregateFunction(HqlParser.AggregateFunctionContext ctx) { + + if (ctx.everyFunction() != null) { + return visit(ctx.everyFunction()); + } + + if (ctx.anyFunction() != null) { + return visit(ctx.anyFunction()); + } + + return visit(ctx.listaggFunction()); + } + + @Override + public QueryTokenStream visitEveryAllQuantifier(HqlParser.EveryAllQuantifierContext ctx) { + + if (ctx.EVERY() != null) { + return QueryRenderer.from(QueryTokens.token(ctx.EVERY())); + } + + return QueryRenderer.from(QueryTokens.token(ctx.ALL())); + } + + @Override + public QueryTokenStream visitAnySomeQuantifier(HqlParser.AnySomeQuantifierContext ctx) { + + if (ctx.ANY() != null) { + return QueryRenderer.from(QueryTokens.token(ctx.ANY())); + } + + return QueryRenderer.from(QueryTokens.token(ctx.SOME())); + } + + @Override + public QueryTokenStream visitListaggFunction(HqlParser.ListaggFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.LISTAGG())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + builder.append(QueryTokens.expression(ctx.DISTINCT())); + } + + builder.appendInline(visit(ctx.expressionOrPredicate(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.expressionOrPredicate(1))); + + if (ctx.onOverflowClause() != null) { + builder.appendExpression(visit(ctx.onOverflowClause())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.withinGroupClause() != null) { + builder.appendExpression(visit(ctx.withinGroupClause())); + } + + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } + + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOnOverflowClause(HqlParser.OnOverflowClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.OVERFLOW())); + + if (ctx.ERROR() != null) { + builder.append(QueryTokens.expression(ctx.ERROR())); + } else { + + builder.append(QueryTokens.expression(ctx.TRUNCATE())); + + if (ctx.expression() != null) { + builder.appendExpression(visit(ctx.expression())); + } + + if (ctx.WITH() != null) { + builder.append(QueryTokens.expression(ctx.WITH())); + } + + if (ctx.WITHOUT() != null) { + builder.append(QueryTokens.expression(ctx.WITHOUT())); + } + + if (ctx.COUNT() != null) { + builder.append(QueryTokens.expression(ctx.COUNT())); + } + } + + return builder; + } + + @Override + public QueryTokenStream visitWithinGroupClause(HqlParser.WithinGroupClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WITHIN())); + builder.append(QueryTokens.expression(ctx.GROUP())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.orderByClause())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitNullsClause(HqlParser.NullsClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.IGNORE() != null) { + builder.append(QueryTokens.expression(ctx.IGNORE())); + } else { + builder.append(QueryTokens.expression(ctx.RESPECT())); + } + + builder.append(QueryTokens.expression(ctx.NULLS())); + + return builder; + } + + @Override + public QueryTokenStream visitNthSideClause(HqlParser.NthSideClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.FROM())); + + if (ctx.FIRST() != null) { + builder.append(QueryTokens.expression(ctx.FIRST())); + } else { + builder.append(QueryTokens.expression(ctx.LAST())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFrameStart(HqlParser.FrameStartContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT() != null) { + + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.UNBOUNDED() != null) { + builder.append(QueryTokens.expression(ctx.UNBOUNDED())); + builder.append(QueryTokens.expression(ctx.PRECEDING())); + } else { + + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.PRECEDING() != null ? ctx.PRECEDING() : ctx.FOLLOWING())); + } + + return builder; + + } + + @Override + public QueryTokenStream visitFrameEnd(HqlParser.FrameEndContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT() != null) { + + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.UNBOUNDED() != null) { + builder.append(QueryTokens.expression(ctx.UNBOUNDED())); + builder.append(QueryTokens.expression(ctx.FOLLOWING())); + } else { + + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.PRECEDING() != null ? ctx.PRECEDING() : ctx.FOLLOWING())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFrameExclusion(HqlParser.FrameExclusionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.EXCLUDE())); + + if (ctx.CURRENT() != null) { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.GROUP() != null) { + builder.append(QueryTokens.expression(ctx.GROUP())); + } else if (ctx.TIES() != null) { + builder.append(QueryTokens.expression(ctx.TIES())); + } else { + builder.append(QueryTokens.expression(ctx.NO())); + builder.append(QueryTokens.expression(ctx.OTHERS())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCollectionQuantifier(HqlParser.CollectionQuantifierContext ctx) { + + if (ctx.elementsValuesQuantifier() != null) { + return visit(ctx.elementsValuesQuantifier()); + } + + return visit(ctx.indicesKeysQuantifier()); + } + + @Override + public QueryTokenStream visitElementsValuesQuantifier(HqlParser.ElementsValuesQuantifierContext ctx) { + return QueryRenderer.from(QueryTokens.token(ctx.ELEMENTS() != null ? ctx.ELEMENTS() : ctx.VALUES())); + } + + @Override + public QueryTokenStream visitIndicesKeysQuantifier(HqlParser.IndicesKeysQuantifierContext ctx) { + return QueryRenderer.from(QueryTokens.token(ctx.INDICES() != null ? ctx.INDICES() : ctx.KEYS())); + } + + @Override + public QueryTokenStream visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { + return visit(ctx.generalPathFragment()); } @Override @@ -1326,12 +2905,12 @@ public QueryTokenStream visitPath(HqlParser.PathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.treatedPath() != null) { + if (ctx.syntacticDomainPath() != null) { - builder.append(visit(ctx.treatedPath())); + builder.append(visit(ctx.syntacticDomainPath())); - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } } else if (ctx.generalPathFragment() != null) { builder.append(visit(ctx.generalPathFragment())); @@ -1485,92 +3064,42 @@ public QueryTokenStream visitGenericFunction(HqlParser.GenericFunctionContext ct QueryRendererBuilder builder = QueryRenderer.builder(); QueryRendererBuilder nested = QueryRenderer.builder(); - nested.append(visit(ctx.functionName())); + nested.append(visit(ctx.genericFunctionName())); nested.append(TOKEN_OPEN_PAREN); - if (ctx.functionArguments() != null) { - nested.appendInline(visit(ctx.functionArguments())); + if (ctx.genericFunctionArguments() != null) { + nested.appendInline(visit(ctx.genericFunctionArguments())); } else if (ctx.ASTERISK() != null) { nested.append(QueryTokens.token(ctx.ASTERISK())); } nested.append(TOKEN_CLOSE_PAREN); - builder.append(nested); - if (ctx.pathContinutation() != null) { - builder.appendInline(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } - if (ctx.filterClause() != null) { - builder.appendExpression(visit(ctx.filterClause())); + if (ctx.nthSideClause() != null) { + builder.appendExpression(visit(ctx.nthSideClause())); } - if (ctx.withinGroup() != null) { - builder.appendExpression(visit(ctx.withinGroup())); + if (ctx.nullsClause() != null) { + builder.appendExpression(visit(ctx.nullsClause())); } - if (ctx.overClause() != null) { - builder.appendExpression(visit(ctx.overClause())); + if (ctx.withinGroupClause() != null) { + builder.appendExpression(visit(ctx.withinGroupClause())); } - return builder; - } - - @Override - public QueryTokenStream visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.functionName())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.subquery())); - builder.append(TOKEN_CLOSE_PAREN); - - return builder; - } - - @Override - public QueryTokenStream visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) { - return visit(ctx.castFunction()); - } - - @Override - public QueryTokenStream visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) { - return visit(ctx.extractFunction()); - } - - @Override - public QueryTokenStream visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) { - return visit(ctx.trimFunction()); - } - - @Override - public QueryTokenStream visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) { - return visit(ctx.everyFunction()); - } - - @Override - public QueryTokenStream visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) { - return visit(ctx.anyFunction()); - } - - @Override - public QueryTokenStream visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) { - return visit(ctx.treatedPath()); - } - - @Override - public QueryTokenStream visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } - if (ctx.DISTINCT() != null) { - builder.append(QueryTokens.expression(ctx.DISTINCT())); + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); } - builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); - return builder; } @@ -1587,20 +3116,6 @@ public QueryTokenStream visitFilterClause(HqlParser.FilterClauseContext ctx) { return builder; } - @Override - public QueryTokenStream visitWithinGroup(HqlParser.WithinGroupContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.WITHIN())); - builder.append(QueryTokens.expression(ctx.GROUP())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.orderByClause())); - builder.append(TOKEN_CLOSE_PAREN); - - return builder; - } - @Override public QueryTokenStream visitOverClause(HqlParser.OverClauseContext ctx) { @@ -1677,140 +3192,6 @@ public QueryTokenStream visitFrameClause(HqlParser.FrameClauseContext ctx) { return builder; } - @Override - public QueryTokenStream visitUnboundedPrecedingFrameStart(HqlParser.UnboundedPrecedingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.UNBOUNDED())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionPrecedingFrameStart(HqlParser.ExpressionPrecedingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameStart(HqlParser.CurrentRowFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionFollowingFrameStart(HqlParser.ExpressionFollowingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameExclusion(HqlParser.CurrentRowFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitGroupFrameExclusion(HqlParser.GroupFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.GROUP())); - - return builder; - } - - @Override - public QueryTokenStream visitTiesFrameExclusion(HqlParser.TiesFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.TIES())); - - return builder; - } - - @Override - public QueryTokenStream visitNoOthersFrameExclusion(HqlParser.NoOthersFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.NO())); - builder.append(QueryTokens.expression(ctx.OTHERS())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionPrecedingFrameEnd(HqlParser.ExpressionPrecedingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameEnd(HqlParser.CurrentRowFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionFollowingFrameEnd(HqlParser.ExpressionFollowingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - - @Override - public QueryTokenStream visitUnboundedFollowingFrameEnd(HqlParser.UnboundedFollowingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.UNBOUNDED())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - @Override public QueryTokenStream visitCastFunction(HqlParser.CastFunctionContext ctx) { @@ -1876,23 +3257,49 @@ public QueryTokenStream visitExtractFunction(HqlParser.ExtractFunctionContext ct QueryRendererBuilder nested = QueryRenderer.builder(); - nested.appendExpression(visit(ctx.expression(0))); + nested.appendExpression(visit(ctx.extractField())); nested.append(QueryTokens.expression(ctx.FROM())); - nested.append(visit(ctx.expression(1))); + nested.append(visit(ctx.expression())); builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); - } else if (ctx.dateTimeFunction() != null) { + } else if (ctx.datetimeField() != null) { - builder.append(visit(ctx.dateTimeFunction())); + builder.append(visit(ctx.datetimeField())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.expression(0))); + builder.appendInline(visit(ctx.expression())); builder.append(TOKEN_CLOSE_PAREN); } return builder; } + @Override + public QueryTokenStream visitExtractField(HqlParser.ExtractFieldContext ctx) { + + if (ctx.datetimeField() != null) { + return visit(ctx.datetimeField()); + } + + if (ctx.dayField() != null) { + return visit(ctx.dayField()); + } + + if (ctx.weekField() != null) { + return visit(ctx.weekField()); + } + + if (ctx.timeZoneField() != null) { + return visit(ctx.timeZoneField()); + } + + if (ctx.dateOrTimeField() != null) { + return visit(ctx.dateOrTimeField()); + } + + return QueryRenderer.builder(); + } + @Override public QueryTokenStream visitTrimFunction(HqlParser.TrimFunctionContext ctx) { @@ -1901,31 +3308,51 @@ public QueryTokenStream visitTrimFunction(HqlParser.TrimFunctionContext ctx) { builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); - if (ctx.LEADING() != null) { - builder.append(QueryTokens.expression(ctx.LEADING())); - } else if (ctx.TRAILING() != null) { - builder.append(QueryTokens.expression(ctx.TRAILING())); - } else if (ctx.BOTH() != null) { - builder.append(QueryTokens.expression(ctx.BOTH())); + if (ctx.trimSpecification() != null) { + builder.appendExpression(visit(ctx.trimSpecification())); } - if (ctx.stringLiteral() != null) { - builder.append(visit(ctx.stringLiteral())); + if (ctx.trimCharacter() != null) { + builder.appendExpression(visit(ctx.trimCharacter())); } if (ctx.FROM() != null) { builder.append(QueryTokens.expression(ctx.FROM())); } - builder.appendInline(visit(ctx.expression())); + if (ctx.expression() != null) { + builder.append(visit(ctx.expression())); + } + builder.append(TOKEN_CLOSE_PAREN); return builder; } @Override - public QueryTokenStream visitDateTimeFunction(HqlParser.DateTimeFunctionContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.d)); + public QueryTokenStream visitTrimSpecification(HqlParser.TrimSpecificationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.BOTH() != null) { + builder.append(QueryTokens.expression(ctx.BOTH())); + } else if (ctx.LEADING() != null) { + builder.append(QueryTokens.expression(ctx.LEADING())); + } else if (ctx.TRAILING() != null) { + builder.append(QueryTokens.expression(ctx.TRAILING())); + } + + return builder.build(); + } + + @Override + public QueryTokenStream visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { + + if (ctx.STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + return visit(ctx.parameter()); } @Override @@ -1933,25 +3360,32 @@ public QueryTokenStream visitEveryFunction(HqlParser.EveryFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.every)); + builder.appendExpression(visit(ctx.everyAllQuantifier())); - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } + if (ctx.predicate() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_OPEN_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } - if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } } else if (ctx.subquery() != null) { - builder.append(visit(ctx.subquery())); - } else if (ctx.simplePath() != null) { - builder.append(visit(ctx.simplePath())); - } + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { - builder.append(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @@ -1961,31 +3395,38 @@ public QueryTokenStream visitAnyFunction(HqlParser.AnyFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.any)); + builder.appendExpression(visit(ctx.anySomeQuantifier())); - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } + if (ctx.predicate() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_OPEN_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } - if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } } else if (ctx.subquery() != null) { - builder.append(visit(ctx.subquery())); - } else if (ctx.simplePath() != null) { - builder.append(visit(ctx.simplePath())); - } + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { - builder.append(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @Override - public QueryTokenStream visitTreatedPath(HqlParser.TreatedPathContext ctx) { + public QueryTokenStream visitTreatedNavigablePath(HqlParser.TreatedNavigablePathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -2000,24 +3441,88 @@ public QueryTokenStream visitTreatedPath(HqlParser.TreatedPathContext ctx) { builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } return builder; } @Override - public QueryTokenStream visitPathContinutation(HqlParser.PathContinutationContext ctx) { + public QueryTokenStream visitCollectionValueNavigablePath(HqlParser.CollectionValueNavigablePathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_DOT); - builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.elementValueQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitMapKeyNavigablePath(HqlParser.MapKeyNavigablePathContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.indexKeyQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitToOneFkReference(HqlParser.ToOneFkReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FK())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } + @Override + public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuantifierContext ctx) { + + if (ctx.ELEMENT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.ELEMENT())); + } + + if (ctx.VALUE() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.VALUE())); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitIndexKeyQuantifier(HqlParser.IndexKeyQuantifierContext ctx) { + + if (ctx.INDEX() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INDEX())); + } + + if (ctx.KEY() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.KEY())); + } + + return QueryTokenStream.empty(); + } + @Override public QueryTokenStream visitIsBooleanPredicate(HqlParser.IsBooleanPredicateContext ctx) { @@ -2262,8 +3767,10 @@ public QueryTokenStream visitStringPatternMatching(HqlParser.StringPatternMatchi builder.append(QueryTokens.expression(ctx.ESCAPE())); - if (ctx.stringLiteral() != null) { - builder.appendExpression(visit(ctx.stringLiteral())); + if (ctx.STRING_LITERAL() != null) { + builder.append(QueryTokens.expression(ctx.STRING_LITERAL())); + } else if (ctx.JAVA_STRING_LITERAL() != null) { + builder.append(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); } else if (ctx.parameter() != null) { builder.appendExpression(visit(ctx.parameter())); } @@ -2421,8 +3928,8 @@ public QueryTokenStream visitVariable(HqlParser.VariableContext ctx) { builder.append(QueryTokens.expression(ctx.AS())); builder.append(visit(ctx.identifier())); - } else if (ctx.reservedWord() != null) { - builder.append(visit(ctx.reservedWord())); + } else if (ctx.nakedIdentifier() != null) { + builder.append(visit(ctx.nakedIdentifier())); } return builder; @@ -2457,30 +3964,33 @@ public QueryTokenStream visitEntityName(HqlParser.EntityNameContext ctx) { @Override public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { - if (ctx.reservedWord() != null) { - return visit(ctx.reservedWord()); - } else { - return QueryTokenStream.empty(); + if (ctx.nakedIdentifier() != null) { + return visit(ctx.nakedIdentifier()); + } else if (ctx.FULL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.FULL())); + } else if (ctx.LEFT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LEFT())); + } else if (ctx.INNER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INNER())); + } else if (ctx.OUTER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.OUTER())); + } else if (ctx.RIGHT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.RIGHT())); } - } - - @Override - public QueryTokenStream visitCharacter(HqlParser.CharacterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); - } - @Override - public QueryTokenStream visitFunctionName(HqlParser.FunctionNameContext ctx) { - return QueryTokenStream.concat(ctx.reservedWord(), this::visit, TOKEN_DOT); + return QueryTokenStream.empty(); } @Override - public QueryTokenStream visitReservedWord(HqlParser.ReservedWordContext ctx) { + public QueryTokenStream visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { - if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + if (ctx.IDENTIFIER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFIER())); + } else if (ctx.QUOTED_IDENTIFIER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.QUOTED_IDENTIFIER())); } else { return QueryRendererBuilder.from(QueryTokens.token(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..21c225d5b4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -162,6 +162,18 @@ private QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx } } + if (ctx.limitClause() != null) { + builder.appendExpression(visit(ctx.limitClause())); + } + + if (ctx.offsetClause() != null) { + builder.appendExpression(visit(ctx.offsetClause())); + } + + if (ctx.fetchClause() != null) { + builder.appendExpression(visit(ctx.fetchClause())); + } + return builder; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/InvalidJpaQueryMethodException.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/InvalidJpaQueryMethodException.java index 5662d7d091..14cdf678d1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/InvalidJpaQueryMethodException.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/InvalidJpaQueryMethodException.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 37ec06e12f..e65fe62343 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,8 +48,11 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; +import java.util.function.Predicate; +import java.util.function.Supplier; import org.springframework.data.domain.Sort; +import org.springframework.data.util.Predicates; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -64,12 +67,12 @@ * @author Geoffrey Deremetz * @author Yanming Zhou * @author Christoph Strobl + * @author Diego Pedregal * @since 2.7.0 */ public class JSqlParserQueryEnhancer implements QueryEnhancer { private final DeclaredQuery query; - private final Statement statement; private final ParsedType parsedType; private final boolean hasConstructorExpression; private final @Nullable String primaryAlias; @@ -84,15 +87,15 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { public JSqlParserQueryEnhancer(DeclaredQuery query) { this.query = query; - this.statement = parseStatement(query.getQueryString(), Statement.class); + Statement statement = parseStatement(query.getQueryString(), Statement.class); this.parsedType = detectParsedType(statement); this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query.getQueryString()); - this.primaryAlias = detectAlias(this.parsedType, this.statement); - this.projection = detectProjection(this.statement); - this.selectAliases = Collections.unmodifiableSet(getSelectionAliases(this.statement)); - this.joinAliases = Collections.unmodifiableSet(getJoinAliases(this.statement)); - this.serialized = SerializationUtils.serialize(this.statement); + this.primaryAlias = detectAlias(this.parsedType, statement); + this.projection = detectProjection(statement); + this.selectAliases = Collections.unmodifiableSet(getSelectionAliases(statement)); + this.joinAliases = Collections.unmodifiableSet(getJoinAliases(statement)); + this.serialized = SerializationUtils.serialize(statement); } /** @@ -144,24 +147,8 @@ private static String detectAlias(ParsedType parsedType, Statement statement) { if (ParsedType.SELECT.equals(parsedType)) { - Select selectStatement = (Select) statement; - - /* - * For all the other types ({@link ValuesStatement} and {@link SetOperationList}) it does not make sense to provide - * alias since: - * ValuesStatement has no alias - * SetOperation can have multiple alias for each operation item - */ - if (!(selectStatement instanceof PlainSelect selectBody)) { - return null; - } - - if (selectBody.getFromItem() == null) { - return null; - } - - Alias alias = selectBody.getFromItem().getAlias(); - return alias == null ? null : alias.getName(); + return doWithPlainSelect(statement, it -> it.getFromItem() == null || it.getFromItem().getAlias() == null, + it -> it.getFromItem().getAlias().getName(), () -> null); } return null; @@ -174,20 +161,24 @@ private static String detectAlias(ParsedType parsedType, Statement statement) { */ private static Set getSelectionAliases(Statement statement) { - if (!(statement instanceof PlainSelect select) || CollectionUtils.isEmpty(select.getSelectItems())) { - return Collections.emptySet(); + if (statement instanceof SetOperationList sel) { + statement = sel.getSelect(0); } - Set set = new HashSet<>(select.getSelectItems().size()); + return doWithPlainSelect(statement, it -> CollectionUtils.isEmpty(it.getSelectItems()), it -> { + + Set set = new HashSet<>(it.getSelectItems().size(), 1.0f); - for (SelectItem selectItem : select.getSelectItems()) { - Alias alias = selectItem.getAlias(); - if (alias != null) { - set.add(alias.getName()); + for (SelectItem selectItem : it.getSelectItems()) { + Alias alias = selectItem.getAlias(); + if (alias != null) { + set.add(alias.getName()); + } } - } - return set; + return set; + + }, Collections::emptySet); } /** @@ -197,21 +188,74 @@ private static Set getSelectionAliases(Statement statement) { */ private static Set getJoinAliases(Statement statement) { - if (!(statement instanceof PlainSelect selectBody) || CollectionUtils.isEmpty(selectBody.getJoins())) { - return Collections.emptySet(); + if (statement instanceof SetOperationList sel) { + statement = sel.getSelect(0); } - Set set = new HashSet<>(selectBody.getJoins().size()); + return doWithPlainSelect(statement, it -> CollectionUtils.isEmpty(it.getJoins()), it -> { - for (Join join : selectBody.getJoins()) { - Alias alias = join.getRightItem().getAlias(); - if (alias != null) { - set.add(alias.getName()); + Set set = new HashSet<>(it.getJoins().size(), 1.0f); + + for (Join join : it.getJoins()) { + Alias alias = join.getRightItem().getAlias(); + if (alias != null) { + set.add(alias.getName()); + } } + return set; + + }, Collections::emptySet); + } + + /** + * Apply a {@link java.util.function.Function mapping function} to the {@link PlainSelect} of the given + * {@link Statement} is or contains a {@link PlainSelect}. + * + * @param statement + * @param mapper + * @param fallback + * @param + * @return + */ + private static T doWithPlainSelect(Statement statement, java.util.function.Function mapper, + Supplier fallback) { + + Predicate neverSkip = Predicates.isFalse(); + return doWithPlainSelect(statement, neverSkip, mapper, fallback); + } + + /** + * Apply a {@link java.util.function.Function mapping function} to the {@link PlainSelect} of the given + * {@link Statement} is or contains a {@link PlainSelect}. + *

+ * The operation is only applied if {@link Predicate skipIf} returns {@literal false} for the given statement + * returning the fallback value from {@code fallback}. + * + * @param statement + * @param skipIf + * @param mapper + * @param fallback + * @param + * @return + */ + private static T doWithPlainSelect(Statement statement, Predicate skipIf, + java.util.function.Function mapper, Supplier fallback) { + + if (!(statement instanceof Select select)) { + return fallback.get(); } - return set; + try { + if (skipIf.test(select.getPlainSelect())) { + return fallback.get(); + } + } + // e.g. SetOperationList is a subclass of Select but it is not a PlainSelect + catch (ClassCastException e) { + return fallback.get(); + } + return mapper.apply(select.getPlainSelect()); } private static String detectProjection(Statement statement) { @@ -230,18 +274,17 @@ private static String detectProjection(Statement statement) { // using the first one since for setoperations the projection has to be the same selectBody = setOperationList.getSelects().get(0); - - if (!(selectBody instanceof PlainSelect)) { - return ""; - } } - StringJoiner joiner = new StringJoiner(", "); - for (SelectItem selectItem : ((PlainSelect) selectBody).getSelectItems()) { - joiner.add(selectItem.toString()); - } - return joiner.toString().trim(); + return doWithPlainSelect(selectBody, it -> CollectionUtils.isEmpty(it.getSelectItems()), it -> { + + StringJoiner joiner = new StringJoiner(", "); + for (SelectItem selectItem : it.getSelectItems()) { + joiner.add(selectItem.toString()); + } + return joiner.toString().trim(); + }, () -> ""); } /** @@ -319,20 +362,22 @@ private String applySorting(Select selectStatement, Sort sort, @Nullable String return applySortingToSetOperationList(setOperationList, sort); } - if (!(selectStatement instanceof PlainSelect selectBody)) { - return selectStatement.toString(); - } + doWithPlainSelect(selectStatement, it -> { - List orderByElements = new ArrayList<>(16); - for (Sort.Order order : sort) { - orderByElements.add(getOrderClause(joinAliases, selectAliases, alias, order)); - } + List orderByElements = new ArrayList<>(16); + for (Sort.Order order : sort) { + orderByElements.add(getOrderClause(joinAliases, selectAliases, alias, order)); + } - if (CollectionUtils.isEmpty(selectBody.getOrderByElements())) { - selectBody.setOrderByElements(orderByElements); - } else { - selectBody.getOrderByElements().addAll(orderByElements); - } + if (CollectionUtils.isEmpty(it.getOrderByElements())) { + it.setOrderByElements(orderByElements); + } else { + it.getOrderByElements().addAll(orderByElements); + } + + return null; + + }, () -> ""); return selectStatement.toString(); } @@ -347,18 +392,13 @@ public String createCountQueryFor(@Nullable String countProjection) { Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty"); Statement statement = (Statement) deserialize(this.serialized); - /* - We only support count queries for {@link PlainSelect}. - */ - if (!(statement instanceof PlainSelect selectBody)) { - return this.query.getQueryString(); - } - return createCountQueryFor(this.query, selectBody, countProjection); + return doWithPlainSelect(statement, it -> createCountQueryFor(it, countProjection, primaryAlias), + this.query::getQueryString); } - private static String createCountQueryFor(DeclaredQuery query, PlainSelect selectBody, - @Nullable String countProjection) { + private static String createCountQueryFor(PlainSelect selectBody, @Nullable String countProjection, + @Nullable String primaryAlias) { // remove order by selectBody.setOrderByElements(null); @@ -373,7 +413,8 @@ private static String createCountQueryFor(DeclaredQuery query, PlainSelect selec selectBody.setDistinct(null); // reset possible distinct Function jSqlCount = getJSqlCount( - Collections.singletonList(countPropertyNameForSelection(selectBody.getSelectItems(), distinct)), distinct); + Collections.singletonList(countPropertyNameForSelection(selectBody.getSelectItems(), distinct, primaryAlias)), + distinct); selectBody.setSelectItems(Collections.singletonList(SelectItem.from(jSqlCount))); } @@ -463,7 +504,8 @@ private static OrderByElement getOrderClause(Set joinAliases, Set> selectItems, boolean distinct) { + private static String countPropertyNameForSelection(List> selectItems, boolean distinct, + @Nullable String tableAlias) { if (onlyASingleColumnProjection(selectItems)) { @@ -472,7 +514,7 @@ private static String countPropertyNameForSelection(List> selectIt return column.getFullyQualifiedName(); } - return (distinct ? "*" : "1"); + return distinct ? ((tableAlias != null ? tableAlias + "." : "") + "*") : "1"; } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java index 5b5cdeb9dd..7c557b5c22 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java index e436624215..5c20838e88 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +15,21 @@ */ package org.springframework.data.jpa.repository.query; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import jakarta.persistence.AttributeNode; import jakarta.persistence.EntityGraph; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import jakarta.persistence.Subgraph; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.springframework.data.jpa.repository.support.MutableQueryHints; import org.springframework.data.jpa.repository.support.QueryHints; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -48,38 +45,16 @@ */ public class Jpa21Utils { - private static final @Nullable Method GET_ENTITY_GRAPH_METHOD; - private static final boolean JPA21_AVAILABLE = ClassUtils.isPresent("jakarta.persistence.NamedEntityGraph", - Jpa21Utils.class.getClassLoader()); - - static { - - if (JPA21_AVAILABLE) { - GET_ENTITY_GRAPH_METHOD = ReflectionUtils.findMethod(EntityManager.class, "getEntityGraph", String.class); - } else { - GET_ENTITY_GRAPH_METHOD = null; - } - } - private Jpa21Utils() { // prevent instantiation } - public static QueryHints getFetchGraphHint(EntityManager em, @Nullable JpaEntityGraph entityGraph, - Class entityType) { + public static QueryHints getFetchGraphHint(EntityManager em, JpaEntityGraph entityGraph, Class entityType) { MutableQueryHints result = new MutableQueryHints(); - if (entityGraph == null) { - return result; - } - EntityGraph graph = tryGetFetchGraph(em, entityGraph, entityType); - if (graph == null) { - return result; - } - result.add(entityGraph.getType().getKey(), graph); return result; } @@ -88,30 +63,27 @@ public static QueryHints getFetchGraphHint(EntityManager em, @Nullable JpaEntity * Adds a JPA 2.1 fetch-graph or load-graph hint to the given {@link Query} if running under JPA 2.1. * * @see Jakarta - * Persistence Specfication - Use of Entity Graphs in find and query operations + * Persistence Specification - Use of Entity Graphs in find and query operations * @param em must not be {@literal null}. * @param jpaEntityGraph must not be {@literal null}. * @param entityType must not be {@literal null}. * @return the {@link EntityGraph} described by the given {@code entityGraph}. */ - @Nullable private static EntityGraph tryGetFetchGraph(EntityManager em, JpaEntityGraph jpaEntityGraph, Class entityType) { Assert.notNull(em, "EntityManager must not be null"); Assert.notNull(jpaEntityGraph, "EntityGraph must not be null"); Assert.notNull(entityType, "EntityType must not be null"); - Assert.isTrue(JPA21_AVAILABLE, "The EntityGraph-Feature requires at least a JPA 2.1 persistence provider"); - Assert.isTrue(GET_ENTITY_GRAPH_METHOD != null, - "It seems that you have the JPA 2.1 API but a JPA 2.0 implementation on the classpath"); + if (StringUtils.hasText(jpaEntityGraph.getName())) { - try { - // first check whether an entityGraph with that name is already registered. - return em.getEntityGraph(jpaEntityGraph.getName()); - } catch (Exception ex) { - // try to create and dynamically register the entityGraph - return createDynamicEntityGraph(em, jpaEntityGraph, entityType); + try { + // check whether an entityGraph with that name is already registered. + return em.getEntityGraph(jpaEntityGraph.getName()); + } catch (Exception ignore) {} } + + return createDynamicEntityGraph(em, jpaEntityGraph, entityType); } /** @@ -129,7 +101,6 @@ private static EntityGraph createDynamicEntityGraph(EntityManager em, JpaEnti Assert.notNull(em, "EntityManager must not be null"); Assert.notNull(jpaEntityGraph, "JpaEntityGraph must not be null"); Assert.notNull(entityType, "Entity type must not be null"); - Assert.isTrue(jpaEntityGraph.isAdHocEntityGraph(), "The given " + jpaEntityGraph + " is not dynamic"); EntityGraph entityGraph = em.createEntityGraph(entityType); configureFetchGraphFrom(jpaEntityGraph, entityGraph); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java index 851a867214..60c4f072ab 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java index d1a6b45935..521532b6ff 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.Arrays; import java.util.List; import org.springframework.data.jpa.repository.EntityGraph; @@ -29,12 +28,11 @@ * * @author Thomas Darimont * @author Mark Paluch + * @author Christoph Strobl * @since 1.6 */ public class JpaEntityGraph { - private static String[] EMPTY_ATTRIBUTE_PATHS = {}; - private final String name; private final EntityGraphType type; private final List attributePaths; @@ -46,8 +44,8 @@ public class JpaEntityGraph { * @param nameFallback must not be {@literal null} or empty. */ public JpaEntityGraph(EntityGraph entityGraph, String nameFallback) { - this(StringUtils.hasText(entityGraph.value()) ? entityGraph.value() : nameFallback, entityGraph.type(), entityGraph - .attributePaths()); + this(StringUtils.hasText(entityGraph.value()) ? entityGraph.value() : nameFallback, entityGraph.type(), + entityGraph.attributePaths()); } /** @@ -65,7 +63,7 @@ public JpaEntityGraph(String name, EntityGraphType type, @Nullable String[] attr this.name = name; this.type = type; - this.attributePaths = Arrays.asList(attributePaths == null ? EMPTY_ATTRIBUTE_PATHS : attributePaths); + this.attributePaths = attributePaths != null ? List.of(attributePaths) : List.of(); } /** @@ -99,9 +97,12 @@ public List getAttributePaths() { /** * Return {@literal true} if this {@link JpaEntityGraph} needs to be generated on-the-fly. * - * @return + * @return {@literal true} if {@link #attributePaths} is not empty. * @since 1.9 + * @deprecated since 3.4.1 as the used evaluation does not represent whether a {@link JpaEntityGraph} is dynamic or + * not. */ + @Deprecated(since = "3.4.1", forRemoval = true) public boolean isAdHocEntityGraph() { return !attributePaths.isEmpty(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java index 3652a84551..f7cc6cc9a0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java index 25e7c25ca9..46908bdd2f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 220a285d8f..b3fc5526f5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java index 6d760d5a3a..e222439a22 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 255ac86dc3..d86880372b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java index c677b2efcc..9d1aaac320 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +20,19 @@ import java.util.function.BiFunction; import java.util.function.Function; +import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTreeVisitor; + import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -65,33 +69,70 @@ class JpaQueryEnhancer implements QueryEnhancer { this.projection = tokens.isEmpty() ? "" : new QueryRenderer.TokenRenderer(tokens).render(); } + /** + * Parse the query and return the parser context (AST). This method attempts parsing the query using + * {@link PredictionMode#SLL} first to attempt a fast-path parse without using the context. If that fails, it retries + * using {@link PredictionMode#LL} which is much slower, however it allows for contextual ambiguity resolution. + */ static

ParserRuleContext parse(String query, Function lexerFactoryFunction, Function parserFactoryFunction, Function parseFunction) { + P parser = getParser(query, lexerFactoryFunction, parserFactoryFunction); + + parser.getInterpreter().setPredictionMode(PredictionMode.SLL); + parser.setErrorHandler(new BailErrorStrategy() { + @Override + public void reportError(Parser recognizer, RecognitionException e) { + + // avoid BadJpqlGrammarException creation in the first pass. + // recover(…) is going to handle cancellation. + } + }); + + try { + + return parseFunction.apply(parser); + } catch (BadJpqlGrammarException | ParseCancellationException e) { + + parser = getParser(query, lexerFactoryFunction, parserFactoryFunction); + // fall back to LL(*)-based parsing + parser.getInterpreter().setPredictionMode(PredictionMode.LL); + + return parseFunction.apply(parser); + } + } + + private static

P getParser(String query, Function lexerFactoryFunction, + Function parserFactoryFunction) { + Lexer lexer = lexerFactoryFunction.apply(CharStreams.fromString(query)); P parser = parserFactoryFunction.apply(new CommonTokenStream(lexer)); - configureParser(query, lexer, parser); + String grammar = lexer.getGrammarFileName(); + int dot = grammar.lastIndexOf('.'); + if (dot != -1) { + grammar = grammar.substring(0, dot); + } + + configureParser(query, grammar.toUpperCase(), lexer, parser); - return parseFunction.apply(parser); + return parser; } /** - * Apply common configuration (SLL prediction for performance, our own error listeners). + * Apply common configuration. * * @param query * @param lexer * @param parser */ - static void configureParser(String query, Lexer lexer, Parser parser) { + static void configureParser(String query, String grammar, Lexer lexer, Parser parser) { - BadJpqlGrammarErrorListener errorListener = new BadJpqlGrammarErrorListener(query); + BadJpqlGrammarErrorListener errorListener = new BadJpqlGrammarErrorListener(query, grammar); lexer.removeErrorListeners(); lexer.addErrorListener(errorListener); - parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - parser.removeErrorListeners(); parser.addErrorListener(errorListener); } @@ -136,6 +177,13 @@ public static JpaQueryEnhancer forEql(DeclaredQuery query) { return EqlQueryParser.parseQuery(query.getQueryString()); } + /** + * @return the parser context (AST) representing the parsed query. + */ + ParserRuleContext getContext() { + return context; + } + /** * Checks if the select clause has a new constructor instantiation in the JPA query. * @@ -180,7 +228,7 @@ public Set getJoinAliases() { */ @Override public DeclaredQuery getQuery() { - throw new UnsupportedOperationException(); + return DeclaredQuery.of(applySorting(Sort.unsorted()), false); } /** 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..35a680c8fe 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java index a998e1a086..82babfb9e4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 136a16b271..6d25af839a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -181,11 +181,9 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate); } - RepositoryQuery query = NamedQuery.lookupFrom(method, em); + RepositoryQuery query = NamedQuery.lookupFrom(method, em, queryRewriter); - return query != null // - ? query // - : NO_QUERY; + return query != null ? query : NO_QUERY; } @Nullable diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 885b989bbe..97b6390d22 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -70,20 +69,10 @@ public class JpaQueryMethod extends QueryMethod { * Persistence Specification: Persistent Fields and Properties - Paragraph starting with * "Collection-valued persistent...". */ - private static final Set> NATIVE_ARRAY_TYPES; + private static final Set> NATIVE_ARRAY_TYPES = Set.of(byte[].class, Byte[].class, char[].class, + Character[].class); private static final StoredProcedureAttributeSource storedProcedureAttributeSource = StoredProcedureAttributeSource.INSTANCE; - static { - - Set> types = new HashSet<>(); - types.add(byte[].class); - types.add(Byte[].class); - types.add(char[].class); - types.add(Character[].class); - - NATIVE_ARRAY_TYPES = Collections.unmodifiableSet(types); - } - private final QueryExtractor extractor; private final Method method; private final Class returnType; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethodFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethodFactory.java index 24326fe0ae..a5b5213127 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethodFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethodFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java index 03c9d82adb..06382e5e9b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..eaecd6e73d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,6 +100,10 @@ private QueryRendererBuilder getDistinctCountSelection(QueryTokenStream selectio if (countSelection.requiresPrimaryAlias()) { // constructor + if (primaryFromAlias == null) { + throw new IllegalStateException( + "Primary alias must be set for DISTINCT count selection using constructor expressions"); + } nested.append(QueryTokens.token(primaryFromAlias)); } else { // keep all the select items to distinct against diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java index 20551ff366..52162e791c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..52455ebe14 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.data.jpa.repository.query.JpqlParser.NullsPrecedenceContext; import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.CollectionUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes. @@ -694,7 +695,7 @@ public QueryTokenStream visitAggregate_expression(JpqlParser.Aggregate_expressio builder.append(QueryTokens.expression(ctx.DISTINCT())); } - builder.appendInline(visit(ctx.state_valued_path_expression())); + builder.appendInline(visit(ctx.simple_select_expression())); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.COUNT() != null) { @@ -703,13 +704,8 @@ public QueryTokenStream visitAggregate_expression(JpqlParser.Aggregate_expressio if (ctx.DISTINCT() != null) { builder.append(QueryTokens.expression(ctx.DISTINCT())); } - if (ctx.identification_variable() != null) { - builder.appendInline(visit(ctx.identification_variable())); - } else if (ctx.state_valued_path_expression() != null) { - builder.appendInline(visit(ctx.state_valued_path_expression())); - } else if (ctx.single_valued_object_path_expression() != null) { - builder.appendInline(visit(ctx.single_valued_object_path_expression())); - } + + builder.appendInline(visit(ctx.simple_select_expression())); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); @@ -1078,8 +1074,8 @@ public QueryTokenStream visitIn_expression(JpqlParser.In_expressionContext ctx) QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.state_valued_path_expression() != null) { - builder.appendExpression(visit(ctx.state_valued_path_expression())); + if (ctx.string_expression() != null) { + builder.appendExpression(visit(ctx.string_expression())); } if (ctx.type_discriminator() != null) { builder.appendExpression(visit(ctx.type_discriminator())); @@ -1113,8 +1109,18 @@ public QueryTokenStream visitIn_item(JpqlParser.In_itemContext ctx) { if (ctx.literal() != null) { return visit(ctx.literal()); + } else if (ctx.string_expression() != null) { + return visit(ctx.string_expression()); + } else if (ctx.boolean_literal() != null) { + return visit(ctx.boolean_literal()); + } else if (ctx.numeric_literal() != null) { + return visit(ctx.numeric_literal()); + } else if (ctx.date_time_timestamp_literal() != null) { + return visit(ctx.date_time_timestamp_literal()); } else if (ctx.single_valued_input_parameter() != null) { return visit(ctx.single_valued_input_parameter()); + } else if (ctx.conditional_expression() != null) { + return visit(ctx.conditional_expression()); } return QueryTokenStream.empty(); @@ -1263,115 +1269,174 @@ public QueryTokenStream visitAll_or_any_expression(JpqlParser.All_or_any_express } @Override - public QueryTokenStream visitComparison_expression(JpqlParser.Comparison_expressionContext ctx) { + public QueryTokenStream visitStringComparison(JpqlParser.StringComparisonContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (!ctx.string_expression().isEmpty()) { + 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))); + } else { + builder.append(visit(ctx.all_or_any_expression())); + } - if (ctx.string_expression(1) != null) { - builder.appendExpression(visit(ctx.string_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.boolean_expression().isEmpty()) { + return builder; + } - builder.appendInline(visit(ctx.boolean_expression(0))); - builder.append(QueryTokens.ventilated(ctx.op)); + @Override + public QueryTokenStream visitBooleanComparison(JpqlParser.BooleanComparisonContext ctx) { - if (ctx.boolean_expression(1) != null) { - builder.appendExpression(visit(ctx.boolean_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.enum_expression().isEmpty()) { + QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendInline(visit(ctx.enum_expression(0))); - builder.append(QueryTokens.ventilated(ctx.op)); + builder.appendInline(visit(ctx.boolean_expression(0))); + builder.append(QueryTokens.ventilated(ctx.op)); - if (ctx.enum_expression(1) != null) { - builder.appendExpression(visit(ctx.enum_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.datetime_expression().isEmpty()) { + if (ctx.boolean_expression(1) != null) { + builder.append(visit(ctx.boolean_expression(1))); + } else { + builder.append(visit(ctx.all_or_any_expression())); + } - builder.appendExpression(visit(ctx.datetime_expression(0))); - builder.appendExpression(visit(ctx.comparison_operator())); + return builder; + } - if (ctx.datetime_expression(1) != null) { - builder.appendExpression(visit(ctx.datetime_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.entity_expression().isEmpty()) { + @Override + public QueryTokenStream visitDirectBooleanCheck(JpqlParser.DirectBooleanCheckContext ctx) { + return visit(ctx.boolean_expression()); + } - builder.appendInline(visit(ctx.entity_expression(0))); - builder.append(QueryTokens.ventilated(ctx.op)); + @Override + public QueryTokenStream visitEnumComparison(JpqlParser.EnumComparisonContext ctx) { - if (ctx.entity_expression(1) != null) { - builder.appendExpression(visit(ctx.entity_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.arithmetic_expression().isEmpty()) { + QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendExpression(visit(ctx.arithmetic_expression(0))); - builder.appendExpression(visit(ctx.comparison_operator())); + builder.appendInline(visit(ctx.enum_expression(0))); + builder.append(QueryTokens.ventilated(ctx.op)); - if (ctx.arithmetic_expression(1) != null) { - builder.appendExpression(visit(ctx.arithmetic_expression(1))); - } else { - builder.appendExpression(visit(ctx.all_or_any_expression())); - } - } else if (!ctx.entity_type_expression().isEmpty()) { + if (ctx.enum_expression(1) != null) { + builder.append(visit(ctx.enum_expression(1))); + } else { + builder.append(visit(ctx.all_or_any_expression())); + } - builder.appendInline(visit(ctx.entity_type_expression(0))); - builder.append(QueryTokens.ventilated(ctx.op)); - builder.appendExpression(visit(ctx.entity_type_expression(1))); + return builder; + } + + @Override + public QueryTokenStream visitDatetimeComparison(JpqlParser.DatetimeComparisonContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.datetime_expression(0))); + builder.append(QueryTokens.ventilated(ctx.comparison_operator().op)); + + if (ctx.datetime_expression(1) != null) { + builder.append(visit(ctx.datetime_expression(1))); + } else { + builder.append(visit(ctx.all_or_any_expression())); } return builder; } + @Override + public QueryTokenStream visitEntityComparison(JpqlParser.EntityComparisonContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.entity_expression(0))); + builder.append(QueryTokens.expression(ctx.op)); + + if (ctx.entity_expression(1) != null) { + builder.append(visit(ctx.entity_expression(1))); + } else { + builder.append(visit(ctx.all_or_any_expression())); + } + + return builder; + } + + @Override + public QueryTokenStream visitArithmeticComparison(JpqlParser.ArithmeticComparisonContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.arithmetic_expression(0))); + builder.append(visit(ctx.comparison_operator())); + + if (ctx.arithmetic_expression(1) != null) { + builder.append(visit(ctx.arithmetic_expression(1))); + } else { + builder.append(visit(ctx.all_or_any_expression())); + } + + return builder; + } + + @Override + public QueryTokenStream visitEntityTypeComparison(JpqlParser.EntityTypeComparisonContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.entity_type_expression(0))); + builder.append(QueryTokens.ventilated(ctx.op)); + builder.append(visit(ctx.entity_type_expression(1))); + + return builder; + } + + @Override + public QueryTokenStream visitRegexpComparison(JpqlParser.RegexpComparisonContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.string_expression())); + builder.append(QueryTokens.expression(ctx.REGEXP())); + builder.appendExpression(visit(ctx.string_literal())); + + return builder; + } + @Override public QueryTokenStream visitComparison_operator(JpqlParser.Comparison_operatorContext ctx) { - return QueryRenderer.from(QueryTokens.token(ctx.op)); + return QueryRendererBuilder.from(QueryTokens.ventilated(ctx.op)); } @Override public QueryTokenStream visitArithmetic_expression(JpqlParser.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.ventilated(ctx.op)); + builder.append(QueryTokens.expression(ctx.op)); builder.append(visit(ctx.arithmetic_term())); - return builder; } else { - return visit(ctx.arithmetic_term()); + builder.append(visit(ctx.arithmetic_term())); } + + return builder; } @Override public QueryTokenStream visitArithmetic_term(JpqlParser.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 { - return visit(ctx.arithmetic_factor()); + builder.append(visit(ctx.arithmetic_factor())); } + + return builder; } @Override @@ -1382,8 +1447,7 @@ public QueryTokenStream visitArithmetic_factor(JpqlParser.Arithmetic_factorConte if (ctx.op != null) { builder.append(QueryTokens.token(ctx.op)); } - - builder.append(visit(ctx.arithmetic_primary())); + builder.appendInline(visit(ctx.arithmetic_primary())); return builder; } @@ -1410,6 +1474,10 @@ 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.arithmetic_cast_function() != null) { + builder.append(visit(ctx.arithmetic_cast_function())); + } else if (ctx.type_cast_function() != null) { + builder.append(visit(ctx.type_cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1439,6 +1507,10 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { builder.append(visit(ctx.case_expression())); + } else if (ctx.string_cast_function() != null) { + builder.append(visit(ctx.string_cast_function())); + } else if (ctx.type_cast_function() != null) { + builder.append(visit(ctx.type_cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1783,6 +1855,62 @@ public QueryTokenStream visitTrim_specification(JpqlParser.Trim_specificationCon } } + @Override + public QueryTokenStream visitArithmetic_cast_function(JpqlParser.Arithmetic_cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.string_expression())); + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + builder.append(QueryTokens.token(ctx.f)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitType_cast_function(JpqlParser.Type_cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.scalar_expression())); + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + builder.appendInline(visit(ctx.identification_variable())); + + if (!CollectionUtils.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 visitString_cast_function(JpqlParser.String_cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.scalar_expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.append(QueryTokens.token(ctx.STRING())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + @Override public QueryTokenStream visitFunction_invocation(JpqlParser.Function_invocationContext ctx) { @@ -1842,16 +1970,7 @@ public QueryTokenStream visitDatetime_part(JpqlParser.Datetime_partContext ctx) @Override public QueryTokenStream visitFunction_arg(JpqlParser.Function_argContext ctx) { - - if (ctx.literal() != null) { - return visit(ctx.literal()); - } else if (ctx.state_valued_path_expression() != null) { - return visit(ctx.state_valued_path_expression()); - } else if (ctx.input_parameter() != null) { - return visit(ctx.input_parameter()); - } else { - return visit(ctx.scalar_expression()); - } + return visit(ctx.simple_select_expression()); } @Override @@ -2049,7 +2168,16 @@ 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())); + + if (ctx.CHARACTER() != null) { + return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + } else if (ctx.character_valued_input_parameter() != null) { + return visit(ctx.character_valued_input_parameter()); + } else if (ctx.string_literal() != null) { + return visit(ctx.string_literal()); + } + + return QueryTokenStream.empty(); } @Override @@ -2108,21 +2236,36 @@ public QueryTokenStream visitSubtype(JpqlParser.SubtypeContext ctx) { @Override public QueryTokenStream visitCollection_valued_field(JpqlParser.Collection_valued_fieldContext ctx) { + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitSingle_valued_object_field(JpqlParser.Single_valued_object_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitState_field(JpqlParser.State_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } return visit(ctx.identification_variable()); } @Override public QueryTokenStream visitCollection_value_field(JpqlParser.Collection_value_fieldContext ctx) { + + if (ctx.reserved_word() != null) { + return visit(ctx.reserved_word()); + } return visit(ctx.identification_variable()); } 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..2b65552fd1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 2942fa0bce..7fb9f3659c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java index 6047c164ca..40aa051983 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java index 79b6d7e0bd..53790bcf4f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 659c04c7de..eeed1593fa 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.jpa.repository.QueryRewriter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryCreationException; import org.springframework.data.repository.query.RepositoryQuery; @@ -53,11 +57,12 @@ final class NamedQuery extends AbstractJpaQuery { private final boolean namedCountQueryIsPresent; private final Lazy declaredQuery; private final QueryParameterSetter.QueryMetadataCache metadataCache; + private final QueryRewriter queryRewriter; /** * Creates a new {@link NamedQuery}. */ - private NamedQuery(JpaQueryMethod method, EntityManager em) { + private NamedQuery(JpaQueryMethod method, EntityManager em, QueryRewriter queryRewriter) { super(method, em); @@ -65,12 +70,14 @@ private NamedQuery(JpaQueryMethod method, EntityManager em) { this.countQueryName = method.getNamedCountQueryName(); QueryExtractor extractor = method.getQueryExtractor(); this.countProjection = method.getCountQueryProjection(); + this.queryRewriter = queryRewriter; Parameters parameters = method.getParameters(); if (parameters.hasSortParameter()) { - throw new IllegalStateException(String.format("Finder method %s is backed by a NamedQuery and must " - + "not contain a sort parameter as we cannot modify the query; Use @Query instead", method)); + throw new IllegalStateException(String.format("Query method %s is backed by a NamedQuery and must " + + "not contain a sort parameter as we cannot modify the query; Use @%s(value=…) instead to apply sorting or remove the 'Sort' parameter.", + method, method.isNativeQuery() ? "NativeQuery" : "Query")); } this.namedCountQueryIsPresent = hasNamedQuery(em, countQueryName); @@ -85,14 +92,14 @@ private NamedQuery(JpaQueryMethod method, EntityManager em) { if (parameters.hasPageableParameter()) { LOG.warn(String.format( - "Finder method %s is backed by a NamedQuery but contains a Pageable parameter; Sorting delivered via this Pageable will not be applied", - method)); + "Query method %s is backed by a NamedQuery but contains a Pageable parameter; Sorting delivered via this Pageable will not be applied; Use @%s(value=…) instead to apply sorting.", + method, method.isNativeQuery() ? "NativeQuery" : "Query")); } String queryString = extractor.extractQueryString(query); - // TODO: Detect whether a named query is a native one. - this.declaredQuery = Lazy.of(() -> DeclaredQuery.of(queryString, query.toString().contains("NativeQuery"))); + this.declaredQuery = Lazy + .of(() -> DeclaredQuery.of(queryString, method.isNativeQuery() || query.toString().contains("NativeQuery"))); this.metadataCache = new QueryParameterSetter.QueryMetadataCache(); } @@ -126,14 +133,15 @@ static boolean hasNamedQuery(EntityManager em, String queryName) { * * @param method must not be {@literal null}. * @param em must not be {@literal null}. + * @param queryRewriter must not be {@literal null}. */ @Nullable - public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em) { + public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em, QueryRewriter queryRewriter) { String queryName = method.getNamedQueryName(); if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Looking up named query %s", queryName)); + LOG.debug(String.format("Looking up named query '%s'", queryName)); } if (!hasNamedQuery(em, queryName)) { @@ -141,12 +149,14 @@ public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em } if (method.isScrollQuery()) { - throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries"); + throw QueryCreationException.create(method, String.format( + "Scroll queries are not supported using String-based queries as we cannot rewrite the query string. Use @%s(value=…) instead.", + method.isNativeQuery() ? "NativeQuery" : "Query")); } - RepositoryQuery query = new NamedQuery(method, em); + RepositoryQuery query = new NamedQuery(method, em, queryRewriter); if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Found named query %s", queryName)); + LOG.debug(String.format("Found named query '%s'", queryName)); } return query; } @@ -184,6 +194,7 @@ protected TypedQuery doCreateCountQuery(JpaParametersParameterAccessor acc } else { String countQueryString = declaredQuery.get().deriveCountQuery(countProjection).getQueryString(); + countQueryString = potentiallyRewriteQuery(countQueryString, accessor.getSort(), accessor.getPageable()); cacheKey = countQueryString; countQuery = em.createQuery(countQueryString, Long.class); } @@ -219,4 +230,20 @@ protected Class getTypeToRead(ReturnedType returnedType) { ? null // : super.getTypeToRead(returnedType); } + + /** + * Use the {@link QueryRewriter}, potentially rewrite the query, using relevant {@link Sort} and {@link Pageable} + * information. + * + * @param originalQuery + * @param sort + * @param pageable + * @return + */ + private String potentiallyRewriteQuery(String originalQuery, Sort sort, Pageable pageable) { + + return pageable.isPaged() // + ? queryRewriter.rewrite(originalQuery, pageable) // + : queryRewriter.rewrite(originalQuery, sort); + } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index 61c4c6ef19..9221cc3807 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,8 +93,15 @@ private Class getTypeToQueryFor(ReturnedType returnedType) { return result; } - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // - ? Tuple.class - : result; + if (returnedType.isProjecting()) { + + if (returnedType.getReturnedType().isInterface()) { + return Tuple.class; + } + + return returnedType.getReturnedType(); + } + + return result; } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index 78fc9531ee..7a49f584a1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java index e5122e93d3..21a715e07f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 493f474f6f..e5cffccaf6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -156,6 +156,10 @@ public Object prepare(@Nullable Object valueToBind) { */ public boolean bindsTo(ParameterBinding other) { + if (getIdentifier().equals(other.getIdentifier())) { + return true; + } + if (identifier.hasName() && other.identifier.hasName()) { if (identifier.getName().equals(other.identifier.getName())) { return true; @@ -503,6 +507,16 @@ static Expression ofExpression(ValueExpression expression) { return new Expression(expression); } + /** + * Creates a {@link MethodInvocationArgument} object for {@code name} + * + * @param name the parameter name from the method invocation. + * @return {@link MethodInvocationArgument} object for {@code name}. + */ + static MethodInvocationArgument ofParameter(String name) { + return ofParameter(name, null); + } + /** * Creates a {@link MethodInvocationArgument} object for {@code name} and {@code position}. Either the name or the * position must be given. 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 213e641a5c..b99f1b93f9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java index fc34eb9b6f..cab53a6e9f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 1d0923f26d..a1246ac056 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java index 6e6a825259..9ebbe5f2a8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Procedure.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java index e86a59151a..0cef0b0a0f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java index 55c168a4f5..1367fa6cad 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java index c40ab11f66..5a2853cb1a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java index a05a34052b..727f61cc81 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 38247f92db..c45c3d8aa3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -240,10 +240,13 @@ public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery decla BindingIdentifier identifier = mia.identifier(); - if (declaredQuery.hasNamedParameter()) { + if (declaredQuery.hasNamedParameter() && identifier.hasName()) { parameter = findParameterForBinding(parameters, identifier.getName()); - } else { + } else if (identifier.hasPosition()) { parameter = findParameterForBinding(parameters, identifier.getPosition() - 1); + } else { + // this can happen when a query uses parameters in ORDER BY and the COUNT query just needs to drop a binding. + parameter = null; } return parameter == null // 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 1bdde97beb..3039ef735a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ * * * @author Mark Paluch + * @author Christoph Strobl */ abstract class QueryRenderer implements QueryTokenStream { @@ -243,7 +244,7 @@ String render() { for (QueryRenderer queryRenderer : nested) { if (lastAppended != null && (lastExpression || queryRenderer.isExpression()) && !builder.isEmpty() - && !lastAppended.endsWith(" ")) { + && (!lastAppended.endsWith(" ") && !lastAppended.endsWith("("))) { builder.append(' '); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java index b3abe97552..85e6d34aa2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryToken.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryToken.java index 9b3c798f15..ac67b8bf0d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryToken.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryToken.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..c91fddb0e4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..90295f3776 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ class QueryTokens { static final QueryToken TOKEN_OPEN_SQUARE_BRACKET = token("["); static final QueryToken TOKEN_CLOSE_SQUARE_BRACKET = token("]"); static final QueryToken TOKEN_COLON = token(":"); + static final QueryToken TOKEN_DASH = token("-"); static final QueryToken TOKEN_QUESTION_MARK = token("?"); static final QueryToken TOKEN_OPEN_BRACE = token("{"); static final QueryToken TOKEN_CLOSE_BRACE = token("}"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java index 46bdc36003..a76749bc69 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,42 @@ static CountSelectionTokenStream create(QueryTokenStream selection) { return new CountSelectionTokenStream(target, containsNew); } + /** + * Filter constructor expression and return the selection list of the constructor. + * + * @return the selection list of the constructor without {@code NEW}, class name, and the first level of + * parentheses. + * @since 3.5.2 + */ + public CountSelectionTokenStream withoutConstructorExpression() { + + if (!requiresPrimaryAlias()) { + return this; + } + + List target = new ArrayList<>(size()); + int nestingLevel = 0; + + for (QueryToken token : this) { + + if (token.equals(TOKEN_OPEN_PAREN)) { + nestingLevel++; + continue; + } + + if (token.equals(TOKEN_CLOSE_PAREN)) { + nestingLevel--; + continue; + } + + if (nestingLevel > 0) { + target.add(token); + } + } + + return new CountSelectionTokenStream(target, requiresPrimaryAlias()); + } + @Override public Iterator iterator() { return tokens.iterator(); 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 3c06c7079b..97506ef9df 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,7 +99,7 @@ public abstract class QueryUtils { public static final String COUNT_QUERY_STRING = "select count(%s) from %s x"; public static final String DELETE_ALL_QUERY_STRING = "delete from %s x"; - public static final String DELETE_ALL_QUERY_BY_ID_STRING = "delete from %s x where %s in :ids"; + public static final String DELETE_ALL_QUERY_BY_ID_STRING = "delete from %s x where x.%s in :ids"; // Used Regex/Unicode categories (see https://www.unicode.org/reports/tr18/#General_Category_Property): // Z Separator @@ -202,17 +202,15 @@ public abstract class QueryUtils { // any function call including parameters within the brackets builder.append("\\w+\\s*\\([\\w\\.,\\s'=:;\\\\?]+\\)"); // the potential alias - builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); + builder.append("\\s+(?:as)+\\s+([\\w\\.]+)"); - FUNCTION_PATTERN = compile(builder.toString()); + FUNCTION_PATTERN = compile(builder.toString(), CASE_INSENSITIVE); builder = new StringBuilder(); - builder.append("\\s+"); // at least one space builder.append("[^\\s\\(\\)]+"); // No white char no bracket - builder.append("\\s+[as|AS]+\\s+(([\\w\\.]+))"); // the potential alias - - FIELD_ALIAS_PATTERN = compile(builder.toString()); + builder.append("\\s+(?:as)+\\s+([\\w\\.]+)"); // the potential alias + FIELD_ALIAS_PATTERN = compile(builder.toString(), CASE_INSENSITIVE); } /** @@ -399,7 +397,7 @@ static Set getOuterJoinAliases(String query) { * @param query a {@literal String} containing a query. Must not be {@literal null}. * @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}. */ - private static Set getFieldAliases(String query) { + static Set getFieldAliases(String query) { Set result = new HashSet<>(); Matcher matcher = FIELD_ALIAS_PATTERN.matcher(query); @@ -889,7 +887,7 @@ private static T getAnnotationProperty(Attribute attribute, String pro } /** - * Returns an existing join for the given attribute if one already exists or creates a new one if not. + * Returns an existing (fetch) join for the given attribute if one already exists or creates a new one if not. * * @param from the {@link From} to get the current joins from. * @param attribute the {@link Attribute} to look for in the current joins. @@ -898,6 +896,13 @@ private static T getAnnotationProperty(Attribute attribute, String pro */ private static Join getOrCreateJoin(From from, String attribute, JoinType joinType) { + for (Fetch fetch : from.getFetches()) { + + if (fetch instanceof Join join && join.getAttribute().getName().equals(attribute)) { + return join; + } + } + for (Join join : from.getJoins()) { if (join.getAttribute().getName().equals(attribute)) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ScrollDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ScrollDelegate.java index 359dfb6ea2..7fbe146bb4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ScrollDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ScrollDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..f2634c1620 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,13 +40,13 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery { * * @param method must not be {@literal null} * @param em must not be {@literal null} - * @param countQueryString + * @param sourceQuery the original source query, must not be {@literal null} or empty. * @param queryRewriter must not be {@literal null} * @param valueExpressionDelegate must not be {@literal null} */ - public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String countQueryString, + public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String sourceQuery, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) { - this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, valueExpressionDelegate); + this(method, em, method.getRequiredAnnotatedQuery(), sourceQuery, queryRewriter, valueExpressionDelegate); } /** @@ -54,21 +54,20 @@ public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String * * @param method must not be {@literal null} * @param em must not be {@literal null} - * @param queryString must not be {@literal null} or empty + * @param sourceQuery the original source query, must not be {@literal null} or empty * @param countQueryString * @param queryRewriter * @param valueExpressionDelegate must not be {@literal null} */ - public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString, QueryRewriter queryRewriter, - ValueExpressionDelegate valueExpressionDelegate) { + public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String sourceQuery, @Nullable String countQueryString, + QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) { - super(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate); + super(method, em, sourceQuery, countQueryString, queryRewriter, valueExpressionDelegate); - validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s", method); + validateQuery(getQuery(), "Query '%s' validation failed for method %s", method); if (method.isPageQuery()) { - validateQuery(getCountQuery().getQueryString(), - String.format("Count query validation failed for method %s", method)); + validateQuery(getCountQuery(), "Count query '%s' validation failed for method %s", method); } } @@ -78,29 +77,20 @@ public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin * @param query * @param errorMessage */ - private void validateQuery(String query, String errorMessage, Object... arguments) { + private void validateQuery(DeclaredQuery query, String errorMessage, JpaQueryMethod method) { if (getQueryMethod().isProcedureQuery()) { return; } - EntityManager validatingEm = null; - - try { - validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager(); - validatingEm.createQuery(query); - + String queryString = query.getQueryString(); + try (EntityManager validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager()) { + validatingEm.createQuery(queryString); } 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(); - } + // https://download.oracle.com/javaee-archive/jpa-spec.java.net/users/2012/07/0404.html + throw new IllegalArgumentException(errorMessage.formatted(queryString, method), e); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java index 770c946f64..2616c3d796 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java index 2acc5f83c1..e7ef76a3eb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java index 54d6b0b24a..8ff29f4ba2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..3e5c7fe733 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -73,6 +74,15 @@ class StringQuery implements DeclaredQuery { * @param query must not be {@literal null} or empty. */ public StringQuery(String query, boolean isNative) { + this(query, isNative, it -> {}); + } + + /** + * Creates a new {@link StringQuery} from the given JPQL query. + * + * @param query must not be {@literal null} or empty. + */ + private StringQuery(String query, boolean isNative, Consumer> parameterPostProcessor) { Assert.hasText(query, "Query must not be null or empty"); @@ -87,6 +97,8 @@ public StringQuery(String query, boolean isNative) { this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters; this.queryEnhancer = QueryEnhancerFactory.forQuery(this); + parameterPostProcessor.accept(this.bindings); + boolean hasNamedParameters = false; for (ParameterBinding parameterBinding : getParameterBindings()) { if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) { @@ -117,15 +129,29 @@ public List getParameterBindings() { @Override public DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection) { - StringQuery stringQuery = new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), // - this.isNative); + // need to copy expression bindings from the declared to the derived query as JPQL query derivation only sees + // JPA parameter markers and not the original expressions anymore. - if (this.hasParameterBindings() && !this.getParameterBindings().equals(stringQuery.getParameterBindings())) { - stringQuery.getParameterBindings().clear(); - stringQuery.getParameterBindings().addAll(this.bindings); - } + return new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), // + this.isNative, derivedBindings -> { + + // need to copy expression bindings from the declared to the derived query as JPQL query derivation only sees + // JPA + // parameter markers and not the original expressions anymore. + if (this.hasParameterBindings() && !this.getParameterBindings().equals(derivedBindings)) { + + for (ParameterBinding binding : bindings) { - return stringQuery; + Predicate identifier = binding::bindsTo; + Predicate notCompatible = Predicate.not(binding::isCompatibleWith); + + // replace incompatible bindings + if (derivedBindings.removeIf(it -> identifier.test(it) && notCompatible.test(it))) { + derivedBindings.add(binding); + } + } + } + }); } @Override @@ -179,7 +205,7 @@ enum ParameterBindingParser { INSTANCE; private static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__"; - public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![#\\w]))"; + public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![\\&\\|#\\w]))"; // .....................................................................^ not followed by a hash or a letter. // .................................................................^ zero or more digits. // .............................................................^ start with a question mark. @@ -243,8 +269,7 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que } ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query, - parametersShouldBeAccessedByIndex, - greatestParameterIndex); + parametersShouldBeAccessedByIndex, greatestParameterIndex); String resultingQuery = parsedQuery.getQueryString(); Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(resultingQuery); @@ -269,7 +294,9 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que Integer parameterIndex = getParameterIndex(parameterIndexString); String match = matcher.group(0); - if (JDBC_STYLE_PARAM.matcher(match).find()) { + Matcher jdbcStyleMatcher = JDBC_STYLE_PARAM.matcher(match); + + if (jdbcStyleMatcher.find()) { queryMeta.usesJdbcStyleParameters = true; } @@ -350,8 +377,7 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que } private static ValueExpressionQueryRewriter.ParsedQuery createSpelExtractor(String queryWithSpel, - boolean parametersShouldBeAccessedByIndex, - int greatestParameterIndex) { + boolean parametersShouldBeAccessedByIndex, int greatestParameterIndex) { /* * If parameters need to be bound by index, we bind the synthetic expression parameters starting from position of the greatest discovered index parameter in order to diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index ae738974b1..4b0b7bacaf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 246e82dcd6..135d3c6e44 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultJpaContext.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultJpaContext.java index e6e51b991d..220ac48587 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultJpaContext.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultJpaContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java index bbf5b12a9a..228251d4f2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ package org.springframework.data.jpa.repository.support; +import jakarta.persistence.EntityManager; + import java.util.Optional; import java.util.function.BiConsumer; -import jakarta.persistence.EntityManager; - import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.Jpa21Utils; import org.springframework.data.jpa.repository.query.JpaEntityGraph; @@ -99,7 +99,7 @@ private QueryHints getFetchGraphs() { return Optionals .mapIfAllPresent(entityManager, metadata.getEntityGraph(), (em, graph) -> Jpa21Utils.getFetchGraphHint(em, getEntityGraph(graph), information.getJavaType())) - .orElse(new MutableQueryHints()); + .orElseGet(MutableQueryHints::new); } private JpaEntityGraph getEntityGraph(EntityGraph entityGraph) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java index 97bf993818..5308fa64b8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java index a3344c2c94..4d84dfc0d4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java index 9ed0a0ce3e..e19e7bf0e2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -132,7 +132,7 @@ public FetchableFluentQuery project(Collection properties) { @Override public R oneValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .limit(2) // Never need more than 2 values .fetch(); @@ -146,7 +146,7 @@ public R oneValue() { @Override public R firstValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .limit(1) // Never need more than 1 value .fetch(); @@ -155,7 +155,11 @@ public R firstValue() { @Override public List all() { - return convert(createSortedAndProjectedQuery().fetch()); + return all(this.sort); + } + + private List all(Sort sort) { + return convert(createSortedAndProjectedQuery(sort).fetch()); } @Override @@ -168,13 +172,13 @@ public Window scroll(ScrollPosition scrollPosition) { @Override public Page page(Pageable pageable) { - return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable); + return pageable.isUnpaged() ? new PageImpl<>(all(pageable.getSortOr(this.sort))) : readPage(pageable); } @Override public Stream stream() { - return createSortedAndProjectedQuery() // + return createSortedAndProjectedQuery(this.sort) // .stream() // .map(getConversionFunction()); } @@ -189,7 +193,7 @@ public boolean exists() { return existsOperation.apply(predicate); } - private AbstractJPAQuery createSortedAndProjectedQuery() { + private AbstractJPAQuery createSortedAndProjectedQuery(Sort sort) { AbstractJPAQuery query = finder.apply(sort); @@ -206,6 +210,7 @@ public boolean exists() { private Page readPage(Pageable pageable) { + Sort sort = pageable.getSortOr(this.sort); AbstractJPAQuery query = pagedFinder.apply(sort, pageable); if (!properties.isEmpty()) { @@ -214,7 +219,8 @@ private Page readPage(Pageable pageable) { List paginatedResults = convert(query.fetch()); - return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(predicate)); + return PageableExecutionUtils.getPage(paginatedResults, withSort(pageable, sort), + () -> countOperation.apply(predicate)); } private List convert(List resultList) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java index 08659af984..4c9f2b501d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,8 +89,14 @@ public FetchableFluentQuery sortBy(Sort sort) { Assert.notNull(sort, "Sort must not be null"); - return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); + Sort sort1 = this.sort.and(sort); + + if (this.sort == sort1) { + return this; + } + + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort1, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -98,8 +104,8 @@ public FetchableFluentQuery limit(int limit) { Assert.isTrue(limit >= 0, "Limit must not be negative"); - return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); + return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -124,7 +130,7 @@ public FetchableFluentQuery project(Collection properties) { @Override public R oneValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(2) // Never need more than 2 values .getResultList(); @@ -138,7 +144,7 @@ public R oneValue() { @Override public R firstValue() { - List results = createSortedAndProjectedQuery() // + List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(1) // Never need more than 1 value .getResultList(); @@ -147,7 +153,11 @@ public R firstValue() { @Override public List all() { - return convert(createSortedAndProjectedQuery().getResultList()); + return all(this.sort); + } + + private List all(Sort sort) { + return convert(createSortedAndProjectedQuery(sort).getResultList()); } @Override @@ -160,13 +170,13 @@ public Window scroll(ScrollPosition scrollPosition) { @Override public Page page(Pageable pageable) { - return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable); + return pageable.isUnpaged() ? new PageImpl<>(all(pageable.getSortOr(this.sort))) : readPage(pageable); } @Override public Stream stream() { - return createSortedAndProjectedQuery() // + return createSortedAndProjectedQuery(this.sort) // .getResultStream() // .map(getConversionFunction()); } @@ -181,7 +191,7 @@ public boolean exists() { return existsOperation.apply(spec); } - private TypedQuery createSortedAndProjectedQuery() { + private TypedQuery createSortedAndProjectedQuery(Sort sort) { TypedQuery query = finder.apply(sort); @@ -198,7 +208,8 @@ private TypedQuery createSortedAndProjectedQuery() { private Page readPage(Pageable pageable) { - TypedQuery pagedQuery = createSortedAndProjectedQuery(); + Sort sort = pageable.getSortOr(this.sort); + TypedQuery pagedQuery = createSortedAndProjectedQuery(sort); if (pageable.isPaged()) { pagedQuery.setFirstResult(PageableUtils.getOffsetAsInteger(pageable)); @@ -207,7 +218,7 @@ private Page readPage(Pageable pageable) { List paginatedResults = convert(pagedQuery.getResultList()); - return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(spec)); + return PageableExecutionUtils.getPage(paginatedResults, withSort(pageable, sort), () -> countOperation.apply(spec)); } private List convert(List resultList) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java index 5917a119f5..27fd7d6b68 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.util.function.Function; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.projection.ProjectionFactory; @@ -49,7 +51,7 @@ abstract class FluentQuerySupport { protected final ProjectionFactory projectionFactory; FluentQuerySupport(Class resultType, Sort sort, int limit, @Nullable Collection properties, - Class entityType, ProjectionFactory projectionFactory) { + Class entityType, ProjectionFactory projectionFactory) { this.resultType = resultType; this.sort = sort; @@ -87,6 +89,15 @@ final Function getConversionFunction(Class inputType, Class tar return o -> DefaultConversionService.getSharedInstance().convert(o, targetType); } + Pageable withSort(Pageable pageable, Sort sort) { + + if (pageable instanceof PageRequest pr && pageable.getSort() != sort) { + return pr.withSort(sort); + } + + return pageable; + } + interface ScrollQueryFactory { Query createQuery(Sort sort, ScrollPosition scrollPosition); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java index 096bd77d21..98828424ab 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java index 00c9f7f27d..6d8c0ba8dc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java index 79b7b10de0..f635a221a4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index 9979fc773b..9293454649 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java index ed0b4644ec..aaaff2050c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java index d5b497f9a2..b8d6e2a587 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 7ecd163244..e14658773b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index f75f45a8d1..86f2f14d6c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java index d3a5b1c5fe..006b39a689 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/MutableQueryHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/MutableQueryHints.java index 4a1e7443b6..46b7a7c77d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/MutableQueryHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/MutableQueryHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java index c32b40f767..0d01e738a8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java index 17e6d01725..8afea10892 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java index 0ade24f133..d895d4717a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,12 +108,11 @@ public JPQLQuery applyPagination(Pageable pageable, JPQLQuery query) { Assert.notNull(pageable, "Pageable must not be null"); Assert.notNull(query, "JPQLQuery must not be null"); - if (pageable.isUnpaged()) { - return query; - } + if (pageable.isPaged()) { - query.offset(pageable.getOffset()); - query.limit(pageable.getPageSize()); + query.offset(pageable.getOffset()); + query.limit(pageable.getPageSize()); + } return applySorting(pageable.getSort(), query); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java index c16f95c0a1..7b31a6ce5c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.util.function.Function; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.Page; @@ -41,6 +42,7 @@ import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.lang.Nullable; @@ -75,6 +77,12 @@ */ public class QuerydslJpaPredicateExecutor implements QuerydslPredicateExecutor, JpaRepositoryConfigurationAware { + private static final String PREDICATE_MUST_NOT_BE_NULL = "Predicate must not be null"; + private static final String ORDER_SPECIFIERS_MUST_NOT_BE_NULL = "Order specifiers must not be null"; + private static final String SORT_MUST_NOT_BE_NULL = "Sort must not be null"; + private static final String PAGEABLE_MUST_NOT_BE_NULL = "Pageable must not be null"; + private static final String QUERY_FUNCTION_MUST_NOT_BE_NULL = "Query function must not be null"; + private final JpaEntityInformation entityInformation; private final EntityPath path; private final Querydsl querydsl; @@ -116,7 +124,7 @@ public void setProjectionFactory(ProjectionFactory projectionFactory) { @Override public Optional findOne(Predicate predicate) { - Assert.notNull(predicate, "Predicate must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); try { return Optional.ofNullable(createQuery(predicate).select(path).limit(2).fetchOne()); @@ -128,7 +136,7 @@ public Optional findOne(Predicate predicate) { @Override public List findAll(Predicate predicate) { - Assert.notNull(predicate, "Predicate must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); return createQuery(predicate).select(path).fetch(); } @@ -136,8 +144,8 @@ public List findAll(Predicate predicate) { @Override public List findAll(Predicate predicate, OrderSpecifier... orders) { - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(orders, "Order specifiers must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); + Assert.notNull(orders, ORDER_SPECIFIERS_MUST_NOT_BE_NULL); return executeSorted(createQuery(predicate).select(path), orders); } @@ -145,8 +153,8 @@ public List findAll(Predicate predicate, OrderSpecifier... orders) { @Override public List findAll(Predicate predicate, Sort sort) { - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(sort, "Sort must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); + Assert.notNull(sort, SORT_MUST_NOT_BE_NULL); return executeSorted(createQuery(predicate).select(path), sort); } @@ -154,7 +162,7 @@ public List findAll(Predicate predicate, Sort sort) { @Override public List findAll(OrderSpecifier... orders) { - Assert.notNull(orders, "Order specifiers must not be null"); + Assert.notNull(orders, ORDER_SPECIFIERS_MUST_NOT_BE_NULL); return executeSorted(createQuery(new Predicate[0]).select(path), orders); } @@ -162,8 +170,8 @@ public List findAll(OrderSpecifier... orders) { @Override public Page findAll(Predicate predicate, Pageable pageable) { - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(pageable, "Pageable must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); + Assert.notNull(pageable, PAGEABLE_MUST_NOT_BE_NULL); final JPQLQuery countQuery = createCountQuery(predicate); JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate).select(path)); @@ -175,8 +183,8 @@ public Page findAll(Predicate predicate, Pageable pageable) { @Override public R findBy(Predicate predicate, Function, R> queryFunction) { - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(queryFunction, "Query function must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); + Assert.notNull(queryFunction, QUERY_FUNCTION_MUST_NOT_BE_NULL); Function> finder = sort -> { AbstractJPAQuery select = (AbstractJPAQuery) createQuery(predicate).select(path); @@ -239,7 +247,14 @@ public R findBy(Predicate predicate, Function) fluentQuery); + R result = queryFunction.apply((FetchableFluentQuery) fluentQuery); + + if (result instanceof FluentQuery) { + throw new InvalidDataAccessApiUsageException( + "findBy(…) queries must result a query result and not the FluentQuery object to ensure that queries are executed within the scope of the findBy(…) method"); + } + + return result; } @Override @@ -260,7 +275,7 @@ public boolean exists(Predicate predicate) { */ protected AbstractJPAQuery createQuery(Predicate... predicate) { - Assert.notNull(predicate, "Predicate must not be null"); + Assert.notNull(predicate, PREDICATE_MUST_NOT_BE_NULL); AbstractJPAQuery query = doCreateQuery(getQueryHints().withFetchGraphs(entityManager), predicate); CrudMethodMetadata metadata = getRepositoryMethodMetadata(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java index 0f32999b14..129d56f6e9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java index a140b734b0..09c43e198b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 bf2faea2f4..3ee2c7736f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition; @@ -62,6 +63,7 @@ import org.springframework.data.jpa.support.PageableUtils; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.util.ProxyUtils; @@ -100,7 +102,11 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation entityInformation; private final EntityManager entityManager; @@ -190,7 +196,7 @@ public void deleteById(ID id) { @SuppressWarnings("unchecked") public void delete(T entity) { - Assert.notNull(entity, "Entity must not be null"); + Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL); if (entityInformation.isNew(entity)) { return; @@ -490,8 +496,8 @@ public long delete(@Nullable Specification spec) { @Override public R findBy(Specification spec, Function, R> queryFunction) { - Assert.notNull(spec, "Specification must not be null"); - Assert.notNull(queryFunction, "Query function must not be null"); + Assert.notNull(spec, SPECIFICATION_MUST_NOT_BE_NULL); + Assert.notNull(queryFunction, QUERY_FUNCTION_MUST_NOT_BE_NULL); return doFindBy(spec, getDomainClass(), queryFunction); } @@ -499,8 +505,8 @@ public R findBy(Specification spec, Function R doFindBy(Specification spec, Class domainClass, Function, R> queryFunction) { - Assert.notNull(spec, "Specification must not be null"); - Assert.notNull(queryFunction, "Query function must not be null"); + Assert.notNull(spec, SPECIFICATION_MUST_NOT_BE_NULL); + Assert.notNull(queryFunction, QUERY_FUNCTION_MUST_NOT_BE_NULL); ScrollQueryFactory scrollFunction = (sort, scrollPosition) -> { @@ -530,7 +536,14 @@ private R doFindBy(Specification spec, Class domainClass, FetchableFluentQueryBySpecification fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass, finder, scrollDelegate, this::count, this::exists, this.entityManager, getProjectionFactory()); - return queryFunction.apply((FetchableFluentQuery) fluentQuery); + R result = queryFunction.apply((FetchableFluentQuery) fluentQuery); + + if (result instanceof FluentQuery) { + throw new InvalidDataAccessApiUsageException( + "findBy(…) queries must result a query result and not the FluentQuery object to ensure that queries are executed within the scope of the findBy(…) method"); + } + + return result; } @Override @@ -589,8 +602,8 @@ public Page findAll(Example example, Pageable pageable) { @Override public R findBy(Example example, Function, R> queryFunction) { - Assert.notNull(example, "Example must not be null"); - Assert.notNull(queryFunction, "Query function must not be null"); + Assert.notNull(example, EXAMPLE_MUST_NOT_BE_NULL); + Assert.notNull(queryFunction, QUERY_FUNCTION_MUST_NOT_BE_NULL); ExampleSpecification spec = new ExampleSpecification<>(example, escapeCharacter); Class probeType = example.getProbeType(); @@ -617,7 +630,7 @@ public long count(@Nullable Specification spec) { @Transactional public S save(S entity) { - Assert.notNull(entity, "Entity must not be null"); + Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL); if (entityInformation.isNew(entity)) { entityManager.persist(entity); @@ -710,7 +723,6 @@ protected Page readPage(TypedQuery query, final Class dom * @param pageable must not be {@literal null}. */ protected TypedQuery getQuery(@Nullable Specification spec, Pageable pageable) { - return getQuery(spec, getDomainClass(), pageable.getSort()); } @@ -723,7 +735,6 @@ protected TypedQuery getQuery(@Nullable Specification spec, Pageable pagea */ protected TypedQuery getQuery(@Nullable Specification spec, Class domainClass, Pageable pageable) { - return getQuery(spec, domainClass, pageable.getSort()); } @@ -990,7 +1001,7 @@ private static class ExampleSpecification implements Specification { */ ExampleSpecification(Example example, EscapeCharacter escapeCharacter) { - Assert.notNull(example, "Example must not be null"); + Assert.notNull(example, EXAMPLE_MUST_NOT_BE_NULL); Assert.notNull(escapeCharacter, "EscapeCharacter must not be null"); this.example = example; 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..324c37f327 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/MergingPersistenceUnitManager.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/MergingPersistenceUnitManager.java index e2703107cc..c3c7fc384f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/MergingPersistenceUnitManager.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/MergingPersistenceUnitManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/PageableUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/PageableUtils.java index c50a8f98aa..fab11c139a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/PageableUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/PageableUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/BeanDefinitionUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/BeanDefinitionUtils.java index 80403737d5..a5c181ee0b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/BeanDefinitionUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/BeanDefinitionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java index 6969cfb4a5..149742c0b7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java index f10b69c61a..fc2cb71ae2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanup.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanup.java index df17d56ade..c9c3e70447 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanup.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanup.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java index c694587167..7c18f5d466 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java index 0cd7169d04..544644ef81 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.data.jpa.convert; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.domain.Example.*; @@ -23,6 +24,7 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -39,6 +41,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -47,6 +51,7 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; +import org.springframework.data.domain.ExampleMatcher.MatchMode; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.util.ObjectUtils; @@ -57,6 +62,7 @@ * @author Mark Paluch * @author Oliver Gierke * @author Jens Schauder + * @author Arnaud Lecointre */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -271,6 +277,21 @@ void likePatternsGetEscapedEnding() { verify(cb, times(1)).like(any(Expression.class), eq("%f\\\\o\\_o"), eq('\\')); } + @ParameterizedTest(name = "Matching {0} on association should join using JoinType.{1} ") // GH-3763 + @CsvSource({ "ALL, INNER", "ANY, LEFT" }) + void matchingAssociationShouldUseTheCorrectJoinType(MatchMode matchMode, JoinType expectedJoinType) { + + Person person = new Person(); + person.father = new Person(); + + ExampleMatcher matcher = matchMode == MatchMode.ALL ? ExampleMatcher.matchingAll() : ExampleMatcher.matchingAny(); + Example example = of(person, matcher); + + QueryByExamplePredicateBuilder.getPredicate(root, cb, example, EscapeCharacter.DEFAULT); + + verify(root, times(1)).join("father", expectedJoinType); + } + @SuppressWarnings("unused") static class Person { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/DateTimeSample.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/DateTimeSample.java index 7743c9868c..579c430b83 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/DateTimeSample.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/DateTimeSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersIntegrationTests.java index 5ef388138e..ff76fbd718 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersUnitTests.java index 92a31cec4a..a1daf39d27 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java index 53d3d0df21..dac929f40d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/SpecificationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/SpecificationUnitTests.java index aeb0c819ea..994f597cf0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/SpecificationUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/SpecificationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,11 +45,12 @@ * @author Jens Schauder * @author Mark Paluch * @author Daniel Shuy + * @author Peter Aisher */ -@SuppressWarnings("serial") +@SuppressWarnings("removal") @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class SpecificationUnitTests implements Serializable { +class SpecificationUnitTests { private Specification spec; @Mock(serializable = true) Root root; @@ -120,7 +121,7 @@ void orConcatenatesNullSpecToSpec() { } @Test // GH-1943 - public void allOfConcatenatesNull() { + void allOfConcatenatesNull() { Specification specification = Specification.allOf(null, spec, null); @@ -129,7 +130,7 @@ public void allOfConcatenatesNull() { } @Test // GH-1943 - public void anyOfConcatenatesNull() { + void anyOfConcatenatesNull() { Specification specification = Specification.anyOf(null, spec, null); @@ -138,7 +139,7 @@ public void anyOfConcatenatesNull() { } @Test // GH-1943 - public void emptyAllOfReturnsEmptySpecification() { + void emptyAllOfReturnsEmptySpecification() { Specification specification = Specification.allOf(); @@ -147,7 +148,7 @@ public void emptyAllOfReturnsEmptySpecification() { } @Test // GH-1943 - public void emptyAnyOfReturnsEmptySpecification() { + void emptyAnyOfReturnsEmptySpecification() { Specification specification = Specification.anyOf(); @@ -163,7 +164,7 @@ void specificationsShouldBeSerializable() { assertThat(specification).isNotNull(); - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "deprecation" }) Specification transferredSpecification = (Specification) deserialize(serialize(specification)); assertThat(transferredSpecification).isNotNull(); @@ -178,7 +179,7 @@ void complexSpecificationsShouldBeSerializable() { assertThat(specification).isNotNull(); - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "deprecation" }) Specification transferredSpecification = (Specification) deserialize(serialize(specification)); assertThat(transferredSpecification).isNotNull(); @@ -206,7 +207,6 @@ void orCombinesSpecificationsInOrder() { Predicate secondPredicate = mock(Predicate.class); Specification first = ((root1, query1, criteriaBuilder) -> firstPredicate); - Specification second = ((root1, query1, criteriaBuilder) -> secondPredicate); first.or(second).toPredicate(root, query, builder); @@ -214,6 +214,15 @@ void orCombinesSpecificationsInOrder() { verify(builder).or(firstPredicate, secondPredicate); } + @Test // GH-3849, GH-4023 + void notWithNullPredicate() { + + Specification notSpec = Specification.not(Specification.where(null)); + + assertThat(notSpec.toPredicate(root, query, builder)).isNull(); + verifyNoInteractions(builder); + } + static class SerializableSpecification implements Serializable, Specification { @Override diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractAnnotatedAuditable.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractAnnotatedAuditable.java index 82adedca45..e3e2ee3472 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractAnnotatedAuditable.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractAnnotatedAuditable.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java index 60a37337b0..801b3fd9c9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Account.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Account.java index 856611e95f..787c4bd64f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Account.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Account.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Address.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Address.java index 468a3ba193..e5db7bfddf 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Address.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AnnotatedAuditableUser.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AnnotatedAuditableUser.java index cbb435fbc2..44bcc91e6f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AnnotatedAuditableUser.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AnnotatedAuditableUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEmbeddable.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEmbeddable.java index 2cb8553073..40563a3f52 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEmbeddable.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEmbeddable.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEntity.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEntity.java index 8cb2b861c8..730fe36d20 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEntity.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableRole.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableRole.java index 9fa9442b30..bfc17a68d2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableRole.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableRole.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java index e69864c553..65d4e6e2ad 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditorAwareStub.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditorAwareStub.java index 2ef54a1ff6..00124d98db 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditorAwareStub.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditorAwareStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java index e66c26ffab..e30fd93869 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Child.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Child.java index 05673e35b0..f3df1a4fe2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Child.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Child.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType1.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType1.java index 90be74aeb7..28fb4e588f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType1.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType1.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType2.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType2.java index 356cc35d52..a30d7b4140 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType2.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ConcreteType2.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/CustomAbstractPersistable.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/CustomAbstractPersistable.java index aa136318aa..dd3fd70449 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/CustomAbstractPersistable.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/CustomAbstractPersistable.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java index 72d01a6267..f8442a9ae4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Dummy.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Dummy.java index e30afcd084..bb8bfe638a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Dummy.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Dummy.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleDepartment.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleDepartment.java index 043457222a..a7aa1b9dfc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleDepartment.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleDepartment.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployee.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployee.java index 0368411310..218c1f6e01 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployee.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployee.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployeePK.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployeePK.java index 1b5e49bef7..393bb6b668 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployeePK.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployeePK.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmployeeWithName.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmployeeWithName.java index ee4f273f10..4668ed04de 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmployeeWithName.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EmployeeWithName.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EntityWithAssignedId.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EntityWithAssignedId.java index 3204f31995..4226b8bb67 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EntityWithAssignedId.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/EntityWithAssignedId.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleDepartment.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleDepartment.java index 2539184afb..7db4bf3486 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleDepartment.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleDepartment.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployee.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployee.java index 18df94dcec..6ad0aeb024 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployee.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployee.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployeePK.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployeePK.java index 142c75b5f9..fd730da59d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployeePK.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployeePK.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Invoice.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Invoice.java index 2f5381f494..a0458759f6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Invoice.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Invoice.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/InvoiceItem.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/InvoiceItem.java index 1ff39954df..c791359af3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/InvoiceItem.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/InvoiceItem.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Item.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Item.java index 1625c2a794..7a01bdf620 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Item.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Item.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemId.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemId.java index 774c609c97..b58f92d049 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemId.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemId.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSite.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSite.java index b5da989a14..f5ad83e344 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSite.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSite.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSiteId.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSiteId.java index c691ac8f50..4d838de5e6 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSiteId.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/ItemSiteId.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailMessage.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailMessage.java index 08d5975559..065e9ec59b 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailMessage.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailSender.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailSender.java index a15de9c1f1..00fffaa244 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailSender.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailUser.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailUser.java index 49911d316a..16d5b2dbcd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailUser.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/MailUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Order.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Order.java index c9146c1bf6..2c544945b7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Order.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Order.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OrmXmlEntity.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OrmXmlEntity.java index b18eb9f18b..48ba2cdeb2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OrmXmlEntity.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OrmXmlEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java index 7661f038a1..820fc189fd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java index 177b8b48ba..f726ca86c1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java index b0a00f0ace..4b9e57c4fe 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClass.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClass.java index 5b6d1277d9..08a31a09bf 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClass.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClassPK.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClassPK.java index 5f7a6ffa43..0dcef9e938 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClassPK.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithIdClassPK.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClass.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClass.java index 38305b4b90..cefd449fc9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClass.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClassPK.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClassPK.java index e405b3c4ba..6466fd1dd8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClassPK.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PersistableWithSingleIdClassPK.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PrimitiveVersionProperty.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PrimitiveVersionProperty.java index c3531adacc..694f896df8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PrimitiveVersionProperty.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/PrimitiveVersionProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Role.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Role.java index f9518a8fbc..101a784ee2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Role.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Role.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java index 8fe586d843..3fafe176f1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntityPK.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntityPK.java index c46143f81d..2ad2f36f3f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntityPK.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntityPK.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithPrimitiveId.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithPrimitiveId.java index a84c1409f5..f1047f1d4f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithPrimitiveId.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithPrimitiveId.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithTimestampVersion.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithTimestampVersion.java index 7dc7d4ab59..3b17fb3a59 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithTimestampVersion.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/SampleWithTimestampVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Site.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Site.java index 996561cdf4..591f4da96e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Site.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Site.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/User.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/User.java index b3e1b4dea2..fafd6fca4a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/User.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java index 3b4d0cbbcd..304dcb5607 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java index b4029f4717..cdfb9a3bfc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java index 348b4433c6..c2ead4cdf7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/VersionedUser.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/VersionedUser.java index 5e5927e1e4..58ff336205 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/VersionedUser.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/VersionedUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AbstractAttributeConverterIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AbstractAttributeConverterIntegrationTests.java index c5a088ddec..34fb8b1bf4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AbstractAttributeConverterIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AbstractAttributeConverterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AnnotationAuditingBeanFactoryPostProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AnnotationAuditingBeanFactoryPostProcessorUnitTests.java index 39160aee50..94702362a0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AnnotationAuditingBeanFactoryPostProcessorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AnnotationAuditingBeanFactoryPostProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessorUnitTests.java index e34cec061d..c5ed0d79df 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingBeanFactoryPostProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerTests.java index e95c8b0542..674fef7bf9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityWithEmbeddableListenerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityWithEmbeddableListenerTests.java index 2887bb4dd3..6701ddc4b9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityWithEmbeddableListenerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityWithEmbeddableListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java index 2835201104..510793ad72 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java index d5711367ef..21c04b3145 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java index 93ff873092..e3cf795046 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java index 284287c990..6ecaceccbc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateTestUtils.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateTestUtils.java index 82efcfadd3..02d29a7673 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateTestUtils.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/HibernateTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java index 2f48b244b4..c821dd76a5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java index 8e437f6473..16983f0f88 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java index 3ed9eaf357..4e2d3c32ea 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextUnitTests.java index ff83c9672e..03962c2aa6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java index e7d42e9e76..ef3af850a1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderIntegrationTests.java index b28943e2d5..0f27bd1422 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java index bcb9582bc6..ba7c3abed7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/AbstractPersistableIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/AbstractPersistableIntegrationTests.java index 67ce3cfa50..bad12168d4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/AbstractPersistableIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/AbstractPersistableIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CrudMethodMetadataUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CrudMethodMetadataUnitTests.java index 76c7e95662..52e217bb71 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CrudMethodMetadataUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CrudMethodMetadataUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomAbstractPersistableIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomAbstractPersistableIntegrationTests.java index 46353534a1..a5ac1bd413 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomAbstractPersistableIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomAbstractPersistableIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomEclipseLinkJpaVendorAdapter.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomEclipseLinkJpaVendorAdapter.java index 124fff7adb..6919ba545d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomEclipseLinkJpaVendorAdapter.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomEclipseLinkJpaVendorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java index 2957ab2e87..359bda9b5e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkEntityGraphRepositoryMethodsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkEntityGraphRepositoryMethodsIntegrationTests.java index e0d21dc716..74820a58a2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkEntityGraphRepositoryMethodsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkEntityGraphRepositoryMethodsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java index a93fb5336b..ad61aa2f55 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java index 06d1575fd5..796e14be8a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkQueryByExampleIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkQueryByExampleIntegrationTests.java index afea59666d..1f0e5e1300 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkQueryByExampleIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkQueryByExampleIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkRepositoryWithCompositeKeyIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkRepositoryWithCompositeKeyIntegrationTests.java index 82ca0c59a9..a2f1bbb5db 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkRepositoryWithCompositeKeyIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkRepositoryWithCompositeKeyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkStoredProcedureIntegrationTests.java index e3ff3d5c07..71a583ebc1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkStoredProcedureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java index 99343adb99..75cfa39d82 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java index 1086577a21..7191b85a4d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityWithAssignedIdIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityWithAssignedIdIntegrationTests.java index 10a84ed886..05e6714895 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityWithAssignedIdIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityWithAssignedIdIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/GreetingsFrom.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/GreetingsFrom.java index 247944ccd0..3e31f95ffb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/GreetingsFrom.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/GreetingsFrom.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateRepositoryTests.java new file mode 100644 index 0000000000..5f965f203e --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/HibernateRepositoryTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assumptions.*; + +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ImportResource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.data.repository.CrudRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +/** + * Hibernate-specific repository tests. + * + * @author Mark Paluch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration() +@Transactional +class HibernateRepositoryTests { + + @Autowired UserRepository userRepository; + @Autowired RoleRepository roleRepository; + @Autowired CteUserRepository cteUserRepository; + @Autowired EntityManager em; + + PersistenceProvider provider; + User dave; + User carter; + User oliver; + Role drummer; + Role guitarist; + Role singer; + + @BeforeEach + void setUp() { + provider = PersistenceProvider.fromEntityManager(em); + + assumeThat(provider).isEqualTo(PersistenceProvider.HIBERNATE); + roleRepository.deleteAll(); + userRepository.deleteAll(); + + drummer = roleRepository.save(new Role("DRUMMER")); + guitarist = roleRepository.save(new Role("GUITARIST")); + singer = roleRepository.save(new Role("SINGER")); + + dave = userRepository.save(new User("Dave", "Matthews", "dave@dmband.com", singer)); + carter = userRepository.save(new User("Carter", "Beauford", "carter@dmband.com", singer, drummer)); + oliver = userRepository.save(new User("Oliver August", "Matthews", "oliver@dmband.com")); + } + + @Test // GH-3726 + void testQueryWithCTE() { + + Page result = cteUserRepository.findWithCTE(PageRequest.of(0, 1)); + assertThat(result.getTotalElements()).isEqualTo(3); + } + + @ImportResource({ "classpath:infrastructure.xml" }) + @Configuration + @EnableJpaRepositories(basePackageClasses = HibernateRepositoryTests.class, considerNestedRepositories = true, + includeFilters = @ComponentScan.Filter( + classes = { CteUserRepository.class, UserRepository.class, RoleRepository.class }, + type = FilterType.ASSIGNABLE_TYPE)) + static class TestConfig {} + + interface CteUserRepository extends CrudRepository { + + /* + WITH entities AS ( + SELECT + e.id as id, + e.number as number + FROM TestEntity e + ) + SELECT new com.example.demo.Result('X', c.id, c.number) + FROM entities c + */ + + @Query(""" + WITH cte_select AS (select u.firstname as firstname, u.lastname as lastname from User u) + SELECT new org.springframework.data.jpa.repository.UserExcerptDto(c.firstname, c.lastname) + FROM cte_select c + """) + Page findWithCTE(Pageable page); + + } + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/JavaConfigUserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/JavaConfigUserRepositoryTests.java index b1aeb29c3a..d87b9e152c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/JavaConfigUserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/JavaConfigUserRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/MappedTypeRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/MappedTypeRepositoryIntegrationTests.java index 1a33ab4bc2..42428a19f2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/MappedTypeRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/MappedTypeRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/NamespaceUserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/NamespaceUserRepositoryTests.java index a37117e848..5c99719d12 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/NamespaceUserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/NamespaceUserRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ORMInfrastructureTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ORMInfrastructureTests.java index 98bd534a78..5cbb6a96bb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ORMInfrastructureTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ORMInfrastructureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaEntityGraphRepositoryMethodsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaEntityGraphRepositoryMethodsIntegrationTests.java index 22390b7682..c42ae99579 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaEntityGraphRepositoryMethodsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaEntityGraphRepositoryMethodsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java index cfe023d9c6..a69fb9e35c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java index 5cf9bc4917..d94ed598c0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaRepositoryWithCompositeKeyIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaRepositoryWithCompositeKeyIntegrationTests.java index e3c8eeb334..c6acc17b33 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaRepositoryWithCompositeKeyIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaRepositoryWithCompositeKeyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaStoredProcedureIntegrationTests.java index 993cb32a04..6984b99e27 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaStoredProcedureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaUserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaUserRepositoryFinderTests.java index d53d6bd531..d1e1b01f66 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaUserRepositoryFinderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/OpenJpaUserRepositoryFinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java index 6b85895950..7b53e6669b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java index a5302d9506..fa9de5e26f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.repository; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; @@ -23,21 +23,27 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import java.util.List; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; /** * @author Greg Turnquist + * @author Christoph Strobl * @since 3.0 */ @ExtendWith(SpringExtension.class) @@ -45,7 +51,8 @@ @Transactional class QueryByExampleIntegrationTests { - @Autowired RoleRepository repository; + @Autowired RoleRepository roleRepository; + @Autowired UserRepository userRepository; @Autowired EntityManager em; private Role drummer; @@ -55,14 +62,14 @@ class QueryByExampleIntegrationTests { @BeforeEach void setUp() { - drummer = repository.save(new Role("drummer")); - guitarist = repository.save(new Role("guitarist")); - singer = repository.save(new Role("singer")); + drummer = roleRepository.save(new Role("drummer")); + guitarist = roleRepository.save(new Role("guitarist")); + singer = roleRepository.save(new Role("singer")); } @AfterEach void clearUp() { - repository.deleteAll(); + roleRepository.deleteAll(); } @Test // GH-2283 @@ -81,6 +88,39 @@ void queryByExampleWithNoPredicatesShouldHaveNoWhereClause() { // then assertThat(predicate).isNull(); - assertThat(repository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + assertThat(roleRepository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + } + + @Test // GH-3763 + void usesAnyMatchOnJoins() { + + User manager = new User("mighty", "super user", "msu@u.io"); + + userRepository.save(manager); + + User dave = new User(); + dave.setFirstname("dave"); + dave.setLastname("matthews"); + dave.setEmailAddress("d@dmb.com"); + dave.addRole(singer); + + User carter = new User(); + carter.setFirstname("carter"); + carter.setLastname("beaufort"); + carter.setEmailAddress("c@dmb.com"); + carter.addRole(drummer); + carter.addRole(singer); + carter.setManager(manager); + + userRepository.saveAllAndFlush(List.of(dave, carter)); + + User probe = new User(); + probe.setLastname(dave.getLastname()); + probe.setManager(manager); + + Example example = Example.of(probe, + ExampleMatcher.matchingAny().withIgnorePaths("id", "createdAt", "age", "active", "emailAddress", + "secondaryEmailAddress", "colleagues", "address", "binaryData", "attributes", "dateOfBirth")); + assertThat(userRepository.findAll(example)).containsExactly(dave, carter); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RedeclaringRepositoryMethodsTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RedeclaringRepositoryMethodsTests.java index b8b1f54511..c4f5c4e30d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RedeclaringRepositoryMethodsTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RedeclaringRepositoryMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java index 64b51acb1c..20613cc1d6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithIdClassKeyTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithIdClassKeyTests.java index b0cbec6b44..5cc0f45ce0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithIdClassKeyTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithIdClassKeyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java index 2a8f849823..5fc3573d64 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SPR8954Tests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SPR8954Tests.java index f731ca2c66..f2a0042a32 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SPR8954Tests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SPR8954Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java index bc11790d8e..efe754ad7b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java index 93615637f5..8cc377e059 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/StoredProcedureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserExcerptDto.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserExcerptDto.java new file mode 100644 index 0000000000..dad70a9a2c --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserExcerptDto.java @@ -0,0 +1,48 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +/** + * Hibernate is still a bit picky on records so let's use a class, just in case. + * + * @author Christoph Strobl + */ +public class UserExcerptDto { + + private String firstname; + private String lastname; + + public UserExcerptDto(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java index 259420a09a..7d8f721c5c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureIntegrationTests.java index de57832f7a..1f158fa4fd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryStoredProcedureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 225336983e..016300697c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -67,9 +68,11 @@ import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.sample.NameOnlyRecord; import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly; +import org.springframework.data.jpa.util.DisabledOnHibernate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; @@ -2353,6 +2356,14 @@ void findByFluentExampleWithSorting() { assertThat(users).containsExactly(thirdUser, firstUser, fourthUser); } + @Test // GH-3294 + void findByFluentFailsReturningFluentQuery() { + + User prototype = new User(); + assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) + .isThrownBy(() -> repository.findBy(of(prototype), Function.identity())); + } + @Test // GH-2294 void findByFluentExampleFirstValue() { @@ -2423,6 +2434,24 @@ void findByFluentExamplePage() { assertThat(page1.getContent()).containsExactly(fourthUser); } + @Test // GH-3762 + void findByFluentExamplePageSortOverride() { + + flushTestUsers(); + + User prototype = new User(); + prototype.setFirstname("v"); + + Example userProbe = of(prototype, matching().withIgnorePaths("age", "createdAt", "active") + .withMatcher("firstname", GenericPropertyMatcher::contains)); + + Page page = repository.findBy(userProbe, // + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2, Sort.by(DESC, "firstname")))); + + assertThat(page.getContent()).containsExactly(fourthUser, firstUser); + assertThat(repository.findAll(page.nextPageable())).containsExactly(secondUser, thirdUser); + } + @Test // GH-2294 void findByFluentExampleWithInterfaceBasedProjection() { @@ -2450,13 +2479,13 @@ void findByFluentExampleWithInterfaceBasedProjectionUsingSpEL() { prototype.setFirstname("v"); List users = repository.findBy( - of(prototype, - matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname", - GenericPropertyMatcher::contains)), // - q -> q.as(UserProjectionUsingSpEL.class).all()); + of(prototype, + matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname", + GenericPropertyMatcher::contains)), // + q -> q.as(UserProjectionUsingSpEL.class).all()); assertThat(users).extracting(UserProjectionUsingSpEL::hello) - .contains(new GreetingsFrom().groot(firstUser.getFirstname())); + .contains(new GreetingsFrom().groot(firstUser.getFirstname())); } @Test // GH-2294 @@ -2678,6 +2707,18 @@ void findByFluentSpecificationPage() { assertThat(page1.getContent()).containsExactly(fourthUser); } + @Test // GH-3762 + void findByFluentSpecificationSortOverridePage() { + + flushTestUsers(); + + Page page = repository.findBy(userHasFirstnameLike("v"), + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2, Sort.by(DESC, "firstname")))); + + assertThat(page.getContent()).containsExactly(fourthUser, firstUser); + assertThat(repository.findAll(page.nextPageable())).containsExactly(secondUser, thirdUser); + } + @Test // GH-2274 void findByFluentSpecificationWithInterfaceBasedProjection() { @@ -2979,6 +3020,20 @@ void supportsInterfaceProjectionsWithNativeQueries() { assertThat(result.getLastname()).isEqualTo(user.getLastname()); } + @Test // GH-2757 + @DisabledOnHibernate("6.2") + void supportsRecordsWithNativeQueries() { + + flushTestUsers(); + + User user = repository.findAll().get(0); + + NameOnlyRecord result = repository.findRecordProjectionByNativeQuery(user.getId()); + + assertThat(result.firstname()).isEqualTo(user.getFirstname()); + assertThat(result.lastname()).isEqualTo(user.getLastname()); + } + @Test // DATAJPA-1248 void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() { @@ -3044,22 +3099,36 @@ void handlesColonsFollowedByIntegerInStringLiteral() { assertThat(users).extracting(User::getId).containsExactly(expected.getId()); } - @Disabled("ORDER BY CASE appears to be a Hibernate-only feature") - @Test // DATAJPA-1233 + @Test // DATAJPA-1233, GH-3756 void handlesCountQueriesWithLessParametersSingleParam() { - // repository.findAllOrderedBySpecialNameSingleParam("Oliver", PageRequest.of(2, 3)); + + flushTestUsers(); + + Page result = repository.findAllOrderedByNamedParam("Oliver", PageRequest.of(0, 3)); + + assertThat(result.getContent()).containsExactly(firstUser, fourthUser, thirdUser); + assertThat(result.getTotalElements()).isEqualTo(4); + + result = repository.findAllOrderedByIndexedParam("Oliver", PageRequest.of(0, 3)); + + assertThat(result.getContent()).containsExactly(firstUser, fourthUser, thirdUser); + assertThat(result.getTotalElements()).isEqualTo(4); } - @Disabled("ORDER BY CASE appears to be a Hibernate-only feature") - @Test // DATAJPA-1233 + @Test // DATAJPA-1233, GH-3756 void handlesCountQueriesWithLessParametersMoreThanOne() { - // repository.findAllOrderedBySpecialNameMultipleParams("Oliver", "x", PageRequest.of(2, 3)); - } - @Disabled("ORDER BY CASE appears to be a Hibernate-only feature") - @Test // DATAJPA-1233 - void handlesCountQueriesWithLessParametersMoreThanOneIndexed() { - // repository.findAllOrderedBySpecialNameMultipleParamsIndexed("x", "Oliver", PageRequest.of(2, 3)); + flushTestUsers(); + + Page result = repository.findAllOrderedBySpecialNameMultipleParams("Oliver", "x", PageRequest.of(0, 3)); + + assertThat(result.getContent()).containsExactly(firstUser, fourthUser, thirdUser); + assertThat(result.getTotalElements()).isEqualTo(4); + + result = repository.findAllOrderedBySpecialNameMultipleParamsIndexed("x", "Oliver", PageRequest.of(0, 3)); + + assertThat(result.getContent()).containsExactly(firstUser, fourthUser, thirdUser); + assertThat(result.getTotalElements()).isEqualTo(4); } // DATAJPA-928 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHintsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHintsUnitTests.java index bce5b13fcd..9f7f6b5f0e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHintsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHintsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java index 1d44e9bf86..e04c20790e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/EntityManagerFactoryProducer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/EntityManagerFactoryProducer.java index a00c0d43b5..d293674d49 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/EntityManagerFactoryProducer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/EntityManagerFactoryProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java index 30d6a7b85d..92994cfef3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtensionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtensionUnitTests.java index 3b76bc9a05..7c8b6c8d94 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtensionUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryExtensionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Person.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Person.java index a889375381..007de3b9c9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Person.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonDB.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonDB.java index c91f3f97bb..abb7b5e6fd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonDB.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonDB.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonRepository.java index 72b4956aba..eb8468db45 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/PersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedCdiConfiguration.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedCdiConfiguration.java index 67e387afbb..ad47eda044 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedCdiConfiguration.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedCdiConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepository.java index 2d41f76bf6..d43704a813 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryBean.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryBean.java index 779fa370ea..7a34f07fb4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryBean.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryCustom.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryCustom.java index e4d49f78a0..ed789c32ca 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryCustom.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedCustomizedUserRepositoryCustom.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedEntityManagerProducer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedEntityManagerProducer.java index 5a2a045bb1..093b5afc16 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedEntityManagerProducer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedEntityManagerProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragment.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragment.java index f379d09c1b..4702152d9b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragment.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragment.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragmentBean.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragmentBean.java index 6ff85427b4..54ce392031 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragmentBean.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedFragmentBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedPersonRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedPersonRepository.java index a9fc2fe9a3..487121fc7a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedPersonRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/QualifiedPersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/RepositoryConsumer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/RepositoryConsumer.java index f9ff0fa711..2833ea63b0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/RepositoryConsumer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/RepositoryConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepository.java index 5e99a709a4..559370d8fe 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryCustom.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryCustom.java index 072332f0fb..66a910a882 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryCustom.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryCustom.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryImpl.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryImpl.java index 4e14a00ea4..ed7ef43cdd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryImpl.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/SamplePersonRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Transactional.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Transactional.java index ddc35d77bc..d50e810b25 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Transactional.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/Transactional.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/TransactionalInterceptor.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/TransactionalInterceptor.java index 1e9813df95..51d2bc3b29 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/TransactionalInterceptor.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/TransactionalInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedEntityManagerProducer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedEntityManagerProducer.java index d18edec881..43eded0a81 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedEntityManagerProducer.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedEntityManagerProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedPersonRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedPersonRepository.java index 47677d70ac..fb7a4dd096 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedPersonRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UnqualifiedPersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UserDB.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UserDB.java index d89737beb0..93f14563c7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UserDB.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/UserDB.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..5285ed2e3e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractRepositoryConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractRepositoryConfigTests.java index 8d6baca3e3..64da44a6a0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractRepositoryConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractRepositoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AllowNestedRepositoriesRepositoryConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AllowNestedRepositoriesRepositoryConfigTests.java index 6110e02b1c..edae851478 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AllowNestedRepositoriesRepositoryConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AllowNestedRepositoriesRepositoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java index 14edf2c213..b739493a23 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/CustomRepositoryFactoryConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/CustomRepositoryFactoryConfigTests.java index 9dc0b3d0fc..02f2f2a1fe 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/CustomRepositoryFactoryConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/CustomRepositoryFactoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/DefaultAuditingViaJavaConfigRepositoriesTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/DefaultAuditingViaJavaConfigRepositoriesTests.java index 69206bb28e..5e40224732 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/DefaultAuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/DefaultAuditingViaJavaConfigRepositoriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/ExplicitAuditingViaJavaConfigRepositoriesTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/ExplicitAuditingViaJavaConfigRepositoriesTests.java index cc3ff4e612..d0c5fd06f4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/ExplicitAuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/ExplicitAuditingViaJavaConfigRepositoriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java index 8542911649..4fdaad1022 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InspectionClassLoaderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InspectionClassLoaderUnitTests.java index beb61ed5ad..0b3da373e5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InspectionClassLoaderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/InspectionClassLoaderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrarUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrarUnitTests.java index 623f3d5249..504ac40d24 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrarUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrarUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java index d6b550f0ea..8d4a11955e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarUnitTests.java index 3c293e245b..3b68b30365 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigDefinitionParserTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigDefinitionParserTests.java index 09751277d3..2cbadc8275 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigDefinitionParserTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java index 54ca71d9f7..df4e2097e5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java index ef9011e437..714abc2afa 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/NestedRepositoriesJavaConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/NestedRepositoriesJavaConfigTests.java index 2cf100c402..7384a64cb6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/NestedRepositoriesJavaConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/NestedRepositoriesJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/QueryLookupStrategyTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/QueryLookupStrategyTests.java index 7ea61726f6..22cb434931 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/QueryLookupStrategyTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/QueryLookupStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoriesJavaConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoriesJavaConfigTests.java index 688e5471a8..bb40ccfc01 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoriesJavaConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoriesJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryAutoConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryAutoConfigTests.java index a6eada978a..f08f7bb2ee 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryAutoConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryAutoConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryConfigTests.java index 10977363f9..722e137690 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/RepositoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/TypeFilterConfigTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/TypeFilterConfigTests.java index b8e35ce52b..56b449f9b6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/TypeFilterConfigTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/TypeFilterConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java index e9c53f287a..00fe7c6aa1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java index ab6b76002b..d898522b32 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactoryBean.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactoryBean.java index 09a702b6f0..b2c13f48dc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactoryBean.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericRepository.java index b39c31c296..b5d8b10b8e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/UserCustomExtendedRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/UserCustomExtendedRepository.java index dd537e7155..1606e832f8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/UserCustomExtendedRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/custom/UserCustomExtendedRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java index e7725e53e6..9c3019106f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java index 364bcbfa7f..ce40a725bc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/MySqlStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/MySqlStoredProcedureIntegrationTests.java index b248e809f6..5366736fc9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/MySqlStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/MySqlStoredProcedureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..af07eb0013 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureNullHandlingIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureNullHandlingIntegrationTests.java index 98783a9c28..c5fba7eff7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureNullHandlingIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureNullHandlingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/StoredProcedureConfigSupport.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/StoredProcedureConfigSupport.java index 0b8ef1f9cd..998245126b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/StoredProcedureConfigSupport.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/StoredProcedureConfigSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionJoinIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionJoinIntegrationTests.java index 1c952765e6..d0bdce94bd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionJoinIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionJoinIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java index f744dd540d..9abc3716e0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java index 1f7db3cc42..8728e03229 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java index 9cb74dcaa0..6590db4022 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java index b2e6ba4fce..d9073be331 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarExceptionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarExceptionUnitTests.java new file mode 100644 index 0000000000..16766673fc --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarExceptionUnitTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link BadJpqlGrammarException}. + * + * @author Mark Paluch + */ +class BadJpqlGrammarExceptionUnitTests { + + @Test // GH-3757 + void shouldContainOriginalText() { + + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> JpaQueryEnhancer.HqlQueryParser + .parseQuery("SELECT e FROM Employee e WHERE FOO(x).bar RESPECTING NULLS")) + .withMessageContaining("no viable alternative") + .withMessageContaining("SELECT e FROM Employee e WHERE FOO(x).bar *RESPECTING NULLS") + .withMessageContaining("Bad HQL grammar [SELECT e FROM Employee e WHERE FOO(x).bar RESPECTING NULLS]"); + } + + @Test // GH-3757 + void shouldReportExtraneousInput() { + + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> JpaQueryEnhancer.HqlQueryParser.parseQuery("select * from User group by name")) + .withMessageContaining("extraneous input '*'") + .withMessageContaining("Bad HQL grammar [select * from User group by name]"); + } + + @Test // GH-3757 + void shouldReportMismatchedInput() { + + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> JpaQueryEnhancer.HqlQueryParser.parseQuery("SELECT AVG(m.price) AS m.avg FROM Magazine m")) + .withMessageContaining("mismatched input '.'").withMessageContaining("expecting one of the following tokens:") + .withMessageContaining("EXCEPT") + .withMessageContaining("Bad HQL grammar [SELECT AVG(m.price) AS m.avg FROM Magazine m]"); + } + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CollectionUtilsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CollectionUtilsUnitTests.java index dd488b1ac5..9ab7aa87da 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CollectionUtilsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CollectionUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CustomNonBindableJpaParametersIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CustomNonBindableJpaParametersIntegrationTests.java index 724f0e1bf7..50ca107e95 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CustomNonBindableJpaParametersIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/CustomNonBindableJpaParametersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java index da2bb066ac..43a87c8282 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryUtilsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryUtilsUnitTests.java index cf3eb4867c..0e6b4a577c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryUtilsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkJpa21UtilsTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkJpa21UtilsTests.java index bd97fbb600..19a2701e0e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkJpa21UtilsTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkJpa21UtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkMetaAnnotatedQueryMethodIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkMetaAnnotatedQueryMethodIntegrationTests.java index df7b27f9d9..bf8e408d52 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkMetaAnnotatedQueryMethodIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkMetaAnnotatedQueryMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java index c89f75309f..1e253c5acb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkParameterMetadataProviderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java index f70c7186f7..ce1b95d90e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EclipseLinkQueryUtilsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,25 @@ */ package org.springframework.data.jpa.repository.query; +import static org.assertj.core.api.Assertions.*; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import org.junit.jupiter.api.Test; + +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.mapping.PropertyPath; import org.springframework.test.context.ContextConfiguration; /** + * EclipseLink variant of {@link QueryUtilsIntegrationTests}. + * * @author Oliver Gierke * @author Jens Schauder + * @author Mark Paluch */ @ContextConfiguration("classpath:eclipselink.xml") class EclipseLinkQueryUtilsIntegrationTests extends QueryUtilsIntegrationTests { @@ -28,4 +42,25 @@ int getNumberOfJoinsAfterCreatingAPath() { return 1; } + @Test // GH-2756 + @Override + void prefersFetchOverJoin() { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(User.class); + Root from = query.from(User.class); + from.fetch("https://github.com/manager"); + from.join("manager"); + + PropertyPath managerFirstname = PropertyPath.from("manager.firstname", User.class); + PropertyPath managerLastname = PropertyPath.from("manager.lastname", User.class); + + QueryUtils.toExpressionRecursively(from, managerLastname); + Path expr = (Path) QueryUtils.toExpressionRecursively(from, managerFirstname); + + assertThat(expr.getParentPath()).hasFieldOrPropertyWithValue("isFetch", true); + assertThat(from.getFetches()).hasSize(1); + assertThat(from.getJoins()).hasSize(1); + } + } 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..2ec5f229a1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,8 @@ import static org.assertj.core.api.Assertions.*; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -40,14 +39,9 @@ class EqlComplianceTests { */ private static String parseWithoutChanges(String query) { - EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); - EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - EqlParser.StartContext parsedQuery = parser.start(); + JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser.parseQuery(query); - return TokenRenderer.render(new EqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new EqlQueryRenderer().visit(parser.getContext())); } private void assertQuery(String query) { @@ -78,6 +72,7 @@ void selectClause() { assertQuery("SELECT COUNT(e) FROM Employee e"); assertQuery("SELECT MAX(e.salary) FROM Employee e"); + assertQuery("select sum(i.size.foo.bar.new) from Item i"); assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); } @@ -348,8 +343,11 @@ 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 SIZE(e.managedEmployees.new) < 2"); assertQuery("SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY"); + assertQuery("SELECT e FROM Employee e WHERE e.managedEmployee.size.new IS EMPTY"); assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities"); + assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities.size"); assertQuery("SELECT p FROM Project p WHERE TYPE(p) = LargeProject"); /** diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlParserQueryEnhancerUnitTests.java index 241a5310b5..8895fc4c19 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlParserQueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..ded1101a54 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,13 @@ import java.util.stream.Stream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -37,6 +36,7 @@ * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch */ class EqlQueryRendererTests { @@ -47,14 +47,9 @@ class EqlQueryRendererTests { */ private static String parseWithoutChanges(String query) { - EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); - EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - EqlParser.StartContext parsedQuery = parser.start(); + JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser.parseQuery(query); - return TokenRenderer.render(new EqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new EqlQueryRenderer().visit(parser.getContext())); } static Stream reservedWords() { @@ -343,6 +338,15 @@ OR TREAT(e AS Contractor).hours > 100 """); } + @Test // GH-3024, GH-3863 + void casting() { + + assertQuery(""" + select cast(i as string) from Item i where cast(i.date as date) <= cast(:currentDateTime as date) + """); + assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0"); + } + @Test void pathExpressionsNamedParametersExample() { @@ -560,6 +564,18 @@ WHERE TYPE(e) IN :empTypes """); } + @Test + void inClauseWithFunctionAndLiterals() { + + assertQuery(""" + select f from FooEntity f where upper(f.name) IN ('Y', 'Basic', 'Remit') + """); + assertQuery( + """ + select count(f) from FooEntity f where f.status IN (com.example.eql_bug_check.entity.FooStatus.FOO, com.example.eql_bug_check.entity.FooStatus.BAR) + """); + } + @Test void notEqualsForTypeShouldWork() { @@ -590,6 +606,14 @@ SELECT c.country, COUNT(c) GROUP BY c.country HAVING COUNT(c) > 30 """); + + assertQuery(""" + SELECT COUNT(f) + FROM FooEntity f + WHERE f.name IN ('Y', 'Basic', 'Remit') + AND f.size = 10 + HAVING COUNT(f) > 0 + """); } @Test @@ -928,6 +952,14 @@ void findOrdersThatHaveProductNamedByAParameter() { """); } + @Test // GH-4013 + void minMaxFunctionsShouldWork() { + assertQuery("SELECT MAX(e.age), e.address.city FROM Employee e"); + assertQuery("SELECT MAX(1), e.address.city FROM Employee e"); + assertQuery("SELECT MAX(MIN(MOD(e.salary, 10))), e.address.city FROM Employee e"); + assertQuery("SELECT MIN(MOD(e.salary, 10)), e.address.city FROM Employee e"); + } + @Test // GH-2982 void floorShouldBeValidEntityName() { @@ -1032,6 +1064,14 @@ void signedExpressionsShouldWork(String query) { assertQuery(query); } + @Test // GH-3873 + void escapeClauseShouldWork() { + assertQuery("select t.name from SomeDbo t where t.name LIKE :name escape '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE ?1"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE :param"); + } + @ParameterizedTest // GH-3451 @MethodSource("reservedWords") void entityNameWithPackageContainingReservedWord(String reservedWord) { @@ -1046,4 +1086,15 @@ void lateralShouldBeAValidParameter() { assertQuery("select e from Employee e where e.lateral = :_lateral"); assertQuery("select te from TestEntity te where te.lateral = :lateral"); } + + @Test // GH-3834 + void reservedWordsShouldWork() { + + assertQuery("select ie from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie.object from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie from ItemExample ie left join ie.object io where io.object = :externalId"); + assertQuery("select ie from ItemExample ie where ie.status = com.app.domain.object.Status.UP"); + assertQuery("select f from FooEntity f where upper(f.name) IN :names"); + assertQuery("select f from FooEntity f where f.size IN :sizes"); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java index a85214f43d..3c1fec2ed3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..bff45ec75d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,9 @@ import static org.assertj.core.api.Assertions.*; -import org.antlr.v4.runtime.CharStreams; -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; /** @@ -37,14 +36,9 @@ class EqlSpecificationTests { private static String parseWithoutChanges(String query) { - EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); - EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - EqlParser.StartContext parsedQuery = parser.start(); + JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser.parseQuery(query); - return TokenRenderer.render(new EqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new EqlQueryRenderer().visit(parser.getContext())); } private void assertQuery(String query) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EscapeCharacterUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EscapeCharacterUnitTests.java index 418fbff3e0..ca16538f7d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EscapeCharacterUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EscapeCharacterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java index d651db1393..049773d450 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,25 +97,13 @@ void shouldDetectBindParameterCountCorrectlyWithJDBCStyleParameters() { assertThat(query.getParameterBindings()).hasSize(8); } - @Test - void shouldDetectComplexNativeQueriesWithSpelAsNonNative() { - - StringQuery query = new ExpressionBasedStringQuery( - "select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )" - + "AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)" - + "AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})" - + "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})", - metadata, PARSER, true); - - assertThat(query.isNativeQuery()).isFalse(); - } + @Test // GH-3979 + void shouldExpandExpressionUsingNativeQueries() { - @Test - void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() { - - StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, PARSER, true); + StringQuery query = new ExpressionBasedStringQuery("select n.* from #{#entityName} n", metadata, PARSER, true); - assertThat(query.isNativeQuery()).isFalse(); + assertThat(query.isNativeQuery()).isTrue(); + assertThat(query.getQueryString()).isEqualTo("select n.* from User n"); } @Test @@ -177,7 +165,7 @@ void indexedExpressionsShouldCreateLikeBindings() { } @Test - public void doesTemplatingWhenEntityNameSpelIsPresent() { + void doesTemplatingWhenEntityNameSpelIsPresent() { StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from #{#entityName} u", metadata, PARSER, false); @@ -186,7 +174,7 @@ public void doesTemplatingWhenEntityNameSpelIsPresent() { } @Test - public void doesNoTemplatingWhenEntityNameSpelIsNotPresent() { + void doesNoTemplatingWhenEntityNameSpelIsNotPresent() { StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from User u", metadata, PARSER, false); @@ -195,7 +183,7 @@ public void doesNoTemplatingWhenEntityNameSpelIsNotPresent() { } @Test - public void doesTemplatingWhenEntityNameSpelIsPresentForBindParameter() { + void doesTemplatingWhenEntityNameSpelIsPresentForBindParameter() { StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u where name = :#{#something}", metadata, PARSER, false); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserQueryEnhancerUnitTests.java index 7d57ed37ad..ef7b269115 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserQueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserUnitTests.java index 62f194ea66..fb56b657f3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlParserUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 8a23e279cd..d064fd4d3e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,6 @@ import java.util.stream.Stream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -37,6 +35,8 @@ * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch + * @author Yannick Brandt * @since 3.1 */ class HqlQueryRendererTests { @@ -48,14 +48,9 @@ class HqlQueryRendererTests { */ private static String parseWithoutChanges(String query) { - HqlLexer lexer = new HqlLexer(CharStreams.fromString(query)); - HqlParser parser = new HqlParser(new CommonTokenStream(lexer)); + JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser.parseQuery(query); - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - HqlParser.StartContext parsedQuery = parser.start(); - - QueryTokenStream tokens = new HqlQueryRenderer().visit(parsedQuery); + QueryTokenStream tokens = new HqlQueryRenderer().visit(parser.getContext()); return QueryRenderer.from(tokens).render(); } @@ -178,6 +173,180 @@ void pathExpressionSyntaxExample1() { """); } + @Test // GH-3711 + void entityTypeReference() { + + assertQuery(""" + SELECT TYPE(e) + FROM Employee e + """); + + assertQuery(""" + SELECT TYPE(?0) + FROM Employee e + """); + } + + @Test // GH-3711 + void entityIdReference() { + + assertQuery(""" + SELECT ID(e) + FROM Employee e + """); + + assertQuery(""" + SELECT ID(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void entityNaturalIdReference() { + + assertQuery(""" + SELECT NATURALID(e) + FROM Employee e + """); + + assertQuery(""" + SELECT NATURALID(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void entityVersionReference() { + + assertQuery(""" + SELECT VERSION(e) + FROM Employee e + """); + } + + @Test // GH-3711 + void treatedNavigablePath() { + + assertQuery(""" + SELECT TREAT(e as Integer).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void collectionValueNavigablePath() { + + assertQuery(""" + SELECT ELEMENT(e) + FROM Employee e + """); + + assertQuery(""" + SELECT ELEMENT(e).foo + FROM Employee e + """); + + assertQuery(""" + SELECT VALUE(e) + FROM Employee e + """); + + assertQuery(""" + SELECT VALUE(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void mapKeyNavigablePath() { + + assertQuery(""" + SELECT KEY(e) + FROM Employee e + """); + + assertQuery(""" + SELECT KEY(e).foo + FROM Employee e + """); + + assertQuery(""" + SELECT INDEX(e) + FROM Employee e + """); + } + + @Test // GH-3711 + void toOneFkReference() { + + assertQuery(""" + SELECT FK(e) + FROM Employee e + """); + + assertQuery(""" + SELECT FK(e.foo) + FROM Employee e + """); + } + + @Test // GH-3711 + void indexedPathAccessFragment() { + + assertQuery(""" + SELECT e.names[0] + FROM Employee e + """); + + assertQuery(""" + SELECT e.payments[1].id + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[0] + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[1].id + FROM Employee e + """); + } + + @Test // GH-3711 + void slicedPathAccessFragment() { + + assertQuery(""" + SELECT e.names[0:1] + FROM Employee e + """); + + assertQuery(""" + SELECT e.payments[1:2].id + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[0:1] + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[1:2].id + FROM Employee e + """); + } + + @Test // GH-3711 + void functionPathContinuation() { + + assertQuery(""" + SELECT some_function().foo + FROM Employee e + """); + } + @Test void joinsExample1() { @@ -298,7 +467,7 @@ void fromClauseDowncastingExample1() { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -307,7 +476,7 @@ void fromClauseDowncastingExample2() { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -322,7 +491,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 @@ -333,7 +502,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 @@ -343,7 +512,7 @@ void fromClauseDowncastingExample4() { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); } @Test @@ -407,7 +576,7 @@ void allExample() { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -419,7 +588,7 @@ void existsSubSelectExample2() { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -487,7 +656,7 @@ void updateCaseExample1() { WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -500,7 +669,7 @@ void updateCaseExample2() { WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -540,7 +709,7 @@ void theRest() { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test @@ -622,6 +791,18 @@ HAVING COUNT(o) >= 5 """); } + @Test + void shouldRenderHavingWithFunction() { + + assertQuery(""" + SELECT COUNT(f) + FROM FooEntity f + WHERE f.name IN ('Y', 'Basic', 'Remit') + AND f.size = 10 + HAVING COUNT(f) > 0 + """); + } + @Test void theRest8() { @@ -1495,6 +1676,20 @@ void orderByWithNullsFirstOrLastShouldWork() { }); } + @Test // GH-3882 + void shouldSupportLimitOffset() { + + assertQuery("SELECT si from StockItem si order by si.id LIMIT 10 OFFSET 10 FETCH FIRST 10 ROWS ONLY"); + assertQuery("SELECT si from StockItem si order by si.id LIMIT ? OFFSET ? FETCH FIRST ? ROWS ONLY"); + assertQuery("SELECT si from StockItem si order by si.id LIMIT :l OFFSET :o"); + assertQuery("SELECT si from StockItem si LIMIT :l OFFSET :o"); + assertQuery("SELECT si from StockItem si order by si.id LIMIT :l"); + assertQuery("SELECT si from StockItem si order by si.id OFFSET 1"); + assertQuery("SELECT si from StockItem si LIMIT 1"); + assertQuery("SELECT si from StockItem si OFFSET 1"); + assertQuery("SELECT si from StockItem si FETCH FIRST 1 ROWS ONLY"); + } + @Test // GH-2964 void roundFunctionShouldWorkLikeAnyOtherFunction() { @@ -1508,6 +1703,22 @@ select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc }); } + @Test // GH-3711 + void ceilingFunctionShouldWork() { + assertQuery("select ceiling(1.5) from Element a"); + } + + @Test // GH-3711 + void lnFunctionShouldWork() { + assertQuery("select ln(7.5) from Element a"); + } + + @Test // GH-4013 + void minMaxFunctionsShouldWork() { + assertQuery("SELECT MAX(MIN(MOD(e.salary, 10))), e.address.city FROM Employee e"); + assertQuery("SELECT MIN(MOD(e.salary, 10)), e.address.city FROM Employee e"); + } + @Test // GH-2981 void cteWithClauseShouldWork() { @@ -1519,6 +1730,31 @@ WITH maxId AS (select max(sr.snapshot.id) snapshotId from SnapshotReference sr """); } + @Test // GH-4012 + void cteWithSearch() { + + assertQuery(""" + WITH Tree AS (SELECT o.uuid AS test_uuid FROM DemoEntity o) + SEARCH BREADTH FIRST BY foo ASC NULLS FIRST, bar DESC NULLS LAST SET baz + SELECT test_uuid FROM Tree + """); + } + + @Test // GH-4012 + void cteWithCycle() { + + assertQuery(""" + WITH Tree AS (SELECT o.uuid AS test_uuid FROM DemoEntity o) CYCLE test_uuid SET circular TO true DEFAULT false + SELECT test_uuid FROM Tree + """); + + assertQuery( + """ + WITH Tree AS (SELECT o.uuid AS test_uuid FROM DemoEntity o) CYCLE test_uuid SET circular TO true DEFAULT false USING bar + SELECT test_uuid FROM Tree + """); + } + @Test // GH-2982 void floorShouldBeValidEntityName() { @@ -1551,9 +1787,52 @@ void castFunctionWithFqdnShouldWork() { assertQuery("SELECT o FROM Order o WHERE CAST(:userId AS java.util.UUID) IS NULL OR o.user.id = :userId"); } - @Test // GH-3025 - void durationLiteralsShouldWork() { - assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE (ce.endDate - ce.startDate) > 5 MINUTE"); + @ParameterizedTest // GH-3025 + @ValueSource(strings = { "YEAR", "MONTH", "DAY", "WEEK", "QUARTER", "HOUR", "MINUTE", "SECOND", "NANOSECOND", + "NANOSECOND", "EPOCH" }) + void durationLiteralsShouldWork(String dtField) { + + assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE (ce.endDate - ce.startDate) > 5 %s".formatted(dtField)); + assertQuery( + "SELECT ce.id FROM CalendarEvent ce WHERE ce.text LIKE :text GROUP BY year(cd.date) HAVING (ce.endDate - ce.startDate) > 5 %s" + .formatted(dtField)); + assertQuery("SELECT ce.id as id, cd.startDate + 5 %s AS summedDate FROM CalendarEvent ce".formatted(dtField)); + } + + @Test // GH-3739 + void dateTimeLiterals() { + + 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'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'something weird'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00+1}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00-1}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00+1:00}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00-1:00}"); + + assertQuery("SELECT e FROM Employee e WHERE e.version = OFFSET DATETIME 2012-01-03 09:00:00+1:01"); + assertQuery("SELECT e FROM Employee e WHERE e.version = OFFSET DATETIME 2012-01-03 09:00:00-1:01"); + } + + @Test + void literals() { + + assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'"); + assertQuery("SELECT e FROM Employee e WHERE e.names = [e.firstName, e.lastName]"); + 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.gender = org.acme.Gender.MALE"); + assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager"); + } + + @ParameterizedTest // GH-3711 + @ValueSource(strings = { "1", "1_000", "1L", "1_000L", "1bi", "1.1f", "2.2d", "2.2bd" }) + void numberLiteralsShouldWork(String literal) { + assertQuery(String.format("SELECT %s FROM User u where u.id = %s", literal, literal)); } @Test // GH-3025 @@ -1567,6 +1846,9 @@ void binaryLiteralsShouldWork() { @Test // GH-3040 void escapeClauseShouldWork() { assertQuery("select t.name from SomeDbo t where t.name LIKE :name escape '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE ?1"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE :param"); } @Test // GH-3062, GH-3056 @@ -1662,6 +1944,23 @@ group by extract(epoch from departureTime) """); } + @Test // GH-3757 + void arithmeticDate() { + + assertQuery("SELECT a FROM foo a WHERE (cast(a.createdAt as date) - CURRENT_DATE()) BY day - 2 = 0"); + assertQuery("SELECT a FROM foo a WHERE (cast(a.createdAt as date) - CURRENT_DATE()) BY day - 2 = 0"); + assertQuery("SELECT a FROM foo a WHERE (cast(a.createdAt as date)) BY day - 2 = 0"); + + assertQuery("SELECT f.start BY DAY - 2 FROM foo f"); + assertQuery("SELECT f.start - 1 minute FROM foo f"); + + assertQuery("SELECT f FROM foo f WHERE (cast(f.start as date) - CURRENT_DATE()) BY day - 2 = 0"); + assertQuery("SELECT 1 week - 1 day FROM foo f"); + assertQuery("SELECT f.birthday - local date day FROM foo f"); + assertQuery("SELECT local datetime - f.birthday FROM foo f"); + assertQuery("SELECT (1 year) by day FROM foo f"); + } + @ParameterizedTest // GH-3342 @ValueSource( strings = { "select 1 from User", "select -1 from User", "select +1 from User", "select +1 * -100 from User", @@ -1684,4 +1983,13 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord); assertQuery(source); } + + @Test + void reservedWordsShouldWork() { + + assertQuery("select ie from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie.object from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie from ItemExample ie left join ie.object io where io.object = :externalId"); + assertQuery("select ie from ItemExample ie where ie.status = com.app.domain.object.Status.UP"); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java index 482b02db47..40a8c4dc7a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch */ class HqlQueryTransformerTests { @@ -86,13 +87,11 @@ void nullFirstLastSorting() { assertThat(createQueryFor(original, Sort.unsorted())).isEqualTo(original); - assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast()))) - .startsWith(original) - .endsWithIgnoringCase("e.lastName DESC NULLS LAST"); + assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast()))).startsWith(original) + .endsWithIgnoringCase("e.lastName DESC NULLS LAST"); - assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst()))) - .startsWith(original) - .endsWithIgnoringCase("e.lastName DESC NULLS FIRST"); + assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst()))).startsWith(original) + .endsWithIgnoringCase("e.lastName DESC NULLS FIRST"); } @Test @@ -151,6 +150,24 @@ void applyCountToAlreadySortedQuery() { assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name"); } + @Test // GH-3726 + void shouldCreateCountQueryForCTE() { + + // given + var original = """ + WITH cte_select AS (select u.firstname as firstname, u.lastname as lastname from User u) + SELECT new org.springframework.data.jpa.repository.sample.UserExcerptDto(c.firstname, c.lastname) + FROM cte_select c + """; + + // when + var results = createCountQueryFor(original); + + // then + assertThat(results).isEqualToIgnoringWhitespace( + "WITH cte_select AS (select u.firstname as firstname, u.lastname as lastname from User u) SELECT count(*) FROM cte_select c"); + } + @Test void multipleAliasesShouldBeGathered() { @@ -166,6 +183,12 @@ void multipleAliasesShouldBeGathered() { @Test void createsCountQueryCorrectly() { + + assertCountQuery("SELECT id FROM Person", "SELECT count(id) FROM Person"); + assertCountQuery("SELECT p.id FROM Person p", "SELECT count(p) FROM Person p"); + assertCountQuery("SELECT id FROM Person p", "SELECT count(id) FROM Person p"); + assertCountQuery("SELECT id, name FROM Person", "SELECT count(*) FROM Person"); + assertCountQuery("SELECT id, name FROM Person p", "SELECT count(p) FROM Person p"); assertCountQuery(QUERY, COUNT_QUERY); } @@ -188,6 +211,9 @@ void createsCountQueryForConstructorQueries() { assertCountQuery("select distinct new com.example.User(u.name) from User u where u.foo = ?1", "select count(distinct u) from User u where u.foo = ?1"); + + assertCountQuery("select distinct new com.example.User(name, lastname) from User where foo = ?1", + "select count(distinct name, lastname) from User where foo = ?1"); } @Test @@ -539,7 +565,7 @@ WITH maxId AS(select max(sr.snapshot.id) snapshotId from SnapshotReference sr """); assertThat(countQuery).startsWith("WITH maxId AS (select max(sr.snapshot.id) snapshotId from SnapshotReference sr") - .endsWith("select count(m) from maxId m join SnapshotReference sr on sr.snapshot.id = m.snapshotId"); + .endsWith("select count(*) from maxId m join SnapshotReference sr on sr.snapshot.id = m.snapshotId"); } @Test // GH-3504 @@ -893,7 +919,7 @@ void queryParserPicksCorrectAliasAmidstMultipleAlises() { assertThat(alias("select u from User as u left join u.roles as r")).isEqualTo("u"); } - @Test // GH-2032 + @Test // GH-2032, GH-3792 void countQueryShouldWorkEvenWithoutExplicitAlias() { assertCountQuery("FROM BookError WHERE portal = :portal", @@ -924,7 +950,7 @@ where exists ( and iu = u ) and ct.id = :teamId - """, relationshipName, joinAlias, joinAlias)); + """, relationshipName, joinAlias, joinAlias)); } static Stream queriesWithReservedWordsAsIdentifiers() { @@ -933,7 +959,6 @@ static Stream queriesWithReservedWordsAsIdentifiers() { Arguments.of("right", "rt"), // Arguments.of("left", "lt"), // Arguments.of("outer", "ou"), // - Arguments.of("full", "full"), // Arguments.of("inner", "inr")); } @@ -1040,8 +1065,7 @@ select max(id), col """, """ delete MyEntity AS mes where mes.col = 'test' - """ - }) // GH-2977, GH-3649 + """ }) // GH-2977, GH-3649 void isSubqueryThrowsException(String query) { assertThat(createQueryFor(query, Sort.unsorted())).isEqualToIgnoringWhitespace(query); } @@ -1090,18 +1114,20 @@ void aliasesShouldNotOverlapWithSortProperties() { "SELECT t3 FROM Test3 t3 JOIN t3.test2 x WHERE x.id = :test2Id order by t3.testDuplicateColumnName desc"); } - @Test // GH-3269 + @Test // GH-3269, GH-3689 void createsCountQueryUsingAliasCorrectly() { - assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee AS __"); - assertCountQuery("SELECT DISTINCT abc AS x FROM T", "SELECT count(DISTINCT abc) FROM T AS __"); - assertCountQuery("select distinct a as x, b as y from Employee", "select count(distinct a, b) from Employee AS __"); + assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee"); + assertCountQuery("SELECT DISTINCT abc AS x FROM T", "SELECT count(DISTINCT abc) FROM T"); + assertCountQuery("select distinct a as x, b as y from Employee", "select count(distinct a, b) from Employee"); assertCountQuery("select distinct sum(amount) as x from Employee GROUP BY n", - "select count(distinct sum(amount)) from Employee AS __ GROUP BY n"); + "select count(distinct sum(amount)) from Employee GROUP BY n"); assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee GROUP BY n", - "select count(distinct a, b, sum(amount), d) from Employee AS __ GROUP BY n"); + "select count(distinct a, b, sum(amount), d) from Employee GROUP BY n"); assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n", - "select count(distinct a, count(b)) from Employee AS __ GROUP BY n"); + "select count(distinct a, count(b)) from Employee GROUP BY n"); + assertCountQuery("select distinct substring(e.firstname, 1, position('a' in e.lastname)) as x from from Employee", + "select count(distinct substring(e.firstname, 1, position('a' in e.lastname))) from from Employee"); } @Test // GH-3427 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java index 62efc2fdc2..be05e3fceb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ import static org.assertj.core.api.Assertions.*; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; 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; /** @@ -31,6 +32,8 @@ * IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist + * @author Mark Paluch + * @author Christoph Strobl * @since 3.1 */ class HqlSpecificationTests { @@ -39,14 +42,9 @@ class HqlSpecificationTests { private static String parseWithoutChanges(String query) { - HqlLexer lexer = new HqlLexer(CharStreams.fromString(query)); - HqlParser parser = new HqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); + JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser.parseQuery(query); - HqlParser.StartContext parsedQuery = parser.start(); - - return TokenRenderer.render(new HqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new HqlQueryRenderer().visit(parser.getContext())); } private void assertQuery(String query) { @@ -331,6 +329,213 @@ OR TREAT(e AS Contractor).hours > 100 """); } + @ParameterizedTest // GH-3689 + @ValueSource(strings = { "RESPECT NULLS", "IGNORE NULLS" }) + void generic(String nullHandling) { + + // not in the official documentation but supported in the grammar. + assertQuery(""" + SELECT e FROM Employee e + WHERE FOO(x).bar %s + """.formatted(nullHandling)); + } + + @Test // GH-3689 + void size() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE SIZE(x) > 1 + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE SIZE(e.skills) > 1 + """); + } + + @Test // GH-3689 + void collectionAggregate() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE MAXELEMENT(foo) > MINELEMENT(bar) + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE MININDEX(foo) > MAXINDEX(bar) + """); + } + + @Test // GH-3689 + void trunc() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(x) = TRUNCATE(y) + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, 'foo') = TRUNCATE(e, 'bar') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, 'YEAR') = TRUNCATE(LOCAL DATETIME, 'YEAR') + """); + } + + @ParameterizedTest // GH-3689 + @ValueSource(strings = { "YEAR", "MONTH", "DAY", "WEEK", "QUARTER", "HOUR", "MINUTE", "SECOND", "NANOSECOND", + "NANOSECOND", "EPOCH" }) + void trunc(String truncation) { + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, %1$s) = TRUNCATE(e, %1$s) + """.formatted(truncation)); + } + + @Test // GH-3689 + void format() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE FORMAT(x AS 'yyyy') = FORMAT(e.hiringDate AS 'yyyy') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE e.hiringDate = format(LOCAL DATETIME as 'yyyy-MM-dd') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE e.hiringDate = format(LOCAL_DATE() as 'yyyy-MM-dd') + """); + } + + @Test // GH-3689 + void collate() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE COLLATE(x AS ucs_basic) = COLLATE(e.name AS ucs_basic) + """); + } + + @Test // GH-3689 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1, position('/0' in c.number)) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1 FOR 2) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1 FOR position('/0' in c.number)) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1) AS shortNumber " + // + "from Call c"); + } + + @Test // GH-3689 + void overlay() { + + assertQuery("select OVERLAY(c.number PLACING 1 FROM 2) " + // + "from Call c "); + + assertQuery("select OVERLAY(p.number PLACING 1 FROM 2 FOR 3) " + // + "from Call c "); + } + + @Test // GH-3689 + void pad() { + + assertQuery("select PAD(c.number WITH 1 LEADING) " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 TRAILING) " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 LEADING '0') " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 TRAILING '0') " + // + "from Call c "); + } + + @Test // GH-3689 + void position() { + + assertQuery("select POSITION(c.number IN 'foo') " + // + "from Call c "); + + assertQuery("select POSITION(c.number IN 'foo') + 1 AS pos " + // + "from Call c "); + } + + @Test // GH-3689 + void currentDateFunctions() { + + assertQuery("select CURRENT DATE, CURRENT_DATE() " + // + "from Call c "); + + assertQuery("select CURRENT TIME, CURRENT_TIME() " + // + "from Call c "); + + assertQuery("select CURRENT TIMESTAMP, CURRENT_TIMESTAMP() " + // + "from Call c "); + + assertQuery("select INSTANT, CURRENT_INSTANT() " + // + "from Call c "); + + assertQuery("select LOCAL DATE, LOCAL_DATE() " + // + "from Call c "); + + assertQuery("select LOCAL TIME, LOCAL_TIME() " + // + "from Call c "); + + assertQuery("select LOCAL DATETIME, LOCAL_DATETIME() " + // + "from Call c "); + + assertQuery("select OFFSET DATETIME, OFFSET_DATETIME() " + // + "from Call c "); + + assertQuery("select OFFSET DATETIME AS offsetDatetime, OFFSET_DATETIME() AS offset_datetime " + // + "from Call c "); + } + + @Test // GH-3689 + void cube() { + + assertQuery("select CUBE(foo), CUBE(foo, bar) " + // + "from Call c "); + + assertQuery("select c.callerId from Call c GROUP BY CUBE(state, province)"); + } + + @Test // GH-3689 + void rollup() { + + assertQuery("select ROLLUP(foo), ROLLUP(foo, bar) " + // + "from Call c "); + + assertQuery("select c.callerId from Call c GROUP BY ROLLUP(state, province)"); + } + @Test void pathExpressionsNamedParametersExample() { @@ -383,6 +588,80 @@ WHERE EXISTS (SELECT spouseEmp """); } + @Test // GH-3689 + void everyAll() { + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE EVERY (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL (foo > 1) OVER (PARTITION BY bar) + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL VALUES (foo) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL ELEMENTS (foo) > 1 + """); + } + + @Test // GH-3689 + void anySome() { + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE SOME (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY (foo > 1) OVER (PARTITION BY bar) + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY VALUES (foo) > 1 + """); + } + + @Test // GH-3689 + void listAgg() { + + assertQuery("select listagg(p.number, ', ') within group (order by p.type, p.number) " + // + "from Phone p " + // + "group by p.person"); + } + @Test void allExample() { @@ -461,16 +740,13 @@ WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE """); } - @Test // GH-3628 - void functionInvocationWithIsBoolean() { - - assertQuery(""" - from RoleTmpl where find_in_set(:appId, appIds) is true - """); + @ParameterizedTest // GH-3628 + @ValueSource(strings = { "is true", "is not true", "is false", "is not false" }) + void functionInvocationWithIsBoolean(String booleanComparison) { assertQuery(""" - from RoleTmpl where find_in_set(:appId, appIds) is false - """); + from RoleTmpl where find_in_set(:appId, appIds) %s + """.formatted(booleanComparison)); } @Test @@ -845,20 +1121,36 @@ void booleanPredicate() { """); } - @Test // GH-3628 - void distinctFromPredicate() { + @ParameterizedTest // GH-3628 + @ValueSource(strings = { "IS DISTINCT FROM", "IS NOT DISTINCT FROM" }) + void distinctFromPredicate(String distinctFrom) { assertQuery(""" SELECT c FROM Customer c - WHERE c.orders IS DISTINCT FROM c.payments - """); + WHERE c.orders %s c.payments + """.formatted(distinctFrom)); assertQuery(""" SELECT c FROM Customer c - WHERE c.orders IS NOT DISTINCT FROM c.payments - """); + WHERE c.orders %s c.payments + """.formatted(distinctFrom)); + + assertQuery(""" + SELECT c + FROM Customer c + GROUP BY c.lastname + HAVING c.orders %s c.payments + """.formatted(distinctFrom)); + + assertQuery(""" + SELECT c + FROM Customer c + WHERE EXISTS (SELECT c2 + FROM Customer c2 + WHERE c2.orders %s c.orders) + """.formatted(distinctFrom)); } @Test @@ -983,6 +1275,29 @@ void theRest38() { """); } + @Test // GH-3689 + void insertQueries() { + + assertQuery("insert Person (id, name) values (100L, 'Jane Doe')"); + + assertQuery("insert Person (id, name) values " + // + "(101L, 'J A Doe III'), " + // + "(102L, 'J X Doe'), " + // + "(103L, 'John Doe, Jr')"); + + assertQuery("insert into Partner (id, name) " + // + "select p.id, p.name from Person p "); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT (range) DO UPDATE SET price = :price, type = :priceType"); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT ON CONSTRAINT foo DO UPDATE SET price = :price, type = :priceType"); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT ON CONSTRAINT foo DO NOTHING"); + } + @Test void hqlQueries() { @@ -1000,15 +1315,7 @@ void hqlQueries() { assertQuery("update versioned Person " + // "set name = :newName " + // "where name = :oldName"); - assertQuery("insert Person (id, name) " + // - "values (100L, 'Jane Doe')"); - assertQuery("insert Person (id, name) " + // - "values (101L, 'J A Doe III'), " + // - "(102L, 'J X Doe'), " + // - "(103L, 'John Doe, Jr')"); - assertQuery("insert into Partner (id, name) " + // - "select p.id, p.name " + // - "from Person p "); + assertQuery("select p " + // "from Person p " + // "where p.name like 'Joe'"); @@ -1104,9 +1411,6 @@ void hqlQueries() { assertQuery("select concat(p.number, ' : ', cast(c.duration as string)) " + // "from Call c " + // "join c.phone p"); - assertQuery("select substring(p.number, 1, 2) " + // - "from Call c " + // - "join c.phone p"); assertQuery("select upper(p.name) " + // "from Person p "); assertQuery("select lower(p.name) " + // @@ -1315,7 +1619,7 @@ void hqlQueries() { assertQuery("select longest.duration " + // "from Phone p " + // "left join lateral (" + // - " select c.duration as duration " + // + "select c.duration as duration " + // " from p.calls c" + // " order by c.duration desc" + // " limit 1 " + // @@ -1435,9 +1739,6 @@ void hqlQueries() { "from Call c " + // "join c.phone p " + // "group by p.number"); - assertQuery("select listagg(p.number, ', ') within group (order by p.type, p.number) " + // - "from Phone p " + // - "group by p.person"); assertQuery("select sum(c.duration) " + // "from Call c "); assertQuery("select p.name, sum(c.duration) " + // diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java index a41b54193c..5ce7b0d31f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ * @author Geoffrey Deremetz * @author Christoph Strobl */ -public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { +class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @Override QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) { @@ -51,6 +51,22 @@ void shouldApplySorting() { assertThat(sql).isEqualTo("SELECT e FROM Employee e ORDER BY e.foo ASC, e.bar ASC"); } + @Test // GH-3707 + void countQueriesShouldConsiderPrimaryTableAlias() { + + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of(""" + SELECT DISTINCT a.*, b.b1 + FROM TableA a + JOIN TableB b ON a.b = b.b + LEFT JOIN TableC c ON b.c = c.c + ORDER BY b.b1, a.a1, a.a2 + """, true)); + + String sql = enhancer.createCountQueryFor(); + + assertThat(sql).startsWith("SELECT count(DISTINCT a.*) FROM TableA a"); + } + @Override @ParameterizedTest // GH-2773 @MethodSource("jpqlCountQueries") @@ -241,4 +257,17 @@ static Stream mergeStatementWorksSource() { null)); } + @Test // GH-3869 + void shouldWorkWithParenthesedSelect() { + + String query = "(SELECT is_contained_in(:innerId, :outerId))"; + + StringQuery stringQuery = new StringQuery(query, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(stringQuery.getQueryString()).isEqualTo(query); + assertThat(stringQuery.getAlias()).isNull(); + assertThat(queryEnhancer.getProjection()).isEqualTo("is_contained_in(:innerId, :outerId)"); + } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java index f09d50be0e..aebad09360 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ * @author Mark Paluch * @author Jens Schauder * @author Krzysztof Krason + * @author Christoph Strobl */ @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:application-context.xml") @@ -166,6 +167,15 @@ void errorsOnUnknownProperties() { em.createEntityGraph(User.class))); } + @Test // GH-3682 + void allowsEmptyGraph() { + + EntityGraph graph = em.createEntityGraph(User.class); + Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("User.NoNamedEntityGraphAvailable", EntityGraphType.FETCH, new String[0]), graph); + + Assertions.assertThat(graph.getAttributeNodes()).isEmpty(); + } + /** * Lookup the {@link AttributeNode} with given {@literal nodeName} in the root of the given {@literal graph}. */ diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsUnitTests.java index 4885869be9..fab642d505 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java index 3835426aba..9afcf27d56 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersUnitTests.java index ea851bb4fe..c798acd8ac 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancerUnitTests.java new file mode 100644 index 0000000000..a11ba9ffdf --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancerUnitTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import java.util.function.Function; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for {@link JpaQueryEnhancer}. + * + * @author Mark Paluch + */ +class JpaQueryEnhancerUnitTests { + + @ParameterizedTest // GH-3997 + @MethodSource("queryEnhancers") + void shouldRemoveCommentsFromJpql(Function enhancerFunction) { + + QueryEnhancer enhancer = enhancerFunction + .apply(DeclaredQuery.of("SELECT /* foo */ some_alias FROM /* some other */ table_name some_alias", false)); + + assertThat(enhancer.getQuery().getQueryString()) + .isEqualToIgnoringCase("SELECT some_alias FROM table_name some_alias"); + + enhancer = enhancerFunction.apply(DeclaredQuery.of(""" + SELECT /* multi + line + comment + */ some_alias FROM /* some other */ table_name some_alias + """, false)); + + assertThat(enhancer.getQuery().getQueryString()) + .isEqualToIgnoringCase("SELECT some_alias FROM table_name some_alias"); + } + + static Stream> queryEnhancers() { + return Stream.of(JpaQueryEnhancer::forHql, JpaQueryEnhancer::forEql, JpaQueryEnhancer::forJpql); + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java index da57f6a899..6d93f6ae9f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index 7a27c1bfc6..a8205cea35 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -163,7 +163,7 @@ void namedQueryWithSortShouldThrowIllegalStateException() throws NoSuchMethodExc assertThatIllegalStateException() .isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries)) .withMessageContaining( - "is backed by a NamedQuery and must not contain a sort parameter as we cannot modify the query; Use @Query instead"); + "is backed by a NamedQuery and must not contain a sort parameter as we cannot modify the query; Use @Query(value=…) instead to apply sorting or remove the 'Sort' parameter."); } @Test // GH-2018 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java index 4d54b1950d..93f01a109c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java index 4c504e8cc9..9637785e39 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,10 @@ import static org.assertj.core.api.Assertions.entry; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,6 +33,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ImportResource; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -44,7 +47,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; /** - * Unit tests for repository with {@link Query} and {@link QueryRewrite}. + * Unit tests for repository with {@link Query} and {@link QueryRewriter}. * * @author Greg Turnquist * @author Krzysztof Krason @@ -60,10 +63,12 @@ class JpaQueryRewriteIntegrationTests { static final String REWRITTEN_QUERY = "rewritten query"; static final String SORT = "sort"; static Map results = new HashMap<>(); + static Set queries = new LinkedHashSet<>(); @BeforeEach void setUp() { results.clear(); + repository.deleteAll(); } @Test @@ -77,15 +82,15 @@ void nativeQueryShouldHandleRewrites() { entry(SORT, Sort.unsorted().toString())); } - @Test + @Test // GH-3801 void nonNativeQueryShouldHandleRewrites() { - repository.findByNonNativeQuery("Matthews"); + repository.save(new User("D", "A", "foo@bar")); - assertThat(results).containsExactly( // - entry(ORIGINAL_QUERY, "select original_user_alias from User original_user_alias"), // - entry(REWRITTEN_QUERY, "select rewritten_user_alias from User rewritten_user_alias"), // - entry(SORT, Sort.unsorted().toString())); + repository.findByNonNativeQuery("Matthews", PageRequest.of(0, 1)); + + assertThat(queries).contains("select original_user_alias from User original_user_alias"); + assertThat(queries).contains("select count(original_user_alias) from User original_user_alias"); } @Test @@ -169,7 +174,7 @@ public interface UserRepositoryWithRewriter List findByNativeQuery(String param); @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class) - List findByNonNativeQuery(String param); + Page findByNonNativeQuery(String param, PageRequest pageRequest); @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class) List findByNonNativeSortedQuery(String param, Sort sort); @@ -214,6 +219,7 @@ private static String replaceAlias(String query, Sort sort) { results.put(ORIGINAL_QUERY, query); results.put(REWRITTEN_QUERY, rewrittenQuery); results.put(SORT, sort.toString()); + queries.add(query); return rewrittenQuery; } 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..81722f9b90 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; /** @@ -32,14 +30,9 @@ class JpqlComplianceTests { private static String parseWithoutChanges(String query) { - JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); - JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); + JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser.parseQuery(query); - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - JpqlParser.StartContext parsedQuery = parser.start(); - - return QueryRenderer.render(new JpqlQueryRenderer().visit(parsedQuery)); + return QueryRenderer.render(new JpqlQueryRenderer().visit(parser.getContext())); } private void assertQuery(String query) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlParserQueryEnhancerUnitTests.java index b867aba845..8b6385e65d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlParserQueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..7b46397124 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,13 @@ import java.util.stream.Stream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -37,6 +36,7 @@ * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch * @since 3.1 */ class JpqlQueryRendererTests { @@ -48,14 +48,9 @@ class JpqlQueryRendererTests { */ private static String parseWithoutChanges(String query) { - JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); - JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - JpqlParser.StartContext parsedQuery = parser.start(); + JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser.parseQuery(query); - return TokenRenderer.render(new JpqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new JpqlQueryRenderer().visit(parser.getContext())); } static Stream reservedWords() { @@ -296,7 +291,7 @@ void fromClauseDowncastingExample1() { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -305,7 +300,7 @@ void fromClauseDowncastingExample2() { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -320,7 +315,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 @@ -331,7 +326,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 @@ -341,7 +336,16 @@ void fromClauseDowncastingExample4() { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); + } + + @Test // GH-3024, GH-3863 + void casting() { + + assertQuery(""" + select cast(i as string) from Item i where cast(i.date as date) <= cast(:currentDateTime as date) + """); + assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0"); } @Test @@ -405,7 +409,7 @@ void allExample() { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -417,7 +421,7 @@ void existsSubSelectExample2() { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -485,7 +489,7 @@ void updateCaseExample1() { WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -498,7 +502,7 @@ void updateCaseExample2() { WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -538,7 +542,7 @@ void inClauseWithTypeLiteralsShouldWork() { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test @@ -561,6 +565,18 @@ WHERE TYPE(e) IN :empTypes """); } + @Test + void inClauseWithFunctionAndLiterals() { + + assertQuery(""" + select f from FooEntity f where upper(f.name) IN ('Y', 'Basic', 'Remit') + """); + assertQuery( + """ + select count(f) from FooEntity f where f.status IN (com.example.eql_bug_check.entity.FooStatus.FOO, com.example.eql_bug_check.entity.FooStatus.BAR) + """); + } + @Test void notEqualsForTypeShouldWork() { @@ -591,6 +607,14 @@ SELECT c.country, COUNT(c) GROUP BY c.country HAVING COUNT(c) > 30 """); + + assertQuery(""" + SELECT COUNT(f) + FROM FooEntity f + WHERE f.name IN ('Y', 'Basic', 'Remit') + AND f.size = 10 + HAVING COUNT(f) > 0 + """); } @Test @@ -929,6 +953,14 @@ void findOrdersThatHaveProductNamedByAParameter() { """); } + @Test // GH-4013 + void minMaxFunctionsShouldWork() { + assertQuery("SELECT MAX(e.age), e.address.city FROM Employee e"); + assertQuery("SELECT MAX(1), e.address.city FROM Employee e"); + assertQuery("SELECT MAX(MIN(MOD(e.salary, 10))), e.address.city FROM Employee e"); + assertQuery("SELECT MIN(MOD(e.salary, 10)), e.address.city FROM Employee e"); + } + @Test // GH-2982 void floorShouldBeValidEntityName() { @@ -1025,6 +1057,14 @@ void signedExpressionsShouldWork(String query) { assertQuery(query); } + @Test // GH-3873 + void escapeClauseShouldWork() { + assertQuery("select t.name from SomeDbo t where t.name LIKE :name escape '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE '\\\\'"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE ?1"); + assertQuery("SELECT e FROM SampleEntity e WHERE LOWER(e.label) LIKE LOWER(?1) ESCAPE :param"); + } + @ParameterizedTest // GH-3451 @MethodSource("reservedWords") void entityNameWithPackageContainingReservedWord(String reservedWord) { @@ -1033,4 +1073,13 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { assertQuery(source); } + @Test // GH-3834 + void reservedWordsShouldWork() { + + assertQuery("select ie from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie.object from ItemExample ie left join ie.object io where io.externalId = :externalId"); + assertQuery("select ie from ItemExample ie left join ie.object io where io.object = :externalId"); + assertQuery("select ie from ItemExample ie where ie.status = com.app.domain.object.Status.UP"); + } + } 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..147477fc2f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..289e522455 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 @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,9 @@ import static org.assertj.core.api.Assertions.*; -import org.antlr.v4.runtime.CharStreams; -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; /** @@ -41,14 +40,9 @@ class JpqlSpecificationTests { */ private static String parseWithoutChanges(String query) { - JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); - JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); - - parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - - JpqlParser.StartContext parsedQuery = parser.start(); + JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser.parseQuery(query); - return TokenRenderer.render(new JpqlQueryRenderer().visit(parsedQuery)); + return TokenRenderer.render(new JpqlQueryRenderer().visit(parser.getContext())); } private void assertQuery(String query) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecificationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecificationUnitTests.java index 39a43cb1d5..9d8c92a18a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecificationUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecificationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java index c67b1ce836..3000455292 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodIntegrationTests.java index 8947bda883..ae76cb023a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodUnitTests.java index b9e899121f..29e50b85b6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/MetaAnnotatedQueryMethodUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java index 844ae69e01..6f1692142d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java index ebdd2a8395..68cae8bc60 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.jpa.repository.QueryRewriter; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; @@ -88,7 +89,8 @@ void rejectsPersistenceProviderIfIncapableOfExtractingQueriesAndPagebleBeingUsed JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, projectionFactory, extractor); when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenThrow(new IllegalArgumentException()); - assertThatExceptionOfType(QueryCreationException.class).isThrownBy(() -> NamedQuery.lookupFrom(queryMethod, em)); + assertThatExceptionOfType(QueryCreationException.class) + .isThrownBy(() -> NamedQuery.lookupFrom(queryMethod, em, QueryRewriter.IdentityQueryRewriter.INSTANCE)); } @Test // DATAJPA-142 @@ -100,7 +102,8 @@ void doesNotRejectPersistenceProviderIfNamedCountQueryIsAvailable() { TypedQuery countQuery = mock(TypedQuery.class); when(em.createNamedQuery(eq(queryMethod.getNamedCountQueryName()), eq(Long.class))).thenReturn(countQuery); - NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em); + NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em, + QueryRewriter.IdentityQueryRewriter.INSTANCE); query.doCreateCountQuery(new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[1])); verify(em, times(1)).createNamedQuery(queryMethod.getNamedCountQueryName(), Long.class); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java index 7a9cf35d1f..40a0279903 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NativeJpaQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaJpa21UtilsTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaJpa21UtilsTests.java index b8299fa9a1..4c5cac42e1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaJpa21UtilsTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaJpa21UtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java index a2013af402..7517a2a7e1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaParameterMetadataProviderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaQueryUtilsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaQueryUtilsIntegrationTests.java index 2363f429d5..fd8f1cb634 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaQueryUtilsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/OpenJpaQueryUtilsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java index 4f90c40c71..e80d9a8692 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java index b4bd22d9ad..edcaf0e4ea 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java index 6d1d5393b9..b706551305 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java index 6dc7b84b1c..c0f86397d3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java index c62b6e8b09..86a4de3ab2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java index ddd71dbfa7..4bd5f9c2dd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License import org.springframework.aop.framework.Advised; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java index 30645398b2..99b8a7a730 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java index 3e3465e3d3..077d469177 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,10 @@ static Stream nativeCountQueries() { "select u from User as u", // "select count(u) from User as u"), + Arguments.of( // + "SELECT id FROM Person", // + "select count(id) from Person"), + Arguments.of( // "SELECT u FROM User u where u.foo.bar = ?", // "select count(u) FROM User u where u.foo.bar = ?"), diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index d476c445b2..3113627c8e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java index 51fb6d8d37..0b35d49b04 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..1d4f917a5d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Root; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceProviderResolver; @@ -91,6 +92,44 @@ void reusesExistingJoinForExpression() { assertThat(from.getJoins()).hasSize(1); } + @Test // GH-2756 + void reusesExistingFetchJoinForExpression() { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(User.class); + Root from = query.from(User.class); + from.fetch("https://github.com/manager"); + + PropertyPath managerFirstname = PropertyPath.from("manager.firstname", User.class); + PropertyPath managerLastname = PropertyPath.from("manager.lastname", User.class); + + QueryUtils.toExpressionRecursively(from, managerLastname); + QueryUtils.toExpressionRecursively(from, managerFirstname); + + assertThat(from.getFetches()).hasSize(1); + assertThat(from.getJoins()).isEmpty(); + } + + @Test // GH-2756 + void prefersFetchOverJoin() { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(User.class); + Root from = query.from(User.class); + from.fetch("https://github.com/manager"); + from.join("manager"); + + PropertyPath managerFirstname = PropertyPath.from("manager.firstname", User.class); + PropertyPath managerLastname = PropertyPath.from("manager.lastname", User.class); + + QueryUtils.toExpressionRecursively(from, managerLastname); + Path expr = (Path) QueryUtils.toExpressionRecursively(from, managerFirstname); + + assertThat(expr.getParentPath()).hasFieldOrPropertyWithValue("fetched", true); + assertThat(from.getFetches()).hasSize(1); + assertThat(from.getJoins()).hasSize(1); + } + @Test // DATAJPA-401, DATAJPA-1238 void createsJoinForNavigationAcrossOptionalAssociation() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java index b2b2c4acd6..717791afec 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ * @author Erik Pellizzon * @author Pranav HS * @author Eduard Dudar + * @author Mark Paluch */ class QueryUtilsUnitTests { @@ -130,13 +131,13 @@ void detectsAliasCorrectly() { .isEqualTo("u"); assertThat(detectAlias( "from Foo f left join f.bar b with type(b) = BarChild where (f.id = (select max(f.id) from Foo f2 where type(f2) = FooChild) or 1 <> 1) and 1=1")) - .isEqualTo("f"); + .isEqualTo("f"); assertThat(detectAlias( "(from Foo f max(f) ((((select * from Foo f2 (from Foo f3) max(*)) (from Foo f4)) max(f5)) (f6)) (from Foo f7))")) - .isEqualTo("f"); + .isEqualTo("f"); assertThat(detectAlias( "SELECT e FROM DbEvent e WHERE (CAST(:modifiedFrom AS date) IS NULL OR e.modificationDate >= :modifiedFrom)")) - .isEqualTo("e"); + .isEqualTo("e"); assertThat(detectAlias("from User u where (cast(:effective as date) is null) OR :effective >= u.createdAt")) .isEqualTo("u"); assertThat(detectAlias("from User u where (cast(:effectiveDate as date) is null) OR :effectiveDate >= u.createdAt")) @@ -145,7 +146,7 @@ void detectsAliasCorrectly() { .isEqualTo("u"); assertThat( detectAlias("from User u where (cast(:e1f2f3ectiveFrom as date) is null) OR :effectiveFrom >= u.createdAt")) - .isEqualTo("u"); + .isEqualTo("u"); } @Test // GH-2260 @@ -175,13 +176,13 @@ void testRemoveSubqueries() throws Exception { .isEqualTo("(select u from User u where not exists )"); assertThat(normalizeWhitespace( removeSubqueries("select u from User u where not exists (from User u2 where not exists (from User u3))"))) - .isEqualTo("select u from User u where not exists"); + .isEqualTo("select u from User u where not exists"); assertThat(normalizeWhitespace( removeSubqueries("select u from User u where not exists ((from User u2 where not exists (from User u3)))"))) - .isEqualTo("select u from User u where not exists ( )"); + .isEqualTo("select u from User u where not exists ( )"); assertThat(normalizeWhitespace( removeSubqueries("(select u from User u where not exists ((from User u2 where not exists (from User u3))))"))) - .isEqualTo("(select u from User u where not exists ( ))"); + .isEqualTo("(select u from User u where not exists ( ))"); } @Test // GH-2581 @@ -543,6 +544,32 @@ void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpa assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc"); } + @Test // GH-3911 + void discoversFunctionAliasesCorrectly() { + + assertThat(getFunctionAliases("SELECT COUNT(1) a alias1,2 s alias2")).isEmpty(); + assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,2 as alias2")).containsExactly("alias1"); + assertThat(getFunctionAliases("SELECT COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2"); + assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1"); + assertThat(getFunctionAliases("SELECT COUNT(1) as alias1, COUNT(2) as alias2")).contains("alias1", "alias2"); + assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2"); + assertThat(getFunctionAliases("COUNT(1) as alias1,COUNT(2) as alias2")).contains("alias1", "alias2"); + assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2"); + assertThat(getFunctionAliases("1 as alias1, COUNT(2) as alias2")).containsExactly("alias2"); + assertThat(getFunctionAliases("COUNT(1) as alias1,2 as alias2")).containsExactly("alias1"); + assertThat(getFunctionAliases("COUNT(1) as alias1, 2 as alias2")).containsExactly("alias1"); + } + + @Test // GH-3911 + void discoversFieldAliasesCorrectly() { + + assertThat(getFieldAliases("SELECT 1 a alias1,2 s alias2")).isEmpty(); + assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2"); + assertThat(getFieldAliases("SELECT 1 as alias1,2 as alias2")).contains("alias1", "alias2"); + assertThat(getFieldAliases("1 as alias1,2 as alias2")).contains("alias1", "alias2"); + assertThat(getFieldAliases("1 as alias1, 2 as alias2")).contains("alias1", "alias2"); + } + @Test // DATAJPA-1000 void discoversCorrectAliasForJoinFetch() { @@ -564,7 +591,7 @@ void discoversAliasWithComplexFunction() { assertThat( QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) // - .contains("myAlias"); + .contains("myAlias"); } @Test // DATAJPA-1506 @@ -784,18 +811,19 @@ void applySortingAccountsForNativeWindowFunction() { // order by in over clause + at the end assertThat( QueryUtils.applySorting("select dense_rank() over (order by lastname) from user u order by u.lastname", sort)) - .isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc"); + .isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc"); // partition by + order by in over clause - assertThat(QueryUtils.applySorting( - "select dense_rank() over (partition by active, age order by lastname) from user u", sort)).isEqualTo( + assertThat(QueryUtils + .applySorting("select dense_rank() over (partition by active, age order by lastname) from user u", sort)) + .isEqualTo( "select dense_rank() over (partition by active, age order by lastname) from user u order by u.age desc"); // partition by + order by in over clause + order by at the end assertThat(QueryUtils.applySorting( "select dense_rank() over (partition by active, age order by lastname) from user u order by active", sort)) - .isEqualTo( - "select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc"); + .isEqualTo( + "select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc"); // partition by + order by in over clause + frame clause assertThat(QueryUtils.applySorting( @@ -812,8 +840,7 @@ void applySortingAccountsForNativeWindowFunction() { // order by in subselect (select expression) assertThat( QueryUtils.applySorting("select lastname, (select i.id from item i order by i.id limit 1) from user u", sort)) - .isEqualTo( - "select lastname, (select i.id from item i order by i.id limit 1) from user u order by u.age desc"); + .isEqualTo("select lastname, (select i.id from item i order by i.id limit 1) from user u order by u.age desc"); // order by in subselect (select expression) + at the end assertThat(QueryUtils.applySorting( @@ -949,7 +976,7 @@ select q.specialist_id, listagg(q.points, '%s') as points @Test // GH-3324 void createCountQueryForSimpleQuery() { - assertCountQuery("select * from User","select count(*) from User"); - assertCountQuery("select * from User u","select count(u) from User u"); + assertCountQuery("select * from User", "select count(*) from User"); + assertCountQuery("select * from User u", "select count(u) from User u"); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java index 59f418aa61..9f7e2da8ea 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index e5a6b04334..0cca2af02c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -193,15 +192,16 @@ void doesNotValidateCountQueryIfNotPagingMethod() throws Exception { createJpaQuery(method); } - @Test // DATAJPA-352 - @SuppressWarnings("unchecked") + @Test // DATAJPA-352, GH-2736 void validatesAndRejectsCountQueryIfPagingMethod() throws Exception { Method method = SampleRepository.class.getMethod("pageByAnnotatedQuery", Pageable.class); when(em.createQuery(Mockito.contains("count"))).thenThrow(IllegalArgumentException.class); - assertThatIllegalArgumentException().isThrownBy(() -> createJpaQuery(method)).withMessageContaining("Count") + assertThatIllegalArgumentException() // + .isThrownBy(() -> createJpaQuery(method)) // + .withMessageContaining("User u") // .withMessageContaining(method.getName()); } @@ -293,19 +293,19 @@ void resolvesExpressionInCountQuery() throws Exception { } private AbstractJpaQuery createJpaQuery(Method method) { - return createJpaQuery(method, null); + return createJpaQuery(method, Optional.empty()); } - private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable String queryString, @Nullable String countQueryString) { + private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, String queryString, @Nullable String countQueryString) { return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em, queryString, countQueryString, QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create()); } - private AbstractJpaQuery createJpaQuery(Method method, @Nullable Optional countQueryString) { + private AbstractJpaQuery createJpaQuery(Method method, Optional countQueryString) { JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); - return createJpaQuery(queryMethod, queryMethod.getAnnotatedQuery(), countQueryString == null ? null : countQueryString.orElse(queryMethod.getCountQuery())); + return createJpaQuery(queryMethod, queryMethod.getAnnotatedQuery(), countQueryString.orElse(queryMethod.getCountQuery())); } interface SampleRepository { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java index bcfc8bf9ad..9e0363671d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSourceUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java index 919f12d1a9..b9c495778d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributesUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index b7cb49db74..05c8cb3a40 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ * @author Diego Krupitza * @author Mark Paluch * @author Aleksei Elin + * @author Gunha Hwang */ class StringQueryUnitTests { @@ -161,6 +162,66 @@ void rewritesNamedLikeToUniqueParametersIfNecessary() { assertThat(((MethodInvocationArgument) parameterBinding.getOrigin()).identifier().getName()).isEqualTo("firstname"); } + @Test // GH-3784 + void rewritesNamedLikeToUniqueParametersRetainingCountQuery() { + + DeclaredQuery query = new StringQuery( + "select u from User u where u.firstname like %:firstname or u.firstname like :firstname% or u.firstname = :firstname", + false).deriveCountQuery(null); + + assertThat(query.getQueryString()) // + .isEqualTo( + "select count(u) from User u where u.firstname like :firstname or u.firstname like :firstname_1 or u.firstname = :firstname_2"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(3); + + LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); + assertThat(binding).isNotNull(); + assertThat(binding.getOrigin()).isEqualTo(ParameterOrigin.ofParameter("firstname")); + assertThat(binding.getName()).isEqualTo("firstname"); + assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); + + binding = (LikeParameterBinding) bindings.get(1); + assertThat(binding).isNotNull(); + assertThat(binding.getOrigin()).isEqualTo(ParameterOrigin.ofParameter("firstname")); + assertThat(binding.getName()).isEqualTo("firstname_1"); + assertThat(binding.getType()).isEqualTo(Type.STARTING_WITH); + + ParameterBinding parameterBinding = bindings.get(2); + assertThat(parameterBinding).isNotNull(); + assertThat(parameterBinding.getOrigin()).isEqualTo(ParameterOrigin.ofParameter("firstname")); + assertThat(parameterBinding.getName()).isEqualTo("firstname_2"); + assertThat(((MethodInvocationArgument) parameterBinding.getOrigin()).identifier().getName()).isEqualTo("firstname"); + } + + @Test // GH-3784 + void rewritesExpressionsLikeToUniqueParametersRetainingCountQuery() { + + DeclaredQuery query = new StringQuery( + "select u from User u where u.firstname like %:#{firstname} or u.firstname like :#{firstname}%", false) + .deriveCountQuery(null); + + assertThat(query.getQueryString()) // + .isEqualTo( + "select count(u) from User u where u.firstname like :__$synthetic$__1 or u.firstname like :__$synthetic$__2"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(2); + + LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); + assertThat(binding).isNotNull(); + assertThat(binding.getOrigin().isExpression()).isTrue(); + assertThat(binding.getName()).isEqualTo("__$synthetic$__1"); + assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); + + binding = (LikeParameterBinding) bindings.get(1); + assertThat(binding).isNotNull(); + assertThat(binding.getOrigin().isExpression()).isTrue(); + assertThat(binding.getName()).isEqualTo("__$synthetic$__2"); + assertThat(binding.getType()).isEqualTo(Type.STARTING_WITH); + } + @Test // GH-3041 void rewritesPositionalLikeToUniqueParametersIfNecessary() { @@ -175,6 +236,21 @@ void rewritesPositionalLikeToUniqueParametersIfNecessary() { assertThat(bindings).hasSize(3); } + @Test // GH-3907 + void rewritesPositionalLikeToUniqueParametersIfNecessaryUsingPostgresJsonbOperator() { + + StringQuery query = new StringQuery( + "select '[\"x\", \"c\"]'::jsonb ?| '[\"x\", \"c\"]'::jsonb from User u where u.firstname like %?1 or u.firstname like ?1% or u.firstname = ?1", + true); + + assertThat(query.hasParameterBindings()).isTrue(); + assertThat(query.getQueryString()).isEqualTo( + "select '[\"x\", \"c\"]'::jsonb ?| '[\"x\", \"c\"]'::jsonb from User u where u.firstname like ?1 or u.firstname like ?2 or u.firstname = ?3"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(3); + } + @Test // GH-3041 void reusesNamedLikeBindingsWherePossible() { @@ -264,6 +340,48 @@ void detectsMultipleNamedInParameterBindings() { assertNamedBinding(ParameterBinding.class, "bar", bindings.get(2)); } + @Test // GH-3784 + void deriveCountQueryWithNamedInRetainsOrigin() { + + String queryString = "select u from User u where (:logins) IS NULL OR LOWER(u.login) IN (:logins)"; + DeclaredQuery query = new StringQuery(queryString, false).deriveCountQuery(null); + + assertThat(query.getQueryString()) + .isEqualTo("select count(u) from User u where (:logins) IS NULL OR LOWER(u.login) IN (:logins_1)"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(2); + + assertNamedBinding(ParameterBinding.class, "logins", bindings.get(0)); + assertThat((MethodInvocationArgument) bindings.get(0).getOrigin()).extracting(MethodInvocationArgument::identifier) + .extracting(BindingIdentifier::getName).isEqualTo("logins"); + + assertNamedBinding(InParameterBinding.class, "logins_1", bindings.get(1)); + assertThat((MethodInvocationArgument) bindings.get(1).getOrigin()).extracting(MethodInvocationArgument::identifier) + .extracting(BindingIdentifier::getName).isEqualTo("logins"); + } + + @Test // GH-3784 + void deriveCountQueryWithPositionalInRetainsOrigin() { + + String queryString = "select u from User u where (?1) IS NULL OR LOWER(u.login) IN (?1)"; + DeclaredQuery query = new StringQuery(queryString, false).deriveCountQuery(null); + + assertThat(query.getQueryString()) + .isEqualTo("select count(u) from User u where (?1) IS NULL OR LOWER(u.login) IN (?2)"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(2); + + assertPositionalBinding(ParameterBinding.class, 1, bindings.get(0)); + assertThat((MethodInvocationArgument) bindings.get(0).getOrigin()).extracting(MethodInvocationArgument::identifier) + .extracting(BindingIdentifier::getPosition).isEqualTo(1); + + assertPositionalBinding(InParameterBinding.class, 2, bindings.get(1)); + assertThat((MethodInvocationArgument) bindings.get(1).getOrigin()).extracting(MethodInvocationArgument::identifier) + .extracting(BindingIdentifier::getPosition).isEqualTo(1); + } + @Test // DATAJPA-461 void detectsPositionalInParameterBindings() { @@ -310,6 +428,56 @@ void allowsReuseOfParameterWithInAndRegularBinding() { assertNamedBinding(InParameterBinding.class, "foo_1", bindings.get(1)); } + @Test // GH-3126 + void countQueryDerivationRetainsNamedExpressionParameters() { + + StringQuery query = new StringQuery( + "select u from User u where foo = :#{bar} ORDER BY CASE WHEN (u.firstname >= :#{name}) THEN 0 ELSE 1 END", + false); + + DeclaredQuery countQuery = query.deriveCountQuery(null); + + assertThat(countQuery.getParameterBindings()).hasSize(1); + assertThat(countQuery.getParameterBindings()).extracting(ParameterBinding::getOrigin) + .extracting(ParameterOrigin::isExpression).isEqualTo(List.of(true)); + + query = new StringQuery( + "select u from User u where foo = :#{bar} and bar = :bar ORDER BY CASE WHEN (u.firstname >= :bar) THEN 0 ELSE 1 END", + false); + + countQuery = query.deriveCountQuery(null); + + assertThat(countQuery.getParameterBindings()).hasSize(2); + assertThat(countQuery.getParameterBindings()) // + .extracting(ParameterBinding::getOrigin) // + .extracting(ParameterOrigin::isExpression).contains(true, false); + } + + @Test // GH-3126 + void countQueryDerivationRetainsIndexedExpressionParameters() { + + StringQuery query = new StringQuery( + "select u from User u where foo = ?#{bar} ORDER BY CASE WHEN (u.firstname >= ?#{name}) THEN 0 ELSE 1 END", + false); + + DeclaredQuery countQuery = query.deriveCountQuery(null); + + assertThat(countQuery.getParameterBindings()).hasSize(1); + assertThat(countQuery.getParameterBindings()).extracting(ParameterBinding::getOrigin) + .extracting(ParameterOrigin::isExpression).isEqualTo(List.of(true)); + + query = new StringQuery( + "select u from User u where foo = ?#{bar} and bar = ?1 ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END", + false); + + countQuery = query.deriveCountQuery(null); + + assertThat(countQuery.getParameterBindings()).hasSize(2); + assertThat(countQuery.getParameterBindings()) // + .extracting(ParameterBinding::getOrigin) // + .extracting(ParameterOrigin::isExpression).contains(true, false); + } + @Test // DATAJPA-461 void detectsMultiplePositionalInParameterBindings() { @@ -341,7 +509,6 @@ void treatsGreaterThanBindingAsSimpleBinding() { assertThat(bindings).hasSize(1); assertPositionalBinding(ParameterBinding.class, 1, bindings.get(0)); - } @Test // DATAJPA-473 @@ -437,6 +604,19 @@ void shouldReplaceExpressionWithLikeParameters() { .isEqualTo("select a from A a where a.b LIKE :__$synthetic$__1 and a.c LIKE :__$synthetic$__2"); } + @Test // GH-3907 + void considersOnlyDedicatedPositionalBindMarkersAsSuch() { + + StringQuery query = new StringQuery( + "select '[\"x\", \"c\"]'::jsonb ?| array[?1]::text[] FROM foo WHERE foo BETWEEN ?1 and ?2", true); + + assertThat(query.getParameterBindings()).hasSize(2); + + query = new StringQuery("select '[\"x\", \"c\"]'::jsonb ?& array[:foo]::text[] FROM foo WHERE foo = :bar", true); + + assertThat(query.getParameterBindings()).hasSize(2); + } + @Test // DATAJPA-712, GH-3619 void shouldReplaceAllPositionExpressionParametersWithInClause() { @@ -539,32 +719,32 @@ private void checkAlias(String query, String expected, String description, boole @Test // DATAJPA-1200 void testHasNamedParameter() { - checkHasNamedParameter("select something from x where id = :id", true, "named parameter", true); - checkHasNamedParameter("in the :id middle", true, "middle", false); - checkHasNamedParameter(":id start", true, "beginning", false); - checkHasNamedParameter(":id", true, "alone", false); - checkHasNamedParameter("select something from x where id = :id", true, "named parameter", true); - checkHasNamedParameter(":UPPERCASE", true, "uppercase", false); - checkHasNamedParameter(":lowercase", true, "lowercase", false); - checkHasNamedParameter(":2something", true, "beginning digit", false); - checkHasNamedParameter(":2", true, "only digit", false); - checkHasNamedParameter(":.something", true, "dot", false); - checkHasNamedParameter(":_something", true, "underscore", false); - checkHasNamedParameter(":$something", true, "dollar", false); - checkHasNamedParameter(":\uFE0F", true, "non basic latin emoji", false); // - checkHasNamedParameter(":\u4E01", true, "chinese japanese korean", false); - - checkHasNamedParameter("no bind variable", false, "no bind variable", false); - checkHasNamedParameter(":\u2004whitespace", false, "non basic latin whitespace", false); - checkHasNamedParameter("select something from x where id = ?1", false, "indexed parameter", true); - checkHasNamedParameter("::", false, "double colon", false); - checkHasNamedParameter(":", false, "end of query", false); - checkHasNamedParameter(":\u0003", false, "non-printable", false); - checkHasNamedParameter(":*", false, "basic latin emoji", false); - checkHasNamedParameter("\\:", false, "escaped colon", false); - checkHasNamedParameter("::id", false, "double colon with identifier", false); - checkHasNamedParameter("\\:id", false, "escaped colon with identifier", false); - checkHasNamedParameter("select something from x where id = #something", false, "hash", true); + checkHasNamedParameter("select something from x where id = :id", true, "named parameter"); + checkHasNamedParameter("in the :id middle", true, "middle"); + checkHasNamedParameter(":id start", true, "beginning"); + checkHasNamedParameter(":id", true, "alone"); + checkHasNamedParameter("select something from x where id = :id", true, "named parameter"); + checkHasNamedParameter(":UPPERCASE", true, "uppercase"); + checkHasNamedParameter(":lowercase", true, "lowercase"); + checkHasNamedParameter(":2something", true, "beginning digit"); + checkHasNamedParameter(":2", true, "only digit"); + checkHasNamedParameter(":.something", true, "dot"); + checkHasNamedParameter(":_something", true, "underscore"); + checkHasNamedParameter(":$something", true, "dollar"); + checkHasNamedParameter(":\uFE0F", true, "non basic latin emoji"); // + checkHasNamedParameter(":\u4E01", true, "chinese japanese korean"); + + checkHasNamedParameter("no bind variable", false, "no bind variable"); + checkHasNamedParameter(":\u2004whitespace", false, "non basic latin whitespace"); + checkHasNamedParameter("select something from x where id = ?1", false, "indexed parameter"); + checkHasNamedParameter("::", false, "double colon"); + checkHasNamedParameter(":", false, "end of query"); + checkHasNamedParameter(":\u0003", false, "non-printable"); + checkHasNamedParameter(":*", false, "basic latin emoji"); + checkHasNamedParameter("\\:", false, "escaped colon"); + checkHasNamedParameter("::id", false, "double colon with identifier"); + checkHasNamedParameter("\\:id", false, "escaped colon with identifier"); + checkHasNamedParameter("select something from x where id = #something", false, "hash"); } @Test // DATAJPA-1235 @@ -725,7 +905,7 @@ void checkNumberOfNamedParameters(String query, int expectedSize, String label, .hasSize(expectedSize); } - private void checkHasNamedParameter(String query, boolean expected, String label, boolean nativeQuery) { + private void checkHasNamedParameter(String query, boolean expected, String label) { List bindings = new ArrayList<>(); StringQuery.ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java index 13dc550fb0..6d3e3a73b4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AnnotatedAuditableUserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AnnotatedAuditableUserRepository.java index 36748415e5..cf549ad521 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AnnotatedAuditableUserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AnnotatedAuditableUserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableEntityRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableEntityRepository.java index 321fdbf179..216d448d84 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableEntityRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableEntityRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableUserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableUserRepository.java index 127c237fbf..b3d39afc9b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableUserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/AuditableUserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java index dd4af2f2d6..c5a89ebf0f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CategoryRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CategoryRepository.java index 601c4647ae..676a095567 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CategoryRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CategoryRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ClassWithNestedRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ClassWithNestedRepository.java index 303910c50b..17089b7e60 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ClassWithNestedRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ClassWithNestedRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository1.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository1.java index 52ff1ee123..78ee0efe1d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository1.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository1.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository2.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository2.java index 5137c87da7..55fef12d72 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository2.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ConcreteRepository2.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CustomAbstractPersistableRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CustomAbstractPersistableRepository.java index 12dcbeed5e..d59a163db7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CustomAbstractPersistableRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/CustomAbstractPersistableRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/DummyRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/DummyRepository.java index 07a00c4424..34aef70172 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/DummyRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/DummyRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java index c52e42343a..ea1bc60f56 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithIdClass.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithIdClass.java index 703501f0d6..e3413fed97 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithIdClass.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithIdClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EntityWithAssignedIdRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EntityWithAssignedIdRepository.java index 505c658d44..fc6f4714bc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EntityWithAssignedIdRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EntityWithAssignedIdRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemRepository.java index 9335ce52d9..a82370752c 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemSiteRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemSiteRepository.java index 0151618d4b..dd6ba72176 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemSiteRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ItemSiteRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MailMessageRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MailMessageRepository.java index 11ecbd0d61..62ad9525e4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MailMessageRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MailMessageRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MappedTypeRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MappedTypeRepository.java index 12c1041ef4..a2ce8ab9b1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MappedTypeRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/MappedTypeRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyDto.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyDto.java index f27d201137..13ee35f497 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyDto.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyDto.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java new file mode 100644 index 0000000000..b72c448345 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.sample; + +public record NameOnlyRecord(String firstname, String lastname) { + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java index 4ab5675004..71f665c311 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ProductRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ProductRepository.java index 3d34f051ea..2de721721e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ProductRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/ProductRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RedeclaringRepositoryMethodsRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RedeclaringRepositoryMethodsRepository.java index dd9343fff3..a5012f6d2b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RedeclaringRepositoryMethodsRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RedeclaringRepositoryMethodsRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java index eee395794b..fe8e0dd4b6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java index 1895ce863a..083e6f03fb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepositoryWithMeta.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepositoryWithMeta.java index 9258d62457..2fc75192e6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepositoryWithMeta.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepositoryWithMeta.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleConfig.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleConfig.java index 7f9ac2aab4..a319360651 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleConfig.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleEvaluationContextExtension.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleEvaluationContextExtension.java index 274ba7e601..a1deba7c24 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleEvaluationContextExtension.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SampleEvaluationContextExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SiteRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SiteRepository.java index 3bbd36c434..ff5ae98e4c 100755 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SiteRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/SiteRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * Copyright 2016-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 01526cd0bf..d25eefa26c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,9 +83,8 @@ public interface UserRepository extends JpaRepository, JpaSpecifi java.util.Optional findById(Integer primaryKey); /** - * Redeclaration of {@link CrudRepository#deleteById(java.lang.Object)}. to make sure the transaction - * configuration of the original method is considered if the redeclaration does not carry a {@link Transactional} - * annotation. + * Redeclaration of {@link CrudRepository#deleteById(java.lang.Object)}. to make sure the transaction configuration of + * the original method is considered if the redeclaration does not carry a {@link Transactional} annotation. */ @Override void deleteById(Integer id); // DATACMNS-649 @@ -419,7 +418,8 @@ Window findTop3ByFirstnameStartingWithOrderByFirstnameAscEmailAddressAsc(S @Query("select u from User u where u.firstname = ?#{[0]} and u.firstname = ?1 and u.lastname like %?#{[1]}% and u.lastname like %?2%") List findByFirstnameAndLastnameWithSpelExpression(String firstname, String lastname); - @Query(value = "select * from SD_User", countQuery = "select count(1) from SD_User u where u.lastname = :#{#lastname}", nativeQuery = true) + @Query(value = "select * from SD_User", + countQuery = "select count(1) from SD_User u where u.lastname = :#{#lastname}", nativeQuery = true) Page findByWithSpelParameterOnlyUsedForCountQuery(String lastname, Pageable page); // DATAJPA-564 @@ -558,6 +558,9 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true) NameOnly findByNativeQuery(Integer id); + @NativeQuery("SELECT firstname, lastname FROM SD_User WHERE id = ?1") + NameOnlyRecord findRecordProjectionByNativeQuery(Integer id); + // GH-3155 @NativeQuery(value = "SELECT emailaddress, secondary_email_address FROM SD_User WHERE id = ?1", sqlResultSetMapping = "emailDto") @@ -570,26 +573,23 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity @Query("SELECT u FROM User u where u.firstname >= ?1 and u.lastname = '000:1'") List queryWithIndexedParameterAndColonFollowedByIntegerInString(String firstname); - /** - * TODO: ORDER BY CASE appears to only with Hibernate. The examples attempting to do this through pure JPQL don't - * appear to work with Hibernate, so we must set them aside until we can implement HQL. - */ - // // DATAJPA-1233 - // @Query(value = "SELECT u FROM User u ORDER BY CASE WHEN (u.firstname >= :name) THEN 0 ELSE 1 END, u.firstname") - // Page findAllOrderedBySpecialNameSingleParam(@Param("name") String name, Pageable page); - // - // // DATAJPA-1233 - // @Query( - // value = "SELECT u FROM User u WHERE :other = 'x' ORDER BY CASE WHEN (u.firstname >= :name) THEN 0 ELSE 1 END, - // u.firstname") - // Page findAllOrderedBySpecialNameMultipleParams(@Param("name") String name, @Param("other") String other, - // Pageable page); - // - // // DATAJPA-1233 - // @Query( - // value = "SELECT u FROM User u WHERE ?2 = 'x' ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END, - // u.firstname") - // Page findAllOrderedBySpecialNameMultipleParamsIndexed(String other, String name, Pageable page); + @Query(value = "SELECT u FROM User u ORDER BY CASE WHEN (u.firstname >= :name) THEN 0 ELSE 1 END, u.firstname") + Page findAllOrderedByNamedParam(@Param("name") String name, Pageable page); + + @Query(value = "SELECT u FROM User u ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END, u.firstname") + Page findAllOrderedByIndexedParam(String name, Pageable page); + + @Query( + value = "SELECT u FROM User u WHERE :other = 'x' ORDER BY CASE WHEN (u.firstname >= :name) THEN 0 ELSE 1 END, u.firstname") + Page findAllOrderedBySpecialNameMultipleParams(@Param("name") String name, @Param("other") String other, + Pageable page); + + // Note that parameters used in the order-by statement are just cut off, so we must declare a query that parameter + // label order remains valid even after truncating the order by part. (i.e. WHERE ?2 = 'x' ORDER BY CASE WHEN + // (u.firstname >= ?1) isn't going to work). + @Query( + value = "SELECT u FROM User u WHERE ?1 = 'x' ORDER BY CASE WHEN (u.firstname >= ?2) THEN 0 ELSE 1 END, u.firstname") + Page findAllOrderedBySpecialNameMultipleParamsIndexed(String other, String name, Pageable page); // DATAJPA-928 Page findByNativeNamedQueryWithPageable(Pageable pageable); @@ -607,7 +607,7 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity Map findMapWithNullValues(); // DATAJPA-1307 - @Query(value = "select * from SD_User u where u.emailAddress = ?", nativeQuery = true) + @Query(value = "select * from SD_#{#entityName} u where u.emailAddress = ?", nativeQuery = true) User findByEmailNativeAddressJdbcStyleParameter(String emailAddress); // DATAJPA-1334 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryCustom.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryCustom.java index 485f71b8d4..86f454c0de 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryCustom.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryCustom.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryImpl.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryImpl.java index f27d136730..965834876f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryImpl.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java index c9bcee59a3..723c34a693 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextIntegrationTests.java index 6b88b69bf4..6018de0c11 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextUnitTests.java index 1ea6993ce2..899cbd406d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaEntityMetadataUnitTest.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaEntityMetadataUnitTest.java index d874935ddb..042ffdd982 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaEntityMetadataUnitTest.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultJpaEntityMetadataUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java index fea4d697b6..c0b69d22e9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultTransactionDisablingIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultTransactionDisablingIntegrationTests.java index 1a8e34d1f5..f27993c8e2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultTransactionDisablingIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/DefaultTransactionDisablingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java index 4922461995..c18afd1320 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java index 34c38daa48..95a9308e02 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,18 @@ */ package org.springframework.data.jpa.repository.support; +import static org.assertj.core.api.Assertions.*; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import org.springframework.data.jpa.domain.sample.User; import org.springframework.test.context.ContextConfiguration; /** @@ -23,10 +34,49 @@ * * @author Oliver Gierke * @author Greg Turnquist + * @author Mark Paluch */ @ContextConfiguration("classpath:eclipselink.xml") class EclipseLinkJpaRepositoryTests extends JpaRepositoryTests { + @PersistenceContext EntityManager em; + + SimpleJpaRepository repository; + User firstUser, secondUser; + + @BeforeEach + @Override + void setUp() { + + super.setUp(); + + repository = new SimpleJpaRepository<>(User.class, em); + + firstUser = new User("Oliver", "Gierke", "gierke@synyx.de"); + firstUser.setAge(28); + secondUser = new User("Joachim", "Arrasz", "arrasz@synyx.de"); + secondUser.setAge(35); + + repository.deleteAll(); + repository.saveAllAndFlush(List.of(firstUser, secondUser)); + } + + @Test // GH-3990 + void deleteAllBySimpleIdInBatch() { + + repository.deleteAllByIdInBatch(List.of(firstUser.getId(), secondUser.getId())); + + assertThat(repository.count()).isZero(); + } + + @Test // GH-3990 + void deleteAllInBatch() { + + repository.deleteAllInBatch(List.of(firstUser, secondUser)); + + assertThat(repository.count()).isZero(); + } + @Override @Disabled("https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477") void deleteAllByIdInBatch() { @@ -38,4 +88,5 @@ void deleteAllByIdInBatch() { void deleteAllByIdInBatchShouldConvertAnIterableToACollection() { // disabled } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkProxyIdAccessorTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkProxyIdAccessorTests.java index eb619200c7..c7259a5b17 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkProxyIdAccessorTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkProxyIdAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java index 647896ffbb..c2039fcd60 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2024 the original author or authors. + * Copyright 2021-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java index a1d020423d..bf2fa3070e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorUnitTests.java index 8a6725a615..a34fc121fa 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefTests.java index e5848bfef9..50380485a6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java index b25682e9a8..a9ea88ad00 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java index f2c5e9f00c..295ed287c9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/HibernateJpaMetamodelEntityInformationIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/HibernateJpaMetamodelEntityInformationIntegrationTests.java index 82824c084b..deffda2a29 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/HibernateJpaMetamodelEntityInformationIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/HibernateJpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JavaConfigDefaultTransactionDisablingIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JavaConfigDefaultTransactionDisablingIntegrationTests.java index 0a5e0d9199..7d985c050d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JavaConfigDefaultTransactionDisablingIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JavaConfigDefaultTransactionDisablingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java index e66e624b12..6b36813294 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java index 0f5c9ab3a7..a04a0ee1c4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java index 1bed3b7fdf..476957a8d2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java index 37050ffe89..03e8b764bb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanEntityPathResolverIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanEntityPathResolverIntegrationTests.java index 73a3c76ce3..d5f1f891af 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanEntityPathResolverIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanEntityPathResolverIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java index bfbbe561cb..dad06c1f9d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java index 983c7c2195..4b5ad4cf3e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java index 27daa255a8..563c0c7a67 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MailMessageRepositoryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MailMessageRepositoryIntegrationTests.java index b813ae8714..3688699d61 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MailMessageRepositoryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MailMessageRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MutableQueryHintsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MutableQueryHintsUnitTests.java index e4c0c3c7c0..c47f5c5c75 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MutableQueryHintsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/MutableQueryHintsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java index 10440974b2..dd8a85fce2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java index 70a8e8ed87..5c0a0600dd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaProxyIdAccessorTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaProxyIdAccessorTests.java index 6162daab39..54372525c8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaProxyIdAccessorTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaProxyIdAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QSimpleEntityPathResolverUnitTests_Sample.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QSimpleEntityPathResolverUnitTests_Sample.java index 2f33aaafd0..773d82abbe 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QSimpleEntityPathResolverUnitTests_Sample.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QSimpleEntityPathResolverUnitTests_Sample.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslIntegrationTests.java index 2c71c33483..4a59ea90a4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java index 7962695b6a..87c4690190 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -282,9 +282,15 @@ void shouldSupportFindAllWithPredicateAndSort() { assertThat(users).contains(carter, dave, oliver); } - @Test // DATAJPA-585 + @Test // DATAJPA-585, 3761 void worksWithUnpagedPageable() { + assertThat(predicateExecutor.findAll(user.dateOfBirth.isNull(), Pageable.unpaged()).getContent()).hasSize(3); + + Page users = predicateExecutor.findAll(user.dateOfBirth.isNull(), + Pageable.unpaged(Sort.by(Direction.ASC, "firstname"))); + + assertThat(users).containsExactly(carter, dave, oliver); } @Test // DATAJPA-912 @@ -393,6 +399,19 @@ void findByFluentPredicatePage() { assertThat(page1.getContent()).containsExactly(oliver); } + @Test // GH-3762 + void findByFluentPredicateSortOverridePage() { + + Predicate predicate = user.firstname.contains("v"); + + Page page = predicateExecutor.findBy(predicate, + q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 1, Sort.by(Direction.DESC, "firstname")))); + + assertThat(page.getContent()).containsOnly(oliver); + assertThat(predicateExecutor.findAll(predicate, page.nextPageable())).containsOnly(dave); + + } + @Test // GH-2294 void findByFluentPredicateWithInterfaceBasedProjection() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepositoryTests.java index 7e4778d531..ece657841b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportIntegrationTests.java index 6c400a05c4..eb411ddce3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportTests.java index 1eb68aa08b..4e1e1d650a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index 5b83077bbb..24e43d24cb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java index 5a29269c82..be195c947c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the original author or authors. + * Copyright 2008-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/XmlConfigDefaultTransactionDisablingIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/XmlConfigDefaultTransactionDisablingIntegrationTests.java index df684286e9..839293c009 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/XmlConfigDefaultTransactionDisablingIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/XmlConfigDefaultTransactionDisablingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessorUnitTests.java index 9e60043821..e60565e8c8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/EntityManagerTestUtils.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/EntityManagerTestUtils.java index 013857d487..479c69c015 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/EntityManagerTestUtils.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/EntityManagerTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/MergingPersistenceUnitManagerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/MergingPersistenceUnitManagerUnitTests.java index 495c89d71f..4233d685fb 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/MergingPersistenceUnitManagerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/MergingPersistenceUnitManagerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2024 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ProxyImageNameSubstitutor.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ProxyImageNameSubstitutor.java index 24fde9888a..89e64a3024 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ProxyImageNameSubstitutor.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/support/ProxyImageNameSubstitutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/BooleanExecutionCondition.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/BooleanExecutionCondition.java index bd617cb557..590d92d4d0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/BooleanExecutionCondition.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/BooleanExecutionCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusions.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusions.java index aa25c22739..f53d4d6ce4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusions.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusions.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusionsExtension.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusionsExtension.java index 46dcd05fb5..b8d76f9285 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusionsExtension.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/ClassPathExclusionsExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java index 0ee4e25989..cdae0ff329 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java index 804309544f..21e2c87b2d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java index 15a977cb5f..80750aeb5d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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..a6e2800784 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HidingClassLoader.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HidingClassLoader.java index ce60e381e2..2ed67c83f1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HidingClassLoader.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HidingClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 the original author or authors. + * Copyright 2017-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanupIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanupIntegrationTests.java index 19f03a9225..65cb8ea1ad 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanupIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelCacheCleanupIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java index a4cd6592bf..73102d6d03 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/PackageExcludingClassLoader.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/PackageExcludingClassLoader.java index b768c90fd4..b5c8ed5216 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/PackageExcludingClassLoader.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/PackageExcludingClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/antora/antora-playbook.yml b/src/main/antora/antora-playbook.yml index 54e23dabe2..04dbefb29a 100644 --- a/src/main/antora/antora-playbook.yml +++ b/src/main/antora/antora-playbook.yml @@ -17,7 +17,7 @@ content: - url: https://github.com/spring-projects/spring-data-commons # Refname matching: # https://docs.antora.org/antora/latest/playbook/content-refname-matching/ - branches: [main, 3.2.x] + branches: [ main, 3.4.x ] start_path: src/main/antora asciidoc: attributes: diff --git a/src/main/antora/modules/ROOT/pages/index.adoc b/src/main/antora/modules/ROOT/pages/index.adoc index 83a6f4ac77..37753da700 100644 --- a/src/main/antora/modules/ROOT/pages/index.adoc +++ b/src/main/antora/modules/ROOT/pages/index.adoc @@ -2,7 +2,6 @@ = Spring Data JPA :revnumber: {version} :revdate: {localdate} -:feature-scroll: true _Spring Data JPA provides repository support for the Jakarta Persistence API (JPA). It eases development of applications with a consistent programming model that need to access JPA data sources._ @@ -15,7 +14,7 @@ Upgrade Notes, Supported Versions, additional cross-version information. Oliver Gierke, Thomas Darimont, Christoph Strobl, Mark Paluch, Jay Bryant, Greg Turnquist -(C) 2008-2024 VMware, Inc. +(C) 2008-{copyright-year} VMware, Inc. Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. diff --git a/src/main/antora/modules/ROOT/pages/jpa/entity-persistence.adoc b/src/main/antora/modules/ROOT/pages/jpa/entity-persistence.adoc index 0c8392585e..ac8fcd4a75 100644 --- a/src/main/antora/modules/ROOT/pages/jpa/entity-persistence.adoc +++ b/src/main/antora/modules/ROOT/pages/jpa/entity-persistence.adoc @@ -18,7 +18,9 @@ Spring Data JPA offers the following strategies to detect whether an entity is n Without such a Version-property Spring Data JPA inspects the identifier property of the given entity. If the identifier property is `null`, then the entity is assumed to be new. Otherwise, it is assumed to be not new. -2. Implementing `Persistable`: If an entity implements `Persistable`, Spring Data JPA delegates the new detection to the `isNew(…)` method of the entity. See the link:$$https://docs.spring.io/spring-data/data-commons/docs/current/api/index.html?org/springframework/data/domain/Persistable.html$$[JavaDoc] for details. +In contrast to other Spring Data modules, JPA considers `0` (zero) as the first inserted version of an entity and therefore, a primitive version property cannot be used to determine whether an entity is new or not. +2. Implementing `Persistable`: If an entity implements `Persistable`, Spring Data JPA delegates the new detection to the `isNew(…)` method of the entity. +See the link:{spring-data-commons-javadoc-base}/org/springframework/data/domain/Persistable.html[JavaDoc] for details. 3. Implementing `EntityInformation`: You can customize the `EntityInformation` abstraction used in the `SimpleJpaRepository` implementation by creating a subclass of `JpaRepositoryFactory` and overriding the `getEntityInformation(…)` method accordingly. You then have to register the custom implementation of `JpaRepositoryFactory` as a Spring bean. Note that this should be rarely necessary. See the javadoc:org.springframework.data.jpa.repository.support.JpaRepositoryFactory[JavaDoc] for details. Option 1 is not an option for entities that use manually assigned identifiers and no version attribute as with those the identifier will always be non-`null`. @@ -39,7 +41,7 @@ public abstract class AbstractEntity implements Persistable { return isNew; <2> } - @PrePersist <3> + @PostPersist <3> @PostLoad void markNotNew() { this.isNew = false; diff --git a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc b/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc index 8657d42ae4..e88908aba5 100644 --- a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc +++ b/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc @@ -74,7 +74,7 @@ NOTE: `In` and `NotIn` also take any subclass of `Collection` as a parameter as ==== `DISTINCT` can be tricky and not always producing the results you expect. For example, `select distinct u from User u` will produce a complete different result than `select distinct u.lastname from User u`. -In the first case, since you are including `User.id`, nothing will duplicated, hence you'll get the whole table, and it would be of `User` objects. +In the first case, since you are including `User.id`, nothing will be duplicated, hence you'll get the whole table, and it would be of `User` objects. However, that latter query would narrow the focus to just `User.lastname` and find all unique last names for that table. This would also yield a `List` result set instead of a `List` result set. @@ -83,7 +83,7 @@ This would also yield a `List` result set instead of a `List` resu `countDistinctByLastname(String lastname)` can also produce unexpected results. Spring Data JPA will derive `select count(distinct u.id) from User u where u.lastname = ?1`. Again, since `u.id` won't hit any duplicates, this query will count up all the users that had the binding last name. -Which would the same as `countByLastname(String lastname)`! +Which would be the same as `countByLastname(String lastname)`! What is the point of this query anyway? To find the number of people with a given last name? To find the number of _distinct_ people with that binding last name? To find the number of _distinct last names_? (That last one is an entirely different query!) @@ -176,8 +176,11 @@ public interface UserRepository extends JpaRepository { Sometimes, no matter how many features you try to apply, it seems impossible to get Spring Data JPA to apply every thing you'd like to a query before it is sent to the `EntityManager`. -You have the ability to get your hands on the query, right before it's sent to the `EntityManager` and "rewrite" it. That is, -you can make any alterations at the last moment. +You have the ability to get your hands on the query, right before it's sent to the `EntityManager` and "rewrite" it. +That is, you can make any alterations at the last moment. +Query rewriting applies to the actual query and, when applicable, to count queries. +Count queries are optimized and therefore, either not necessary or a count is obtained through other means, such as derived from a Hibernate `SelectionQuery`. + .Declare a QueryRewriter using `@Query` ==== @@ -321,7 +324,7 @@ A similar approach also works with named native queries, by adding the `.count` Next to obtaining mapped results, native queries allow you to read the raw `Tuple` from the database by choosing a `Map` container as the method's return type. The resulting map contains key/value pairs representing the actual database column name and the value. -.Native query retuning raw column name/value pairs +.Native query returning raw column name/value pairs ==== [source, java] ---- @@ -391,7 +394,7 @@ You have multiple options to consume large query results: You have learned in the previous chapter about `Pageable` and `PageRequest`. 2. <>. This is a lighter variant than paging because it does not require the total result count. -3. <>. +3. <>. This method avoids https://use-the-index-luke.com/no-offset[the shortcomings of offset-based result retrieval by leveraging database indexes]. Read more on <> for your particular arrangement. @@ -503,7 +506,7 @@ public interface ConcreteRepository In the preceding example, the `MappedTypeRepository` interface is the common parent interface for a few domain types extending `AbstractMappedType`. It also defines the generic `findAllByAttribute(…)` method, which can be used on instances of the specialized repository interfaces. -If you now invoke `findByAllAttribute(…)` on `ConcreteRepository`, the query becomes `select t from ConcreteType t where t.attribute = ?1`. +If you now invoke `findAllByAttribute(…)` on `ConcreteRepository`, the query becomes `select t from ConcreteType t where t.attribute = ?1`. You can also use Expressions to control arguments may also be used to control method arguments. In these expressions the entity name is not available, but the arguments are. @@ -629,6 +632,9 @@ To make sure lifecycle queries are actually invoked, an invocation of `deleteByR In fact, a derived delete query is a shortcut for running the query and then calling `CrudRepository.delete(Iterable users)` on the result and keeping behavior in sync with the implementations of other `delete(…)` methods in `CrudRepository`. +NOTE: When deleting a lot of objects you will need to consider the performance implications to ensure sufficient memory availability. +All resulting objects are loaded into memory before being deleted and are held in the session until flushing or completing the transaction. + [[jpa.query-hints]] == Applying Query Hints To apply JPA query hints to the queries declared in your repository interface, you can use the `@QueryHints` annotation. It takes an array of JPA `@QueryHint` annotations plus a boolean flag to potentially disable the hints applied to the additional count query triggered when applying pagination, as shown in the following example: @@ -824,7 +830,7 @@ public interface GroupRepository extends CrudRepository { It is also possible to define ad hoc entity graphs by using `@EntityGraph`. The provided `attributePaths` are translated into the according `EntityGraph` without needing to explicitly add `@NamedEntityGraph` to your domain types, as shown in the following example: -.Using AD-HOC entity graph definition on an repository query method. +.Using ad-hoc entity graph definitions on a repository query method ==== [source, java] ---- diff --git a/src/main/antora/modules/ROOT/pages/jpa/transactions.adoc b/src/main/antora/modules/ROOT/pages/jpa/transactions.adoc index abd25d4f5f..c3ca330b30 100644 --- a/src/main/antora/modules/ROOT/pages/jpa/transactions.adoc +++ b/src/main/antora/modules/ROOT/pages/jpa/transactions.adoc @@ -89,3 +89,7 @@ Typically, you want the `readOnly` flag to be set to `true`, as most of the quer You can use transactions for read-only queries and mark them as such by setting the `readOnly` flag. Doing so does not, however, act as a check that you do not trigger a manipulating query (although some databases reject `INSERT` and `UPDATE` statements inside a read-only transaction). The `readOnly` flag is instead propagated as a hint to the underlying JDBC driver for performance optimizations. Furthermore, Spring performs some optimizations on the underlying JPA provider. For example, when used with Hibernate, the flush mode is set to `NEVER` when you configure a transaction as `readOnly`, which causes Hibernate to skip dirty checks (a noticeable improvement on large object trees). ==== +[NOTE] +==== +While examples discuss `@Transactional` usage on the repository, we generally recommend declaring transaction boundaries when starting a unit of work to ensure proper consistency and desired transaction participation. +==== diff --git a/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc b/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc index a7c2ff8d3c..754f08c357 100644 --- a/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc +++ b/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc @@ -1 +1,145 @@ -include::{commons}@data-commons::page$repositories/core-extensions.adoc[] +[[core.extensions]] += Spring Data Extensions + +This section documents a set of Spring Data extensions that enable Spring Data usage in a variety of contexts. +Currently, most of the integration is targeted towards Spring MVC. + +include::{commons}@data-commons::page$repositories/core-extensions-querydsl.adoc[leveloffset=1] + +[[jpa.repositories.queries.type-safe.apt]] +=== Setting up Annotation Processing + +To use Querydsl with Spring Data JPA, you need to set up annotation processing in your build system that generates the `Q` classes. +While you could write the `Q` classes by hand, it is recommended to use the Querydsl annotation processor to generate them for you to keep your `Q` classes in sync with your domain model. + +Most Spring Data users do not use Querydsl, so it does not make sense to require additional mandatory dependencies for projects that would not benefit from Querydsl. +Hence, you need to activate annotation processing in your build system. + +The following example shows how to set up annotation processing by mentioning dependencies and compiler config changes in Maven and Gradle: + +[tabs] +====== +Maven:: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="primary"] +---- + + + com.querydsl + querydsl-jpa + ${querydslVersion} + jakarta + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + com.querydsl + querydsl-apt + ${querydslVersion} + jakarta + + + jakarta.persistence + jakarta.persistence-api + + + + + target/generated-test-sources + target/generated-sources + + + + +---- + +Gradle:: ++ +==== +[source,groovy,indent=0,subs="verbatim,quotes",role="secondary"] +---- +dependencies { + + implementation 'com.querydsl:querydsl-jpa:${querydslVersion}:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}:jakarta' + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + testAnnotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}:jakarta' + testAnnotationProcessor 'jakarta.persistence:jakarta.persistence-api' +} +---- +==== + +Maven (OpenFeign):: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="primary"] +---- + + + io.github.openfeign.querydsl + querydsl-jpa + ${querydslVersion} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + io.github.openfeign.querydsl + querydsl-apt + ${querydslVersion} + jpa + + + jakarta.persistence + jakarta.persistence-api + + + + target/generated-test-sources + target/generated-sources + + + + +---- + +Gradle (OpenFeign):: ++ +==== +[source,groovy,indent=0,subs="verbatim,quotes",role="secondary"] +---- +dependencies { + + implementation "io.github.openfeign.querydsl:querydsl-jpa:${querydslVersion}" + annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:${querydslVersion}:jpa" + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + testAnnotationProcessor "io.github.openfeign.querydsl:querydsl-apt:${querydslVersion}:jpa" + testAnnotationProcessor 'jakarta.persistence:jakarta.persistence-api' +} +---- +==== +====== + +Note that the setup above shows the simplemost usage omitting any other options or dependencies that your project might require. + +include::{commons}@data-commons::page$repositories/core-extensions-web.adoc[leveloffset=1] + +include::{commons}@data-commons::page$repositories/core-extensions-populators.adoc[leveloffset=1] diff --git a/src/main/antora/modules/ROOT/pages/repositories/projections.adoc b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc index 556eebe91f..00a6a8c8ef 100644 --- a/src/main/antora/modules/ROOT/pages/repositories/projections.adoc +++ b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc @@ -3,15 +3,42 @@ :projection-collection: Collection -include::{commons}@data-commons::page$repositories/projections-intro.adoc[] +== Introduction -NOTE: It is important to note that <> with JPQL is limited to *constructor expressions* in your JPQL expression, e.g. `SELECT new com.example.NamesOnly(u.firstname, u.lastname) from User u`. +include::{commons}@data-commons::page$repositories/projections-intro.adoc[leveloffset+=1] + +include::{commons}@data-commons::page$repositories/projections-interface.adoc[leveloffset=2] + +include::{commons}@data-commons::page$repositories/projections-class.adoc[leveloffset=2] + +== Using Projections with JPA + +You can use Projections with JPA in several ways. +Depending on the technique and query type, you need to apply specific considerations. + +Spring Data JPA uses generally `Tuple` queries to construct interface proxies for <>. + +=== Derived queries + +Query derivation supports both, class-based and interface projections by introspecting the returned type. +Class-based projections use JPA's instantiation mechanism (constructor expressions) to create the projection instance. + +Projections limit the selection to top-level properties of the target entity. +Any nested properties resolving to joins select the entire nested property causing the full join to materialize. + +=== String-based queries + +Support for string-based queries covers both, JPQL queries(`@Query`) and native queries (`@NativeQuery`). + +==== JPQL Queries + +When using <> with JPQL, you must use *constructor expressions* in your JPQL query, e.g. `SELECT new com.example.NamesOnly(u.firstname, u.lastname) from User u`. (Note the usage of a FQDN for the DTO type!) This JPQL expression can be used in `@Query` annotations as well where you define any named queries. -And it's important to point out that class-based projections do not work with native queries AT ALL. As a workaround you may use named queries with `ResultSetMapping` or the Hibernate-specific javadoc:{hibernatejavadocurl}org.hibernate.query.ResultListTransformer[] -include::{commons}@data-commons::page$repositories/projections-interface.adoc[leveloffset=1] - -include::{commons}@data-commons::page$repositories/projections-class.adoc[leveloffset=1] +==== Native Queries +When using <>, their usage requires slightly more consideration depending on your : +* If properties of the result type map directly to the result (the order of columns and their types match the constructor arguments), then you can declare the query result type as the DTO type without further hints (or use the DTO class through dynamic projections). +* If the properties do not match or require transformation, use `@SqlResultSetMapping` through JPA's annotations map the result set to the DTO and provide the result mapping name through `@NativeQuery(resultSetMapping = "…")`. diff --git a/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc index 513ba122f0..3804f1d6c5 100644 --- a/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc +++ b/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc @@ -1,3 +1,4 @@ +:support-qbe-collection: false include::{commons}@data-commons::page$query-by-example.adoc[] [[query-by-example.running]] diff --git a/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc index dfe4814955..614da0b059 100644 --- a/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc +++ b/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc @@ -1 +1,2 @@ +:feature-scroll: include::{commons}@data-commons::page$repositories/query-methods-details.adoc[] diff --git a/src/main/antora/resources/antora-resources/antora.yml b/src/main/antora/resources/antora-resources/antora.yml index ed14d8c6d8..b0a1b58fdb 100644 --- a/src/main/antora/resources/antora-resources/antora.yml +++ b/src/main/antora/resources/antora-resources/antora.yml @@ -3,19 +3,21 @@ prerelease: ${antora-component.prerelease} asciidoc: attributes: - version: ${project.version} - springversionshort: ${spring.short} - springversion: ${spring} attribute-missing: 'warn' - commons: ${springdata.commons.docs} + chomp: 'all' + version: '${project.version}' + copyright-year: '${current.year}' + springversionshort: '${spring.short}' + springversion: '${spring}' + commons: '${springdata.commons.docs}' include-xml-namespaces: false - spring-data-commons-docs-url: https://docs.spring.io/spring-data/commons/reference - spring-data-commons-javadoc-base: https://docs.spring.io/spring-data/commons/docs/${springdata.commons}/api/ - springdocsurl: https://docs.spring.io/spring-framework/reference/{springversionshort} - springjavadocurl: https://docs.spring.io/spring-framework/docs/${spring}/javadoc-api + spring-data-commons-docs-url: '${documentation.baseurl}/spring-data/commons/reference/${springdata.commons.short}' + spring-data-commons-javadoc-base: '{spring-data-commons-docs-url}/api/java' + springdocsurl: '${documentation.baseurl}/spring-framework/reference/{springversionshort}' spring-framework-docs: '{springdocsurl}' + springjavadocurl: '${documentation.spring-javadoc-url}' spring-framework-javadoc: '{springjavadocurl}' - springhateoasversion: ${spring-hateoas} + springhateoasversion: '${spring-hateoas}' hibernatejavadocurl: https://docs.jboss.org/hibernate/orm/6.6/javadocs/ - releasetrainversion: ${releasetrain} + releasetrainversion: '${releasetrain}' store: Jpa