From 0635abd78ba95d5ca77d5192f5330e5b8598818f Mon Sep 17 00:00:00 2001 From: XDEV Renovate Bot Date: Sat, 27 Jul 2024 02:24:46 +0000 Subject: [PATCH 01/26] Update net.sourceforge.pmd to v7.4.0 --- pom.xml | 4 ++-- template-placeholder/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index d15d6dce..cea11b45 100644 --- a/pom.xml +++ b/pom.xml @@ -82,12 +82,12 @@ net.sourceforge.pmd pmd-core - 7.3.0 + 7.4.0 net.sourceforge.pmd pmd-java - 7.3.0 + 7.4.0 diff --git a/template-placeholder/pom.xml b/template-placeholder/pom.xml index 3c968887..a6011a30 100644 --- a/template-placeholder/pom.xml +++ b/template-placeholder/pom.xml @@ -284,12 +284,12 @@ net.sourceforge.pmd pmd-core - 7.3.0 + 7.4.0 net.sourceforge.pmd pmd-java - 7.3.0 + 7.4.0 From d994a2dcbcb4e9814b613828d6a73911b9990881 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 07:51:37 +0200 Subject: [PATCH 02/26] Update antora.yml --- docs/antora.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/antora.yml b/docs/antora.yml index f0e594e0..829c1a02 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,14 +1,14 @@ name: ROOT title: Spring-Data-Eclipse-Store version: master -display_version: '2.0.0' +display_version: '2.0.1' start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: 'Spring-Data-Eclipse-Store' - display-version: '2.0.0' - maven-version: '2.0.0' + display-version: '2.0.1' + maven-version: '2.0.1' page-editable: false page-out-of-support: false From 5c841fc6f0df89dde6ef9b1b0b22e20900dae9cc Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 08:05:29 +0200 Subject: [PATCH 03/26] Update antora.yml --- docs/antora.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/antora.yml b/docs/antora.yml index 829c1a02..16b71187 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,14 +1,14 @@ name: ROOT title: Spring-Data-Eclipse-Store version: master -display_version: '2.0.1' +display_version: '2.0.2' start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: 'Spring-Data-Eclipse-Store' - display-version: '2.0.1' - maven-version: '2.0.1' + display-version: '2.0.2' + maven-version: '2.0.2' page-editable: false page-out-of-support: false From 78d8d68e5ff92da13ba344ab416adcfa735bf694 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 08:08:39 +0200 Subject: [PATCH 04/26] Updated version --- CHANGELOG.md | 4 ++++ docs/antora.yml | 6 +++--- pom.xml | 2 +- spring-data-eclipse-store-benchmark/pom.xml | 2 +- spring-data-eclipse-store-demo/pom.xml | 2 +- spring-data-eclipse-store-jpa/pom.xml | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c93ca89..aca42919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.0 + +* Implemented composite primary keys. + # 2.0.1 * Fix for Issue [#131](https://github.com/xdev-software/spring-data-eclipse-store/issues/131) diff --git a/docs/antora.yml b/docs/antora.yml index 16b71187..fc9ac74e 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,14 +1,14 @@ name: ROOT title: Spring-Data-Eclipse-Store version: master -display_version: '2.0.2' +display_version: '2.1.0' start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: 'Spring-Data-Eclipse-Store' - display-version: '2.0.2' - maven-version: '2.0.2' + display-version: '2.1.0' + maven-version: '2.1.0' page-editable: false page-out-of-support: false diff --git a/pom.xml b/pom.xml index d2133d1e..8b5e8c37 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev spring-data-eclipse-store-root - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT pom diff --git a/spring-data-eclipse-store-benchmark/pom.xml b/spring-data-eclipse-store-benchmark/pom.xml index 37281087..3554596d 100644 --- a/spring-data-eclipse-store-benchmark/pom.xml +++ b/spring-data-eclipse-store-benchmark/pom.xml @@ -5,7 +5,7 @@ software.xdev spring-data-eclipse-store-root - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT spring-data-eclipse-store-benchmark diff --git a/spring-data-eclipse-store-demo/pom.xml b/spring-data-eclipse-store-demo/pom.xml index b05209a0..d0247c66 100644 --- a/spring-data-eclipse-store-demo/pom.xml +++ b/spring-data-eclipse-store-demo/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT spring-data-eclipse-store-demo diff --git a/spring-data-eclipse-store-jpa/pom.xml b/spring-data-eclipse-store-jpa/pom.xml index 7dc07535..7fd88888 100644 --- a/spring-data-eclipse-store-jpa/pom.xml +++ b/spring-data-eclipse-store-jpa/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT spring-data-eclipse-store-jpa From 851ff87ada881eba508baf570fdac08b3dd9d27a Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 08:55:17 +0200 Subject: [PATCH 05/26] Added Auto UUID --- CHANGELOG.md | 1 + docs/modules/ROOT/pages/features/ids.adoc | 8 ++ .../support/id/strategy/IdFinder.java | 6 ++ .../id/strategy/auto/AutoUUIDIdFinder.java | 40 ++++++++ .../integration/isolated/tests/id/IdTest.java | 96 ++++++++++++++----- .../tests/id/model/CustomerWithIdUuid.java | 86 +++++++++++++++++ .../model/CustomerWithIdUuidRepository.java | 25 +++++ 7 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 docs/modules/ROOT/pages/features/ids.adoc create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/auto/AutoUUIDIdFinder.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuid.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuidRepository.java diff --git a/CHANGELOG.md b/CHANGELOG.md index aca42919..d1cdb9ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 2.1.0 +* Implemented auto-id-generation for UUIDs. * Implemented composite primary keys. # 2.0.1 diff --git a/docs/modules/ROOT/pages/features/ids.adoc b/docs/modules/ROOT/pages/features/ids.adoc new file mode 100644 index 00000000..0750aaf7 --- /dev/null +++ b/docs/modules/ROOT/pages/features/ids.adoc @@ -0,0 +1,8 @@ += IDs + +{product-name} supports the following types with auto generating (``GenerationType.AUTO``) values: + +* ``int`` / ``Integer`` +* ``long`` / ``Long`` +* ``String`` +* ``UUID`` diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/IdFinder.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/IdFinder.java index d9119b3f..488eeef8 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/IdFinder.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/IdFinder.java @@ -17,6 +17,7 @@ import java.lang.reflect.Field; import java.util.Objects; +import java.util.UUID; import java.util.function.Supplier; import jakarta.persistence.GeneratedValue; @@ -26,6 +27,7 @@ import software.xdev.spring.data.eclipse.store.repository.support.id.strategy.auto.AutoIntegerIdFinder; import software.xdev.spring.data.eclipse.store.repository.support.id.strategy.auto.AutoLongIdFinder; import software.xdev.spring.data.eclipse.store.repository.support.id.strategy.auto.AutoStringIdFinder; +import software.xdev.spring.data.eclipse.store.repository.support.id.strategy.auto.AutoUUIDIdFinder; /** @@ -56,6 +58,10 @@ else if(Long.class.isAssignableFrom(idField.getType()) || long.class.isAssignabl { return (IdFinder)new AutoLongIdFinder(lastIdGetter); } + else if(idField.getType().equals(UUID.class)) + { + return (IdFinder)new AutoUUIDIdFinder(lastIdGetter); + } } throw new IdGeneratorNotSupportedException(String.format( "Id generator with strategy %s for type %s is not supported.", diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/auto/AutoUUIDIdFinder.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/auto/AutoUUIDIdFinder.java new file mode 100644 index 00000000..e8240c6c --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/strategy/auto/AutoUUIDIdFinder.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.repository.support.id.strategy.auto; + +import java.util.UUID; +import java.util.function.Supplier; + + +public class AutoUUIDIdFinder extends AbstractAutoIdFinder +{ + public AutoUUIDIdFinder(final Supplier idGetter) + { + super(() -> (UUID)idGetter.get()); + } + + @Override + protected UUID getNext(final UUID oldId) + { + return UUID.randomUUID(); + } + + @Override + public UUID getDefaultValue() + { + return null; + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java index ca31f1e0..aa757a01 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -40,6 +41,8 @@ import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithIdLongRepository; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithIdString; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithIdStringRepository; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithIdUuid; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithIdUuidRepository; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithPurchase; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.CustomerWithPurchaseRepository; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model.Purchase; @@ -60,7 +63,7 @@ public IdTest(final IdTestConfiguration configuration) } @Test - void testCreateSingleWithAutoIdInteger(@Autowired final CustomerWithIdIntegerRepository customerRepository) + void createSingleWithAutoIdInteger(@Autowired final CustomerWithIdIntegerRepository customerRepository) { final CustomerWithIdInteger customer1 = new CustomerWithIdInteger(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -138,7 +141,7 @@ void saveBulkWithAutoIdIntAndHardcodedId(@Autowired final CustomerWithIdIntRepos * no previous method is called before the test. */ @Test - void testSaveSingleWithoutAnyPreviousCall(@Autowired final CustomerWithIdIntegerRepository customerRepository) + void saveSingleWithoutAnyPreviousCall(@Autowired final CustomerWithIdIntegerRepository customerRepository) { restartDatastore(this.configuration); final CustomerWithIdInteger customer1 = new CustomerWithIdInteger(TestData.FIRST_NAME, TestData.LAST_NAME); @@ -155,7 +158,7 @@ void testSaveSingleWithoutAnyPreviousCall(@Autowired final CustomerWithIdInteger } @Test - void testCreateSingleWithAutoIdIntegerWorkingCopyIdSet( + void createSingleWithAutoIdIntegerWorkingCopyIdSet( @Autowired final CustomerWithIdIntegerRepository customerRepository ) { @@ -166,7 +169,7 @@ void testCreateSingleWithAutoIdIntegerWorkingCopyIdSet( } @Test - void testCreateMultipleWithAutoIdInteger(@Autowired final CustomerWithIdIntegerRepository customerRepository) + void createMultipleWithAutoIdInteger(@Autowired final CustomerWithIdIntegerRepository customerRepository) { final CustomerWithIdInteger customer1 = new CustomerWithIdInteger(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -186,7 +189,7 @@ void testCreateMultipleWithAutoIdInteger(@Autowired final CustomerWithIdIntegerR } @Test - void testCreateMultipleWithAutoIdIntegerSingleFinds( + void createMultipleWithAutoIdIntegerSingleFinds( @Autowired final CustomerWithIdIntegerRepository customerRepository ) { @@ -208,7 +211,32 @@ void testCreateMultipleWithAutoIdIntegerSingleFinds( } @Test - void testCreateSingleWithAutoIdInt(@Autowired final CustomerWithIdIntRepository customerRepository) + void createMultipleWithAutoIdUuidSingleFinds( + @Autowired final CustomerWithIdUuidRepository customerRepository + ) + { + final CustomerWithIdUuid customer1 = new CustomerWithIdUuid(TestData.FIRST_NAME, TestData.LAST_NAME); + customerRepository.save(customer1); + final CustomerWithIdUuid customer2 = + new CustomerWithIdUuid(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + customerRepository.save(customer2); + + final UUID generatedId1 = customerRepository.findAll().get(0).getId(); + final UUID generatedId2 = customerRepository.findAll().get(1).getId(); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional loadedCustomer1 = customerRepository.findById(generatedId1); + Assertions.assertEquals(customer1, loadedCustomer1.get()); + final Optional loadedCustomer2 = customerRepository.findById(generatedId2); + Assertions.assertEquals(customer2, loadedCustomer2.get()); + } + ); + } + + @Test + void createSingleWithAutoIdInt(@Autowired final CustomerWithIdIntRepository customerRepository) { final CustomerWithIdInt customer1 = new CustomerWithIdInt(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -224,7 +252,7 @@ void testCreateSingleWithAutoIdInt(@Autowired final CustomerWithIdIntRepository } @Test - void testCreateSingleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) + void createSingleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) { final CustomerWithIdString customer1 = new CustomerWithIdString(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -240,7 +268,25 @@ void testCreateSingleWithAutoIdString(@Autowired final CustomerWithIdStringRepos } @Test - void testSaveAfterRestartSingleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) + void createSingleWithAutoIdUuid(@Autowired final CustomerWithIdUuidRepository customerRepository) + { + final CustomerWithIdUuid customer1 = new CustomerWithIdUuid(TestData.FIRST_NAME, TestData.LAST_NAME); + customerRepository.save(customer1); + + final UUID generatedId = customerRepository.findAll().get(0).getId(); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional loadedCustomer = customerRepository.findById(generatedId); + Assertions.assertTrue(loadedCustomer.isPresent()); + Assertions.assertEquals(customer1, loadedCustomer.get()); + } + ); + } + + @Test + void saveAfterRestartSingleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) { final CustomerWithIdString customer1 = new CustomerWithIdString(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -263,7 +309,7 @@ void testSaveAfterRestartSingleWithAutoIdString(@Autowired final CustomerWithIdS } @Test - void testCreateMultipleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) + void createMultipleWithAutoIdString(@Autowired final CustomerWithIdStringRepository customerRepository) { final CustomerWithIdString customer1 = new CustomerWithIdString(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -283,7 +329,7 @@ void testCreateMultipleWithAutoIdString(@Autowired final CustomerWithIdStringRep } @Test - void testCreateSingleWithAutoIdLong(@Autowired final CustomerWithIdLongRepository customerRepository) + void createSingleWithAutoIdLong(@Autowired final CustomerWithIdLongRepository customerRepository) { final CustomerWithIdLong customer1 = new CustomerWithIdLong(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -300,7 +346,7 @@ void testCreateSingleWithAutoIdLong(@Autowired final CustomerWithIdLongRepositor } @Test - void testCreateMultipleWithAutoIdLong(@Autowired final CustomerWithIdLongRepository customerRepository) + void createMultipleWithAutoIdLong(@Autowired final CustomerWithIdLongRepository customerRepository) { final CustomerWithIdLong customer1 = new CustomerWithIdLong(TestData.FIRST_NAME, TestData.LAST_NAME); customerRepository.save(customer1); @@ -323,7 +369,7 @@ void testCreateMultipleWithAutoIdLong(@Autowired final CustomerWithIdLongReposit } @Test - void testCreateSingleWithNoAutoIdInteger( + void createSingleWithNoAutoIdInteger( @Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate customer1 = @@ -341,7 +387,7 @@ void testCreateSingleWithNoAutoIdInteger( } @Test - void testCreateMultipleWithNoAutoIdInteger( + void createMultipleWithNoAutoIdInteger( @Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate customer1 = @@ -364,7 +410,7 @@ void testCreateMultipleWithNoAutoIdInteger( } @Test - void testSaveSingleWithAutoIdInteger( + void saveSingleWithAutoIdInteger( @Autowired final CustomerWithIdIntegerRepository customerRepository ) { @@ -385,7 +431,7 @@ void testSaveSingleWithAutoIdInteger( } @Test - void testSaveSingleWithNoAutoIdInteger( + void saveSingleWithNoAutoIdInteger( @Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate customer1 = @@ -397,7 +443,7 @@ void testSaveSingleWithNoAutoIdInteger( } @Test - void testAutoIdWithSubnodeWithId( + void autoIdWithSubnodeWithId( @Autowired final CustomerWithPurchaseRepository customerRepository) { final String purchaseName = "bag"; @@ -419,7 +465,7 @@ void testAutoIdWithSubnodeWithId( } @Test - void testAutoIdWithTwoSubnodeWithId( + void autoIdWithTwoSubnodeWithId( @Autowired final CustomerWithPurchaseRepository customerRepository) { final String purchaseName = "bag"; @@ -442,7 +488,7 @@ void testAutoIdWithTwoSubnodeWithId( } @Test - void testAutoIdWithTwoSameSubnodesWithSameIdDifferentNod( + void autoIdWithTwoSameSubnodesWithSameIdDifferentNod( @Autowired final CustomerWithPurchaseRepository customerRepository) { final Purchase purchase = new Purchase("bag"); @@ -475,7 +521,7 @@ void testAutoIdWithTwoSameSubnodesWithSameIdDifferentNod( } @Test - void testAutoIdWithTwoSameSubnodesWithSameIdSameNode( + void autoIdWithTwoSameSubnodesWithSameIdSameNode( @Autowired final CustomerWithPurchaseRepository customerRepository) { final Purchase purchase = new Purchase("bag"); @@ -498,7 +544,7 @@ void testAutoIdWithTwoSameSubnodesWithSameIdSameNode( } @Test - void testReplaceWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) + void replaceWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate existingCustomer = new CustomerWithIdIntegerNoAutoGenerate(1, TestData.FIRST_NAME, TestData.LAST_NAME); @@ -523,7 +569,7 @@ void testReplaceWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepos } @Test - void testReplaceWithAutoId(@Autowired final CustomerWithIdIntegerRepository customerRepository) + void replaceWithAutoId(@Autowired final CustomerWithIdIntegerRepository customerRepository) { final CustomerWithIdInteger existingCustomer = new CustomerWithIdInteger(TestData.FIRST_NAME, TestData.LAST_NAME); @@ -550,7 +596,7 @@ void testReplaceWithAutoId(@Autowired final CustomerWithIdIntegerRepository cust } @Test - void testReplaceWithIdSaveAll(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) + void replaceWithIdSaveAll(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate existingCustomer = new CustomerWithIdIntegerNoAutoGenerate(1, TestData.FIRST_NAME, TestData.LAST_NAME); @@ -565,7 +611,7 @@ void testReplaceWithIdSaveAll(@Autowired final CustomerWithIdIntegerNoAutoGenera } @Test - void testAddTwoWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) + void addTwoWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository) { final CustomerWithIdIntegerNoAutoGenerate existingCustomer = new CustomerWithIdIntegerNoAutoGenerate(1, TestData.FIRST_NAME, TestData.LAST_NAME); @@ -588,7 +634,7 @@ void testAddTwoWithId(@Autowired final CustomerWithIdIntegerNoAutoGenerateReposi } @Test - void testIdsInMultipleTransactions( + void idsInMultipleTransactions( @Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository, @Autowired final PlatformTransactionManager transactionManager ) @@ -626,7 +672,7 @@ void testIdsInMultipleTransactions( } @Test - void testIdsInSingleTransactions( + void idsInSingleTransactions( @Autowired final CustomerWithIdIntegerNoAutoGenerateRepository customerRepository, @Autowired final PlatformTransactionManager transactionManager ) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuid.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuid.java new file mode 100644 index 00000000..d764a86a --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuid.java @@ -0,0 +1,86 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; + +import java.util.Objects; +import java.util.UUID; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + + +public class CustomerWithIdUuid +{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + private final String firstName; + private final String lastName; + + public CustomerWithIdUuid(final String firstName, final String lastName) + { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() + { + return this.firstName; + } + + public String getLastName() + { + return this.lastName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s', lastName='%s']", + this.firstName, this.lastName); + } + + public UUID getId() + { + return this.id; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithIdUuid customer = (CustomerWithIdUuid)o; + return Objects.equals(this.firstName, customer.firstName) && Objects.equals( + this.lastName, + customer.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName, this.lastName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuidRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuidRepository.java new file mode 100644 index 00000000..a3a22ccb --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithIdUuidRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; + +import java.util.UUID; + +import org.springframework.data.repository.ListCrudRepository; + + +public interface CustomerWithIdUuidRepository extends ListCrudRepository +{ +} From ee3d3ee2677d11f5ce770a64746fa6a9cf4c6399 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 09:24:26 +0200 Subject: [PATCH 06/26] Simplest test with Custom Class as id --- .../tests/id/custom/CustomIdTest.java | 83 +++++++++++++++++++ .../tests/id/custom/SingleTestDataset.java | 38 +++++++++ .../tests/id/custom/model/CompositeKey.java | 37 +++++++++ .../id/custom/model/CompositeKeyAsRecord.java | 5 ++ .../model/CustomerWithIdCompositeKey.java | 74 +++++++++++++++++ .../CustomerWithIdCompositeKeyRepository.java | 24 ++++++ 6 files changed, 261 insertions(+) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/SingleTestDataset.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKey.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyRepository.java diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java new file mode 100644 index 00000000..ba0acf70 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.helper.TestData; +import software.xdev.spring.data.eclipse.store.helper.TestUtil; +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.IdTestConfiguration; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CompositeKey; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKey; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyRepository; +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +@SuppressWarnings("OptionalGetWithoutIsPresent") +@IsolatedTestAnnotations +@ContextConfiguration(classes = {IdTestConfiguration.class}) +class CustomIdTest +{ + public static Stream generateData() + { + return Stream.of( + new SingleTestDataset<>( + id -> new CustomerWithIdCompositeKey(id, TestData.FIRST_NAME), + context -> context.getBean(CustomerWithIdCompositeKeyRepository.class), + () -> new CompositeKey(1, 1), + () -> new CompositeKey(2, 2) + ).toArguments() + ); + } + + private final IdTestConfiguration configuration; + + @Autowired + public CustomIdTest(final IdTestConfiguration configuration) + { + this.configuration = configuration; + } + + @ParameterizedTest + @MethodSource("generateData") + void createSingleWithAutoIdInteger( + final SingleTestDataset data, @Autowired final ApplicationContext context) + { + final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); + + final T customer = data.enitityGenerator().apply(data.firstIdSupplier().get()); + repository.save(customer); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional loadedCustomer = repository.findById(data.firstIdSupplier().get()); + Assertions.assertTrue(loadedCustomer.isPresent()); + Assertions.assertEquals(customer, loadedCustomer.get()); + } + ); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/SingleTestDataset.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/SingleTestDataset.java new file mode 100644 index 00000000..b17085da --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/SingleTestDataset.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.jupiter.params.provider.Arguments; +import org.springframework.context.ApplicationContext; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public record SingleTestDataset( + Function enitityGenerator, + Function> repositoryGenerator, + Supplier firstIdSupplier, + Supplier secondIdSupplier +) +{ + public Arguments toArguments() + { + return Arguments.of(this); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java new file mode 100644 index 00000000..e76681d9 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java @@ -0,0 +1,37 @@ +package software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.util.Objects; + + +public class CompositeKey +{ + private final int idPart1; + private final int idPart2; + + public CompositeKey(final int idPart1, final int idPart2) + { + this.idPart1 = idPart1; + this.idPart2 = idPart2; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CompositeKey that = (CompositeKey)o; + return this.idPart1 == that.idPart1 && this.idPart2 == that.idPart2; + } + + @Override + public int hashCode() + { + return Objects.hash(this.idPart1, this.idPart2); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java new file mode 100644 index 00000000..be1b9826 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java @@ -0,0 +1,5 @@ +package software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +public record CompositeKeyAsRecord(int keyPart1, int keyPart2) +{ +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKey.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKey.java new file mode 100644 index 00000000..dad3fc07 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKey.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.util.Objects; + +import jakarta.persistence.Id; + + +public class CustomerWithIdCompositeKey +{ + @Id + private CompositeKey id; + + private final String firstName; + + public CustomerWithIdCompositeKey(final CompositeKey id, final String firstName) + { + this.id = id; + this.firstName = firstName; + } + + public String getFirstName() + { + return this.firstName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s']", + this.firstName); + } + + public CompositeKey getId() + { + return this.id; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithIdCompositeKey customer = (CustomerWithIdCompositeKey)o; + return Objects.equals(this.firstName, customer.firstName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyRepository.java new file mode 100644 index 00000000..c57cbd00 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public interface CustomerWithIdCompositeKeyRepository + extends EclipseStoreRepository +{ +} From 54745ddafb27e1254cf8aac47f03a9ed13d36f60 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 10:59:53 +0200 Subject: [PATCH 07/26] Tests with EmbeddedIds and CustomId-classes --- .../support/AnnotatedFieldFinder.java | 10 +- .../tests/id/custom/CustomIdTest.java | 119 +++++++++++++++++- .../tests/id/custom/model/CompositeKey.java | 3 +- .../CustomerWithIdCompositeKeyAsRecord.java | 74 +++++++++++ ...rWithIdCompositeKeyAsRecordRepository.java | 24 ++++ .../CustomerWithIdCompositeKeyEmbeddedId.java | 74 +++++++++++ ...ithIdCompositeKeyEmbeddedIdRepository.java | 24 ++++ .../custom/model/CustomerWithIdLocalDate.java | 75 +++++++++++ .../CustomerWithIdLocalDateRepository.java | 26 ++++ .../id/model/CustomerWithMultipleId.java | 92 ++++++++++++++ .../CustomerWithMultipleIdRepository.java | 23 ++++ 11 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecord.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecordRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedId.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedIdRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDate.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDateRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java index 3230cbbe..27332eef 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java @@ -31,8 +31,9 @@ private AnnotatedFieldFinder() } /** - * Finds any field in a class with an ID-Annotation ({@link jakarta.persistence.Id} or - * {@link org.springframework.data.annotation.Id}). Finds this field recursively in the Hierarchy-tree. + * Finds any field in a class with an ID-Annotation ({@link jakarta.persistence.Id}, + * {@link org.springframework.data.annotation.Id} or {@link jakarta.persistence.EmbeddedId}). Finds this field + * recursively in the Hierarchy-tree. * * @return field with ID-Annotation. Is {@link Optional#empty()} if no field was found. */ @@ -40,7 +41,10 @@ public static Optional findIdField(final Class domainClass) { return findAnnotatedField( domainClass, - List.of(jakarta.persistence.Id.class, org.springframework.data.annotation.Id.class) + List.of( + jakarta.persistence.Id.class, + org.springframework.data.annotation.Id.class, + jakarta.persistence.EmbeddedId.class) ); } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java index ba0acf70..bf5dc52d 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/CustomIdTest.java @@ -15,6 +15,8 @@ */ package software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom; +import java.time.LocalDate; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -31,8 +33,15 @@ import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.IdTestConfiguration; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CompositeKey; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CompositeKeyAsRecord; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKey; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyAsRecord; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyAsRecordRepository; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyEmbeddedId; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyEmbeddedIdRepository; import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdCompositeKeyRepository; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdLocalDate; +import software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model.CustomerWithIdLocalDateRepository; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; @@ -49,6 +58,24 @@ public static Stream generateData() context -> context.getBean(CustomerWithIdCompositeKeyRepository.class), () -> new CompositeKey(1, 1), () -> new CompositeKey(2, 2) + ).toArguments(), + new SingleTestDataset<>( + id -> new CustomerWithIdCompositeKeyEmbeddedId(id, TestData.FIRST_NAME), + context -> context.getBean(CustomerWithIdCompositeKeyEmbeddedIdRepository.class), + () -> new CompositeKey(1, 1), + () -> new CompositeKey(2, 2) + ).toArguments(), + new SingleTestDataset<>( + id -> new CustomerWithIdCompositeKeyAsRecord(id, TestData.FIRST_NAME), + context -> context.getBean(CustomerWithIdCompositeKeyAsRecordRepository.class), + () -> new CompositeKeyAsRecord(1, 1), + () -> new CompositeKeyAsRecord(2, 2) + ).toArguments(), + new SingleTestDataset<>( + id -> new CustomerWithIdLocalDate(id, TestData.FIRST_NAME), + context -> context.getBean(CustomerWithIdLocalDateRepository.class), + () -> LocalDate.of(2024, 1, 1), + () -> LocalDate.of(2024, 1, 2) ).toArguments() ); } @@ -63,7 +90,7 @@ public CustomIdTest(final IdTestConfiguration configuration) @ParameterizedTest @MethodSource("generateData") - void createSingleWithAutoIdInteger( + void createSingleWithCustomKey( final SingleTestDataset data, @Autowired final ApplicationContext context) { final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); @@ -80,4 +107,94 @@ void createSingleWithAutoIdInteger( } ); } + + @ParameterizedTest + @MethodSource("generateData") + void createDoubleWithCustomKey( + final SingleTestDataset data, @Autowired final ApplicationContext context) + { + final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); + + final T customer1 = data.enitityGenerator().apply(data.firstIdSupplier().get()); + repository.save(customer1); + + final T customer2 = data.enitityGenerator().apply(data.secondIdSupplier().get()); + repository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + Assertions.assertEquals(2, repository.findAll().size()); + Assertions.assertEquals( + 2, + repository.findAllById(List.of(data.firstIdSupplier().get(), data.secondIdSupplier().get())) + .size()); + + final Optional loadedCustomer1 = repository.findById(data.firstIdSupplier().get()); + Assertions.assertTrue(loadedCustomer1.isPresent()); + Assertions.assertEquals(customer1, loadedCustomer1.get()); + + final Optional loadedCustomer2 = repository.findById(data.secondIdSupplier().get()); + Assertions.assertTrue(loadedCustomer2.isPresent()); + Assertions.assertEquals(customer2, loadedCustomer2.get()); + } + ); + } + + @ParameterizedTest + @MethodSource("generateData") + void createNullWithCustomKey( + final SingleTestDataset data, @Autowired final ApplicationContext context) + { + final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); + + final T customer = data.enitityGenerator().apply(null); + Assertions.assertThrows(IllegalArgumentException.class, () -> repository.save(customer)); + } + + @ParameterizedTest + @MethodSource("generateData") + void deleteBeforeRestartWithCustomKey( + final SingleTestDataset data, @Autowired final ApplicationContext context) + { + final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); + + final T customer = data.enitityGenerator().apply(data.firstIdSupplier().get()); + repository.save(customer); + + repository.deleteById(data.firstIdSupplier().get()); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + Assertions.assertTrue(repository.findAll().isEmpty()); + final Optional loadedCustomer = repository.findById(data.firstIdSupplier().get()); + Assertions.assertFalse(loadedCustomer.isPresent()); + } + ); + } + + @ParameterizedTest + @MethodSource("generateData") + void deleteAfterRestartWithCustomKey( + final SingleTestDataset data, @Autowired final ApplicationContext context) + { + final EclipseStoreRepository repository = data.repositoryGenerator().apply(context); + + final T customer = data.enitityGenerator().apply(data.firstIdSupplier().get()); + repository.save(customer); + + TestUtil.restartDatastore(this.configuration); + + repository.deleteById(data.firstIdSupplier().get()); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + Assertions.assertTrue(repository.findAll().isEmpty()); + final Optional loadedCustomer = repository.findById(data.firstIdSupplier().get()); + Assertions.assertFalse(loadedCustomer.isPresent()); + } + ); + } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java index e76681d9..bf5eda06 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java @@ -1,9 +1,10 @@ package software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; +import java.io.Serializable; import java.util.Objects; -public class CompositeKey +public class CompositeKey implements Serializable { private final int idPart1; private final int idPart2; diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecord.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecord.java new file mode 100644 index 00000000..6d6fa3e7 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecord.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.util.Objects; + +import jakarta.persistence.Id; + + +public class CustomerWithIdCompositeKeyAsRecord +{ + @Id + private CompositeKeyAsRecord id; + + private final String firstName; + + public CustomerWithIdCompositeKeyAsRecord(final CompositeKeyAsRecord id, final String firstName) + { + this.id = id; + this.firstName = firstName; + } + + public String getFirstName() + { + return this.firstName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s']", + this.firstName); + } + + public CompositeKeyAsRecord getId() + { + return this.id; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithIdCompositeKeyAsRecord customer = (CustomerWithIdCompositeKeyAsRecord)o; + return Objects.equals(this.firstName, customer.firstName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecordRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecordRepository.java new file mode 100644 index 00000000..26ac79f8 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyAsRecordRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public interface CustomerWithIdCompositeKeyAsRecordRepository + extends EclipseStoreRepository +{ +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedId.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedId.java new file mode 100644 index 00000000..19cef1c5 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedId.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.util.Objects; + +import jakarta.persistence.EmbeddedId; + + +public class CustomerWithIdCompositeKeyEmbeddedId +{ + @EmbeddedId + private final CompositeKey id; + + private final String firstName; + + public CustomerWithIdCompositeKeyEmbeddedId(final CompositeKey id, final String firstName) + { + this.id = id; + this.firstName = firstName; + } + + public String getFirstName() + { + return this.firstName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s']", + this.firstName); + } + + public CompositeKey getId() + { + return this.id; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithIdCompositeKeyEmbeddedId customer = (CustomerWithIdCompositeKeyEmbeddedId)o; + return Objects.equals(this.firstName, customer.firstName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedIdRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedIdRepository.java new file mode 100644 index 00000000..98a591a9 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdCompositeKeyEmbeddedIdRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public interface CustomerWithIdCompositeKeyEmbeddedIdRepository + extends EclipseStoreRepository +{ +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDate.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDate.java new file mode 100644 index 00000000..126ffb14 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDate.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.time.LocalDate; +import java.util.Objects; + +import jakarta.persistence.Id; + + +public class CustomerWithIdLocalDate +{ + @Id + private LocalDate id; + + private final String firstName; + + public CustomerWithIdLocalDate(final LocalDate id, final String firstName) + { + this.id = id; + this.firstName = firstName; + } + + public String getFirstName() + { + return this.firstName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s']", + this.firstName); + } + + public LocalDate getId() + { + return this.id; + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithIdLocalDate customer = (CustomerWithIdLocalDate)o; + return Objects.equals(this.firstName, customer.firstName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDateRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDateRepository.java new file mode 100644 index 00000000..39260256 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CustomerWithIdLocalDateRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; + +import java.time.LocalDate; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public interface CustomerWithIdLocalDateRepository + extends EclipseStoreRepository +{ +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java new file mode 100644 index 00000000..2f04bd43 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java @@ -0,0 +1,92 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; + +import java.util.List; +import java.util.Objects; + +import jakarta.persistence.Id; + + +public class CustomerWithMultipleId +{ + @Id + private int id1; + @Id + private int id2; + + private String firstName; + private String lastName; + + public CustomerWithMultipleId() + { + } + + public CustomerWithMultipleId(final String firstName, final String lastName) + { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() + { + return this.firstName; + } + + public String getLastName() + { + return this.lastName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s', lastName='%s']", + this.firstName, this.lastName); + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithMultipleId customer = (CustomerWithMultipleId)o; + return Objects.equals(this.firstName, customer.firstName) && Objects.equals( + this.lastName, + customer.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName, this.lastName); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + public static CustomerWithMultipleId getCustomerWithFirstName( + final List customers, + final String firstName) + { + return customers.stream().filter(customer -> customer.getFirstName().equals(firstName)).findFirst().get(); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java new file mode 100644 index 00000000..59ba8a67 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; + +import org.springframework.data.repository.ListCrudRepository; + + +public interface CustomerWithMultipleIdRepository extends ListCrudRepository +{ +} From eebf560488f5db3230a824785e74370922e53f09 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 11:33:56 +0200 Subject: [PATCH 08/26] Updated docs and implemented check for multiple versions and ids --- docs/modules/ROOT/pages/features/ids.adoc | 11 +++ ...alException.java => IdFieldException.java} | 4 +- .../support/AnnotatedFieldFinder.java | 52 +++++++++-- .../SimpleEntityVersionIncrementer.java | 4 +- .../repository/support/id/SimpleIdSetter.java | 4 +- .../id/model/CustomerWithMultipleId.java | 92 ------------------- .../CustomerWithMultipleIdRepository.java | 23 ----- 7 files changed, 60 insertions(+), 130 deletions(-) rename spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/{IdFieldFinalException.java => IdFieldException.java} (86%) delete mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java delete mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java diff --git a/docs/modules/ROOT/pages/features/ids.adoc b/docs/modules/ROOT/pages/features/ids.adoc index 0750aaf7..8d9cdb35 100644 --- a/docs/modules/ROOT/pages/features/ids.adoc +++ b/docs/modules/ROOT/pages/features/ids.adoc @@ -6,3 +6,14 @@ * ``long`` / ``Long`` * ``String`` * ``UUID`` + +Other generation types are currently not supported. + +== Composite keys + +It is possible to use **any class as https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/id[``@Id``]** but without any auto generation. +Most importantly the used class **must have a valid ``hashCode``** since a ``HashMap`` is used to store and manage entities. + +{product-name} can also handle https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/embeddedid[``@EmbeddedId``] which results in the same behavior as ``@Id`` but the id-class must then implement ``Serializable``. + +Multiple Ids for a single entity and https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/idclass[``@IdClass``] are **not** supported. diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldFinalException.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldException.java similarity index 86% rename from spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldFinalException.java rename to spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldException.java index 144e2c55..4e4fd186 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldFinalException.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/exceptions/IdFieldException.java @@ -15,9 +15,9 @@ */ package software.xdev.spring.data.eclipse.store.exceptions; -public class IdFieldFinalException extends RuntimeException +public class IdFieldException extends RuntimeException { - public IdFieldFinalException(final String message) + public IdFieldException(final String message) { super(message); } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java index 27332eef..03826ee3 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/AnnotatedFieldFinder.java @@ -17,10 +17,17 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Id; +import jakarta.persistence.Version; + +import software.xdev.spring.data.eclipse.store.exceptions.IdFieldException; +import software.xdev.spring.data.eclipse.store.exceptions.InvalidVersionException; import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; @@ -39,13 +46,26 @@ private AnnotatedFieldFinder() */ public static Optional findIdField(final Class domainClass) { - return findAnnotatedField( + final List idFields = findAnnotatedFields( domainClass, List.of( - jakarta.persistence.Id.class, + Id.class, org.springframework.data.annotation.Id.class, - jakarta.persistence.EmbeddedId.class) + EmbeddedId.class) ); + + if(idFields.isEmpty()) + { + return Optional.empty(); + } + else + { + if(idFields.size() > 1) + { + throw new IdFieldException("Only one id field is allowed"); + } + return Optional.of(idFields.get(0)); + } } /** @@ -56,21 +76,35 @@ public static Optional findIdField(final Class domainClass) */ public static Optional findVersionField(final Class domainClass) { - return findAnnotatedField( + final List versionFields = findAnnotatedFields( domainClass, - List.of(jakarta.persistence.Version.class, org.springframework.data.annotation.Version.class) + List.of(Version.class, org.springframework.data.annotation.Version.class) ); + + if(versionFields.isEmpty()) + { + return Optional.empty(); + } + else + { + if(versionFields.size() > 1) + { + throw new InvalidVersionException("Only one version field is allowed"); + } + return Optional.of(versionFields.get(0)); + } } /** * Finds any field in a class with specified annotations. Finds this field recursively in the Hierarchy-tree. * - * @return field with annotation. Is {@link Optional#empty()} if no field was found. + * @return fields with annotation. */ - public static Optional findAnnotatedField( + public static List findAnnotatedFields( final Class domainClass, final Collection> annotations) { + final ArrayList foundFields = new ArrayList<>(); final Collection classFields = AccessHelper.getInheritedPrivateFieldsByName(domainClass).values(); for(final Field currentField : classFields) { @@ -78,10 +112,10 @@ public static Optional findAnnotatedField( { if(currentField.getAnnotationsByType(annotation).length > 0) { - return Optional.of(currentField); + foundFields.add(currentField); } } } - return Optional.empty(); + return foundFields; } } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/version/SimpleEntityVersionIncrementer.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/version/SimpleEntityVersionIncrementer.java index cb873e85..046d4001 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/version/SimpleEntityVersionIncrementer.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/version/SimpleEntityVersionIncrementer.java @@ -19,7 +19,7 @@ import java.lang.reflect.Modifier; import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException; -import software.xdev.spring.data.eclipse.store.exceptions.IdFieldFinalException; +import software.xdev.spring.data.eclipse.store.exceptions.IdFieldException; import software.xdev.spring.data.eclipse.store.repository.access.modifier.FieldAccessModifier; import software.xdev.spring.data.eclipse.store.repository.support.copier.version.incrementer.VersionIncrementer; @@ -43,7 +43,7 @@ private void checkIfVersionFieldIsFinal() final int fieldModifiers = this.versionField.getModifiers(); if(Modifier.isFinal(fieldModifiers)) { - throw new IdFieldFinalException(String.format( + throw new IdFieldException(String.format( "Field %s is final and cannot be modified. Version fields must not be final.", this.versionField.getName())); } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/SimpleIdSetter.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/SimpleIdSetter.java index 11c97285..08cfb984 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/SimpleIdSetter.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/id/SimpleIdSetter.java @@ -20,7 +20,7 @@ import java.util.function.Consumer; import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException; -import software.xdev.spring.data.eclipse.store.exceptions.IdFieldFinalException; +import software.xdev.spring.data.eclipse.store.exceptions.IdFieldException; import software.xdev.spring.data.eclipse.store.repository.access.modifier.FieldAccessModifier; import software.xdev.spring.data.eclipse.store.repository.support.id.strategy.IdFinder; @@ -45,7 +45,7 @@ private void checkIfIdFieldIsFinal() final int fieldModifiers = this.idField.getModifiers(); if(Modifier.isFinal(fieldModifiers)) { - throw new IdFieldFinalException(String.format( + throw new IdFieldException(String.format( "Field %s is final and cannot be modified. ID fields must not be final.", this.idField.getName())); } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java deleted file mode 100644 index 2f04bd43..00000000 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleId.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * 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 - * - * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; - -import java.util.List; -import java.util.Objects; - -import jakarta.persistence.Id; - - -public class CustomerWithMultipleId -{ - @Id - private int id1; - @Id - private int id2; - - private String firstName; - private String lastName; - - public CustomerWithMultipleId() - { - } - - public CustomerWithMultipleId(final String firstName, final String lastName) - { - this.firstName = firstName; - this.lastName = lastName; - } - - public String getFirstName() - { - return this.firstName; - } - - public String getLastName() - { - return this.lastName; - } - - @Override - public String toString() - { - return String.format( - "Customer[firstName='%s', lastName='%s']", - this.firstName, this.lastName); - } - - @Override - public boolean equals(final Object o) - { - if(this == o) - { - return true; - } - if(o == null || this.getClass() != o.getClass()) - { - return false; - } - final CustomerWithMultipleId customer = (CustomerWithMultipleId)o; - return Objects.equals(this.firstName, customer.firstName) && Objects.equals( - this.lastName, - customer.lastName); - } - - @Override - public int hashCode() - { - return Objects.hash(this.firstName, this.lastName); - } - - @SuppressWarnings("OptionalGetWithoutIsPresent") - public static CustomerWithMultipleId getCustomerWithFirstName( - final List customers, - final String firstName) - { - return customers.stream().filter(customer -> customer.getFirstName().equals(firstName)).findFirst().get(); - } -} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java deleted file mode 100644 index 59ba8a67..00000000 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/model/CustomerWithMultipleIdRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * 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 - * - * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.model; - -import org.springframework.data.repository.ListCrudRepository; - - -public interface CustomerWithMultipleIdRepository extends ListCrudRepository -{ -} From b8d9d558e39dee10e6cecc58be723ec28332be41 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 11:35:31 +0200 Subject: [PATCH 09/26] Added JavaDoc --- .../tests/id/custom/model/CompositeKey.java | 15 +++++++++++++++ .../id/custom/model/CompositeKeyAsRecord.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java index bf5eda06..157b4be8 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKey.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; import java.io.Serializable; diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java index be1b9826..4ae66c86 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/custom/model/CompositeKeyAsRecord.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.id.custom.model; public record CompositeKeyAsRecord(int keyPart1, int keyPart2) From 9de24c456fb8556e7ad360e5becfd85d3e1e9d3e Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 11:51:42 +0200 Subject: [PATCH 10/26] Update .gitignore --- .gitignore | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 3a2e1e20..3370dfc0 100644 --- a/.gitignore +++ b/.gitignore @@ -62,11 +62,12 @@ hs_err_pid* .tern-project # EclipseStore -storage -storage-person -storage-invoice -storage-complex -storage-eclipsestore +spring-data-eclipse-store/storage +spring-data-eclipse-store-demo/storage +spring-data-eclipse-store-demo/storage-person +spring-data-eclipse-store-demo/storage-invoice +spring-data-eclipse-store-demo/storage-complex +spring-data-eclipse-store-jpa/storage-eclipsestore spring-data-eclipse-store-jpa/storage-h2.mv.db spring-data-eclipse-store-jpa/storage-h2.trace.db From 1acd1b0b116840a16b19663e08e068045e69c551 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 11:51:50 +0200 Subject: [PATCH 11/26] Fixed tests --- .../DualStorageDemoApplicationTest.java | 44 +++++++++++++++++++ .../integration/isolated/tests/id/IdTest.java | 3 +- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 spring-data-eclipse-store-demo/src/test/java/software/xdev/spring/data/eclipse/store/demo/dual/storage/DualStorageDemoApplicationTest.java diff --git a/spring-data-eclipse-store-demo/src/test/java/software/xdev/spring/data/eclipse/store/demo/dual/storage/DualStorageDemoApplicationTest.java b/spring-data-eclipse-store-demo/src/test/java/software/xdev/spring/data/eclipse/store/demo/dual/storage/DualStorageDemoApplicationTest.java new file mode 100644 index 00000000..d70af695 --- /dev/null +++ b/spring-data-eclipse-store-demo/src/test/java/software/xdev/spring/data/eclipse/store/demo/dual/storage/DualStorageDemoApplicationTest.java @@ -0,0 +1,44 @@ +package software.xdev.spring.data.eclipse.store.demo.dual.storage; + +import java.io.File; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import software.xdev.spring.data.eclipse.store.demo.TestUtil; +import software.xdev.spring.data.eclipse.store.demo.dual.storage.invoice.PersistenceInvoiceConfiguration; +import software.xdev.spring.data.eclipse.store.demo.dual.storage.person.PersistencePersonConfiguration; + + +@SpringBootTest(classes = DualStorageDemoApplication.class) +class DualStorageDemoApplicationTest +{ + private final PersistenceInvoiceConfiguration invoiceConfiguration; + private final PersistencePersonConfiguration personConfiguration; + + @Autowired + public DualStorageDemoApplicationTest( + final PersistenceInvoiceConfiguration invoiceConfiguration, + final PersistencePersonConfiguration personConfiguration) + { + this.invoiceConfiguration = invoiceConfiguration; + this.personConfiguration = personConfiguration; + } + + @BeforeAll + static void clearPreviousData() + { + TestUtil.deleteDirectory(new File("./" + PersistenceInvoiceConfiguration.STORAGE_PATH)); + TestUtil.deleteDirectory(new File("./" + PersistencePersonConfiguration.STORAGE_PATH)); + } + + @Test + void checkPossibilityToSimplyStartAndRestartApplication() + { + this.invoiceConfiguration.getStorageInstance().stop(); + this.personConfiguration.getStorageInstance().stop(); + DualStorageDemoApplication.main(new String[]{}); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java index aa757a01..fd216e61 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/id/IdTest.java @@ -228,9 +228,8 @@ void createMultipleWithAutoIdUuidSingleFinds( this.configuration, () -> { final Optional loadedCustomer1 = customerRepository.findById(generatedId1); - Assertions.assertEquals(customer1, loadedCustomer1.get()); final Optional loadedCustomer2 = customerRepository.findById(generatedId2); - Assertions.assertEquals(customer2, loadedCustomer2.get()); + Assertions.assertNotEquals(loadedCustomer2, loadedCustomer1); } ); } From 73701061cb00a978794293fa3eb9b33936cd8a8f Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 13:34:56 +0200 Subject: [PATCH 12/26] Restructured query tests --- .../isolated/tests/query/by/string/Child.java | 82 ++++ .../tests/query/by/string/Customer.java | 89 ++++ .../query/by/string/CustomerRepository.java | 45 ++ .../query/by/string/CustomerWithChild.java | 104 +++++ .../string/CustomerWithChildRepository.java | 26 ++ .../tests/query/by/string/QueryPageTest.java | 174 ++++++++ .../tests/query/by/string/QuerySortTest.java | 123 ++++++ .../tests/query/by/string/QueryTest.java | 184 ++++++++ .../by/string/QueryTestConfiguration.java | 38 ++ .../integration/shared/tests/QueryTest.java | 408 ------------------ 10 files changed, 865 insertions(+), 408 deletions(-) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Child.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Customer.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChild.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChildRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryPageTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QuerySortTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTestConfiguration.java delete mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/QueryTest.java diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Child.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Child.java new file mode 100644 index 00000000..ef7da71a --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Child.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.Objects; + + +public class Child +{ + private String firstName; + private String lastName; + + public Child(final String firstName, final String lastName) + { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() + { + return this.firstName; + } + + public void setFirstName(final String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return this.lastName; + } + + public void setLastName(final String lastName) + { + this.lastName = lastName; + } + + @Override + public String toString() + { + return String.format( + "Child[firstName='%s', lastName='%s']", + this.firstName, this.lastName); + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final Child customer = (Child)o; + return Objects.equals(this.firstName, customer.firstName) && Objects.equals( + this.lastName, + customer.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName, this.lastName); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Customer.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Customer.java new file mode 100644 index 00000000..98b6daba --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/Customer.java @@ -0,0 +1,89 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; +import java.util.Objects; + + +public class Customer +{ + private String firstName; + private String lastName; + + public Customer(final String firstName, final String lastName) + { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() + { + return this.firstName; + } + + public void setFirstName(final String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return this.lastName; + } + + public void setLastName(final String lastName) + { + this.lastName = lastName; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s', lastName='%s']", + this.firstName, this.lastName); + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final Customer customer = (Customer)o; + return Objects.equals(this.firstName, customer.firstName) && Objects.equals( + this.lastName, + customer.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName, this.lastName); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + public static Customer getCustomerWithFirstName(final List customers, final String firstName) + { + return customers.stream().filter(customer -> customer.getFirstName().equals(firstName)).findFirst().get(); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerRepository.java new file mode 100644 index 00000000..562f7a60 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerRepository.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + + +public interface CustomerRepository + extends CrudRepository, PagingAndSortingRepository +{ + Optional findByFirstName(String firstName); + + Iterable findAllByLastName(String lastName); + + @Override + Page findAll(Pageable pageable); + + Page findAllByLastName(String lastName, Pageable pageable); + + List findByOrderByLastNameAsc(); + + Iterable findAllByLastName(String lastName, Sort sort); + + List findAllByFirstName(String lastName, Pageable pageable); +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChild.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChild.java new file mode 100644 index 00000000..e143dc6c --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChild.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; +import java.util.Objects; + + +public class CustomerWithChild +{ + private String firstName; + private String lastName; + + private Child child; + + public CustomerWithChild(final String firstName, final String lastName, final Child child) + { + this.firstName = firstName; + this.lastName = lastName; + this.child = child; + } + + public String getFirstName() + { + return this.firstName; + } + + public void setFirstName(final String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return this.lastName; + } + + public void setLastName(final String lastName) + { + this.lastName = lastName; + } + + public Child getChild() + { + return this.child; + } + + public void setChild(final Child child) + { + this.child = child; + } + + @Override + public String toString() + { + return String.format( + "Customer[firstName='%s', lastName='%s']", + this.firstName, this.lastName); + } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final CustomerWithChild customer = (CustomerWithChild)o; + return Objects.equals(this.firstName, customer.firstName) && Objects.equals( + this.lastName, + customer.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(this.firstName, this.lastName); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + public static CustomerWithChild getCustomerWithFirstName( + final List customers, + final String firstName) + { + return customers.stream().filter(customer -> customer.getFirstName().equals(firstName)).findFirst().get(); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChildRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChildRepository.java new file mode 100644 index 00000000..e708d148 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/CustomerWithChildRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; + + +public interface CustomerWithChildRepository extends CrudRepository +{ + Optional findByChild(Child child); +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryPageTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryPageTest.java new file mode 100644 index 00000000..5a345241 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryPageTest.java @@ -0,0 +1,174 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +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.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.helper.TestData; +import software.xdev.spring.data.eclipse.store.helper.TestUtil; +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; + + +@IsolatedTestAnnotations +@ContextConfiguration(classes = {QueryTestConfiguration.class}) +class QueryPageTest +{ + @Autowired + private CustomerRepository customerRepository; + @Autowired + private QueryTestConfiguration configuration; + + @Test + void pageableFindAllTwoPages() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Pageable pageable = PageRequest.of(0, 1); + final List customersPage1 = + TestUtil.iterableToList(this.customerRepository.findAll(pageable)); + Assertions.assertEquals(1, customersPage1.size()); + + final List customersPage2 = + TestUtil.iterableToList(this.customerRepository.findAll(pageable.next())); + Assertions.assertEquals(1, customersPage2.size()); + + Assertions.assertNotEquals(customersPage1.get(0), customersPage2.get(0)); + } + ); + } + + @Test + void pageableFindAllTwoPagesWithNextPageable() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Page customersPage1 = this.customerRepository.findAll(PageRequest.of(0, 1)); + Assertions.assertEquals(1, customersPage1.getContent().size()); + + final List customersPage2 = + TestUtil.iterableToList(this.customerRepository.findAll(customersPage1.nextPageable())); + Assertions.assertEquals(1, customersPage2.size()); + + Assertions.assertNotEquals(customersPage1.getContent().get(0), customersPage2.get(0)); + } + ); + } + + @Test + void pageableFindAllUnpaged() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Pageable pageable = Pageable.unpaged(); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAll(pageable)); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertNotEquals(customersPage.get(0), customersPage.get(1)); + } + ); + } + + @Test + void pageableFindAllOnePage() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Pageable pageable = PageRequest.of(0, 2); + final List customersPage1 = + TestUtil.iterableToList(this.customerRepository.findAll(pageable)); + Assertions.assertEquals(2, customersPage1.size()); + Assertions.assertEquals( + customer1, + Customer.getCustomerWithFirstName(customersPage1, TestData.FIRST_NAME)); + Assertions.assertEquals( + customer2, + Customer.getCustomerWithFirstName(customersPage1, TestData.FIRST_NAME_ALTERNATIVE)); + } + ); + } + + @Test + void pageableFindByLastName() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Pageable pageable = PageRequest.of(0, 10); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, pageable)); + Assertions.assertEquals(1, customersPage.size()); + Assertions.assertEquals(customer1, customersPage.get(0)); + } + ); + } + + @Test + void pageableFindByFirstNameWithList() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Pageable pageable = PageRequest.of(0, 10); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAllByFirstName(TestData.FIRST_NAME, pageable)); + Assertions.assertEquals(1, customersPage.size()); + Assertions.assertEquals(customer1, customersPage.get(0)); + } + ); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QuerySortTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QuerySortTest.java new file mode 100644 index 00000000..3eb98691 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QuerySortTest.java @@ -0,0 +1,123 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.helper.TestData; +import software.xdev.spring.data.eclipse.store.helper.TestUtil; +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; + + +@IsolatedTestAnnotations +@ContextConfiguration(classes = {QueryTestConfiguration.class}) +class QuerySortTest +{ + @Autowired + private CustomerRepository customerRepository; + @Autowired + private QueryTestConfiguration configuration; + + @Test + void sortFindByLastNameDefault() + { + final Customer customer1 = new Customer("B", TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer("A", TestData.LAST_NAME); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Sort sort = Sort.by("firstName"); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertEquals(customer2, customersPage.get(0)); + Assertions.assertEquals(customer1, customersPage.get(1)); + } + ); + } + + @Test + void sortFindByFirstNameAscending() + { + final Customer customer1 = new Customer("B", TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer("A", TestData.LAST_NAME); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Sort sort = Sort.by("firstName").ascending(); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertEquals(customer2, customersPage.get(0)); + Assertions.assertEquals(customer1, customersPage.get(1)); + } + ); + } + + @Test + void sortFindByFirstNameAscendingVariant() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, "B"); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME, "A"); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Sort sort = Sort.by(Sort.Direction.ASC, "lastName"); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAll(sort)); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertEquals(customer2, customersPage.get(0)); + Assertions.assertEquals(customer1, customersPage.get(1)); + } + ); + } + + @Test + void sortFindByLastNameDescending() + { + final Customer customer1 = new Customer("B", TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer("A", TestData.LAST_NAME); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Sort sort = Sort.by("firstName").descending(); + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertEquals(customer1, customersPage.get(0)); + Assertions.assertEquals(customer2, customersPage.get(1)); + } + ); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTest.java new file mode 100644 index 00000000..b3fe7e27 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTest.java @@ -0,0 +1,184 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.helper.TestData; +import software.xdev.spring.data.eclipse.store.helper.TestUtil; +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; + + +@IsolatedTestAnnotations +@ContextConfiguration(classes = {QueryTestConfiguration.class}) +class QueryTest +{ + @Autowired + private CustomerRepository customerRepository; + @Autowired + private CustomerWithChildRepository customerWithChildRepository; + + @Autowired + private QueryTestConfiguration configuration; + + @Test + void basicFindAllByLastNameTwoResults() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final List customers = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME)); + Assertions.assertEquals(2, customers.size()); + Assertions.assertEquals(customer1, Customer.getCustomerWithFirstName(customers, TestData.FIRST_NAME)); + Assertions.assertEquals( + customer2, + Customer.getCustomerWithFirstName(customers, TestData.FIRST_NAME_ALTERNATIVE)); + } + ); + } + + @Test + void basicFindAllByLastNameOneResult() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final List customers = + TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME_ALTERNATIVE)); + Assertions.assertEquals(1, customers.size()); + Assertions.assertEquals(customer2, customers.get(0)); + } + ); + } + + @Test + void basicFindByFirstNameOneResult() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); + this.customerRepository.save(customer1); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional foundCustomer = this.customerRepository.findByFirstName(TestData.FIRST_NAME); + Assertions.assertTrue(foundCustomer.isPresent()); + Assertions.assertEquals(TestData.FIRST_NAME, foundCustomer.get().getFirstName()); + } + ); + } + + @Test + void findByOrderByLastNameAsc() + { + final Customer customer1 = new Customer(TestData.FIRST_NAME, "B"); + this.customerRepository.save(customer1); + final Customer customer2 = new Customer(TestData.FIRST_NAME, "A"); + this.customerRepository.save(customer2); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final List customersPage = + TestUtil.iterableToList(this.customerRepository.findByOrderByLastNameAsc()); + Assertions.assertEquals(2, customersPage.size()); + Assertions.assertEquals(customer2, customersPage.get(0)); + Assertions.assertEquals(customer1, customersPage.get(1)); + } + ); + } + + @Test + void findByChildExistingChild() + { + final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); + this.customerWithChildRepository.save(customer); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional foundCustomer = this.customerWithChildRepository.findByChild(child); + Assertions.assertTrue(foundCustomer.isPresent()); + } + ); + } + + @Test + void findByChildNewChild() + { + final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); + this.customerWithChildRepository.save(customer); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Child queryChild = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + final Optional foundCustomer = + this.customerWithChildRepository.findByChild(queryChild); + Assertions.assertTrue(foundCustomer.isPresent()); + } + ); + } + + @Test + void findByChildWhenNullNotExists() + { + final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); + final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); + this.customerWithChildRepository.save(customer); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional foundCustomer = this.customerWithChildRepository.findByChild(null); + Assertions.assertTrue(foundCustomer.isEmpty()); + } + ); + } + + @Test + void findByChildWhenNullExists() + { + final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, null); + this.customerWithChildRepository.save(customer); + + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final Optional foundCustomer = this.customerWithChildRepository.findByChild(null); + Assertions.assertTrue(foundCustomer.isPresent()); + } + ); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTestConfiguration.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTestConfiguration.java new file mode 100644 index 00000000..bab32f2e --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/QueryTestConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import org.eclipse.store.integrations.spring.boot.types.configuration.EclipseStoreProperties; +import org.eclipse.store.integrations.spring.boot.types.factories.EmbeddedStorageFoundationFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import software.xdev.spring.data.eclipse.store.integration.TestConfiguration; +import software.xdev.spring.data.eclipse.store.repository.config.EnableEclipseStoreRepositories; + + +@Configuration +@EnableEclipseStoreRepositories +public class QueryTestConfiguration extends TestConfiguration +{ + @Autowired + protected QueryTestConfiguration( + final EclipseStoreProperties defaultEclipseStoreProperties, + final EmbeddedStorageFoundationFactory defaultEclipseStoreProvider) + { + super(defaultEclipseStoreProperties, defaultEclipseStoreProvider); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/QueryTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/QueryTest.java deleted file mode 100644 index 00d2905b..00000000 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/QueryTest.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * 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 - * - * http://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 software.xdev.spring.data.eclipse.store.integration.shared.tests; - -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -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 software.xdev.spring.data.eclipse.store.helper.TestData; -import software.xdev.spring.data.eclipse.store.helper.TestUtil; -import software.xdev.spring.data.eclipse.store.integration.shared.DefaultTestAnnotations; -import software.xdev.spring.data.eclipse.store.integration.shared.SharedTestConfiguration; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.Child; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.Customer; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.CustomerRepository; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.CustomerWithChild; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.CustomerWithChildRepository; - - -@DefaultTestAnnotations -class QueryTest -{ - @Autowired - private CustomerRepository customerRepository; - @Autowired - private CustomerWithChildRepository customerWithChildRepository; - - @Autowired - private SharedTestConfiguration configuration; - - @Test - void testBasicFindAllByLastNameTwoResults() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final List customers = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME)); - Assertions.assertEquals(2, customers.size()); - Assertions.assertEquals(customer1, Customer.getCustomerWithFirstName(customers, TestData.FIRST_NAME)); - Assertions.assertEquals( - customer2, - Customer.getCustomerWithFirstName(customers, TestData.FIRST_NAME_ALTERNATIVE)); - } - ); - } - - @Test - void testBasicFindAllByLastNameOneResult() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final List customers = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME_ALTERNATIVE)); - Assertions.assertEquals(1, customers.size()); - Assertions.assertEquals(customer2, customers.get(0)); - } - ); - } - - @Test - void testBasicFindByFirstNameOneResult() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Optional foundCustomer = this.customerRepository.findByFirstName(TestData.FIRST_NAME); - Assertions.assertTrue(foundCustomer.isPresent()); - Assertions.assertEquals(TestData.FIRST_NAME, foundCustomer.get().getFirstName()); - } - ); - } - - @Test - void testPageableFindAllTwoPages() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Pageable pageable = PageRequest.of(0, 1); - final List customersPage1 = - TestUtil.iterableToList(this.customerRepository.findAll(pageable)); - Assertions.assertEquals(1, customersPage1.size()); - - final List customersPage2 = - TestUtil.iterableToList(this.customerRepository.findAll(pageable.next())); - Assertions.assertEquals(1, customersPage2.size()); - - Assertions.assertNotEquals(customersPage1.get(0), customersPage2.get(0)); - } - ); - } - - @Test - void testPageableFindAllTwoPagesWithNextPageable() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Page customersPage1 = this.customerRepository.findAll(PageRequest.of(0, 1)); - Assertions.assertEquals(1, customersPage1.getContent().size()); - - final List customersPage2 = - TestUtil.iterableToList(this.customerRepository.findAll(customersPage1.nextPageable())); - Assertions.assertEquals(1, customersPage2.size()); - - Assertions.assertNotEquals(customersPage1.getContent().get(0), customersPage2.get(0)); - } - ); - } - - @Test - void testPageableFindAllUnpaged() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Pageable pageable = Pageable.unpaged(); - final List customersPage = TestUtil.iterableToList(this.customerRepository.findAll(pageable)); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertNotEquals(customersPage.get(0), customersPage.get(1)); - } - ); - } - - @Test - void testPageableFindAllOnePage() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Pageable pageable = PageRequest.of(0, 2); - final List customersPage1 = - TestUtil.iterableToList(this.customerRepository.findAll(pageable)); - Assertions.assertEquals(2, customersPage1.size()); - Assertions.assertEquals( - customer1, - Customer.getCustomerWithFirstName(customersPage1, TestData.FIRST_NAME)); - Assertions.assertEquals( - customer2, - Customer.getCustomerWithFirstName(customersPage1, TestData.FIRST_NAME_ALTERNATIVE)); - } - ); - } - - @Test - void testPageableFindByLastName() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Pageable pageable = PageRequest.of(0, 10); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, pageable)); - Assertions.assertEquals(1, customersPage.size()); - Assertions.assertEquals(customer1, customersPage.get(0)); - } - ); - } - - @Test - void testPageableFindByFirstNameWithList() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Pageable pageable = PageRequest.of(0, 10); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAllByFirstName(TestData.FIRST_NAME, pageable)); - Assertions.assertEquals(1, customersPage.size()); - Assertions.assertEquals(customer1, customersPage.get(0)); - } - ); - } - - @Test - void testSortFindByLastNameDefault() - { - final Customer customer1 = new Customer("B", TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer("A", TestData.LAST_NAME); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Sort sort = Sort.by("firstName"); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertEquals(customer2, customersPage.get(0)); - Assertions.assertEquals(customer1, customersPage.get(1)); - } - ); - } - - @Test - void testSortFindByFirstNameAscending() - { - final Customer customer1 = new Customer("B", TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer("A", TestData.LAST_NAME); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Sort sort = Sort.by("firstName").ascending(); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertEquals(customer2, customersPage.get(0)); - Assertions.assertEquals(customer1, customersPage.get(1)); - } - ); - } - - @Test - void testSortFindByFirstNameAscendingVariant() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, "B"); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME, "A"); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Sort sort = Sort.by(Sort.Direction.ASC, "lastName"); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAll(sort)); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertEquals(customer2, customersPage.get(0)); - Assertions.assertEquals(customer1, customersPage.get(1)); - } - ); - } - - @Test - void testFindByOrderByLastNameAsc() - { - final Customer customer1 = new Customer(TestData.FIRST_NAME, "B"); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer(TestData.FIRST_NAME, "A"); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findByOrderByLastNameAsc()); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertEquals(customer2, customersPage.get(0)); - Assertions.assertEquals(customer1, customersPage.get(1)); - } - ); - } - - @Test - void testSortFindByLastNameDescending() - { - final Customer customer1 = new Customer("B", TestData.LAST_NAME); - this.customerRepository.save(customer1); - final Customer customer2 = new Customer("A", TestData.LAST_NAME); - this.customerRepository.save(customer2); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Sort sort = Sort.by("firstName").descending(); - final List customersPage = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME, sort)); - Assertions.assertEquals(2, customersPage.size()); - Assertions.assertEquals(customer1, customersPage.get(0)); - Assertions.assertEquals(customer2, customersPage.get(1)); - } - ); - } - - @Test - void testFindByChildExistingChild() - { - final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); - this.customerWithChildRepository.save(customer); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Optional foundCustomer = this.customerWithChildRepository.findByChild(child); - Assertions.assertTrue(foundCustomer.isPresent()); - } - ); - } - - @Test - void testFindByChildNewChild() - { - final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); - this.customerWithChildRepository.save(customer); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Child queryChild = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - final Optional foundCustomer = - this.customerWithChildRepository.findByChild(queryChild); - Assertions.assertTrue(foundCustomer.isPresent()); - } - ); - } - - @Test - void testFindByChildWhenNullNotExists() - { - final Child child = new Child(TestData.FIRST_NAME_ALTERNATIVE, TestData.LAST_NAME_ALTERNATIVE); - final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, child); - this.customerWithChildRepository.save(customer); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Optional foundCustomer = this.customerWithChildRepository.findByChild(null); - Assertions.assertTrue(foundCustomer.isEmpty()); - } - ); - } - - @Test - void testFindByChildWhenNullExists() - { - final CustomerWithChild customer = new CustomerWithChild(TestData.FIRST_NAME, TestData.LAST_NAME, null); - this.customerWithChildRepository.save(customer); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Optional foundCustomer = this.customerWithChildRepository.findByChild(null); - Assertions.assertTrue(foundCustomer.isPresent()); - } - ); - } -} From 53b3423286ef475ac2b4194699dcec958a42ef77 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 14:06:00 +0200 Subject: [PATCH 13/26] Added plenty more tests for queries --- .../query/criteria/AbstractCriteriaNode.java | 6 +- .../isolated/tests/query/by/string/User.java | 135 ++++++ .../tests/query/by/string/UserRepository.java | 98 ++++ .../query/by/string/UserRepositoryTests.java | 442 ++++++++++++++++++ 4 files changed, 678 insertions(+), 3 deletions(-) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java index fc62538a..0f1e9a7c 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java @@ -152,14 +152,14 @@ public AbstractCriteriaNode like(final String like) final String completeRegex = sqlLikeStringToRegex(like); this.predicates.add(entity -> { final String fieldValue = (String)Objects.requireNonNull(this.field).readValue(entity); - return fieldValue != null && fieldValue.toUpperCase().matches(completeRegex); + return fieldValue != null && fieldValue.matches(completeRegex); }); return this; } private static String sqlLikeStringToRegex(final String like) { - String regex = like.toUpperCase(); + String regex = like; regex = regex.replace(".", "\\."); regex = regex.replace("_", "."); return regex.replace("%", ".*"); @@ -185,7 +185,7 @@ public AbstractCriteriaNode notLike(final String notLikeString) final String completeRegex = sqlLikeStringToRegex(notLikeString); this.predicates.add(entity -> { final String fieldValue = (String)Objects.requireNonNull(this.field).readValue(entity); - return fieldValue != null && !fieldValue.toUpperCase().matches(completeRegex); + return fieldValue != null && !fieldValue.matches(completeRegex); }); return this; } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java new file mode 100644 index 00000000..631d4ffb --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java @@ -0,0 +1,135 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.time.LocalDate; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + + +public class User +{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String firstName; + private String lastName; + private Integer age; + private String email; + private String city; + private LocalDate dateOfBirth; + private Boolean isActive; + + // Constructors, Getters, and Setters + + public User() + { + } + + public User(final String firstName, final String lastName, final Integer age, final String email, final String city, final LocalDate dateOfBirth, final Boolean isActive) + { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.email = email; + this.city = city; + this.dateOfBirth = dateOfBirth; + this.isActive = isActive; + } + + public Long getId() + { + return this.id; + } + + public void setId(final Long id) + { + this.id = id; + } + + public String getFirstName() + { + return this.firstName; + } + + public void setFirstName(final String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return this.lastName; + } + + public void setLastName(final String lastName) + { + this.lastName = lastName; + } + + public Integer getAge() + { + return this.age; + } + + public void setAge(final Integer age) + { + this.age = age; + } + + public String getEmail() + { + return this.email; + } + + public void setEmail(final String email) + { + this.email = email; + } + + public String getCity() + { + return this.city; + } + + public void setCity(final String city) + { + this.city = city; + } + + public LocalDate getDateOfBirth() + { + return this.dateOfBirth; + } + + public void setDateOfBirth(final LocalDate dateOfBirth) + { + this.dateOfBirth = dateOfBirth; + } + + public Boolean getActive() + { + return this.isActive; + } + + public void setActive(final Boolean active) + { + this.isActive = active; + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java new file mode 100644 index 00000000..6087736f --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java @@ -0,0 +1,98 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +@Repository +public interface UserRepository extends EclipseStoreRepository +{ + + // Test keyword: And + List findByFirstNameAndLastName(String firstName, String lastName); + + // Test keyword: Or + List findByFirstNameOrLastName(String firstName, String lastName); + + // Test keyword: Between + List findByAgeBetween(Integer startAge, Integer endAge); + + // Test keyword: LessThan + List findByAgeLessThan(Integer age); + + // Test keyword: LessThanEqual + List findByAgeLessThanEqual(Integer age); + + // Test keyword: GreaterThan + List findByAgeGreaterThan(Integer age); + + // Test keyword: GreaterThanEqual + List findByAgeGreaterThanEqual(Integer age); + + // Test keyword: After + List findByDateOfBirthAfter(LocalDate date); + + // Test keyword: Before + List findByDateOfBirthBefore(LocalDate date); + + // Test keyword: IsNull + List findByEmailIsNull(); + + // Test keyword: IsNotNull + List findByEmailIsNotNull(); + + // Test keyword: Like + List findByFirstNameLike(String pattern); + + // Test keyword: NotLike + List findByFirstNameNotLike(String pattern); + + // Test keyword: StartingWith + List findByFirstNameStartingWith(String prefix); + + // Test keyword: EndingWith + List findByFirstNameEndingWith(String suffix); + + // Test keyword: Containing + List findByFirstNameContaining(String infix); + + // Test keyword: OrderBy + List findByCityOrderByFirstNameAsc(String city); + + // Test keyword: Not + List findByFirstNameNot(String firstName); + + // Test keyword: In + List findByAgeIn(List ages); + + // Test keyword: NotIn + List findByAgeNotIn(List ages); + + // Test keyword: True + List findByIsActiveTrue(); + + // Test keyword: False + List findByIsActiveFalse(); + + // Additional fields to handle boolean flag for active status + List findByIsActive(Boolean isActive); +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java new file mode 100644 index 00000000..38f05474 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java @@ -0,0 +1,442 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.string; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; + + +@IsolatedTestAnnotations +@ContextConfiguration(classes = {QueryTestConfiguration.class}) +public class UserRepositoryTests +{ + @Autowired + private UserRepository userRepository; + + @BeforeEach + public void setUp() + { + this.userRepository.deleteAll(); + + this.userRepository.save(new User( + "John", + "Doe", + 25, + "john.doe@example.com", + "New York", + LocalDate.of(1998, 1, 1), + true)); + this.userRepository.save(new User( + "Jane", + "Doe", + 30, + "jane.doe@example.com", + "Los Angeles", + LocalDate.of(1993, 2, 2), + false)); + this.userRepository.save(new User( + "Alice", + "Smith", + 28, + "alice.smith@example.com", + "New York", + LocalDate.of(1996, 3, 3), + true)); + this.userRepository.save(new User( + "Bob", + "Brown", + 35, + "bob.brown@example.com", + "Chicago", + LocalDate.of(1988, 4, 4), + true)); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameAndLastName") + void testFindByFirstNameAndLastName(final String firstName, final String lastName, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameAndLastName(firstName, lastName); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameAndLastName() + { + return Stream.of( + Arguments.of("John", "Doe", 1), + Arguments.of("Jane", "Doe", 1), + Arguments.of("Alice", "Smith", 1), + Arguments.of("Bob", "Brown", 1), + Arguments.of("NonExistent", "User", 0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameOrLastName") + void testFindByFirstNameOrLastName(final String firstName, final String lastName, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameOrLastName(firstName, lastName); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameOrLastName() + { + return Stream.of( + Arguments.of("John", "Smith", 2), + Arguments.of("Jane", "Doe", 2), + Arguments.of("NonExistent", "Brown", 1) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeBetween") + void testFindByAgeBetween(final int startAge, final int endAge, final int expectedSize) + { + final List users = this.userRepository.findByAgeBetween(startAge, endAge); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeBetween() + { + return Stream.of( + Arguments.of(20, 30, 3), + Arguments.of(25, 35, 4), + Arguments.of(30, 40, 2) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeLessThan") + void testFindByAgeLessThan(final int age, final int expectedSize) + { + final List users = this.userRepository.findByAgeLessThan(age); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeLessThan() + { + return Stream.of( + Arguments.of(30, 2), + Arguments.of(35, 3), + Arguments.of(40, 4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeLessThanEqual") + void testFindByAgeLessThanEqual(final int age, final int expectedSize) + { + final List users = this.userRepository.findByAgeLessThanEqual(age); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeLessThanEqual() + { + return Stream.of( + Arguments.of(30, 3), + Arguments.of(35, 4), + Arguments.of(25, 1) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeGreaterThan") + void testFindByAgeGreaterThan(final int age, final int expectedSize) + { + final List users = this.userRepository.findByAgeGreaterThan(age); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeGreaterThan() + { + return Stream.of( + Arguments.of(25, 3), + Arguments.of(30, 1), + Arguments.of(35, 0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeGreaterThanEqual") + void testFindByAgeGreaterThanEqual(final int age, final int expectedSize) + { + final List users = this.userRepository.findByAgeGreaterThanEqual(age); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeGreaterThanEqual() + { + return Stream.of( + Arguments.of(25, 4), + Arguments.of(30, 2), + Arguments.of(35, 1) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByDateOfBirthAfter") + void testFindByDateOfBirthAfter(final LocalDate date, final int expectedSize) + { + final List users = this.userRepository.findByDateOfBirthAfter(date); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByDateOfBirthAfter() + { + return Stream.of( + Arguments.of(LocalDate.of(1990, 1, 1), 3), + Arguments.of(LocalDate.of(2000, 1, 1), 0), + Arguments.of(LocalDate.of(1985, 1, 1), 4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByDateOfBirthBefore") + void testFindByDateOfBirthBefore(final LocalDate date, final int expectedSize) + { + final List users = this.userRepository.findByDateOfBirthBefore(date); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByDateOfBirthBefore() + { + return Stream.of( + Arguments.of(LocalDate.of(1990, 1, 1), 1), + Arguments.of(LocalDate.of(2000, 1, 1), 4), + Arguments.of(LocalDate.of(1985, 1, 1), 0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByEmailIsNull") + void testFindByEmailIsNull(final int expectedSize) + { + final List users = this.userRepository.findByEmailIsNull(); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByEmailIsNull() + { + return Stream.of( + Arguments.of(0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByEmailIsNotNull") + void testFindByEmailIsNotNull(final int expectedSize) + { + final List users = this.userRepository.findByEmailIsNotNull(); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByEmailIsNotNull() + { + return Stream.of( + Arguments.of(4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameLike") + void testFindByFirstNameLike(final String pattern, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameLike(pattern); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameLike() + { + return Stream.of( + Arguments.of("John", 1), + Arguments.of("J%", 2), + Arguments.of("A%", 1) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameNotLike") + void testFindByFirstNameNotLike(final String pattern, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameNotLike(pattern); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameNotLike() + { + return Stream.of( + Arguments.of("John", 3), + Arguments.of("J%", 2), + Arguments.of("A%", 3) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameStartingWith") + void testFindByFirstNameStartingWith(final String prefix, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameStartingWith(prefix); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameStartingWith() + { + return Stream.of( + Arguments.of("J", 2), + Arguments.of("A", 1), + Arguments.of("B", 1), + Arguments.of("b", 0), + Arguments.of("x", 0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameEndingWith") + void testFindByFirstNameEndingWith(final String suffix, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameEndingWith(suffix); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameEndingWith() + { + return Stream.of( + Arguments.of("n", 1), + Arguments.of("e", 2), + Arguments.of("b", 1), + Arguments.of("x", 0), + Arguments.of("", 4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameContaining") + void testFindByFirstNameContaining(final String infix, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameContaining(infix); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameContaining() + { + return Stream.of( + Arguments.of("o", 2), + Arguments.of("a", 1), + Arguments.of("A", 1), + Arguments.of("i", 1), + Arguments.of("", 4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByCityOrderByFirstNameAsc") + void testFindByCityOrderByFirstNameAsc(final String city, final int expectedSize, final String firstName) + { + final List users = this.userRepository.findByCityOrderByFirstNameAsc(city); + Assertions.assertEquals(expectedSize, users.size()); + Assertions.assertEquals(firstName, users.get(0).getFirstName()); + } + + static Stream provideArgumentsForFindByCityOrderByFirstNameAsc() + { + return Stream.of( + Arguments.of("New York", 2, "Alice"), + Arguments.of("Los Angeles", 1, "Jane"), + Arguments.of("Chicago", 1, "Bob") + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameNot") + void testFindByFirstNameNot(final String firstName, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameNot(firstName); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameNot() + { + return Stream.of( + Arguments.of("John", 3), + Arguments.of("Jane", 3), + Arguments.of("Alice", 3), + Arguments.of("Bob", 3), + Arguments.of("Sepp", 4) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeIn") + void testFindByAgeIn(final List ages, final int expectedSize) + { + final List users = this.userRepository.findByAgeIn(ages); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeIn() + { + return Stream.of( + Arguments.of(List.of(25, 30), 2), + Arguments.of(List.of(28, 35), 2), + Arguments.of(List.of(40), 0) + ); + } + + @ParameterizedTest + @MethodSource("provideArgumentsForFindByAgeNotIn") + void testFindByAgeNotIn(final List ages, final int expectedSize) + { + final List users = this.userRepository.findByAgeNotIn(ages); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByAgeNotIn() + { + return Stream.of( + Arguments.of(List.of(25, 30), 2), + Arguments.of(List.of(28, 35), 2), + Arguments.of(List.of(40), 4) + ); + } + + @Test + void testFindByIsActiveTrue() + { + // Assuming all users are active for this example + final List users = this.userRepository.findByIsActiveTrue(); + Assertions.assertEquals(3, users.size()); + } + + @Test + void testFindByIsActiveFalse() + { + // Assuming all users are inactive for this example + final List users = this.userRepository.findByIsActiveFalse(); + Assertions.assertEquals(1, users.size()); + } +} From 2d4b3d46f21616d55074a3b8da7d00a0cc521eed Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 14:42:19 +0200 Subject: [PATCH 14/26] Added Ignore Case keyword for queries --- .../query/EclipseStoreQueryCreator.java | 15 ++++--- .../query/criteria/AbstractCriteriaNode.java | 40 +++++++++++-------- .../tests/query/by/string/UserRepository.java | 2 + .../query/by/string/UserRepositoryTests.java | 19 +++++++++ .../EclipseStoreQueryCreatorStringTest.java | 8 ++-- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreator.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreator.java index 8f1ade91..1a40b12c 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreator.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreator.java @@ -126,6 +126,9 @@ private AbstractCriteriaNode from( { Objects.requireNonNull(criteria); final Part.Type type = Objects.requireNonNull(part).getType(); + final Part.IgnoreCaseType ignoreCaseType = part.shouldIgnoreCase(); + final boolean doIgnoreCase = + ignoreCaseType == Part.IgnoreCaseType.ALWAYS || ignoreCaseType == Part.IgnoreCaseType.WHEN_POSSIBLE; switch(type) { @@ -169,27 +172,27 @@ private AbstractCriteriaNode from( } case LIKE -> { - return criteria.like((String)Objects.requireNonNull(parameters).next()); + return criteria.like((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case STARTING_WITH -> { - return criteria.startWith((String)Objects.requireNonNull(parameters).next()); + return criteria.startWith((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case ENDING_WITH -> { - return criteria.endWith((String)Objects.requireNonNull(parameters).next()); + return criteria.endWith((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case CONTAINING -> { - return criteria.containing((String)Objects.requireNonNull(parameters).next()); + return criteria.containing((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case NOT_LIKE -> { - return criteria.notLike((String)Objects.requireNonNull(parameters).next()); + return criteria.notLike((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case NOT_CONTAINING -> { - return criteria.notContaining((String)Objects.requireNonNull(parameters).next()); + return criteria.notContaining((String)Objects.requireNonNull(parameters).next(), doIgnoreCase); } case EXISTS -> { diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java index 0f1e9a7c..56de748d 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/AbstractCriteriaNode.java @@ -147,51 +147,59 @@ public AbstractCriteriaNode exists(final boolean value) return this; } - public AbstractCriteriaNode like(final String like) + public AbstractCriteriaNode like(final String like, final boolean doIgnoreCase) { - final String completeRegex = sqlLikeStringToRegex(like); + final String completeRegex = sqlLikeStringToRegex(like, doIgnoreCase); this.predicates.add(entity -> { final String fieldValue = (String)Objects.requireNonNull(this.field).readValue(entity); - return fieldValue != null && fieldValue.matches(completeRegex); + if(fieldValue == null) + { + return false; + } + return (doIgnoreCase ? fieldValue.toUpperCase() : fieldValue).matches(completeRegex); }); return this; } - private static String sqlLikeStringToRegex(final String like) + private static String sqlLikeStringToRegex(final String like, final boolean doIgnoreCase) { - String regex = like; + String regex = doIgnoreCase ? like.toUpperCase() : like; regex = regex.replace(".", "\\."); regex = regex.replace("_", "."); return regex.replace("%", ".*"); } - public AbstractCriteriaNode startWith(final String startString) + public AbstractCriteriaNode startWith(final String startString, final boolean doIgnoreCase) { - return this.like(startString + "%"); + return this.like(startString + "%", doIgnoreCase); } - public AbstractCriteriaNode endWith(final String endString) + public AbstractCriteriaNode endWith(final String endString, final boolean doIgnoreCase) { - return this.like("%" + endString); + return this.like("%" + endString, doIgnoreCase); } - public AbstractCriteriaNode containing(final String containedString) + public AbstractCriteriaNode containing(final String containedString, final boolean doIgnoreCase) { - return this.like("%" + containedString + "%"); + return this.like("%" + containedString + "%", doIgnoreCase); } - public AbstractCriteriaNode notLike(final String notLikeString) + public AbstractCriteriaNode notLike(final String notLikeString, final boolean doIgnoreCase) { - final String completeRegex = sqlLikeStringToRegex(notLikeString); + final String completeRegex = sqlLikeStringToRegex(notLikeString, doIgnoreCase); this.predicates.add(entity -> { final String fieldValue = (String)Objects.requireNonNull(this.field).readValue(entity); - return fieldValue != null && !fieldValue.matches(completeRegex); + if(fieldValue == null) + { + return false; + } + return !(doIgnoreCase ? fieldValue.toUpperCase() : fieldValue).matches(completeRegex); }); return this; } - public AbstractCriteriaNode notContaining(final String containedString) + public AbstractCriteriaNode notContaining(final String containedString, final boolean doIgnoreCase) { - return this.notLike("%" + containedString + "%"); + return this.notLike("%" + containedString + "%", doIgnoreCase); } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java index 6087736f..1f1fdc2c 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java @@ -63,6 +63,8 @@ public interface UserRepository extends EclipseStoreRepository // Test keyword: Like List findByFirstNameLike(String pattern); + List findByFirstNameLikeIgnoreCase(String pattern); + // Test keyword: NotLike List findByFirstNameNotLike(String pattern); diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java index 38f05474..c34a7a0d 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepositoryTests.java @@ -279,6 +279,25 @@ static Stream provideArgumentsForFindByFirstNameLike() ); } + @ParameterizedTest + @MethodSource("provideArgumentsForFindByFirstNameLikeIgnoreCase") + void testFindByFirstNameLikeIgnoreCase(final String pattern, final int expectedSize) + { + final List users = this.userRepository.findByFirstNameLikeIgnoreCase(pattern); + Assertions.assertEquals(expectedSize, users.size()); + } + + static Stream provideArgumentsForFindByFirstNameLikeIgnoreCase() + { + return Stream.of( + Arguments.of("John", 1), + Arguments.of("J%", 2), + Arguments.of("j%", 2), + Arguments.of("A%", 1), + Arguments.of("a%", 1) + ); + } + @ParameterizedTest @MethodSource("provideArgumentsForFindByFirstNameNotLike") void testFindByFirstNameNotLike(final String pattern, final int expectedSize) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreatorStringTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreatorStringTest.java index 905ba506..bcdf402e 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreatorStringTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/repository/query/EclipseStoreQueryCreatorStringTest.java @@ -48,8 +48,8 @@ static Stream generateDataWithCountOfFirstNameLike() return Stream.of( Arguments.of("%", 3), Arguments.of("M%", 2), - Arguments.of("m%", 2), - Arguments.of("m_ck", 2), + Arguments.of("m%", 0), + Arguments.of("m_ck", 0), Arguments.of("%ick", 2), Arguments.of("%k", 2), Arguments.of("%ck%", 2), @@ -93,8 +93,8 @@ static Stream generateDataWithCountOfFirstNameNotLike() return Stream.of( Arguments.of("%", 0), Arguments.of("M%", 1), - Arguments.of("m%", 1), - Arguments.of("m_ck", 1), + Arguments.of("m%", 3), + Arguments.of("m_ck", 3), Arguments.of("%ick", 1), Arguments.of("%k", 1), Arguments.of("%ck%", 1), From 7bb00cc033914a6b5d430b527cb6dc7da74bd36a Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 14:45:06 +0200 Subject: [PATCH 15/26] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1cdb9ab..84555bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Implemented auto-id-generation for UUIDs. * Implemented composite primary keys. +* Keyword "ignoreCase" now available for queries. # 2.0.1 From 45ac7db462e7ea3151c01ebd6afbe41afb96473a Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 1 Aug 2024 15:01:24 +0200 Subject: [PATCH 16/26] Update User.java --- .../integration/isolated/tests/query/by/string/User.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java index 631d4ffb..252a1364 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/User.java @@ -42,7 +42,14 @@ public User() { } - public User(final String firstName, final String lastName, final Integer age, final String email, final String city, final LocalDate dateOfBirth, final Boolean isActive) + public User( + final String firstName, + final String lastName, + final Integer age, + final String email, + final String city, + final LocalDate dateOfBirth, + final Boolean isActive) { this.firstName = firstName; this.lastName = lastName; From d57302135c5e6e6444d5094509a00c78476ef599 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Fri, 2 Aug 2024 13:58:27 +0200 Subject: [PATCH 17/26] Added ANTLR Support and basic queryProvider --- spring-data-eclipse-store/pom.xml | 29 + .../src/main/antlr4/Hql.g4 | 1097 +++++++++++++++++ .../data/eclipse/store/repository/Query.java | 2 +- .../repository/query/HSqlQueryProvider.java | 63 + .../query/antlr/HSqlQueryExecutor.java | 31 + .../EclipseStoreQueryLookupStrategy.java | 34 +- 6 files changed, 1250 insertions(+), 6 deletions(-) create mode 100644 spring-data-eclipse-store/src/main/antlr4/Hql.g4 create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java diff --git a/spring-data-eclipse-store/pom.xml b/spring-data-eclipse-store/pom.xml index cf40fb0e..18539549 100644 --- a/spring-data-eclipse-store/pom.xml +++ b/spring-data-eclipse-store/pom.xml @@ -47,7 +47,10 @@ ${javaVersion} UTF-8 + UTF-8 + 4.13.1 + 4.13.1 3.3.2 @@ -164,6 +167,12 @@ + + org.antlr + antlr4-runtime + ${antlr4.version} + + org.junit.jupiter junit-jupiter-engine @@ -309,6 +318,26 @@ + + org.antlr + antlr4-maven-plugin + ${antlr4.plugin.version} + + + + antlr4 + + + + + src/main/antlr4 + target/generated-sources/antlr4 + + -package + software.xdev.spring.data.eclipse.store.repository.query.antlr + + + diff --git a/spring-data-eclipse-store/src/main/antlr4/Hql.g4 b/spring-data-eclipse-store/src/main/antlr4/Hql.g4 new file mode 100644 index 00000000..5edddf97 --- /dev/null +++ b/spring-data-eclipse-store/src/main/antlr4/Hql.g4 @@ -0,0 +1,1097 @@ +/* + * Copyright 2011-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +grammar Hql; + +@header { +/** + * HQL per https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#query-language + * + * This is a mixture of Hibernate's BNF and missing bits of grammar. There are gaps and inconsistencies in the + * BNF itself, explained by other fragments of their spec. Additionally, alternate labels are used to provide easier + * management of complex rules in the generated Visitor. Finally, there are labels applied to rule elements (op=('+'|'-') + * to simplify the processing. + * + * @author Greg Turnquist + * @since 3.1 + */ +} + +/* + Parser rules + */ + +start + : ql_statement EOF + ; + +ql_statement + : selectStatement + | updateStatement + | deleteStatement + | insertStatement + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select +selectStatement + : queryExpression + ; + +queryExpression + : withClause? orderedQuery (setOperator orderedQuery)* + ; + +withClause + : WITH cte (',' cte)* + ; + +cte + : identifier AS (NOT? MATERIALIZED)? '(' queryExpression ')' searchClause? cycleClause? + ; + +searchClause + : SEARCH (BREADTH | DEPTH) FIRST BY searchSpecifications SET identifier + ; + +searchSpecifications + : searchSpecification (',' searchSpecification)* + ; + +searchSpecification + : identifier sortDirection? nullsPrecedence? + ; + +cycleClause + : CYCLE cteAttributes SET identifier (TO literal DEFAULT literal)? (USING identifier)? + ; + +cteAttributes + : identifier (',' identifier)* + ; + +orderedQuery + : (query | '(' queryExpression ')') queryOrder? + ; + +query + : selectClause fromClause? whereClause? (groupByClause havingClause?)? # SelectQuery + | fromClause whereClause? (groupByClause havingClause?)? selectClause? # FromQuery + ; + +queryOrder + : orderByClause limitClause? offsetClause? fetchClause? + ; + +fromClause + : FROM entityWithJoins (',' entityWithJoins)* + ; + +entityWithJoins + : fromRoot (joinSpecifier)* + ; + +joinSpecifier + : join + | crossJoin + | jpaCollectionJoin + ; + +fromRoot + : entityName variable? + | LATERAL? '(' subquery ')' variable? + ; + +join + : joinType JOIN FETCH? joinTarget joinRestriction? // Spec BNF says joinType isn't optional, but text says that it is. + ; + +joinTarget + : path variable? # JoinPath + | LATERAL? '(' subquery ')' variable? # JoinSubquery + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-update +updateStatement + : UPDATE VERSIONED? targetEntity setClause whereClause? + ; + +targetEntity + : entityName variable? + ; + +setClause + : SET assignment (',' assignment)* + ; + +assignment + : simplePath '=' expressionOrPredicate + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-delete +deleteStatement + : DELETE FROM? targetEntity whereClause? + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert +insertStatement + : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) + ; + +// Already defined underneath updateStatement +//targetEntity +// : entityName variable? +// ; + +targetFields + : '(' simplePath (',' simplePath)* ')' + ; + +valuesList + : VALUES values (',' values)* + ; + +values + : '(' expression (',' expression)* ')' + ; + +instantiation + : NEW instantiationTarget '(' instantiationArguments ')' + ; + +alias + : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word + ; + +groupedItem + : identifier + | INTEGER_LITERAL + | expression + ; + +sortedItem + : sortExpression sortDirection? nullsPrecedence? + ; + +sortExpression + : identifier + | INTEGER_LITERAL + | expression + ; + +sortDirection + : ASC + | DESC + ; + +nullsPrecedence + : NULLS (FIRST | LAST) + ; + +limitClause + : LIMIT parameterOrIntegerLiteral + ; + +offsetClause + : OFFSET parameterOrIntegerLiteral (ROW | ROWS)? + ; + +fetchClause + : FETCH (FIRST | NEXT) (parameterOrIntegerLiteral | parameterOrNumberLiteral '%') (ROW | ROWS) (ONLY | WITH TIES) + ; + +/******************* + Gaps in the spec. + *******************/ + +subquery + : queryExpression + ; + +selectClause + : SELECT DISTINCT? selectionList + ; + +selectionList + : selection (',' selection)* + ; + +selection + : selectExpression variable? + ; + +selectExpression + : instantiation + | mapEntrySelection + | jpaSelectObjectSyntax + | expressionOrPredicate + ; + +mapEntrySelection + : ENTRY '(' path ')' + ; + +/** + * Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate + */ +jpaSelectObjectSyntax + : OBJECT '(' identifier ')' + ; + +whereClause + : WHERE predicate (',' predicate)* + ; + +joinType + : INNER? + | (LEFT | RIGHT | FULL)? OUTER? + | CROSS + ; + +crossJoin + : CROSS JOIN entityName variable? + ; + +joinRestriction + : (ON | WITH) predicate + ; + +// Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate +jpaCollectionJoin + : ',' IN '(' path ')' variable? + ; + +groupByClause + : GROUP BY groupedItem (',' groupedItem)* + ; + +orderByClause + : ORDER BY sortedItem (',' sortedItem)* + ; + +havingClause + : HAVING predicate (',' predicate)* + ; + +setOperator + : UNION ALL? + | INTERSECT ALL? + | EXCEPT ALL? + ; + +// Literals +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-literals +literal + : NULL + | booleanLiteral + | stringLiteral + | numericLiteral + | dateTimeLiteral + | binaryLiteral + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals +booleanLiteral + : TRUE + | 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 + | FLOAT_LITERAL + | HEXLITERAL + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals +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 + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals +datetimeField + : YEAR + | MONTH + | DAY + | WEEK + | QUARTER + | HOUR + | MINUTE + | SECOND + | NANOSECOND + | EPOCH + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals +binaryLiteral + : BINARY_LITERAL + | '{' HEXLITERAL (',' HEXLITERAL)* '}' + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals +// TBD + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-java-constants +// TBD + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-entity-name-literals +// TBD + +// Expressions +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-expressions +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-concatenation +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-arithmetic +expression + : '(' expression ')' # GroupedExpression + | '(' expressionOrPredicate (',' expressionOrPredicate)+ ')' # TupleExpression + | '(' subquery ')' # SubqueryExpression + | primaryExpression # PlainPrimaryExpression + | op=('+' | '-') numericLiteral # SignedNumericLiteral + | op=('+' | '-') expression # SignedExpression + | expression datetimeField # ToDurationExpression + | expression BY datetimeField # FromDurationExpression + | expression op=('*' | '/') expression # MultiplicationExpression + | expression op=('+' | '-') expression # AdditionExpression + | expression '||' expression # HqlConcatenationExpression + | DAY OF WEEK # DayOfWeekExpression + | DAY OF MONTH # DayOfMonthExpression + | WEEK OF YEAR # WeekOfYearExpression + ; + +primaryExpression + : caseList # CaseExpression + | literal # LiteralExpression + | parameter # ParameterExpression + | function # FunctionExpression + | generalPathFragment # GeneralPathExpression + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-Datetime-arithmetic +// TBD + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-path-expressions +identificationVariable + : identifier + | simplePath + ; + +path + : treatedPath pathContinutation? + | generalPathFragment + ; + +generalPathFragment + : simplePath indexedPathAccessFragment? + ; + +indexedPathAccessFragment + : '[' expression ']' ('.' generalPathFragment)? + ; + +simplePath + : identifier simplePathElement* + ; + +simplePathElement + : '.' identifier + ; + +// 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 +// 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 + ; + +functionArguments + : DISTINCT? expressionOrPredicate (',' expressionOrPredicate)* + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-filter +filterClause + : FILTER '(' whereClause ')' + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset +withinGroup + : WITHIN GROUP '(' orderByClause ')' + ; + +overClause + : OVER '(' partitionClause? orderByClause? frameClause? ')' + ; + +partitionClause + : PARTITION BY expression (',' expression)* + ; + +frameClause + : (RANGE|ROWS|GROUPS) frameStart frameExclusion? + | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? + ; + +frameStart + : UNBOUNDED PRECEDING # UnboundedPrecedingFrameStart + | expression PRECEDING # ExpressionPrecedingFrameStart + | CURRENT ROW # CurrentRowFrameStart + | expression FOLLOWING # ExpressionFollowingFrameStart + ; + +frameExclusion + : EXCLUDE CURRENT ROW # CurrentRowFrameExclusion + | EXCLUDE GROUP # GroupFrameExclusion + | EXCLUDE TIES # TiesFrameExclusion + | EXCLUDE NO OTHERS # NoOthersFrameExclusion + ; + +frameEnd + : expression PRECEDING # ExpressionPrecedingFrameEnd + | CURRENT ROW # CurrentRowFrameEnd + | expression FOLLOWING # ExpressionFollowingFrameEnd + | UNBOUNDED FOLLOWING # UnboundedFollowingFrameEnd + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions +castFunction + : CAST '(' expression AS castTarget ')' + ; + +castTarget + : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? + ; + +castTargetType + returns [String fullTargetName] + : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* + ; + +extractFunction + : EXTRACT '(' expression FROM expression ')' + | dateTimeFunction '(' expression ')' + ; + +trimFunction + : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')' + ; + +dateTimeFunction + : d=(YEAR + | MONTH + | DAY + | WEEK + | QUARTER + | HOUR + | MINUTE + | SECOND + | NANOSECOND + | EPOCH) + ; + +everyFunction + : every=(EVERY | ALL) '(' predicate ')' + | every=(EVERY | ALL) '(' subquery ')' + | every=(EVERY | ALL) (ELEMENTS | INDICES) '(' simplePath ')' + ; + +anyFunction + : any=(ANY | SOME) '(' predicate ')' + | any=(ANY | SOME) '(' subquery ')' + | any=(ANY | SOME) (ELEMENTS | INDICES) '(' simplePath ')' + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-treat-type +treatedPath + : TREAT '(' path AS simplePath')' pathContinutation? + ; + +pathContinutation + : '.' simplePath + ; + +// Predicates +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-conditional-expressions +predicate + : '(' predicate ')' # GroupedPredicate + | dealingWithNullExpression # NullExpressionPredicate + | inExpression # InPredicate + | betweenExpression # BetweenPredicate + | relationalExpression # RelationalPredicate + | stringPatternMatching # LikePredicate + | existsExpression # ExistsPredicate + | collectionExpression # CollectionPredicate + | NOT predicate # NotPredicate + | predicate AND predicate # AndPredicate + | predicate OR predicate # OrPredicate + | expression # ExpressionPredicate + ; + +expressionOrPredicate + : expression + | predicate + ; + +// 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 + : expression op=('=' | '>' | '>=' | '<' | '<=' | '<>' | '!=' | '^=' ) expression + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-between-predicate +betweenExpression + : expression NOT? BETWEEN expression AND expression + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-null-predicate +dealingWithNullExpression + : expression IS NOT? NULL + | expression IS NOT? DISTINCT FROM expression + ; + +// 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))? + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-elements-indices +// TBD + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-in-predicate +inExpression + : expression NOT? IN inList + ; + +inList + : (ELEMENTS | INDICES) '(' simplePath ')' + | '(' subquery ')' + | parameter + | '(' (expressionOrPredicate (',' expressionOrPredicate)*)? ')' + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exists-predicate +existsExpression + : EXISTS (ELEMENTS | INDICES) '(' simplePath ')' + | EXISTS expression + ; + +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-collection-operators +collectionExpression + : expression IS NOT? EMPTY + | expression NOT? MEMBER OF path + ; + +// Projection +// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select-new +instantiationTarget + : LIST + | MAP + | simplePath + ; + +instantiationArguments + : instantiationArgument (',' instantiationArgument)* + ; + +instantiationArgument + : (expressionOrPredicate | instantiation) variable? + ; + +// Low level parsing rules + +parameterOrIntegerLiteral + : parameter + | INTEGER_LITERAL + ; + +parameterOrNumberLiteral + : parameter + | numericLiteral + ; + +variable + : AS identifier + | reservedWord + ; + +parameter + : prefix=':' identifier + | prefix='?' INTEGER_LITERAL? + ; + +entityName + : identifier ('.' identifier)* + ; + +identifier + : reservedWord + ; + +character + : CHARACTER + ; + +functionName + : reservedWord ('.' reservedWord)* + ; + +reservedWord + : IDENTIFICATION_VARIABLE + | f=(ALL + | AND + | ANY + | AS + | ASC + | AVG + | BETWEEN + | BOTH + | BREADTH + | BY + | CASE + | CAST + | COLLATE + | COUNT + | CROSS + | CUBE + | CURRENT + | CURRENT_DATE + | CURRENT_INSTANT + | CURRENT_TIME + | CURRENT_TIMESTAMP + | CYCLE + | DATE + | DATETIME + | DAY + | DEFAULT + | DELETE + | DEPTH + | DESC + | DISTINCT + | ELEMENT + | ELEMENTS + | ELSE + | EMPTY + | END + | ENTRY + | EPOCH + | ERROR + | ESCAPE + | EVERY + | EXCEPT + | EXCLUDE + | EXISTS + | EXP + | EXTRACT + | FETCH + | FILTER + | FIRST + | FLOOR + | FOLLOWING + | FOR + | FORMAT + | FROM + | FULL + | FUNCTION + | GROUP + | GROUPS + | HAVING + | HOUR + | ID + | IGNORE + | ILIKE + | IN + | INDEX + | INDICES + | INNER + | INSERT + | INSTANT + | INTERSECT + | INTO + | IS + | JOIN + | KEY + | LAST + | LATERAL + | LEADING + | LEFT + | LIKE + | LIMIT + | LIST + | LISTAGG + | LOCAL + | LOCAL_DATE + | LOCAL_DATETIME + | LOCAL_TIME + | MAP + | MATERIALIZED + | MAX + | MAXELEMENT + | MAXINDEX + | MEMBER + | MICROSECOND + | MILLISECOND + | MIN + | MINELEMENT + | MININDEX + | MINUTE + | MONTH + | NANOSECOND + | NATURALID + | NEW + | NEXT + | NO + | NOT + | NULLS + | OBJECT + | OF + | OFFSET + | OFFSET_DATETIME + | ON + | ONLY + | OR + | ORDER + | OTHERS + | OUTER + | OVER + | OVERFLOW + | OVERLAY + | PAD + | PARTITION + | PERCENT + | PLACING + | POSITION + | POWER + | PRECEDING + | QUARTER + | RANGE + | RESPECT + | RIGHT + | ROLLUP + | ROW + | ROWS + | SEARCH + | SECOND + | SELECT + | SET + | SIZE + | SOME + | SUBSTRING + | SUM + | THEN + | TIES + | TIME + | TIMESTAMP + | TIMEZONE_HOUR + | TIMEZONE_MINUTE + | TO + | TRAILING + | TREAT + | TRIM + | TRUNC + | TRUNCATE + | TYPE + | UNBOUNDED + | UNION + | UPDATE + | USING + | VALUE + | VALUES + | VERSION + | VERSIONED + | WEEK + | WHEN + | WHERE + | WITH + | WITHIN + | WITHOUT + | YEAR) + ; + +/* + Lexer rules + */ + + +WS : [ \t\r\n] -> skip ; + +// Build up case-insentive tokens + +fragment A: 'a' | 'A'; +fragment B: 'b' | 'B'; +fragment C: 'c' | 'C'; +fragment D: 'd' | 'D'; +fragment E: 'e' | 'E'; +fragment F: 'f' | 'F'; +fragment G: 'g' | 'G'; +fragment H: 'h' | 'H'; +fragment I: 'i' | 'I'; +fragment J: 'j' | 'J'; +fragment K: 'k' | 'K'; +fragment L: 'l' | 'L'; +fragment M: 'm' | 'M'; +fragment N: 'n' | 'N'; +fragment O: 'o' | 'O'; +fragment P: 'p' | 'P'; +fragment Q: 'q' | 'Q'; +fragment R: 'r' | 'R'; +fragment S: 's' | 'S'; +fragment T: 't' | 'T'; +fragment U: 'u' | 'U'; +fragment V: 'v' | 'V'; +fragment W: 'w' | 'W'; +fragment X: 'x' | 'X'; +fragment Y: 'y' | 'Y'; +fragment Z: 'z' | 'Z'; + +// The following are reserved identifiers: + +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; +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; +COUNT : C O U N T; +CROSS : C R O S S; +CUBE : C U B E; +CURRENT : C U R R E N T; +CURRENT_DATE : C U R R E N T '_' D A T E; +CURRENT_INSTANT : C U R R E N T '_' I N S T A N T; +CURRENT_TIME : C U R R E N T '_' T I M E; +CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P; +CYCLE : C Y C L E; +DATE : D A T E; +DATETIME : D A T E T I M E ; +DAY : D A Y; +DEFAULT : D E F A U L T; +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; +ELEMENT : E L E M E N T; +ELEMENTS : E L E M E N T S; +ELSE : E L S E; +EMPTY : E M P T Y; +END : E N D; +ENTRY : E N T R Y; +EPOCH : E P O C H; +ERROR : E R R O R; +ESCAPE : E S C A P E; +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; +FROM : F R O M; +FULL : F U L L; +FUNCTION : F U N C T I O N; +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; +INDEX : I N D E X; +INDICES : I N D I C E S; +INNER : I N N E R; +INSERT : I N S E R T; +INSTANT : I N S T A N T; +INTERSECT : I N T E R S E C T; +INTO : I N T O; +IS : I S; +JOIN : J O I N; +KEY : K E Y; +LAST : L A S T; +LATERAL : L A T E R A L; +LEADING : L E A D I N G; +LEFT : L E F T; +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; +LOCAL_TIME : L O C A L '_' T I M E; +MAP : M A P; +MATERIALIZED : M A T E R I A L I Z E D; +MAX : M A X; +MAXELEMENT : M A X E L E M E N T; +MAXINDEX : M A X I N D E X; +MEMBER : M E M B E R; +MICROSECOND : M I C R O S E C O N D; +MILLISECOND : M I L L I S E C O N D; +MIN : M I N; +MINELEMENT : M I N E L E M E N T; +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; +NULLS : N U L L S; +OBJECT : O B J E C T; +OF : O F; +OFFSET : O F F S E T; +OFFSET_DATETIME : O F F S E T '_' D A T E T I M E; +ON : O N; +ONLY : O N L Y; +OR : O R; +ORDER : O R D E R; +OTHERS : O T H E R S; +OUTER : O U T E R; +OVER : O V E R; +OVERFLOW : O V E R F L O W; +OVERLAY : O V E R L A Y; +PAD : P A D; +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; +RESPECT : R E S P E C T; +RIGHT : R I G H T; +ROLLUP : R O L L U P; +ROW : R O W; +ROWS : R O W S; +SEARCH : S E A R C H; +SECOND : S E C O N D; +SELECT : S E L E C T; +SET : S E T; +SIZE : S I Z E; +SOME : S O M E; +SUBSTRING : S U B S T R I N G; +SUM : S U M; +THEN : T H E N; +TIES : T I E S; +TIME : T I M E; +TIMESTAMP : T I M E S T A M P; +TIMEZONE_HOUR : T I M E Z O N E '_' H O U R; +TIMEZONE_MINUTE : T I M E Z O N E '_' M I N U T E; +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; +UNBOUNDED : U N B O U N D E D; +UNION : U N I O N; +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; +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; + +fragment INTEGER_NUMBER : ('0' .. '9')+ ; +fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ; +fragment HEX_DIGIT : [0-9a-fA-F]; + + +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' | '$' | '_')* ; + diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/Query.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/Query.java index 9099f492..d1e18c7b 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/Query.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/Query.java @@ -40,5 +40,5 @@ @QueryAnnotation public @interface Query { - @SuppressWarnings("unused") String value() default ""; + String value() default ""; } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java new file mode 100644 index 00000000..2a0aec40 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -0,0 +1,63 @@ +package software.xdev.spring.data.eclipse.store.repository.query; + +import java.lang.reflect.Method; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.data.util.TypeInformation; + +import software.xdev.spring.data.eclipse.store.core.EntityListProvider; +import software.xdev.spring.data.eclipse.store.repository.query.antlr.HSqlQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier; + + +public class HSqlQueryProvider implements RepositoryQuery +{ + private static final Logger LOG = LoggerFactory.getLogger(HSqlQueryProvider.class); + + private final PartTree tree; + private final Parameters parameters; + private final EntityListProvider entityListProvider; + private final Class domainClass; + private final TypeInformation typeInformation; + private final WorkingCopier copier; + private final QueryMethod queryMethod; + private final HSqlQueryExecutor executor; + + public HSqlQueryProvider( + final String sqlValue, + final QueryMethod queryMethod, + final Method method, + final Class domainClass, + final EntityListProvider entityListProvider, + final WorkingCopier copier + ) + { + Objects.requireNonNull(method); + this.queryMethod = queryMethod; + this.domainClass = Objects.requireNonNull(domainClass); + this.entityListProvider = Objects.requireNonNull(entityListProvider); + this.tree = new PartTree(method.getName(), domainClass); + this.typeInformation = TypeInformation.fromReturnTypeOf(method); + this.parameters = queryMethod.getParameters(); + this.copier = Objects.requireNonNull(copier); + this.executor = new HSqlQueryExecutor(sqlValue); + } + + @Override + public Object execute(final Object[] parameters) + { + return null; + } + + @Override + public QueryMethod getQueryMethod() + { + return null; + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java new file mode 100644 index 00000000..5d897813 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java @@ -0,0 +1,31 @@ +package software.xdev.spring.data.eclipse.store.repository.query.antlr; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; + + +public class HSqlQueryExecutor +{ + private final + software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlParser parser; + + public HSqlQueryExecutor(final String sqlString) + { + // Create a lexer that feeds off of input CharStream + final software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlLexer lexer = + new software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlLexer(CharStreams.fromString(sqlString)); + + // Create a buffer of tokens between the lexer and parser + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + // Create a parser that feeds off the tokens buffer + this.parser = + new software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlParser(tokens); + } + + public Object execute(final Object[] parameters) + { + // TODO + return null; + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java index 06ca3f2b..6b871955 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java @@ -17,6 +17,8 @@ import java.lang.reflect.Method; +import jakarta.annotation.Nonnull; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.projection.ProjectionFactory; @@ -26,10 +28,10 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; -import jakarta.annotation.Nonnull; import software.xdev.spring.data.eclipse.store.repository.EclipseStoreStorage; import software.xdev.spring.data.eclipse.store.repository.Query; import software.xdev.spring.data.eclipse.store.repository.query.FindAllEclipseStoreQueryProvider; +import software.xdev.spring.data.eclipse.store.repository.query.HSqlQueryProvider; import software.xdev.spring.data.eclipse.store.repository.query.StringBasedEclipseStoreQueryProvider; import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopierCreator; @@ -58,11 +60,9 @@ public RepositoryQuery resolveQuery( { final QueryMethod queryMethod = new QueryMethod(method, metadata, factory); - if(method.getAnnotation(Query.class) != null) + final Query queryAnnotation = method.getAnnotation(Query.class); + if(queryAnnotation != null) { - LOG.warn( - "Annotation @Query is used in Repository {}. This is useless for now and should be deleted.", - metadata.getRepositoryInterface().getSimpleName()); if(method.getName().equalsIgnoreCase("findall")) { // Special case for Queries that have findAll and are annotated with Query @@ -72,6 +72,13 @@ public RepositoryQuery resolveQuery( method ); } + + return this.createHSqlQueryProvider( + queryAnnotation.value(), + metadata.getDomainType(), + queryMethod, + method + ); } return this.createStringBasedEclipseStoreQueryProvider( @@ -110,4 +117,21 @@ private RepositoryQuery createStringBasedEclipseStoreQueryProvider( this.workingCopierCreator.createWorkingCopier(domainType, this.storage) ); } + + private RepositoryQuery createHSqlQueryProvider( + final String sqlString, + final Class domainType, + final QueryMethod queryMethod, + final Method method + ) + { + return new HSqlQueryProvider<>( + sqlString, + queryMethod, + method, + domainType, + this.storage, + this.workingCopierCreator.createWorkingCopier(domainType, this.storage) + ); + } } From 0ad7f772e3e7f8d50c38aab4cb6943f7ee98f440 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Fri, 2 Aug 2024 14:30:55 +0200 Subject: [PATCH 18/26] Create Test infrastructure for HsqlTests --- .../repository/query/HSqlQueryProvider.java | 5 +- .../isolated/tests/query/hsql/HsqlTest.java | 45 ++++++++ .../query/hsql/HsqlTestConfiguration.java | 38 +++++++ .../isolated/tests/query/hsql/MyEntity.java | 105 ++++++++++++++++++ .../tests/query/hsql/MyEntityRepository.java | 93 ++++++++++++++++ 5 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTestConfiguration.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index 2a0aec40..3b7ed0bf 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -8,7 +8,6 @@ import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.util.TypeInformation; import software.xdev.spring.data.eclipse.store.core.EntityListProvider; @@ -20,7 +19,7 @@ public class HSqlQueryProvider implements RepositoryQuery { private static final Logger LOG = LoggerFactory.getLogger(HSqlQueryProvider.class); - private final PartTree tree; + // private final PartTree tree; private final Parameters parameters; private final EntityListProvider entityListProvider; private final Class domainClass; @@ -42,7 +41,7 @@ public HSqlQueryProvider( this.queryMethod = queryMethod; this.domainClass = Objects.requireNonNull(domainClass); this.entityListProvider = Objects.requireNonNull(entityListProvider); - this.tree = new PartTree(method.getName(), domainClass); + // this.tree = new PartTree(method.getName(), domainClass); this.typeInformation = TypeInformation.fromReturnTypeOf(method); this.parameters = queryMethod.getParameters(); this.copier = Objects.requireNonNull(copier); diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java new file mode 100644 index 00000000..aaa725c3 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; + + +@IsolatedTestAnnotations +@ContextConfiguration(classes = {HsqlTestConfiguration.class}) +class HsqlTest +{ + @Autowired + private MyEntityRepository repository; + + @Autowired + private HsqlTestConfiguration configuration; + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTestConfiguration.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTestConfiguration.java new file mode 100644 index 00000000..cfd5d871 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTestConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; + +import org.eclipse.store.integrations.spring.boot.types.configuration.EclipseStoreProperties; +import org.eclipse.store.integrations.spring.boot.types.factories.EmbeddedStorageFoundationFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import software.xdev.spring.data.eclipse.store.integration.TestConfiguration; +import software.xdev.spring.data.eclipse.store.repository.config.EnableEclipseStoreRepositories; + + +@Configuration +@EnableEclipseStoreRepositories +public class HsqlTestConfiguration extends TestConfiguration +{ + @Autowired + protected HsqlTestConfiguration( + final EclipseStoreProperties defaultEclipseStoreProperties, + final EmbeddedStorageFoundationFactory defaultEclipseStoreProvider) + { + super(defaultEclipseStoreProperties, defaultEclipseStoreProvider); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java new file mode 100644 index 00000000..f8b68bfd --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java @@ -0,0 +1,105 @@ +package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; + +import java.time.LocalDate; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + + +public class MyEntity +{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + + private int age; + + private LocalDate creationDate; + + private boolean active; + + private Object otherEntity; + + public MyEntity() + { + } + + public MyEntity( + final Long id, + final String name, + final int age, + final LocalDate creationDate, + final boolean active, + final Object otherEntity) + { + this.id = id; + this.name = name; + this.age = age; + this.creationDate = creationDate; + this.active = active; + this.otherEntity = otherEntity; + } + + public Long getId() + { + return this.id; + } + + public void setId(final Long id) + { + this.id = id; + } + + public String getName() + { + return this.name; + } + + public void setName(final String name) + { + this.name = name; + } + + public int getAge() + { + return this.age; + } + + public void setAge(final int age) + { + this.age = age; + } + + public LocalDate getCreationDate() + { + return this.creationDate; + } + + public void setCreationDate(final LocalDate creationDate) + { + this.creationDate = creationDate; + } + + public boolean isActive() + { + return this.active; + } + + public void setActive(final boolean active) + { + this.active = active; + } + + public Object getOtherEntity() + { + return this.otherEntity; + } + + public void setOtherEntity(final Object otherEntity) + { + this.otherEntity = otherEntity; + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java new file mode 100644 index 00000000..0f2ca870 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java @@ -0,0 +1,93 @@ +package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.data.repository.ListCrudRepository; + +import software.xdev.spring.data.eclipse.store.repository.Query; + + +public interface MyEntityRepository extends ListCrudRepository +{ + // 1. Simple Select + @Query("SELECT e FROM MyEntity e") + List findAllEntities(); + + // 2. Select with a where clause + @Query("SELECT e FROM MyEntity e WHERE e.name = ?1") + List findByName(String name); + + // 3. Select with multiple where clauses + @Query("SELECT e FROM MyEntity e WHERE e.name = ?1 AND e.age > ?2") + List findByNameAndAgeGreaterThan(String name, int age); + + // 4. Select with order by + @Query("SELECT e FROM MyEntity e ORDER BY e.age DESC") + List findAllOrderByAgeDesc(); + + // 5. Select with limit + @Query(value = "SELECT e FROM MyEntity e ORDER BY e.age DESC") + List findTop5ByOrderByAgeDesc(); + + // 6. Select with distinct + @Query("SELECT DISTINCT e.name FROM MyEntity e") + List findDistinctNames(); + + // 7. Select with join + @Query("SELECT e FROM MyEntity e JOIN e.otherEntity o WHERE o.id = ?1") + List findByOtherEntityId(Long otherEntityId); + + // 8. Select with group by + @Query("SELECT e.name, COUNT(e) FROM MyEntity e GROUP BY e.name") + List countByName(); + + // 9. Select with having + @Query("SELECT e.name, COUNT(e) FROM MyEntity e GROUP BY e.name HAVING COUNT(e) > ?1") + List countByNameHavingMoreThan(long count); + + // 10. Select with subquery + @Query("SELECT e FROM MyEntity e WHERE e.age = (SELECT MAX(e2.age) FROM MyEntity e2)") + MyEntity findEntityWithMaxAge(); + + // 11. Select with IN clause + @Query("SELECT e FROM MyEntity e WHERE e.name IN ?1") + List findByNameIn(List names); + + // 12. Select with LIKE clause + @Query("SELECT e FROM MyEntity e WHERE e.name LIKE %?1%") + List findByNameContaining(String keyword); + + // 13. Select with native query + @Query(value = "SELECT * FROM my_entity WHERE name = ?1") + List findByNameNative(String name); + + // 14. Select with date comparison + @Query("SELECT e FROM MyEntity e WHERE e.creationDate > ?1") + List findByCreationDateAfter(LocalDate date); + + // 15. Select with between clause + @Query("SELECT e FROM MyEntity e WHERE e.age BETWEEN ?1 AND ?2") + List findByAgeBetween(int startAge, int endAge); + + // 16. Select with boolean condition + @Query("SELECT e FROM MyEntity e WHERE e.active = true") + List findAllActive(); + + // 17. Select with is null condition + @Query("SELECT e FROM MyEntity e WHERE e.otherEntity IS NULL") + List findWhereOtherEntityIsNull(); + + // 18. Select with is not null condition + @Query("SELECT e FROM MyEntity e WHERE e.otherEntity IS NOT NULL") + List findWhereOtherEntityIsNotNull(); + + // TODO + // 19. Select with a custom projection + // @Query("SELECT new com.example.demo.dto.MyEntityDTO(e.name, e.age) FROM MyEntity e") + // List findAllAsDTO(); + + // 20. Select with function + @Query("SELECT e FROM MyEntity e WHERE FUNCTION('YEAR', e.creationDate) = ?1") + List findByCreationYear(int year); +} From 512d03e19f0490237a9e5247c4f8501a905dc446 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Mon, 5 Aug 2024 07:34:43 +0200 Subject: [PATCH 19/26] Finished creating tests for HSQL --- .../isolated/tests/query/hsql/HsqlTest.java | 232 +++++++++++++++++- .../isolated/tests/query/hsql/MyEntity.java | 8 +- .../tests/query/hsql/OtherEntity.java | 41 ++++ 3 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java index aaa725c3..43f8bfe9 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java @@ -16,13 +16,14 @@ package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.time.LocalDate; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.stream.Stream; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -39,7 +40,232 @@ class HsqlTest @Autowired private MyEntityRepository repository; - @Autowired - private HsqlTestConfiguration configuration; + @ParameterizedTest + @MethodSource("provideTestData") + void testFindAllEntities(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findAllEntities(); + assertEquals(entities.size(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByName(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByName("John"); + assertEquals(1, result.size()); + assertEquals("John", result.get(0).getName()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByNameAndAgeGreaterThan(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByNameAndAgeGreaterThan("John", 25); + assertEquals(1, result.size()); + assertEquals("John", result.get(0).getName()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindAllOrderByAgeDesc(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findAllOrderByAgeDesc(); + assertEquals(entities.size(), result.size()); + assertEquals(40, result.get(0).getAge()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindTop5ByOrderByAgeDesc(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findTop5ByOrderByAgeDesc(); + assertEquals(Math.min(5, entities.size()), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindDistinctNames(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findDistinctNames(); + assertEquals(entities.stream().map(MyEntity::getName).distinct().count(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestDataWithOtherEntity") + void testFindByOtherEntityId(final List entities, final OtherEntity otherEntity) + { + this.repository.saveAll(entities); + final List result = this.repository.findByOtherEntityId(otherEntity.getId()); + assertEquals(entities.stream() + .filter(e -> e.getOtherEntity() != null && e.getOtherEntity().getId().equals(otherEntity.getId())) + .count(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testCountByName(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.countByName(); + assertNotNull(result); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testCountByNameHavingMoreThan(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.countByNameHavingMoreThan(1); + assertNotNull(result); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindEntityWithMaxAge(final List entities) + { + this.repository.saveAll(entities); + final MyEntity result = this.repository.findEntityWithMaxAge(); + assertEquals(40, result.getAge()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByNameIn(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByNameIn(Arrays.asList("John", "Jane")); + assertEquals(2, result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByNameContaining(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByNameContaining("Jo"); + assertEquals(1, result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByNameNative(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByNameNative("John"); + assertEquals(1, result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByCreationDateAfter(final List entities) + { + this.repository.saveAll(entities); + final List result = + this.repository.findByCreationDateAfter(LocalDate.now().minusDays(1)); + assertEquals(entities.size(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByAgeBetween(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByAgeBetween(20, 30); + assertEquals(2, result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindAllActive(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findAllActive(); + assertEquals(entities.stream().filter(MyEntity::isActive).count(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindWhereOtherEntityIsNull(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findWhereOtherEntityIsNull(); + assertEquals(entities.stream().filter(e -> e.getOtherEntity() == null).count(), result.size()); + } + + @ParameterizedTest + @MethodSource("provideTestDataWithOtherEntity") + void testFindWhereOtherEntityIsNotNull(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findWhereOtherEntityIsNotNull(); + assertEquals(entities.stream().filter(e -> e.getOtherEntity() != null).count(), result.size()); + } + // + // @ParameterizedTest + // @MethodSource("provideTestData") + // void testFindAllAsDTO(List entities) { + // repository.saveAll(entities); + // List result = repository.findAllAsDTO(); + // assertEquals(entities.size(), result.size()); + // } + + @ParameterizedTest + @MethodSource("provideTestData") + void testFindByCreationYear(final List entities) + { + this.repository.saveAll(entities); + final List result = this.repository.findByCreationYear(Calendar.getInstance().get(Calendar.YEAR)); + assertEquals(entities.size(), result.size()); + } + + private static Stream provideTestData() + { + return Stream.of( + Arguments.of(Arrays.asList( + createMyEntity("John", 30, true, null), + createMyEntity("Jane", 25, false, null), + createMyEntity("Doe", 40, true, null) + )), + Arguments.of(Arrays.asList( + createMyEntity("Alice", 22, true, null), + createMyEntity("Bob", 28, false, null), + createMyEntity("Charlie", 35, true, null) + )) + ); + } + + private static Stream provideTestDataWithOtherEntity() + { + final OtherEntity otherEntity = new OtherEntity(); + otherEntity.setDescription("Test OtherEntity"); + return Stream.of( + Arguments.of(Arrays.asList( + createMyEntity("John", 30, true, otherEntity), + createMyEntity("Jane", 25, false, null), + createMyEntity("Doe", 40, true, otherEntity) + ), otherEntity) + ); + } + + private static MyEntity createMyEntity( + final String name, + final int age, + final boolean active, + final OtherEntity otherEntity) + { + final MyEntity entity = new MyEntity(); + entity.setName(name); + entity.setAge(age); + entity.setCreationDate(LocalDate.now()); + entity.setActive(active); + entity.setOtherEntity(otherEntity); + return entity; } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java index f8b68bfd..ebdb43ac 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java @@ -21,7 +21,7 @@ public class MyEntity private boolean active; - private Object otherEntity; + private OtherEntity otherEntity; public MyEntity() { @@ -33,7 +33,7 @@ public MyEntity( final int age, final LocalDate creationDate, final boolean active, - final Object otherEntity) + final OtherEntity otherEntity) { this.id = id; this.name = name; @@ -93,12 +93,12 @@ public void setActive(final boolean active) this.active = active; } - public Object getOtherEntity() + public OtherEntity getOtherEntity() { return this.otherEntity; } - public void setOtherEntity(final Object otherEntity) + public void setOtherEntity(final OtherEntity otherEntity) { this.otherEntity = otherEntity; } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java new file mode 100644 index 00000000..5d34cd39 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java @@ -0,0 +1,41 @@ +package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + + +public class OtherEntity +{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String description; + + public OtherEntity(final Long id, final String description) + { + this.id = id; + this.description = description; + } + + public OtherEntity() + { + } + + public String getDescription() + { + return this.description; + } + + public void setDescription(final String description) + { + this.description = description; + } + + public Long getId() + { + return this.id; + } +} + From eb84f3b4ad3d21cf39ed2e98566e3d26f6a6a191 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Mon, 5 Aug 2024 10:39:01 +0200 Subject: [PATCH 20/26] Update HSqlQueryProvider.java --- .../data/eclipse/store/repository/query/HSqlQueryProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index 3b7ed0bf..b30806f2 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -51,7 +51,7 @@ public HSqlQueryProvider( @Override public Object execute(final Object[] parameters) { - return null; + return this.executor.execute(parameters); } @Override From 1fc4c0f44cb32d40b2029c998b802a3341f981ae Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Mon, 5 Aug 2024 15:53:36 +0200 Subject: [PATCH 21/26] Switching from Antlr to CQEngine for SQL-Interpretation --- spring-data-eclipse-store/pom.xml | 30 +- .../src/main/antlr4/Hql.g4 | 1097 ----------------- .../repository/query/HSqlQueryProvider.java | 7 +- .../query/antlr/HSqlQueryExecutor.java | 87 +- .../isolated/tests/query/hsql/HsqlTest.java | 358 ++++-- .../isolated/tests/query/hsql/MyEntity.java | 4 +- .../tests/query/hsql/MyEntityRepository.java | 47 +- 7 files changed, 352 insertions(+), 1278 deletions(-) delete mode 100644 spring-data-eclipse-store/src/main/antlr4/Hql.g4 diff --git a/spring-data-eclipse-store/pom.xml b/spring-data-eclipse-store/pom.xml index 18539549..980a430e 100644 --- a/spring-data-eclipse-store/pom.xml +++ b/spring-data-eclipse-store/pom.xml @@ -49,8 +49,6 @@ UTF-8 UTF-8 - 4.13.1 - 4.13.1 3.3.2 @@ -166,11 +164,11 @@ - + - org.antlr - antlr4-runtime - ${antlr4.version} + com.googlecode.cqengine + cqengine + 3.6.0 @@ -318,26 +316,6 @@ - - org.antlr - antlr4-maven-plugin - ${antlr4.plugin.version} - - - - antlr4 - - - - - src/main/antlr4 - target/generated-sources/antlr4 - - -package - software.xdev.spring.data.eclipse.store.repository.query.antlr - - - diff --git a/spring-data-eclipse-store/src/main/antlr4/Hql.g4 b/spring-data-eclipse-store/src/main/antlr4/Hql.g4 deleted file mode 100644 index 5edddf97..00000000 --- a/spring-data-eclipse-store/src/main/antlr4/Hql.g4 +++ /dev/null @@ -1,1097 +0,0 @@ -/* - * Copyright 2011-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -grammar Hql; - -@header { -/** - * HQL per https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#query-language - * - * This is a mixture of Hibernate's BNF and missing bits of grammar. There are gaps and inconsistencies in the - * BNF itself, explained by other fragments of their spec. Additionally, alternate labels are used to provide easier - * management of complex rules in the generated Visitor. Finally, there are labels applied to rule elements (op=('+'|'-') - * to simplify the processing. - * - * @author Greg Turnquist - * @since 3.1 - */ -} - -/* - Parser rules - */ - -start - : ql_statement EOF - ; - -ql_statement - : selectStatement - | updateStatement - | deleteStatement - | insertStatement - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select -selectStatement - : queryExpression - ; - -queryExpression - : withClause? orderedQuery (setOperator orderedQuery)* - ; - -withClause - : WITH cte (',' cte)* - ; - -cte - : identifier AS (NOT? MATERIALIZED)? '(' queryExpression ')' searchClause? cycleClause? - ; - -searchClause - : SEARCH (BREADTH | DEPTH) FIRST BY searchSpecifications SET identifier - ; - -searchSpecifications - : searchSpecification (',' searchSpecification)* - ; - -searchSpecification - : identifier sortDirection? nullsPrecedence? - ; - -cycleClause - : CYCLE cteAttributes SET identifier (TO literal DEFAULT literal)? (USING identifier)? - ; - -cteAttributes - : identifier (',' identifier)* - ; - -orderedQuery - : (query | '(' queryExpression ')') queryOrder? - ; - -query - : selectClause fromClause? whereClause? (groupByClause havingClause?)? # SelectQuery - | fromClause whereClause? (groupByClause havingClause?)? selectClause? # FromQuery - ; - -queryOrder - : orderByClause limitClause? offsetClause? fetchClause? - ; - -fromClause - : FROM entityWithJoins (',' entityWithJoins)* - ; - -entityWithJoins - : fromRoot (joinSpecifier)* - ; - -joinSpecifier - : join - | crossJoin - | jpaCollectionJoin - ; - -fromRoot - : entityName variable? - | LATERAL? '(' subquery ')' variable? - ; - -join - : joinType JOIN FETCH? joinTarget joinRestriction? // Spec BNF says joinType isn't optional, but text says that it is. - ; - -joinTarget - : path variable? # JoinPath - | LATERAL? '(' subquery ')' variable? # JoinSubquery - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-update -updateStatement - : UPDATE VERSIONED? targetEntity setClause whereClause? - ; - -targetEntity - : entityName variable? - ; - -setClause - : SET assignment (',' assignment)* - ; - -assignment - : simplePath '=' expressionOrPredicate - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-delete -deleteStatement - : DELETE FROM? targetEntity whereClause? - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert -insertStatement - : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) - ; - -// Already defined underneath updateStatement -//targetEntity -// : entityName variable? -// ; - -targetFields - : '(' simplePath (',' simplePath)* ')' - ; - -valuesList - : VALUES values (',' values)* - ; - -values - : '(' expression (',' expression)* ')' - ; - -instantiation - : NEW instantiationTarget '(' instantiationArguments ')' - ; - -alias - : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word - ; - -groupedItem - : identifier - | INTEGER_LITERAL - | expression - ; - -sortedItem - : sortExpression sortDirection? nullsPrecedence? - ; - -sortExpression - : identifier - | INTEGER_LITERAL - | expression - ; - -sortDirection - : ASC - | DESC - ; - -nullsPrecedence - : NULLS (FIRST | LAST) - ; - -limitClause - : LIMIT parameterOrIntegerLiteral - ; - -offsetClause - : OFFSET parameterOrIntegerLiteral (ROW | ROWS)? - ; - -fetchClause - : FETCH (FIRST | NEXT) (parameterOrIntegerLiteral | parameterOrNumberLiteral '%') (ROW | ROWS) (ONLY | WITH TIES) - ; - -/******************* - Gaps in the spec. - *******************/ - -subquery - : queryExpression - ; - -selectClause - : SELECT DISTINCT? selectionList - ; - -selectionList - : selection (',' selection)* - ; - -selection - : selectExpression variable? - ; - -selectExpression - : instantiation - | mapEntrySelection - | jpaSelectObjectSyntax - | expressionOrPredicate - ; - -mapEntrySelection - : ENTRY '(' path ')' - ; - -/** - * Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate - */ -jpaSelectObjectSyntax - : OBJECT '(' identifier ')' - ; - -whereClause - : WHERE predicate (',' predicate)* - ; - -joinType - : INNER? - | (LEFT | RIGHT | FULL)? OUTER? - | CROSS - ; - -crossJoin - : CROSS JOIN entityName variable? - ; - -joinRestriction - : (ON | WITH) predicate - ; - -// Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate -jpaCollectionJoin - : ',' IN '(' path ')' variable? - ; - -groupByClause - : GROUP BY groupedItem (',' groupedItem)* - ; - -orderByClause - : ORDER BY sortedItem (',' sortedItem)* - ; - -havingClause - : HAVING predicate (',' predicate)* - ; - -setOperator - : UNION ALL? - | INTERSECT ALL? - | EXCEPT ALL? - ; - -// Literals -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-literals -literal - : NULL - | booleanLiteral - | stringLiteral - | numericLiteral - | dateTimeLiteral - | binaryLiteral - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals -booleanLiteral - : TRUE - | 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 - | FLOAT_LITERAL - | HEXLITERAL - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals -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 - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals -datetimeField - : YEAR - | MONTH - | DAY - | WEEK - | QUARTER - | HOUR - | MINUTE - | SECOND - | NANOSECOND - | EPOCH - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals -binaryLiteral - : BINARY_LITERAL - | '{' HEXLITERAL (',' HEXLITERAL)* '}' - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals -// TBD - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-java-constants -// TBD - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-entity-name-literals -// TBD - -// Expressions -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-expressions -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-concatenation -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-arithmetic -expression - : '(' expression ')' # GroupedExpression - | '(' expressionOrPredicate (',' expressionOrPredicate)+ ')' # TupleExpression - | '(' subquery ')' # SubqueryExpression - | primaryExpression # PlainPrimaryExpression - | op=('+' | '-') numericLiteral # SignedNumericLiteral - | op=('+' | '-') expression # SignedExpression - | expression datetimeField # ToDurationExpression - | expression BY datetimeField # FromDurationExpression - | expression op=('*' | '/') expression # MultiplicationExpression - | expression op=('+' | '-') expression # AdditionExpression - | expression '||' expression # HqlConcatenationExpression - | DAY OF WEEK # DayOfWeekExpression - | DAY OF MONTH # DayOfMonthExpression - | WEEK OF YEAR # WeekOfYearExpression - ; - -primaryExpression - : caseList # CaseExpression - | literal # LiteralExpression - | parameter # ParameterExpression - | function # FunctionExpression - | generalPathFragment # GeneralPathExpression - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-Datetime-arithmetic -// TBD - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-path-expressions -identificationVariable - : identifier - | simplePath - ; - -path - : treatedPath pathContinutation? - | generalPathFragment - ; - -generalPathFragment - : simplePath indexedPathAccessFragment? - ; - -indexedPathAccessFragment - : '[' expression ']' ('.' generalPathFragment)? - ; - -simplePath - : identifier simplePathElement* - ; - -simplePathElement - : '.' identifier - ; - -// 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 -// 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 - ; - -functionArguments - : DISTINCT? expressionOrPredicate (',' expressionOrPredicate)* - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-filter -filterClause - : FILTER '(' whereClause ')' - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset -withinGroup - : WITHIN GROUP '(' orderByClause ')' - ; - -overClause - : OVER '(' partitionClause? orderByClause? frameClause? ')' - ; - -partitionClause - : PARTITION BY expression (',' expression)* - ; - -frameClause - : (RANGE|ROWS|GROUPS) frameStart frameExclusion? - | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? - ; - -frameStart - : UNBOUNDED PRECEDING # UnboundedPrecedingFrameStart - | expression PRECEDING # ExpressionPrecedingFrameStart - | CURRENT ROW # CurrentRowFrameStart - | expression FOLLOWING # ExpressionFollowingFrameStart - ; - -frameExclusion - : EXCLUDE CURRENT ROW # CurrentRowFrameExclusion - | EXCLUDE GROUP # GroupFrameExclusion - | EXCLUDE TIES # TiesFrameExclusion - | EXCLUDE NO OTHERS # NoOthersFrameExclusion - ; - -frameEnd - : expression PRECEDING # ExpressionPrecedingFrameEnd - | CURRENT ROW # CurrentRowFrameEnd - | expression FOLLOWING # ExpressionFollowingFrameEnd - | UNBOUNDED FOLLOWING # UnboundedFollowingFrameEnd - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions -castFunction - : CAST '(' expression AS castTarget ')' - ; - -castTarget - : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? - ; - -castTargetType - returns [String fullTargetName] - : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* - ; - -extractFunction - : EXTRACT '(' expression FROM expression ')' - | dateTimeFunction '(' expression ')' - ; - -trimFunction - : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')' - ; - -dateTimeFunction - : d=(YEAR - | MONTH - | DAY - | WEEK - | QUARTER - | HOUR - | MINUTE - | SECOND - | NANOSECOND - | EPOCH) - ; - -everyFunction - : every=(EVERY | ALL) '(' predicate ')' - | every=(EVERY | ALL) '(' subquery ')' - | every=(EVERY | ALL) (ELEMENTS | INDICES) '(' simplePath ')' - ; - -anyFunction - : any=(ANY | SOME) '(' predicate ')' - | any=(ANY | SOME) '(' subquery ')' - | any=(ANY | SOME) (ELEMENTS | INDICES) '(' simplePath ')' - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-treat-type -treatedPath - : TREAT '(' path AS simplePath')' pathContinutation? - ; - -pathContinutation - : '.' simplePath - ; - -// Predicates -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-conditional-expressions -predicate - : '(' predicate ')' # GroupedPredicate - | dealingWithNullExpression # NullExpressionPredicate - | inExpression # InPredicate - | betweenExpression # BetweenPredicate - | relationalExpression # RelationalPredicate - | stringPatternMatching # LikePredicate - | existsExpression # ExistsPredicate - | collectionExpression # CollectionPredicate - | NOT predicate # NotPredicate - | predicate AND predicate # AndPredicate - | predicate OR predicate # OrPredicate - | expression # ExpressionPredicate - ; - -expressionOrPredicate - : expression - | predicate - ; - -// 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 - : expression op=('=' | '>' | '>=' | '<' | '<=' | '<>' | '!=' | '^=' ) expression - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-between-predicate -betweenExpression - : expression NOT? BETWEEN expression AND expression - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-null-predicate -dealingWithNullExpression - : expression IS NOT? NULL - | expression IS NOT? DISTINCT FROM expression - ; - -// 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))? - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-elements-indices -// TBD - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-in-predicate -inExpression - : expression NOT? IN inList - ; - -inList - : (ELEMENTS | INDICES) '(' simplePath ')' - | '(' subquery ')' - | parameter - | '(' (expressionOrPredicate (',' expressionOrPredicate)*)? ')' - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exists-predicate -existsExpression - : EXISTS (ELEMENTS | INDICES) '(' simplePath ')' - | EXISTS expression - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-collection-operators -collectionExpression - : expression IS NOT? EMPTY - | expression NOT? MEMBER OF path - ; - -// Projection -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select-new -instantiationTarget - : LIST - | MAP - | simplePath - ; - -instantiationArguments - : instantiationArgument (',' instantiationArgument)* - ; - -instantiationArgument - : (expressionOrPredicate | instantiation) variable? - ; - -// Low level parsing rules - -parameterOrIntegerLiteral - : parameter - | INTEGER_LITERAL - ; - -parameterOrNumberLiteral - : parameter - | numericLiteral - ; - -variable - : AS identifier - | reservedWord - ; - -parameter - : prefix=':' identifier - | prefix='?' INTEGER_LITERAL? - ; - -entityName - : identifier ('.' identifier)* - ; - -identifier - : reservedWord - ; - -character - : CHARACTER - ; - -functionName - : reservedWord ('.' reservedWord)* - ; - -reservedWord - : IDENTIFICATION_VARIABLE - | f=(ALL - | AND - | ANY - | AS - | ASC - | AVG - | BETWEEN - | BOTH - | BREADTH - | BY - | CASE - | CAST - | COLLATE - | COUNT - | CROSS - | CUBE - | CURRENT - | CURRENT_DATE - | CURRENT_INSTANT - | CURRENT_TIME - | CURRENT_TIMESTAMP - | CYCLE - | DATE - | DATETIME - | DAY - | DEFAULT - | DELETE - | DEPTH - | DESC - | DISTINCT - | ELEMENT - | ELEMENTS - | ELSE - | EMPTY - | END - | ENTRY - | EPOCH - | ERROR - | ESCAPE - | EVERY - | EXCEPT - | EXCLUDE - | EXISTS - | EXP - | EXTRACT - | FETCH - | FILTER - | FIRST - | FLOOR - | FOLLOWING - | FOR - | FORMAT - | FROM - | FULL - | FUNCTION - | GROUP - | GROUPS - | HAVING - | HOUR - | ID - | IGNORE - | ILIKE - | IN - | INDEX - | INDICES - | INNER - | INSERT - | INSTANT - | INTERSECT - | INTO - | IS - | JOIN - | KEY - | LAST - | LATERAL - | LEADING - | LEFT - | LIKE - | LIMIT - | LIST - | LISTAGG - | LOCAL - | LOCAL_DATE - | LOCAL_DATETIME - | LOCAL_TIME - | MAP - | MATERIALIZED - | MAX - | MAXELEMENT - | MAXINDEX - | MEMBER - | MICROSECOND - | MILLISECOND - | MIN - | MINELEMENT - | MININDEX - | MINUTE - | MONTH - | NANOSECOND - | NATURALID - | NEW - | NEXT - | NO - | NOT - | NULLS - | OBJECT - | OF - | OFFSET - | OFFSET_DATETIME - | ON - | ONLY - | OR - | ORDER - | OTHERS - | OUTER - | OVER - | OVERFLOW - | OVERLAY - | PAD - | PARTITION - | PERCENT - | PLACING - | POSITION - | POWER - | PRECEDING - | QUARTER - | RANGE - | RESPECT - | RIGHT - | ROLLUP - | ROW - | ROWS - | SEARCH - | SECOND - | SELECT - | SET - | SIZE - | SOME - | SUBSTRING - | SUM - | THEN - | TIES - | TIME - | TIMESTAMP - | TIMEZONE_HOUR - | TIMEZONE_MINUTE - | TO - | TRAILING - | TREAT - | TRIM - | TRUNC - | TRUNCATE - | TYPE - | UNBOUNDED - | UNION - | UPDATE - | USING - | VALUE - | VALUES - | VERSION - | VERSIONED - | WEEK - | WHEN - | WHERE - | WITH - | WITHIN - | WITHOUT - | YEAR) - ; - -/* - Lexer rules - */ - - -WS : [ \t\r\n] -> skip ; - -// Build up case-insentive tokens - -fragment A: 'a' | 'A'; -fragment B: 'b' | 'B'; -fragment C: 'c' | 'C'; -fragment D: 'd' | 'D'; -fragment E: 'e' | 'E'; -fragment F: 'f' | 'F'; -fragment G: 'g' | 'G'; -fragment H: 'h' | 'H'; -fragment I: 'i' | 'I'; -fragment J: 'j' | 'J'; -fragment K: 'k' | 'K'; -fragment L: 'l' | 'L'; -fragment M: 'm' | 'M'; -fragment N: 'n' | 'N'; -fragment O: 'o' | 'O'; -fragment P: 'p' | 'P'; -fragment Q: 'q' | 'Q'; -fragment R: 'r' | 'R'; -fragment S: 's' | 'S'; -fragment T: 't' | 'T'; -fragment U: 'u' | 'U'; -fragment V: 'v' | 'V'; -fragment W: 'w' | 'W'; -fragment X: 'x' | 'X'; -fragment Y: 'y' | 'Y'; -fragment Z: 'z' | 'Z'; - -// The following are reserved identifiers: - -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; -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; -COUNT : C O U N T; -CROSS : C R O S S; -CUBE : C U B E; -CURRENT : C U R R E N T; -CURRENT_DATE : C U R R E N T '_' D A T E; -CURRENT_INSTANT : C U R R E N T '_' I N S T A N T; -CURRENT_TIME : C U R R E N T '_' T I M E; -CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P; -CYCLE : C Y C L E; -DATE : D A T E; -DATETIME : D A T E T I M E ; -DAY : D A Y; -DEFAULT : D E F A U L T; -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; -ELEMENT : E L E M E N T; -ELEMENTS : E L E M E N T S; -ELSE : E L S E; -EMPTY : E M P T Y; -END : E N D; -ENTRY : E N T R Y; -EPOCH : E P O C H; -ERROR : E R R O R; -ESCAPE : E S C A P E; -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; -FROM : F R O M; -FULL : F U L L; -FUNCTION : F U N C T I O N; -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; -INDEX : I N D E X; -INDICES : I N D I C E S; -INNER : I N N E R; -INSERT : I N S E R T; -INSTANT : I N S T A N T; -INTERSECT : I N T E R S E C T; -INTO : I N T O; -IS : I S; -JOIN : J O I N; -KEY : K E Y; -LAST : L A S T; -LATERAL : L A T E R A L; -LEADING : L E A D I N G; -LEFT : L E F T; -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; -LOCAL_TIME : L O C A L '_' T I M E; -MAP : M A P; -MATERIALIZED : M A T E R I A L I Z E D; -MAX : M A X; -MAXELEMENT : M A X E L E M E N T; -MAXINDEX : M A X I N D E X; -MEMBER : M E M B E R; -MICROSECOND : M I C R O S E C O N D; -MILLISECOND : M I L L I S E C O N D; -MIN : M I N; -MINELEMENT : M I N E L E M E N T; -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; -NULLS : N U L L S; -OBJECT : O B J E C T; -OF : O F; -OFFSET : O F F S E T; -OFFSET_DATETIME : O F F S E T '_' D A T E T I M E; -ON : O N; -ONLY : O N L Y; -OR : O R; -ORDER : O R D E R; -OTHERS : O T H E R S; -OUTER : O U T E R; -OVER : O V E R; -OVERFLOW : O V E R F L O W; -OVERLAY : O V E R L A Y; -PAD : P A D; -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; -RESPECT : R E S P E C T; -RIGHT : R I G H T; -ROLLUP : R O L L U P; -ROW : R O W; -ROWS : R O W S; -SEARCH : S E A R C H; -SECOND : S E C O N D; -SELECT : S E L E C T; -SET : S E T; -SIZE : S I Z E; -SOME : S O M E; -SUBSTRING : S U B S T R I N G; -SUM : S U M; -THEN : T H E N; -TIES : T I E S; -TIME : T I M E; -TIMESTAMP : T I M E S T A M P; -TIMEZONE_HOUR : T I M E Z O N E '_' H O U R; -TIMEZONE_MINUTE : T I M E Z O N E '_' M I N U T E; -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; -UNBOUNDED : U N B O U N D E D; -UNION : U N I O N; -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; -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; - -fragment INTEGER_NUMBER : ('0' .. '9')+ ; -fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ; -fragment HEX_DIGIT : [0-9a-fA-F]; - - -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' | '$' | '_')* ; - diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index b30806f2..590cdba0 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -27,6 +27,7 @@ public class HSqlQueryProvider implements RepositoryQuery private final WorkingCopier copier; private final QueryMethod queryMethod; private final HSqlQueryExecutor executor; + private final String sqlValue; public HSqlQueryProvider( final String sqlValue, @@ -41,17 +42,17 @@ public HSqlQueryProvider( this.queryMethod = queryMethod; this.domainClass = Objects.requireNonNull(domainClass); this.entityListProvider = Objects.requireNonNull(entityListProvider); - // this.tree = new PartTree(method.getName(), domainClass); this.typeInformation = TypeInformation.fromReturnTypeOf(method); this.parameters = queryMethod.getParameters(); this.copier = Objects.requireNonNull(copier); - this.executor = new HSqlQueryExecutor(sqlValue); + this.executor = new HSqlQueryExecutor(this.domainClass, this.entityListProvider); + this.sqlValue = sqlValue; } @Override public Object execute(final Object[] parameters) { - return this.executor.execute(parameters); + return this.executor.execute(this.sqlValue, parameters); } @Override diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java index 5d897813..dbcd6522 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java @@ -1,31 +1,80 @@ package software.xdev.spring.data.eclipse.store.repository.query.antlr; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import com.googlecode.cqengine.ConcurrentIndexedCollection; +import com.googlecode.cqengine.IndexedCollection; +import com.googlecode.cqengine.attribute.Attribute; +import com.googlecode.cqengine.attribute.ReflectiveAttribute; +import com.googlecode.cqengine.query.parser.sql.SQLParser; +import com.googlecode.cqengine.resultset.ResultSet; -public class HSqlQueryExecutor +import software.xdev.spring.data.eclipse.store.core.EntityListProvider; +import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; + + +public class HSqlQueryExecutor { - private final - software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlParser parser; + private final SQLParser parser; + private final EntityListProvider entityListProvider; + private final Class domainClass; + + public HSqlQueryExecutor(final Class domainClass, final EntityListProvider entityListProvider) + { + this.domainClass = domainClass; + this.parser = SQLParser.forPojoWithAttributes(domainClass, this.createAttributes(domainClass)); + this.entityListProvider = entityListProvider; + } + + public Object execute(final String sqlValue, final Object[] parameters) + { + final IndexedCollection entities = new ConcurrentIndexedCollection<>(); + entities.addAll(this.entityListProvider.getEntityProvider(this.domainClass).toCollection()); + final String sqlStringWithReplacedValues = this.replacePlaceholders(sqlValue, parameters); + final ResultSet retrieve = this.parser.retrieve(entities, sqlStringWithReplacedValues); + return retrieve.stream().toList(); + } - public HSqlQueryExecutor(final String sqlString) + private String replacePlaceholders(String sqlValue, final Object[] parameters) { - // Create a lexer that feeds off of input CharStream - final software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlLexer lexer = - new software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlLexer(CharStreams.fromString(sqlString)); - - // Create a buffer of tokens between the lexer and parser - final CommonTokenStream tokens = new CommonTokenStream(lexer); - - // Create a parser that feeds off the tokens buffer - this.parser = - new software.xdev.spring.data.eclipse.store.repository.query.antlr.HqlParser(tokens); + // Replace positional placeholders with actual parameter values + for(int i = 0; i < parameters.length; i++) + { + final String placeholder = "\\?" + (i + 1); + String value = parameters[i].toString(); + if(parameters[i] instanceof String) + { + value = "'" + value + "'"; + } + if(parameters[i] instanceof final Collection collection) + { + value = + collection.stream() + .map(o -> "'" + o.toString() + "'") + .collect(Collectors.joining(", ", "(", ")")) + .toString(); + } + sqlValue = sqlValue.replaceAll(placeholder, value); + } + return sqlValue; } - public Object execute(final Object[] parameters) + private Map> createAttributes(final Class domainClass) { - // TODO - return null; + final Map> attributes = new TreeMap<>(); + AccessHelper.getInheritedPrivateFieldsByName(domainClass).forEach( + (fieldName, field) -> attributes.put( + fieldName, + new ReflectiveAttribute<>( + domainClass, + field.getType(), + fieldName + ) + ) + ); + return attributes; } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java index 43f8bfe9..0cea2edd 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java @@ -17,10 +17,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.time.LocalDate; import java.util.Arrays; -import java.util.Calendar; import java.util.List; import java.util.stream.Stream; @@ -40,230 +40,386 @@ class HsqlTest @Autowired private MyEntityRepository repository; + private static Stream provideTestDataFindAllEntities() + { + return Stream.of( + Arguments.of(createEntityLists(0), 3), + Arguments.of(createEntityLists(1), 3), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindAllEntities(final List entities) + @MethodSource("provideTestDataFindAllEntities") + void findAllEntities(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findAllEntities(); - assertEquals(entities.size(), result.size()); + assertEquals(expectedSize, result.size()); } + private static Stream provideTestDataFindByName() + { + return Stream.of( + Arguments.of(createEntityLists(0), 1), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByName(final List entities) + @MethodSource("provideTestDataFindByName") + void findByName(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByName("John"); - assertEquals(1, result.size()); - assertEquals("John", result.get(0).getName()); + assertEquals(expectedSize, result.size()); + if(expectedSize > 0) + { + assertEquals("John", result.get(0).getName()); + } } + private static Stream provideTestDataFindByNameAndAgeGreaterThan() + { + return Stream.of( + Arguments.of(createEntityLists(0), 0), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByNameAndAgeGreaterThan(final List entities) + @MethodSource("provideTestDataFindByNameAndAgeGreaterThan") + void findByNameAndAgeGreaterThan(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByNameAndAgeGreaterThan("John", 25); - assertEquals(1, result.size()); - assertEquals("John", result.get(0).getName()); + assertEquals(expectedSize, result.size()); + if(expectedSize > result.size()) + { + assertEquals("John", result.get(0).getName()); + } } + private static Stream provideTestDataFindAllOrderByAgeDesc() + { + return Stream.of( + Arguments.of(createEntityLists(0), 40), + Arguments.of(createEntityLists(1), 40), + Arguments.of(createEntityLists(2), 28), + Arguments.of(createEntityLists(3), null) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindAllOrderByAgeDesc(final List entities) + @MethodSource("provideTestDataFindAllOrderByAgeDesc") + void findAllOrderByAgeDesc(final List entities, final Integer expectedFirstAge) { this.repository.saveAll(entities); final List result = this.repository.findAllOrderByAgeDesc(); assertEquals(entities.size(), result.size()); - assertEquals(40, result.get(0).getAge()); + if(expectedFirstAge != null) + { + assertEquals(expectedFirstAge, result.get(0).getAge()); + } } + private static Stream provideTestDataFindTop5ByOrderByAgeDesc() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 2), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindTop5ByOrderByAgeDesc(final List entities) + @MethodSource("provideTestDataFindTop5ByOrderByAgeDesc") + void findTop5ByOrderByAgeDesc(final List entities, final int expectedSize) { this.repository.saveAll(entities); - final List result = this.repository.findTop5ByOrderByAgeDesc(); - assertEquals(Math.min(5, entities.size()), result.size()); + final List result = this.repository.findTop2ByOrderByAgeDesc(); + assertEquals(Math.min(2, expectedSize), result.size()); } + private static Stream provideTestDataFindDistinctNames() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 3), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindDistinctNames(final List entities) + @MethodSource("provideTestDataFindDistinctNames") + void findDistinctNames(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findDistinctNames(); - assertEquals(entities.stream().map(MyEntity::getName).distinct().count(), result.size()); + assertEquals(expectedSize, result.size()); } - @ParameterizedTest - @MethodSource("provideTestDataWithOtherEntity") - void testFindByOtherEntityId(final List entities, final OtherEntity otherEntity) + private static Stream provideTestDataFindCountByName() { - this.repository.saveAll(entities); - final List result = this.repository.findByOtherEntityId(otherEntity.getId()); - assertEquals(entities.stream() - .filter(e -> e.getOtherEntity() != null && e.getOtherEntity().getId().equals(otherEntity.getId())) - .count(), result.size()); + return Stream.of( + Arguments.of(createEntityLists(0), 1), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); } - @ParameterizedTest - @MethodSource("provideTestData") - void testCountByName(final List entities) + @MethodSource("provideTestDataFindCountByName") + void testCountByName(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.countByName(); assertNotNull(result); } + private static Stream provideTestDataFindCountByNameHavingMoreThan() + { + return Stream.of( + Arguments.of(createEntityLists(0), 1), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testCountByNameHavingMoreThan(final List entities) + @MethodSource("provideTestDataFindCountByNameHavingMoreThan") + void testCountByNameHavingMoreThan(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.countByNameHavingMoreThan(1); assertNotNull(result); } + private static Stream provideTestDataFindEntityWithMaxAge() + { + return Stream.of( + Arguments.of(createEntityLists(0), 40), + Arguments.of(createEntityLists(1), 40), + Arguments.of(createEntityLists(2), 28), + Arguments.of(createEntityLists(3), null) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindEntityWithMaxAge(final List entities) + @MethodSource("provideTestDataFindEntityWithMaxAge") + void findEntityWithMaxAge(final List entities, final Integer expectedMaxAge) { this.repository.saveAll(entities); final MyEntity result = this.repository.findEntityWithMaxAge(); - assertEquals(40, result.getAge()); + + if(expectedMaxAge != null) + { + assertEquals(40, result.getAge()); + } + else + { + assertNull(result); + } } + private static Stream provideTestDataFindByNameIn() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 2), + Arguments.of(createEntityLists(2), 1), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByNameIn(final List entities) + @MethodSource("provideTestDataFindByNameIn") + void findByNameIn(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByNameIn(Arrays.asList("John", "Jane")); - assertEquals(2, result.size()); + assertEquals(expectedSize, result.size()); } + private static Stream provideTestDataFindByNameContaining() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByNameContaining(final List entities) + @MethodSource("provideTestDataFindByNameContaining") + void findByNameContaining(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByNameContaining("Jo"); - assertEquals(1, result.size()); + assertEquals(expectedSize, result.size()); } + private static Stream provideTestDataFindByNameNative() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByNameNative(final List entities) + @MethodSource("provideTestDataFindByNameNative") + void findByNameNative(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByNameNative("John"); - assertEquals(1, result.size()); + assertEquals(expectedSize, result.size()); } + private static Stream provideTestDataFindByCreationDateAfter() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 3), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByCreationDateAfter(final List entities) + @MethodSource("provideTestDataFindByCreationDateAfter") + void findByCreationDateAfter(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByCreationDateAfter(LocalDate.now().minusDays(1)); - assertEquals(entities.size(), result.size()); + assertEquals(expectedSize, result.size()); } + private static Stream provideTestDataFindByAgeBetween() + { + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 2), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByAgeBetween(final List entities) + @MethodSource("provideTestDataFindByAgeBetween") + void findByAgeBetween(final List entities, final int expectedSize) { this.repository.saveAll(entities); final List result = this.repository.findByAgeBetween(20, 30); - assertEquals(2, result.size()); + assertEquals(expectedSize, result.size()); } - @ParameterizedTest - @MethodSource("provideTestData") - void testFindAllActive(final List entities) + private static Stream provideTestDataFindAllActive() { - this.repository.saveAll(entities); - final List result = this.repository.findAllActive(); - assertEquals(entities.stream().filter(MyEntity::isActive).count(), result.size()); + return Stream.of( + Arguments.of(createEntityLists(0), 2), + Arguments.of(createEntityLists(1), 0), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); } - @ParameterizedTest - @MethodSource("provideTestData") - void testFindWhereOtherEntityIsNull(final List entities) + @MethodSource("provideTestDataFindAllActive") + void findAllActive(final List entities, final int expectedSize) { this.repository.saveAll(entities); - final List result = this.repository.findWhereOtherEntityIsNull(); - assertEquals(entities.stream().filter(e -> e.getOtherEntity() == null).count(), result.size()); + final List result = this.repository.findAllActive(); + assertEquals(expectedSize, result.size()); } - @ParameterizedTest - @MethodSource("provideTestDataWithOtherEntity") - void testFindWhereOtherEntityIsNotNull(final List entities) + private static Stream provideTestDataFindWhereOtherEntityIsNull() { - this.repository.saveAll(entities); - final List result = this.repository.findWhereOtherEntityIsNotNull(); - assertEquals(entities.stream().filter(e -> e.getOtherEntity() != null).count(), result.size()); - } - // - // @ParameterizedTest - // @MethodSource("provideTestData") - // void testFindAllAsDTO(List entities) { - // repository.saveAll(entities); - // List result = repository.findAllAsDTO(); - // assertEquals(entities.size(), result.size()); - // } + return Stream.of( + Arguments.of(createEntityLists(0), 0), + Arguments.of(createEntityLists(1), 1), + Arguments.of(createEntityLists(2), 2), + Arguments.of(createEntityLists(3), 0) + ); + } @ParameterizedTest - @MethodSource("provideTestData") - void testFindByCreationYear(final List entities) + @MethodSource("provideTestDataFindWhereOtherEntityIsNull") + void findWhereOtherEntityIsNull(final List entities, final int expectedSize) { this.repository.saveAll(entities); - final List result = this.repository.findByCreationYear(Calendar.getInstance().get(Calendar.YEAR)); - assertEquals(entities.size(), result.size()); + final List result = this.repository.findWhereOtherEntityIsNull(); + assertEquals(expectedSize, result.size()); } - private static Stream provideTestData() + private static Stream provideTestDataFindWhereOtherEntityIsNotNull() { return Stream.of( - Arguments.of(Arrays.asList( - createMyEntity("John", 30, true, null), - createMyEntity("Jane", 25, false, null), - createMyEntity("Doe", 40, true, null) - )), - Arguments.of(Arrays.asList( - createMyEntity("Alice", 22, true, null), - createMyEntity("Bob", 28, false, null), - createMyEntity("Charlie", 35, true, null) - )) + Arguments.of(createEntityLists(0), 3), + Arguments.of(createEntityLists(1), 2), + Arguments.of(createEntityLists(2), 0), + Arguments.of(createEntityLists(3), 0) ); } + @ParameterizedTest + @MethodSource("provideTestDataFindWhereOtherEntityIsNotNull") + void findWhereOtherEntityIsNotNull(final List entities, final int expectedSize) + { + this.repository.saveAll(entities); + final List result = this.repository.findWhereOtherEntityIsNotNull(); + assertEquals(expectedSize, result.size()); + } - private static Stream provideTestDataWithOtherEntity() + private static List createEntityLists(final int testDataSetIndex) { final OtherEntity otherEntity = new OtherEntity(); otherEntity.setDescription("Test OtherEntity"); - return Stream.of( - Arguments.of(Arrays.asList( - createMyEntity("John", 30, true, otherEntity), - createMyEntity("Jane", 25, false, null), + + return switch(testDataSetIndex) + { + case 0 -> Arrays.asList( + createMyEntity("John", 21, LocalDate.now().minusYears(1), true, otherEntity), + createMyEntity("John", 25, false, otherEntity), createMyEntity("Doe", 40, true, otherEntity) - ), otherEntity) + ); + case 1 -> Arrays.asList( + createMyEntity("John", 30, false, otherEntity), + createMyEntity("Jane", 25, false, otherEntity), + createMyEntity("Doe", 40, false, null) + ); + case 2 -> Arrays.asList( + createMyEntity("Jane", 22, true, null), + createMyEntity("Bob", 28, true, null) + ); + case 3 -> Arrays.asList(); + default -> throw new RuntimeException("Wrong index!"); + }; + } + + private static MyEntity createMyEntity( + final String name, + final int age, + final boolean active, + final OtherEntity otherEntity) + { + return createMyEntity( + name, + age, + LocalDate.now(), + active, + otherEntity ); } private static MyEntity createMyEntity( final String name, final int age, + final LocalDate creationDate, final boolean active, final OtherEntity otherEntity) { final MyEntity entity = new MyEntity(); entity.setName(name); entity.setAge(age); - entity.setCreationDate(LocalDate.now()); + entity.setCreationDate(creationDate); entity.setActive(active); entity.setOtherEntity(otherEntity); return entity; diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java index ebdb43ac..1c03d3e7 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java @@ -15,11 +15,11 @@ public class MyEntity private String name; - private int age; + private Integer age; private LocalDate creationDate; - private boolean active; + private Boolean active; private OtherEntity otherEntity; diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java index 0f2ca870..4805a9a2 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java @@ -11,51 +11,47 @@ public interface MyEntityRepository extends ListCrudRepository { // 1. Simple Select - @Query("SELECT e FROM MyEntity e") + @Query("SELECT * FROM MyEntity") List findAllEntities(); // 2. Select with a where clause - @Query("SELECT e FROM MyEntity e WHERE e.name = ?1") + @Query("SELECT * FROM MyEntity WHERE name = ?1") List findByName(String name); // 3. Select with multiple where clauses - @Query("SELECT e FROM MyEntity e WHERE e.name = ?1 AND e.age > ?2") + @Query(" SELECT * FROM MyEntity WHERE (name = ?1 AND age > ?2)") List findByNameAndAgeGreaterThan(String name, int age); // 4. Select with order by - @Query("SELECT e FROM MyEntity e ORDER BY e.age DESC") + @Query(" SELECT * FROM MyEntity ORDER BY age DESC") List findAllOrderByAgeDesc(); // 5. Select with limit - @Query(value = "SELECT e FROM MyEntity e ORDER BY e.age DESC") - List findTop5ByOrderByAgeDesc(); + @Query(value = " SELECT * FROM MyEntity ORDER BY age DESC LIMIT 2") + List findTop2ByOrderByAgeDesc(); // 6. Select with distinct - @Query("SELECT DISTINCT e.name FROM MyEntity e") + @Query("SELECT DISTINCT name FROM MyEntity") List findDistinctNames(); - // 7. Select with join - @Query("SELECT e FROM MyEntity e JOIN e.otherEntity o WHERE o.id = ?1") - List findByOtherEntityId(Long otherEntityId); - // 8. Select with group by - @Query("SELECT e.name, COUNT(e) FROM MyEntity e GROUP BY e.name") + @Query("SELECT name, COUNT(*) FROM MyEntity GROUP BY name") List countByName(); // 9. Select with having - @Query("SELECT e.name, COUNT(e) FROM MyEntity e GROUP BY e.name HAVING COUNT(e) > ?1") + @Query("SELECT name, COUNT(*) FROM MyEntity GROUP BY name HAVING COUNT(*) > ?1") List countByNameHavingMoreThan(long count); // 10. Select with subquery - @Query("SELECT e FROM MyEntity e WHERE e.age = (SELECT MAX(e2.age) FROM MyEntity e2)") + @Query("SELECT * FROM MyEntity WHERE age = (SELECT MAX(age) FROM MyEntity2)") MyEntity findEntityWithMaxAge(); // 11. Select with IN clause - @Query("SELECT e FROM MyEntity e WHERE e.name IN ?1") + @Query(" SELECT * FROM MyEntity WHERE name IN ?1") List findByNameIn(List names); // 12. Select with LIKE clause - @Query("SELECT e FROM MyEntity e WHERE e.name LIKE %?1%") + @Query(" SELECT * FROM MyEntity WHERE 'name' LIKE '%?1%'") List findByNameContaining(String keyword); // 13. Select with native query @@ -63,31 +59,22 @@ public interface MyEntityRepository extends ListCrudRepository List findByNameNative(String name); // 14. Select with date comparison - @Query("SELECT e FROM MyEntity e WHERE e.creationDate > ?1") + @Query(" SELECT * FROM MyEntity WHERE creationDate > ?1") List findByCreationDateAfter(LocalDate date); // 15. Select with between clause - @Query("SELECT e FROM MyEntity e WHERE e.age BETWEEN ?1 AND ?2") + @Query(" SELECT * FROM MyEntity WHERE age BETWEEN ?1 AND ?2") List findByAgeBetween(int startAge, int endAge); // 16. Select with boolean condition - @Query("SELECT e FROM MyEntity e WHERE e.active = true") + @Query(" SELECT * FROM MyEntity WHERE active = true") List findAllActive(); // 17. Select with is null condition - @Query("SELECT e FROM MyEntity e WHERE e.otherEntity IS NULL") + @Query(" SELECT * FROM MyEntity WHERE otherEntity IS NULL") List findWhereOtherEntityIsNull(); // 18. Select with is not null condition - @Query("SELECT e FROM MyEntity e WHERE e.otherEntity IS NOT NULL") + @Query(" SELECT * FROM MyEntity WHERE otherEntity IS NOT NULL") List findWhereOtherEntityIsNotNull(); - - // TODO - // 19. Select with a custom projection - // @Query("SELECT new com.example.demo.dto.MyEntityDTO(e.name, e.age) FROM MyEntity e") - // List findAllAsDTO(); - - // 20. Select with function - @Query("SELECT e FROM MyEntity e WHERE FUNCTION('YEAR', e.creationDate) = ?1") - List findByCreationYear(int year); } From 9b88b08c9f466af3f81f625e1809797e63f94a5c Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 6 Aug 2024 10:37:11 +0200 Subject: [PATCH 22/26] Added basic working sql queries --- pom.xml | 2 +- spring-data-eclipse-store-benchmark/pom.xml | 2 +- spring-data-eclipse-store-demo/pom.xml | 2 +- spring-data-eclipse-store-jpa/pom.xml | 2 +- spring-data-eclipse-store/pom.xml | 14 ++ .../repository/query/HSqlQueryProvider.java | 45 +++-- .../query/antlr/HSqlQueryExecutor.java | 46 +++-- .../EclipseStoreQueryLookupStrategy.java | 7 +- .../isolated/tests/query/hsql/HsqlTest.java | 170 +++++------------- .../isolated/tests/query/hsql/MyEntity.java | 51 +++++- .../tests/query/hsql/MyEntityRepository.java | 80 ++++----- .../tests/query/hsql/OtherEntity.java | 15 ++ .../CustomerRepositoryWithQuery.java | 36 ---- .../shared/tests/JpaCompatibilityTest.java | 94 ---------- 14 files changed, 223 insertions(+), 343 deletions(-) delete mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/repositories/CustomerRepositoryWithQuery.java delete mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/JpaCompatibilityTest.java diff --git a/pom.xml b/pom.xml index 8b5e8c37..a753f6be 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.0-SNAPSHOT + 2.1.1-SNAPSHOT pom diff --git a/spring-data-eclipse-store-benchmark/pom.xml b/spring-data-eclipse-store-benchmark/pom.xml index 3554596d..0b85fcba 100644 --- a/spring-data-eclipse-store-benchmark/pom.xml +++ b/spring-data-eclipse-store-benchmark/pom.xml @@ -5,7 +5,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.0-SNAPSHOT + 2.1.1-SNAPSHOT spring-data-eclipse-store-benchmark diff --git a/spring-data-eclipse-store-demo/pom.xml b/spring-data-eclipse-store-demo/pom.xml index d0247c66..7bd7dbe4 100644 --- a/spring-data-eclipse-store-demo/pom.xml +++ b/spring-data-eclipse-store-demo/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.0-SNAPSHOT + 2.1.1-SNAPSHOT spring-data-eclipse-store-demo diff --git a/spring-data-eclipse-store-jpa/pom.xml b/spring-data-eclipse-store-jpa/pom.xml index 7fd88888..1f71f1c8 100644 --- a/spring-data-eclipse-store-jpa/pom.xml +++ b/spring-data-eclipse-store-jpa/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.0-SNAPSHOT + 2.1.1-SNAPSHOT spring-data-eclipse-store-jpa diff --git a/spring-data-eclipse-store/pom.xml b/spring-data-eclipse-store/pom.xml index 980a430e..8dc5b80e 100644 --- a/spring-data-eclipse-store/pom.xml +++ b/spring-data-eclipse-store/pom.xml @@ -169,6 +169,20 @@ com.googlecode.cqengine cqengine 3.6.0 + + + kryo + com.esotericsoftware + + + kryo-serializers + de.javakaffee + + + sqlite-jdbc + org.xerial + + diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index 590cdba0..f0f51ea0 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -1,14 +1,24 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.repository.query; -import java.lang.reflect.Method; import java.util.Objects; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.util.TypeInformation; import software.xdev.spring.data.eclipse.store.core.EntityListProvider; import software.xdev.spring.data.eclipse.store.repository.query.antlr.HSqlQueryExecutor; @@ -17,35 +27,24 @@ public class HSqlQueryProvider implements RepositoryQuery { - private static final Logger LOG = LoggerFactory.getLogger(HSqlQueryProvider.class); - - // private final PartTree tree; - private final Parameters parameters; - private final EntityListProvider entityListProvider; - private final Class domainClass; - private final TypeInformation typeInformation; - private final WorkingCopier copier; - private final QueryMethod queryMethod; private final HSqlQueryExecutor executor; private final String sqlValue; + private final QueryMethod queryMethod; public HSqlQueryProvider( final String sqlValue, final QueryMethod queryMethod, - final Method method, final Class domainClass, final EntityListProvider entityListProvider, final WorkingCopier copier ) { - Objects.requireNonNull(method); this.queryMethod = queryMethod; - this.domainClass = Objects.requireNonNull(domainClass); - this.entityListProvider = Objects.requireNonNull(entityListProvider); - this.typeInformation = TypeInformation.fromReturnTypeOf(method); - this.parameters = queryMethod.getParameters(); - this.copier = Objects.requireNonNull(copier); - this.executor = new HSqlQueryExecutor(this.domainClass, this.entityListProvider); + this.executor = new HSqlQueryExecutor( + Objects.requireNonNull(domainClass), + Objects.requireNonNull(entityListProvider), + copier + ); this.sqlValue = sqlValue; } @@ -58,6 +57,6 @@ public Object execute(final Object[] parameters) @Override public QueryMethod getQueryMethod() { - return null; + return this.queryMethod; } } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java index dbcd6522..56fad615 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java @@ -1,6 +1,24 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.repository.query.antlr; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; @@ -14,6 +32,7 @@ import software.xdev.spring.data.eclipse.store.core.EntityListProvider; import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; +import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier; public class HSqlQueryExecutor @@ -21,34 +40,37 @@ public class HSqlQueryExecutor private final SQLParser parser; private final EntityListProvider entityListProvider; private final Class domainClass; + private final WorkingCopier copier; - public HSqlQueryExecutor(final Class domainClass, final EntityListProvider entityListProvider) + public HSqlQueryExecutor( + final Class domainClass, + final EntityListProvider entityListProvider, + final WorkingCopier copier) { this.domainClass = domainClass; this.parser = SQLParser.forPojoWithAttributes(domainClass, this.createAttributes(domainClass)); this.entityListProvider = entityListProvider; + this.copier = copier; } - public Object execute(final String sqlValue, final Object[] parameters) + public List execute(final String sqlValue, final Object[] parameters) { final IndexedCollection entities = new ConcurrentIndexedCollection<>(); entities.addAll(this.entityListProvider.getEntityProvider(this.domainClass).toCollection()); final String sqlStringWithReplacedValues = this.replacePlaceholders(sqlValue, parameters); final ResultSet retrieve = this.parser.retrieve(entities, sqlStringWithReplacedValues); - return retrieve.stream().toList(); + final List results = retrieve.stream().toList(); + return this.copier.copy(results); } - private String replacePlaceholders(String sqlValue, final Object[] parameters) + private String replacePlaceholders(final String sqlValue, final Object[] parameters) { + String stringWithReplacedValues = sqlValue; // Replace positional placeholders with actual parameter values for(int i = 0; i < parameters.length; i++) { final String placeholder = "\\?" + (i + 1); String value = parameters[i].toString(); - if(parameters[i] instanceof String) - { - value = "'" + value + "'"; - } if(parameters[i] instanceof final Collection collection) { value = @@ -57,9 +79,13 @@ private String replacePlaceholders(String sqlValue, final Object[] parameters) .collect(Collectors.joining(", ", "(", ")")) .toString(); } - sqlValue = sqlValue.replaceAll(placeholder, value); + if(parameters[i] instanceof final LocalDate localDate) + { + value = localDate.format(DateTimeFormatter.ofPattern("YYYY-MM-dd")); + } + stringWithReplacedValues = stringWithReplacedValues.replaceAll(placeholder, value); } - return sqlValue; + return stringWithReplacedValues; } private Map> createAttributes(final Class domainClass) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java index 6b871955..13853b2d 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java @@ -76,8 +76,7 @@ public RepositoryQuery resolveQuery( return this.createHSqlQueryProvider( queryAnnotation.value(), metadata.getDomainType(), - queryMethod, - method + queryMethod ); } @@ -121,14 +120,12 @@ private RepositoryQuery createStringBasedEclipseStoreQueryProvider( private RepositoryQuery createHSqlQueryProvider( final String sqlString, final Class domainType, - final QueryMethod queryMethod, - final Method method + final QueryMethod queryMethod ) { return new HSqlQueryProvider<>( sqlString, queryMethod, - method, domainType, this.storage, this.workingCopierCreator.createWorkingCopier(domainType, this.storage) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java index 0cea2edd..620afbc7 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/HsqlTest.java @@ -16,20 +16,22 @@ package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; -import java.time.LocalDate; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; +import software.xdev.spring.data.eclipse.store.helper.TestData; import software.xdev.spring.data.eclipse.store.integration.isolated.IsolatedTestAnnotations; @@ -40,6 +42,19 @@ class HsqlTest @Autowired private MyEntityRepository repository; + @Test + void workingCopyAndNotSameObject() + { + final MyEntity testEntity = new MyEntity(); + testEntity.setName(TestData.FIRST_NAME); + this.repository.save(testEntity); + + final List result = this.repository.findAllEntities(); + assertEquals(1, result.size()); + assertEquals(testEntity, result.get(0)); + assertNotSame(testEntity, result.get(0)); + } + private static Stream provideTestDataFindAllEntities() { return Stream.of( @@ -61,7 +76,7 @@ void findAllEntities(final List entities, final int expectedSize) private static Stream provideTestDataFindByName() { return Stream.of( - Arguments.of(createEntityLists(0), 1), + Arguments.of(createEntityLists(0), 2), Arguments.of(createEntityLists(1), 1), Arguments.of(createEntityLists(2), 0), Arguments.of(createEntityLists(3), 0) @@ -124,104 +139,6 @@ void findAllOrderByAgeDesc(final List entities, final Integer expected } } - private static Stream provideTestDataFindTop5ByOrderByAgeDesc() - { - return Stream.of( - Arguments.of(createEntityLists(0), 2), - Arguments.of(createEntityLists(1), 2), - Arguments.of(createEntityLists(2), 2), - Arguments.of(createEntityLists(3), 0) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindTop5ByOrderByAgeDesc") - void findTop5ByOrderByAgeDesc(final List entities, final int expectedSize) - { - this.repository.saveAll(entities); - final List result = this.repository.findTop2ByOrderByAgeDesc(); - assertEquals(Math.min(2, expectedSize), result.size()); - } - - private static Stream provideTestDataFindDistinctNames() - { - return Stream.of( - Arguments.of(createEntityLists(0), 2), - Arguments.of(createEntityLists(1), 3), - Arguments.of(createEntityLists(2), 2), - Arguments.of(createEntityLists(3), 0) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindDistinctNames") - void findDistinctNames(final List entities, final int expectedSize) - { - this.repository.saveAll(entities); - final List result = this.repository.findDistinctNames(); - assertEquals(expectedSize, result.size()); - } - - private static Stream provideTestDataFindCountByName() - { - return Stream.of( - Arguments.of(createEntityLists(0), 1), - Arguments.of(createEntityLists(1), 1), - Arguments.of(createEntityLists(2), 0), - Arguments.of(createEntityLists(3), 0) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindCountByName") - void testCountByName(final List entities, final int expectedSize) - { - this.repository.saveAll(entities); - final List result = this.repository.countByName(); - assertNotNull(result); - } - - private static Stream provideTestDataFindCountByNameHavingMoreThan() - { - return Stream.of( - Arguments.of(createEntityLists(0), 1), - Arguments.of(createEntityLists(1), 1), - Arguments.of(createEntityLists(2), 0), - Arguments.of(createEntityLists(3), 0) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindCountByNameHavingMoreThan") - void testCountByNameHavingMoreThan(final List entities, final int expectedSize) - { - this.repository.saveAll(entities); - final List result = this.repository.countByNameHavingMoreThan(1); - assertNotNull(result); - } - - private static Stream provideTestDataFindEntityWithMaxAge() - { - return Stream.of( - Arguments.of(createEntityLists(0), 40), - Arguments.of(createEntityLists(1), 40), - Arguments.of(createEntityLists(2), 28), - Arguments.of(createEntityLists(3), null) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindEntityWithMaxAge") - void findEntityWithMaxAge(final List entities, final Integer expectedMaxAge) - { - this.repository.saveAll(entities); - final MyEntity result = this.repository.findEntityWithMaxAge(); - - if(expectedMaxAge != null) - { - assertEquals(40, result.getAge()); - } - else - { - assertNull(result); - } - } - private static Stream provideTestDataFindByNameIn() { return Stream.of( @@ -276,24 +193,26 @@ void findByNameNative(final List entities, final int expectedSize) assertEquals(expectedSize, result.size()); } - private static Stream provideTestDataFindByCreationDateAfter() - { - return Stream.of( - Arguments.of(createEntityLists(0), 2), - Arguments.of(createEntityLists(1), 3), - Arguments.of(createEntityLists(2), 2), - Arguments.of(createEntityLists(3), 0) - ); - } - @ParameterizedTest - @MethodSource("provideTestDataFindByCreationDateAfter") - void findByCreationDateAfter(final List entities, final int expectedSize) - { - this.repository.saveAll(entities); - final List result = - this.repository.findByCreationDateAfter(LocalDate.now().minusDays(1)); - assertEquals(expectedSize, result.size()); - } + // TODO: This does not work currently, due to non existing parser in + // com.googlecode.cqengine.query.parser.common.QueryParser + // private static Stream provideTestDataFindByCreationDateAfter() + // { + // return Stream.of( + // Arguments.of(createEntityLists(0), 2), + // Arguments.of(createEntityLists(1), 3), + // Arguments.of(createEntityLists(2), 2), + // Arguments.of(createEntityLists(3), 0) + // ); + // } + // @ParameterizedTest + // @MethodSource("provideTestDataFindByCreationDateAfter") + // void findByCreationDateAfter(final List entities, final int expectedSize) + // { + // this.repository.saveAll(entities); + // final List result = + // this.repository.findByCreationDateAfter(LocalDate.now().minusDays(1)); + // assertEquals(expectedSize, result.size()); + // } private static Stream provideTestDataFindByAgeBetween() { @@ -373,10 +292,13 @@ private static List createEntityLists(final int testDataSetIndex) final OtherEntity otherEntity = new OtherEntity(); otherEntity.setDescription("Test OtherEntity"); + final Calendar calendarPastOneYear = Calendar.getInstance(); + calendarPastOneYear.add(Calendar.YEAR, -1); + return switch(testDataSetIndex) { case 0 -> Arrays.asList( - createMyEntity("John", 21, LocalDate.now().minusYears(1), true, otherEntity), + createMyEntity("John", 21, calendarPastOneYear.getTime(), true, otherEntity), createMyEntity("John", 25, false, otherEntity), createMyEntity("Doe", 40, true, otherEntity) ); @@ -389,7 +311,7 @@ private static List createEntityLists(final int testDataSetIndex) createMyEntity("Jane", 22, true, null), createMyEntity("Bob", 28, true, null) ); - case 3 -> Arrays.asList(); + case 3 -> List.of(); default -> throw new RuntimeException("Wrong index!"); }; } @@ -403,7 +325,7 @@ private static MyEntity createMyEntity( return createMyEntity( name, age, - LocalDate.now(), + new Date(), active, otherEntity ); @@ -412,7 +334,7 @@ private static MyEntity createMyEntity( private static MyEntity createMyEntity( final String name, final int age, - final LocalDate creationDate, + final Date creationDate, final boolean active, final OtherEntity otherEntity) { diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java index 1c03d3e7..41f7259c 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntity.java @@ -1,6 +1,22 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; -import java.time.LocalDate; +import java.util.Date; +import java.util.Objects; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -17,7 +33,7 @@ public class MyEntity private Integer age; - private LocalDate creationDate; + private Date creationDate; private Boolean active; @@ -31,7 +47,7 @@ public MyEntity( final Long id, final String name, final int age, - final LocalDate creationDate, + final Date creationDate, final boolean active, final OtherEntity otherEntity) { @@ -73,12 +89,12 @@ public void setAge(final int age) this.age = age; } - public LocalDate getCreationDate() + public Date getCreationDate() { return this.creationDate; } - public void setCreationDate(final LocalDate creationDate) + public void setCreationDate(final Date creationDate) { this.creationDate = creationDate; } @@ -102,4 +118,29 @@ public void setOtherEntity(final OtherEntity otherEntity) { this.otherEntity = otherEntity; } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final MyEntity myEntity = (MyEntity)o; + return Objects.equals(this.id, myEntity.id) && Objects.equals(this.name, myEntity.name) + && Objects.equals(this.age, myEntity.age) && Objects.equals(this.creationDate, myEntity.creationDate) + && Objects.equals(this.active, myEntity.active) && Objects.equals( + this.otherEntity, + myEntity.otherEntity); + } + + @Override + public int hashCode() + { + return Objects.hash(this.id, this.name, this.age, this.creationDate, this.active, this.otherEntity); + } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java index 4805a9a2..2e9b7ae6 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java @@ -1,6 +1,20 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; -import java.time.LocalDate; import java.util.List; import org.springframework.data.repository.ListCrudRepository; @@ -10,71 +24,53 @@ public interface MyEntityRepository extends ListCrudRepository { - // 1. Simple Select + // Simple Select @Query("SELECT * FROM MyEntity") List findAllEntities(); - // 2. Select with a where clause - @Query("SELECT * FROM MyEntity WHERE name = ?1") + // Select with a where clause + @Query("SELECT * FROM MyEntity WHERE name = '?1'") List findByName(String name); - // 3. Select with multiple where clauses - @Query(" SELECT * FROM MyEntity WHERE (name = ?1 AND age > ?2)") + // Select with multiple where clauses + @Query(" SELECT * FROM MyEntity WHERE (name = '?1' AND age > ?2)") List findByNameAndAgeGreaterThan(String name, int age); - // 4. Select with order by + // Select with order by @Query(" SELECT * FROM MyEntity ORDER BY age DESC") List findAllOrderByAgeDesc(); - // 5. Select with limit - @Query(value = " SELECT * FROM MyEntity ORDER BY age DESC LIMIT 2") - List findTop2ByOrderByAgeDesc(); - - // 6. Select with distinct - @Query("SELECT DISTINCT name FROM MyEntity") - List findDistinctNames(); - - // 8. Select with group by - @Query("SELECT name, COUNT(*) FROM MyEntity GROUP BY name") - List countByName(); - - // 9. Select with having - @Query("SELECT name, COUNT(*) FROM MyEntity GROUP BY name HAVING COUNT(*) > ?1") - List countByNameHavingMoreThan(long count); - - // 10. Select with subquery - @Query("SELECT * FROM MyEntity WHERE age = (SELECT MAX(age) FROM MyEntity2)") - MyEntity findEntityWithMaxAge(); - - // 11. Select with IN clause + // Select with IN clause @Query(" SELECT * FROM MyEntity WHERE name IN ?1") List findByNameIn(List names); - // 12. Select with LIKE clause + // Select with LIKE clause @Query(" SELECT * FROM MyEntity WHERE 'name' LIKE '%?1%'") List findByNameContaining(String keyword); - // 13. Select with native query - @Query(value = "SELECT * FROM my_entity WHERE name = ?1") + // Select with native query + @Query(value = "SELECT * FROM my_entity WHERE name = '?1'") List findByNameNative(String name); - // 14. Select with date comparison - @Query(" SELECT * FROM MyEntity WHERE creationDate > ?1") - List findByCreationDateAfter(LocalDate date); + // Select with date comparison + // TODO: This does not work currently, due to non existing parser in + // com.googlecode.cqengine.query.parser.common.QueryParser + // @Query("SELECT * FROM MyEntity WHERE creationDate > '?1'") + // List findByCreationDateAfter(LocalDate date); - // 15. Select with between clause - @Query(" SELECT * FROM MyEntity WHERE age BETWEEN ?1 AND ?2") + // Select with between clause + @Query("SELECT * FROM MyEntity WHERE age BETWEEN ?1 AND ?2") List findByAgeBetween(int startAge, int endAge); - // 16. Select with boolean condition - @Query(" SELECT * FROM MyEntity WHERE active = true") + // Select with boolean condition + @Query("SELECT * FROM MyEntity WHERE active = true") List findAllActive(); - // 17. Select with is null condition - @Query(" SELECT * FROM MyEntity WHERE otherEntity IS NULL") + // Select with is null condition + @Query("SELECT * FROM MyEntity WHERE otherEntity IS NULL") List findWhereOtherEntityIsNull(); - // 18. Select with is not null condition - @Query(" SELECT * FROM MyEntity WHERE otherEntity IS NOT NULL") + // Select with is not null condition + @Query("SELECT * FROM MyEntity WHERE otherEntity IS NOT NULL") List findWhereOtherEntityIsNotNull(); } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java index 5d34cd39..da86a4d1 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/OtherEntity.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * 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 + * + * http://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 software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.hsql; import jakarta.persistence.GeneratedValue; diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/repositories/CustomerRepositoryWithQuery.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/repositories/CustomerRepositoryWithQuery.java deleted file mode 100644 index 86d5b1d2..00000000 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/repositories/CustomerRepositoryWithQuery.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * 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 - * - * http://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 software.xdev.spring.data.eclipse.store.integration.shared.repositories; - -import java.util.List; -import java.util.Optional; - -import org.springframework.data.repository.Repository; - -import software.xdev.spring.data.eclipse.store.repository.Query; - - -public interface CustomerRepositoryWithQuery extends Repository -{ - List findAll(); - - Optional findByFirstName(String firstName); - - @Query() - Iterable findAllByLastName(String lastName); - - void save(CustomerWithQuery customer); -} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/JpaCompatibilityTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/JpaCompatibilityTest.java deleted file mode 100644 index 5a1a095f..00000000 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/shared/tests/JpaCompatibilityTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * 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 - * - * http://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 software.xdev.spring.data.eclipse.store.integration.shared.tests; - -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import software.xdev.spring.data.eclipse.store.helper.TestData; -import software.xdev.spring.data.eclipse.store.helper.TestUtil; -import software.xdev.spring.data.eclipse.store.integration.shared.DefaultTestAnnotations; -import software.xdev.spring.data.eclipse.store.integration.shared.SharedTestConfiguration; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.CustomerRepositoryWithQuery; -import software.xdev.spring.data.eclipse.store.integration.shared.repositories.CustomerWithQuery; - - -@DefaultTestAnnotations -class JpaCompatibilityTest -{ - @Autowired - private CustomerRepositoryWithQuery customerRepository; - - @Autowired - private SharedTestConfiguration configuration; - - @Test - void testUseQueryAnnotationFindAll() - { - final CustomerWithQuery customer1 = new CustomerWithQuery(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final List customers = TestUtil.iterableToList(this.customerRepository.findAll()); - Assertions.assertEquals(1, customers.size()); - Assertions.assertEquals(customer1, customers.get(0)); - Assertions.assertNotSame(customer1, customers.get(0)); - } - ); - } - - @Test - void testUseQueryAnnotationFindByFirstName() - { - final CustomerWithQuery customer1 = new CustomerWithQuery(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final Optional foundCustomer = - this.customerRepository.findByFirstName(TestData.FIRST_NAME); - Assertions.assertTrue(foundCustomer.isPresent()); - Assertions.assertEquals(customer1, foundCustomer.get()); - Assertions.assertNotSame(customer1, foundCustomer.get()); - } - ); - } - - @Test - void testUseQueryAnnotationFindAllByLastName() - { - final CustomerWithQuery customer1 = new CustomerWithQuery(TestData.FIRST_NAME, TestData.LAST_NAME); - this.customerRepository.save(customer1); - - TestUtil.doBeforeAndAfterRestartOfDatastore( - this.configuration, - () -> { - final List customers = - TestUtil.iterableToList(this.customerRepository.findAllByLastName(TestData.LAST_NAME)); - Assertions.assertEquals(1, customers.size()); - Assertions.assertEquals(customer1, customers.get(0)); - Assertions.assertNotSame(customer1, customers.get(0)); - } - ); - } -} From bc4d869f10f56e50a7b8de024ff3524edfbfb211 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 6 Aug 2024 11:21:36 +0200 Subject: [PATCH 23/26] Updated version and docs --- CHANGELOG.md | 4 + docs/antora.yml | 6 +- docs/modules/ROOT/nav.adoc | 4 +- .../modules/ROOT/pages/features/features.adoc | 2 + docs/modules/ROOT/pages/features/queries.adoc | 73 +++++++++++++++++++ docs/modules/ROOT/pages/known-issues.adoc | 12 --- docs/modules/ROOT/pages/migration.adoc | 2 +- pom.xml | 2 +- spring-data-eclipse-store-benchmark/pom.xml | 2 +- spring-data-eclipse-store-demo/pom.xml | 2 +- spring-data-eclipse-store-jpa/pom.xml | 2 +- 11 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 docs/modules/ROOT/pages/features/queries.adoc diff --git a/CHANGELOG.md b/CHANGELOG.md index 84555bcf..ac6f601a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.2.0 + +* Implemented ``@Query`` annotation with simple SQL-Selects + # 2.1.0 * Implemented auto-id-generation for UUIDs. diff --git a/docs/antora.yml b/docs/antora.yml index fc9ac74e..1ff4aa82 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,14 +1,14 @@ name: ROOT title: Spring-Data-Eclipse-Store version: master -display_version: '2.1.0' +display_version: '2.2.0' start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: 'Spring-Data-Eclipse-Store' - display-version: '2.1.0' - maven-version: '2.1.0' + display-version: '2.2.0' + maven-version: '2.2.0' page-editable: false page-out-of-support: false diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 49a238b7..7f76b1f6 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -3,8 +3,10 @@ * xref:configuration.adoc[Configuration] * xref:working-copies.adoc[Working Copies] * xref:features/features.adoc[Features] +** xref:features/ids.adoc[IDs] ** xref:features/lazies.adoc[Lazy References] +** xref:features/queries.adoc[Queries] ** xref:features/transactions.adoc[Transactions] ** xref:features/versions.adoc[Versions] -* xref:migration.adoc[Migration] +* xref:migration.adoc[Migration from JPA] * xref:known-issues.adoc[Known issues] diff --git a/docs/modules/ROOT/pages/features/features.adoc b/docs/modules/ROOT/pages/features/features.adoc index 8f85b440..6c511a04 100644 --- a/docs/modules/ROOT/pages/features/features.adoc +++ b/docs/modules/ROOT/pages/features/features.adoc @@ -1,5 +1,7 @@ = Features +* xref:features/ids.adoc[IDs] * xref:features/lazies.adoc[Lazy References] +* xref:features/queries.adoc[Queries] * xref:features/transactions.adoc[Transactions] * xref:features/versions.adoc[Versions] diff --git a/docs/modules/ROOT/pages/features/queries.adoc b/docs/modules/ROOT/pages/features/queries.adoc new file mode 100644 index 00000000..e956f710 --- /dev/null +++ b/docs/modules/ROOT/pages/features/queries.adoc @@ -0,0 +1,73 @@ += Queries + +== Keywords + +It is possible to use **most of the standard query keywords** for repositories defined in Spring Data JPA: https://docs.spring.io/spring-data/jpa/reference/repositories/query-keywords-reference.html[Spring Data JPA - Repository query keywords]. + +Here are a few examples: + +[source,java] +---- +@Repository +public interface UserRepository extends EclipseStoreRepository +{ + List findByFirstName(String firstName, String lastName); + List findByFirstNameAndLastName(String firstName, String lastName); + List findByDateOfBirthBefore(LocalDate date); + List findByAgeIn(List ages); + List findByIsActiveFalse(); +} +---- + +More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java[test-cases]. + +== Query by Example + +Developers can also use https://docs.spring.io/spring-data/jpa/reference/repositories/query-by-example.html[Query by Example] if preferred. + +An example: + +[source,java] +---- +public List findAllUsersNamedMick() +{ + final User probe = new User(1, "Mick", BigDecimal.TEN); + return userRepository.findAll(Example.of(probe)); +} +---- + +More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java[test-cases]. + +== @Query annotation + +The support for a ``@Query``-Annotation is currently quite limited, but useful nonetheless. + +To keep parse and execute SQL-Queries we use the https://github.com/npgall/cqengine[cqengine] by https://github.com/npgall[Niall Gallagher]. +It offers rudimentary support of some SQL-Queries, but not all. + +[NOTE] +==== +https://github.com/npgall/cqengine[cqengine] parses the SQL String as a SQLite-SQL-String and is therefore different from the https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.at-query[HQL or JPQL] of Spring Data JPA. +==== + +Here are some working examples: + +[source,java] +---- +public interface MyEntityRepository extends ListCrudRepository +{ + @Query("SELECT * FROM MyEntity WHERE name = '?1'") + List findByName(String name); + + @Query("SELECT * FROM MyEntity WHERE (name = '?1' AND age > ?2)") + List findByNameAndAgeGreaterThan(String name, int age); + + @Query("SELECT * FROM MyEntity WHERE 'name' LIKE '%?1%'") + List findByNameContaining(String keyword); + + @Query("SELECT * FROM MyEntity WHERE otherEntity IS NOT NULL") + List findWhereOtherEntityIsNotNull(); +} +---- + +More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java[test-cases]. diff --git a/docs/modules/ROOT/pages/known-issues.adoc b/docs/modules/ROOT/pages/known-issues.adoc index 8cbdc247..f6ad7ee1 100644 --- a/docs/modules/ROOT/pages/known-issues.adoc +++ b/docs/modules/ROOT/pages/known-issues.adoc @@ -1,17 +1,5 @@ = Known issues -== Query annotations - -In Spring-Data-JPA you can write a Query over methods of repositories like this: - -[source,java,title="From https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java[spring-petclinic]"] ----- -@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") -List findPetTypes(); ----- - -We created https://github.com/xdev-software/spring-data-eclipse-store/issues/32[an issue] for that but right now we *do not support Query annotations*. - == Data changes There are two basic ways to keep your data up to date. diff --git a/docs/modules/ROOT/pages/migration.adoc b/docs/modules/ROOT/pages/migration.adoc index 24148348..833cf77e 100644 --- a/docs/modules/ROOT/pages/migration.adoc +++ b/docs/modules/ROOT/pages/migration.adoc @@ -1,4 +1,4 @@ -= Migration += Migration from JPA Migrating from Spring Data JPA is very easy. We implemented a https://github.com/xdev-software/spring-data-eclipse-store-migration[OpenRewrite recipe] for that. diff --git a/pom.xml b/pom.xml index a753f6be..5add980c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT pom diff --git a/spring-data-eclipse-store-benchmark/pom.xml b/spring-data-eclipse-store-benchmark/pom.xml index 0b85fcba..5ef84bd1 100644 --- a/spring-data-eclipse-store-benchmark/pom.xml +++ b/spring-data-eclipse-store-benchmark/pom.xml @@ -5,7 +5,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT spring-data-eclipse-store-benchmark diff --git a/spring-data-eclipse-store-demo/pom.xml b/spring-data-eclipse-store-demo/pom.xml index 7bd7dbe4..291bef15 100644 --- a/spring-data-eclipse-store-demo/pom.xml +++ b/spring-data-eclipse-store-demo/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT spring-data-eclipse-store-demo diff --git a/spring-data-eclipse-store-jpa/pom.xml b/spring-data-eclipse-store-jpa/pom.xml index 1f71f1c8..d5636034 100644 --- a/spring-data-eclipse-store-jpa/pom.xml +++ b/spring-data-eclipse-store-jpa/pom.xml @@ -7,7 +7,7 @@ software.xdev spring-data-eclipse-store-root - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT spring-data-eclipse-store-jpa From 453cab10079b6e6fbb7d90a67466a132710d059b Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 6 Aug 2024 11:38:27 +0200 Subject: [PATCH 24/26] Refactoring --- .../eclipse/store/repository/query/HSqlQueryProvider.java | 5 +++-- .../store/repository/query/antlr/HSqlQueryExecutor.java | 4 ++-- .../repository/support/EclipseStoreQueryLookupStrategy.java | 3 --- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index f0f51ea0..ab6e243a 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -19,6 +19,7 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.lang.Nullable; import software.xdev.spring.data.eclipse.store.core.EntityListProvider; import software.xdev.spring.data.eclipse.store.repository.query.antlr.HSqlQueryExecutor; @@ -27,7 +28,7 @@ public class HSqlQueryProvider implements RepositoryQuery { - private final HSqlQueryExecutor executor; + private final HSqlQueryExecutor executor; private final String sqlValue; private final QueryMethod queryMethod; @@ -40,7 +41,7 @@ public HSqlQueryProvider( ) { this.queryMethod = queryMethod; - this.executor = new HSqlQueryExecutor( + this.executor = new HSqlQueryExecutor<>( Objects.requireNonNull(domainClass), Objects.requireNonNull(entityListProvider), copier diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java index 56fad615..2fc35198 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java @@ -71,7 +71,7 @@ private String replacePlaceholders(final String sqlValue, final Object[] paramet { final String placeholder = "\\?" + (i + 1); String value = parameters[i].toString(); - if(parameters[i] instanceof final Collection collection) + if(parameters[i] instanceof final Collection collection) { value = collection.stream() @@ -81,7 +81,7 @@ private String replacePlaceholders(final String sqlValue, final Object[] paramet } if(parameters[i] instanceof final LocalDate localDate) { - value = localDate.format(DateTimeFormatter.ofPattern("YYYY-MM-dd")); + value = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); } stringWithReplacedValues = stringWithReplacedValues.replaceAll(placeholder, value); } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java index 13853b2d..2f132717 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreQueryLookupStrategy.java @@ -19,8 +19,6 @@ import jakarta.annotation.Nonnull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; @@ -38,7 +36,6 @@ public class EclipseStoreQueryLookupStrategy implements QueryLookupStrategy { - private static final Logger LOG = LoggerFactory.getLogger(EclipseStoreQueryLookupStrategy.class); private final EclipseStoreStorage storage; private final WorkingCopierCreator workingCopierCreator; From 47dc3fcf751861e962ae1d5f315b6648b99d3cc2 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 6 Aug 2024 11:44:26 +0200 Subject: [PATCH 25/26] Refactor --- .../data/eclipse/store/repository/query/HSqlQueryProvider.java | 1 - .../store/repository/query/antlr/HSqlQueryExecutor.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java index ab6e243a..32661493 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/HSqlQueryProvider.java @@ -19,7 +19,6 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.lang.Nullable; import software.xdev.spring.data.eclipse.store.core.EntityListProvider; import software.xdev.spring.data.eclipse.store.repository.query.antlr.HSqlQueryExecutor; diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java index 2fc35198..cff88bb1 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/antlr/HSqlQueryExecutor.java @@ -76,8 +76,7 @@ private String replacePlaceholders(final String sqlValue, final Object[] paramet value = collection.stream() .map(o -> "'" + o.toString() + "'") - .collect(Collectors.joining(", ", "(", ")")) - .toString(); + .collect(Collectors.joining(", ", "(", ")")); } if(parameters[i] instanceof final LocalDate localDate) { From c3d8396acdcd96944b8e56538d6eccf924571186 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 6 Aug 2024 11:49:16 +0200 Subject: [PATCH 26/26] Changed version --- CHANGELOG.md | 5 +---- docs/antora.yml | 6 +++--- spring-data-eclipse-store-benchmark/pom.xml | 2 +- spring-data-eclipse-store-demo/pom.xml | 2 +- spring-data-eclipse-store-jpa/pom.xml | 2 +- spring-data-eclipse-store/pom.xml | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6f601a..a1d2dc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,9 @@ -# 2.2.0 - -* Implemented ``@Query`` annotation with simple SQL-Selects - # 2.1.0 * Implemented auto-id-generation for UUIDs. * Implemented composite primary keys. * Keyword "ignoreCase" now available for queries. +* Implemented ``@Query`` annotation with simple SQL-Selects # 2.0.1 diff --git a/docs/antora.yml b/docs/antora.yml index 1ff4aa82..fc9ac74e 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,14 +1,14 @@ name: ROOT title: Spring-Data-Eclipse-Store version: master -display_version: '2.2.0' +display_version: '2.1.0' start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: 'Spring-Data-Eclipse-Store' - display-version: '2.2.0' - maven-version: '2.2.0' + display-version: '2.1.0' + maven-version: '2.1.0' page-editable: false page-out-of-support: false diff --git a/spring-data-eclipse-store-benchmark/pom.xml b/spring-data-eclipse-store-benchmark/pom.xml index 5ef84bd1..722b57f9 100644 --- a/spring-data-eclipse-store-benchmark/pom.xml +++ b/spring-data-eclipse-store-benchmark/pom.xml @@ -9,7 +9,7 @@ spring-data-eclipse-store-benchmark - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT jar 2023 diff --git a/spring-data-eclipse-store-demo/pom.xml b/spring-data-eclipse-store-demo/pom.xml index 291bef15..9249afbc 100644 --- a/spring-data-eclipse-store-demo/pom.xml +++ b/spring-data-eclipse-store-demo/pom.xml @@ -11,7 +11,7 @@ spring-data-eclipse-store-demo - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT jar diff --git a/spring-data-eclipse-store-jpa/pom.xml b/spring-data-eclipse-store-jpa/pom.xml index d5636034..79006829 100644 --- a/spring-data-eclipse-store-jpa/pom.xml +++ b/spring-data-eclipse-store-jpa/pom.xml @@ -11,7 +11,7 @@ spring-data-eclipse-store-jpa - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT jar 2023 diff --git a/spring-data-eclipse-store/pom.xml b/spring-data-eclipse-store/pom.xml index 8dc5b80e..26676304 100644 --- a/spring-data-eclipse-store/pom.xml +++ b/spring-data-eclipse-store/pom.xml @@ -6,7 +6,7 @@ software.xdev spring-data-eclipse-store - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT jar spring-data-eclipse-store