From c4bb2553c4ed4e895c8e7fd0515838674a14b531 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Tue, 11 Jun 2024 15:26:17 +0200 Subject: [PATCH 1/7] Created tests and todo-implementation for QueryByExample --- .../EclipseStoreQueryByExampleExecutor.java | 25 ++++ .../interfaces/EclipseStoreRepository.java | 3 +- .../support/SimpleEclipseStoreRepository.java | 58 ++++++++- .../query/by/example/QueryByExampleTest.java | 118 ++++++++++++++++++ .../QueryByExampleTestConfiguration.java | 51 ++++++++ .../isolated/tests/query/by/example/User.java | 68 ++++++++++ .../query/by/example/UserRepository.java | 23 ++++ 7 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreQueryByExampleExecutor.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java create mode 100644 spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/UserRepository.java diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreQueryByExampleExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreQueryByExampleExecutor.java new file mode 100644 index 00000000..c15f1ee6 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreQueryByExampleExecutor.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.repository.interfaces; + +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.query.QueryByExampleExecutor; + + +@NoRepositoryBean +public interface EclipseStoreQueryByExampleExecutor extends QueryByExampleExecutor +{ +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreRepository.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreRepository.java index f70c739c..7cde04a0 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreRepository.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/interfaces/EclipseStoreRepository.java @@ -22,6 +22,7 @@ public interface EclipseStoreRepository extends EclipseStoreListCrudRepository, - EclipseStoreListPagingAndSortingRepositoryRepository + EclipseStoreListPagingAndSortingRepositoryRepository, + EclipseStoreQueryByExampleExecutor { } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java index ddfafe0b..74620326 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java @@ -21,15 +21,18 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import jakarta.annotation.Nonnull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery; import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException; import software.xdev.spring.data.eclipse.store.exceptions.NoIdFieldFoundException; @@ -39,6 +42,7 @@ import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreListCrudRepository; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreListPagingAndSortingRepositoryRepository; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStorePagingAndSortingRepositoryRepository; +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreQueryByExampleExecutor; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; import software.xdev.spring.data.eclipse.store.repository.query.executors.ListQueryExecutor; @@ -55,7 +59,8 @@ public class SimpleEclipseStoreRepository EclipseStorePagingAndSortingRepositoryRepository, EclipseStoreListPagingAndSortingRepositoryRepository, EclipseStoreCrudRepository, - EclipseStoreListCrudRepository + EclipseStoreListCrudRepository, + EclipseStoreQueryByExampleExecutor { private static final Logger LOG = LoggerFactory.getLogger(SimpleEclipseStoreRepository.class); private final EclipseStoreStorage storage; @@ -343,4 +348,55 @@ public Page findAll(@Nonnull final Pageable pageable) } ); } + + @Override + public Optional findOne(final Example example) + { + // TODO + return Optional.empty(); + } + + @Override + public Iterable findAll(final Example example) + { + // TODO + return null; + } + + @Override + public Iterable findAll(final Example example, final Sort sort) + { + // TODO + return null; + } + + @Override + public Page findAll(final Example example, final Pageable pageable) + { + // TODO + return null; + } + + @Override + public long count(final Example example) + { + // TODO + return 0; + } + + @Override + public boolean exists(final Example example) + { + // TODO + return false; + } + + @Override + public R findBy( + final Example example, + final Function, R> queryFunction) + { + // TODO + return null; + } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java new file mode 100644 index 00000000..fd4abf1e --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java @@ -0,0 +1,118 @@ +/* + * 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.example; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +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 = {QueryByExampleTestConfiguration.class}) +class QueryByExampleTest +{ + private final QueryByExampleTestConfiguration configuration; + private final UserRepository userRepository; + private User user1; + private User user2; + + @Autowired + public QueryByExampleTest(final QueryByExampleTestConfiguration configuration, final UserRepository userRepository) + { + this.configuration = configuration; + this.userRepository = userRepository; + } + + @BeforeEach + void initData() + { + this.user1 = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); + this.user2 = new User(2, TestData.FIRST_NAME_ALTERNATIVE, BigDecimal.TEN); + this.userRepository.saveAll(List.of(this.user1, this.user2)); + } + + @Test + void simpleEqualsQuery() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); + final List foundUsers = TestUtil.iterableToList(this.userRepository.findAll(Example.of(probe))); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void fluentApiFindSingle() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); + final Optional foundUser = + this.userRepository.findBy( + Example.of(probe), + user -> user + .sortBy(Sort.by("name").descending()) + .first() + ); + Assertions.assertTrue(foundUser.isPresent()); + Assertions.assertEquals(this.user1, foundUser.get()); + } + ); + } + + @Test + void exampleMatcher() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.endsWith()) + .withMatcher("balance", ); + + final Optional foundUser = + this.userRepository.findBy( + Example.of( + probe, + + ) + ); + Assertions.assertTrue(foundUser.isPresent()); + Assertions.assertEquals(this.user1, foundUser.get()); + } + ); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java new file mode 100644 index 00000000..9b92780f --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java @@ -0,0 +1,51 @@ +/* + * 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.example; + +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.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +import software.xdev.spring.data.eclipse.store.integration.TestConfiguration; +import software.xdev.spring.data.eclipse.store.repository.config.EnableEclipseStoreRepositories; + + +@Configuration +@EnableEclipseStoreRepositories +public class QueryByExampleTestConfiguration extends TestConfiguration +{ + @Autowired + protected QueryByExampleTestConfiguration( + final EclipseStoreProperties defaultEclipseStoreProperties, + final EmbeddedStorageFoundationFactory defaultEclipseStoreProvider) + { + super(defaultEclipseStoreProperties, defaultEclipseStoreProvider); + } + + @Bean + @Override + public PlatformTransactionManager transactionManager( + final ObjectProvider transactionManagerCustomizers + ) + { + return super.transactionManager(transactionManagerCustomizers); + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java new file mode 100644 index 00000000..0af2c86f --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java @@ -0,0 +1,68 @@ +/* + * 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.example; + +import java.math.BigDecimal; + +import jakarta.persistence.Id; + + +public class User +{ + @Id + private int id; + + private String name; + + private BigDecimal balance; + + public User(final int id, final String name, final BigDecimal balance) + { + this.id = id; + this.name = name; + this.balance = balance; + } + + public int getId() + { + return this.id; + } + + public void setId(final int id) + { + this.id = id; + } + + public BigDecimal getBalance() + { + return this.balance; + } + + public void setBalance(final BigDecimal balance) + { + this.balance = balance; + } + + public String getName() + { + return this.name; + } + + public void setName(final String name) + { + this.name = name; + } +} diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/UserRepository.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/UserRepository.java new file mode 100644 index 00000000..55080155 --- /dev/null +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/UserRepository.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.query.by.example; + +import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; + + +public interface UserRepository extends EclipseStoreRepository +{ +} From aec47579dd39b1c21ff311064f1c5b7bca987148 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 13 Jun 2024 10:02:19 +0200 Subject: [PATCH 2/7] Extended QueryByExample tests --- .../query/by/example/QueryByExampleTest.java | 159 ++++++++++++++++-- .../QueryByExampleTestConfiguration.java | 13 -- .../isolated/tests/query/by/example/User.java | 24 +++ 3 files changed, 172 insertions(+), 24 deletions(-) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java index fd4abf1e..fc49df82 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java @@ -17,6 +17,7 @@ import java.math.BigDecimal; import java.util.List; +import java.util.Locale; import java.util.Optional; import org.junit.jupiter.api.Assertions; @@ -92,7 +93,53 @@ void fluentApiFindSingle() } @Test - void exampleMatcher() + void exampleMatcherEndsWithName() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.substring(2), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.endsWith()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void exampleMatcherStartsWithName() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.substring(0, 1), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void exampleMatcheExactBalanceAndExactName() { TestUtil.doBeforeAndAfterRestartOfDatastore( this.configuration, @@ -100,18 +147,108 @@ void exampleMatcher() final User probe = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); final ExampleMatcher matcher = ExampleMatcher.matching() - .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.endsWith()) - .withMatcher("balance", ); + .withMatcher("balance", ExampleMatcher.GenericPropertyMatchers.exact()) + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.exact()); - final Optional foundUser = - this.userRepository.findBy( - Example.of( - probe, - - ) + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) ); - Assertions.assertTrue(foundUser.isPresent()); - Assertions.assertEquals(this.user1, foundUser.get()); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void exampleMatcherExactName() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME, BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.exact()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void exampleMatcherExactNameWrongName() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, "UselessData", BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.exact()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertTrue(foundUsers.isEmpty()); + } + ); + } + + @Test + void exampleMatcherExactIgnoreCase() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.toLowerCase(Locale.getDefault()), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.ignoreCase()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertEquals(1, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + } + ); + } + + @Test + void exampleMatcherExactBalance() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, "", BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("balance", ExampleMatcher.GenericPropertyMatchers.exact()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher) + ) + ); + Assertions.assertEquals(2, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); } ); } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java index 9b92780f..d3b9dd13 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTestConfiguration.java @@ -17,12 +17,8 @@ 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.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.PlatformTransactionManager; import software.xdev.spring.data.eclipse.store.integration.TestConfiguration; import software.xdev.spring.data.eclipse.store.repository.config.EnableEclipseStoreRepositories; @@ -39,13 +35,4 @@ protected QueryByExampleTestConfiguration( { super(defaultEclipseStoreProperties, defaultEclipseStoreProvider); } - - @Bean - @Override - public PlatformTransactionManager transactionManager( - final ObjectProvider transactionManagerCustomizers - ) - { - return super.transactionManager(transactionManagerCustomizers); - } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java index 0af2c86f..20e1c5f5 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/User.java @@ -16,6 +16,7 @@ package software.xdev.spring.data.eclipse.store.integration.isolated.tests.query.by.example; import java.math.BigDecimal; +import java.util.Objects; import jakarta.persistence.Id; @@ -65,4 +66,27 @@ public void setName(final String name) { this.name = name; } + + @Override + public boolean equals(final Object o) + { + if(this == o) + { + return true; + } + if(o == null || this.getClass() != o.getClass()) + { + return false; + } + final User user = (User)o; + return this.id == user.id && Objects.equals(this.name, user.name) && Objects.equals( + this.balance, + user.balance); + } + + @Override + public int hashCode() + { + return Objects.hash(this.id, this.name, this.balance); + } } From a780a13464b41b9b7b767f695044172892ee6c91 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 13 Jun 2024 13:02:16 +0200 Subject: [PATCH 3/7] Extended QueryByExample tests --- .../query/by/example/QueryByExampleTest.java | 86 ++++++++++++++++++- .../integration/shared/tests/QueryTest.java | 24 ++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java index fc49df82..a72cb1c6 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java @@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.test.context.ContextConfiguration; @@ -115,6 +117,87 @@ void exampleMatcherEndsWithName() ); } + @Test + void exampleMatcherPaging() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.substring(2), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("balance", ExampleMatcher.GenericPropertyMatchers.exact()); + + final Page foundUsers = + this.userRepository.findAll( + Example.of(probe, matcher), + Pageable.ofSize(1) + ); + Assertions.assertEquals(2, foundUsers.getTotalPages()); + Assertions.assertEquals(1, foundUsers.getContent().size()); + + final Page nextFoundUsers = + this.userRepository.findAll( + Example.of(probe, matcher), + foundUsers.nextPageable() + ); + Assertions.assertEquals(1, nextFoundUsers.getContent().size()); + + Assertions.assertNotEquals(foundUsers.getContent().get(0), nextFoundUsers.getContent().get(0)); + } + ); + } + + @Test + void exampleMatcherSorting() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.substring(2), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("balance", ExampleMatcher.GenericPropertyMatchers.exact()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher), + Sort.by("name") + ) + ); + Assertions.assertEquals(2, foundUsers.size()); + Assertions.assertEquals(this.user1, foundUsers.get(0)); + Assertions.assertEquals(this.user2, foundUsers.get(1)); + } + ); + } + + @Test + void exampleMatcherSortingInverted() + { + TestUtil.doBeforeAndAfterRestartOfDatastore( + this.configuration, + () -> { + final User probe = new User(1, TestData.FIRST_NAME.substring(2), BigDecimal.TEN); + + final ExampleMatcher matcher = ExampleMatcher.matching() + .withMatcher("balance", ExampleMatcher.GenericPropertyMatchers.exact()); + + final List foundUsers = + TestUtil.iterableToList( + this.userRepository.findAll( + Example.of(probe, matcher), + Sort.by("name").descending() + ) + ); + Assertions.assertEquals(2, foundUsers.size()); + Assertions.assertEquals(this.user2, foundUsers.get(0)); + Assertions.assertEquals(this.user1, foundUsers.get(1)); + } + ); + } + @Test void exampleMatcherStartsWithName() { @@ -248,7 +331,8 @@ void exampleMatcherExactBalance() ) ); Assertions.assertEquals(2, foundUsers.size()); - Assertions.assertEquals(this.user1, foundUsers.get(0)); + Assertions.assertEquals(this.user1.getBalance(), foundUsers.get(0).getBalance()); + Assertions.assertEquals(this.user1.getBalance(), foundUsers.get(1).getBalance()); } ); } 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 index bf6d24b1..00d2905b 100644 --- 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 @@ -21,6 +21,7 @@ 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; @@ -129,6 +130,29 @@ void testPageableFindAllTwoPages() ); } + @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() { From c044869bc73ad9baae8381c75a6c14c4d1097556 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 13 Jun 2024 13:05:49 +0200 Subject: [PATCH 4/7] Implemented basic QueryByExample functionality --- .../query/EclipseStoreQueryCreator.java | 19 +- .../repository/query/ReflectedField.java | 17 ++ .../query/criteria/CriteriaByExample.java | 177 ++++++++++++++++++ .../query/executors/CountQueryExecutor.java | 65 +++++++ .../query/executors/ExistsQueryExecutor.java | 77 ++++++++ .../executors/PageableQueryExecutor.java | 8 +- .../support/SimpleEclipseStoreRepository.java | 141 +++++++++----- 7 files changed, 435 insertions(+), 69 deletions(-) create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/CountQueryExecutor.java create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/ExistsQueryExecutor.java 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 6bed9ef5..341afd13 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 @@ -19,6 +19,9 @@ import java.util.Iterator; import java.util.Objects; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; @@ -28,10 +31,6 @@ import org.springframework.data.util.TypeInformation; import org.springframework.util.ObjectUtils; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; -import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException; -import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; import software.xdev.spring.data.eclipse.store.repository.query.criteria.AbstractCriteriaNode; import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; import software.xdev.spring.data.eclipse.store.repository.query.criteria.CriteriaSingleNode; @@ -249,16 +248,6 @@ private boolean isSimpleComparisonPossible(final Part part) private ReflectedField getDeclaredField(final Part part) { final String fieldName = part.getProperty().getSegment(); - try - { - return new ReflectedField<>(AccessHelper.getInheritedPrivateField(this.domainClass, fieldName)); - } - catch(final NoSuchFieldException e) - { - throw new FieldAccessReflectionException(String.format( - "Field %s in class %s was not found!", - fieldName, - this.domainClass.getSimpleName()), e); - } + return ReflectedField.createReflectedField(this.domainClass, fieldName); } } diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/ReflectedField.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/ReflectedField.java index 2fb19a78..71ec1c66 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/ReflectedField.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/ReflectedField.java @@ -19,6 +19,8 @@ import java.util.Objects; import jakarta.annotation.Nonnull; + +import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException; import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; @@ -38,6 +40,21 @@ public ReflectedField(final Field field) this.field = Objects.requireNonNull(field); } + public static ReflectedField createReflectedField(final Class domainClass, final String fieldName) + { + try + { + return new ReflectedField<>(AccessHelper.getInheritedPrivateField(domainClass, fieldName)); + } + catch(final NoSuchFieldException e) + { + throw new FieldAccessReflectionException(String.format( + "Field %s in class %s was not found!", + fieldName, + domainClass.getSimpleName()), e); + } + } + /** * Reads the field of the given object. If the fields is not accessible, it is made accessible with the * {@link AccessHelper#readFieldVariable(Field, Object)}. diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java new file mode 100644 index 00000000..dd139c20 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java @@ -0,0 +1,177 @@ +/* + * 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.criteria; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; + +import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; +import software.xdev.spring.data.eclipse.store.repository.query.ReflectedField; + + +public class CriteriaByExample implements Criteria +{ + private final Predicate predicate; + + public CriteriaByExample(final Example example) + { + if(example.getMatcher().isAllMatching()) + { + this.predicate = entity -> this.getDefinedOrDefaultSpecifiers(example) + .stream() + .allMatch(this.createPredicateForSpecifier(example, entity)); + } + else + { + this.predicate = entity -> this.getDefinedOrDefaultSpecifiers(example) + .stream() + .anyMatch(this.createPredicateForSpecifier(example, entity)); + } + } + + private Collection getDefinedOrDefaultSpecifiers(final Example example) + { + final Collection specifiers = + example.getMatcher().getPropertySpecifiers().getSpecifiers(); + if(!specifiers.isEmpty()) + { + return specifiers; + } + + ExampleMatcher matcher = ExampleMatcher.matching(); + + final Map allFields = AccessHelper.getInheritedPrivateFieldsByName(example.getProbeType()); + for(final String fieldName : allFields.keySet()) + { + matcher = matcher.withMatcher(fieldName, ExampleMatcher.GenericPropertyMatchers.exact()); + } + return matcher.getPropertySpecifiers().getSpecifiers(); + } + + @Override + public boolean evaluate(final T object) + { + return this.predicate.test(object); + } + + private Predicate createPredicateForSpecifier( + final Example example, + final T entity) + { + return specifier -> + { + final ReflectedField reflectedField = + (ReflectedField)ReflectedField.createReflectedField( + example.getProbeType(), + specifier.getPath()); + + final Object exampleValue = reflectedField.readValue((T)example.getProbe()); + final Optional transformedExampledValue = + specifier.getPropertyValueTransformer().apply(Optional.ofNullable(exampleValue)); + + if(transformedExampledValue.isEmpty()) + { + return true; + } + + final Object value = reflectedField.readValue(entity); + final Optional transformedValue = + specifier.getPropertyValueTransformer().apply(Optional.ofNullable(value)); + + final ExampleMatcher.StringMatcher setOrDefaultMatcher = specifier.getStringMatcher() == null ? + example.getMatcher().getDefaultStringMatcher() : + specifier.getStringMatcher(); + + switch(setOrDefaultMatcher) + { + case DEFAULT, EXACT -> + { + if(transformedExampledValue.get() instanceof String) + { + return this.valueToString(transformedValue, specifier).equals(this.valueToString( + transformedExampledValue, + specifier)); + } + return transformedExampledValue.equals(transformedValue); + } + case STARTING -> + { + final Optional valueAsString = this.valueToString(transformedValue, specifier); + if(valueAsString.isEmpty()) + { + return false; + } + return valueAsString.get() + .startsWith(this.valueToString(transformedExampledValue, specifier).get()); + } + case ENDING -> + { + final Optional valueAsString = this.valueToString(transformedValue, specifier); + if(valueAsString.isEmpty()) + { + return false; + } + return valueAsString.get().endsWith(this.valueToString(transformedExampledValue, specifier).get()); + } + case CONTAINING -> + { + final Optional valueAsString = this.valueToString(transformedValue, specifier); + if(valueAsString.isEmpty()) + { + return false; + } + return valueAsString.get().contains(this.valueToString(transformedExampledValue, specifier).get()); + } + case REGEX -> + { + final Optional valueAsString = this.valueToString(transformedValue, specifier); + if(valueAsString.isEmpty()) + { + return false; + } + return Pattern.compile( + this.valueToString(transformedExampledValue, specifier).get() + ) + .matcher(valueAsString.get()).find(); + } + } + return true; + }; + } + + private Optional valueToString( + final Optional value, + final ExampleMatcher.PropertySpecifier specifier) + { + if(value.isEmpty()) + { + return Optional.empty(); + } + if(specifier != null && Boolean.TRUE.equals(specifier.getIgnoreCase())) + { + return Optional.of(value.get().toString().toLowerCase(Locale.ROOT)); + } + return Optional.of(value.get().toString()); + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/CountQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/CountQueryExecutor.java new file mode 100644 index 00000000..50fe9746 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/CountQueryExecutor.java @@ -0,0 +1,65 @@ +/* + * 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.executors; + +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Stream; + +import jakarta.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; + + +/** + * Executes queries that are optionally sorted and paged in collections. + **/ +public class CountQueryExecutor implements QueryExecutor +{ + private static final Logger LOG = LoggerFactory.getLogger(CountQueryExecutor.class); + private final Criteria criteria; + + public CountQueryExecutor(final Criteria criteria) + { + this.criteria = criteria; + } + + /** + * {@inheritDoc} + * + * @return a list of the found/sorted/paged entities + */ + @Override + public Long execute(final Class clazz, @Nullable final Collection entities, final Object[] values) + { + Objects.requireNonNull(entities); + + final Stream entityStream = entities + .stream() + .filter(this.criteria::evaluate); + + final long result = entityStream.count(); + + if(LOG.isTraceEnabled()) + { + LOG.trace("Found {} entries.", result); + } + return result; + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/ExistsQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/ExistsQueryExecutor.java new file mode 100644 index 00000000..aab38ed6 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/ExistsQueryExecutor.java @@ -0,0 +1,77 @@ +/* + * 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.executors; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import jakarta.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; + + +/** + * Queries entities and returns the result wrapped in an optional. + * + * @param Entity-Type to query + */ +public class ExistsQueryExecutor implements QueryExecutor +{ + private static final Logger LOG = LoggerFactory.getLogger(ExistsQueryExecutor.class); + private final Criteria criteria; + + public ExistsQueryExecutor(final Criteria criteria) + { + this.criteria = Objects.requireNonNull(criteria); + } + + /** + * {@inheritDoc} + * + * @return whether the entity exists + */ + @Override + public Boolean execute( + final Class clazz, + @Nullable final Collection entities, + @Nullable final Object[] values) + { + Objects.requireNonNull(clazz); + if(entities == null || entities.isEmpty()) + { + return false; + } + final Stream entityStream = entities + .stream() + .filter(this.criteria::evaluate); + + final Optional result = entityStream.findAny(); + if(LOG.isDebugEnabled()) + { + LOG.debug( + "Query for class {} found an entity: {}", + clazz.getSimpleName(), + result.isPresent() + ); + } + return result.isPresent(); + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/PageableQueryExecutor.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/PageableQueryExecutor.java index c21ffe16..598170d7 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/PageableQueryExecutor.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/executors/PageableQueryExecutor.java @@ -18,12 +18,13 @@ import java.util.Collection; import java.util.Objects; +import jakarta.annotation.Nullable; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import jakarta.annotation.Nullable; import software.xdev.spring.data.eclipse.store.exceptions.NoPageableObjectFoundException; import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier; @@ -37,10 +38,12 @@ public class PageableQueryExecutor implements QueryExecutor { private final PageableSortableCollectionQuerier querier; + private final CountQueryExecutor countQueryExecutor; public PageableQueryExecutor(final WorkingCopier copier, final Criteria criteria, final Sort sort) { this.querier = new PageableSortableCollectionQuerier<>(copier, criteria, sort); + this.countQueryExecutor = new CountQueryExecutor<>(criteria); } /** @@ -61,7 +64,8 @@ public Page execute(final Class clazz, @Nullable final Collection entit { if(values[values.length - 1] instanceof final Pageable pageable) { - return new PageImpl<>(this.querier.getEntities(entities, pageable, clazz)); + final Long total = this.countQueryExecutor.execute(clazz, entities, null); + return new PageImpl<>(this.querier.getEntities(entities, pageable, clazz), pageable, total); } if(values[values.length - 1] instanceof final Sort sort) { diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java index 74620326..16c0abed 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java @@ -45,8 +45,12 @@ import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreQueryByExampleExecutor; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; +import software.xdev.spring.data.eclipse.store.repository.query.criteria.CriteriaByExample; +import software.xdev.spring.data.eclipse.store.repository.query.executors.CountQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.ExistsQueryExecutor; import software.xdev.spring.data.eclipse.store.repository.query.executors.ListQueryExecutor; import software.xdev.spring.data.eclipse.store.repository.query.executors.PageableQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.SingleOptionalQueryExecutor; import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier; import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopierResult; import software.xdev.spring.data.eclipse.store.transactions.EclipseStoreTransaction; @@ -230,34 +234,34 @@ public List findAllById(@Nonnull final Iterable idsToFind) // o3 should be the same no matter from where it is referenced. () -> this.copier.copy( this.storage - .getEntityList(this.domainClass) + .getEntityList(this.domainClass) .parallelStream() - .filter( - entity -> - { - try(final FieldAccessModifier fam = FieldAccessModifier.prepareForField( - this.getIdField(), - entity)) + .filter( + entity -> { - final Object idOfEntity = fam.getValueOfField(entity); - for(final ID idToFind : idsToFind) + try(final FieldAccessModifier fam = FieldAccessModifier.prepareForField( + this.getIdField(), + entity)) { - if(idToFind.equals(idOfEntity)) + final Object idOfEntity = fam.getValueOfField(entity); + for(final ID idToFind : idsToFind) { - return true; + if(idToFind.equals(idOfEntity)) + { + return true; + } } } + catch(final Exception e) + { + throw new FieldAccessReflectionException(String.format( + FieldAccessReflectionException.COULD_NOT_READ_FIELD, + this.getIdField().getName()), e); + } + return false; } - catch(final Exception e) - { - throw new FieldAccessReflectionException(String.format( - FieldAccessReflectionException.COULD_NOT_READ_FIELD, - this.getIdField().getName()), e); - } - return false; - } - ) - .toList() + ) + .toList() ) ); } @@ -296,19 +300,29 @@ public void delete(@Nonnull final T entity) @Override public void deleteAllById(final Iterable ids) { - for(final ID id : ids) - { - this.deleteById(id); - } + this.storage.getReadWriteLock().write( + () -> + { + for(final ID id : ids) + { + this.deleteById(id); + } + } + ); } @Override public void deleteAll(final Iterable entities) { - for(final T entity : entities) - { - this.delete(entity); - } + this.storage.getReadWriteLock().write( + () -> + { + for(final T entity : entities) + { + this.delete(entity); + } + } + ); } @Override @@ -322,14 +336,13 @@ public void deleteAll() @Nonnull public List findAll(@Nonnull final Sort sort) { + final ListQueryExecutor query = new ListQueryExecutor<>(this.copier, Criteria.createNoCriteria()); return this.storage.getReadWriteLock().read( - () -> { - final ListQueryExecutor query = new ListQueryExecutor<>(this.copier, Criteria.createNoCriteria()); - return query.execute( + () -> + query.execute( this.domainClass, this.storage.getEntityList(this.domainClass), - new Object[]{sort}); - } + new Object[]{sort}) ); } @@ -337,58 +350,82 @@ public List findAll(@Nonnull final Sort sort) @Nonnull public Page findAll(@Nonnull final Pageable pageable) { + final PageableQueryExecutor pageableQuery = + new PageableQueryExecutor<>(this.copier, Criteria.createNoCriteria(), null); return this.storage.getReadWriteLock().read( - () -> { - final PageableQueryExecutor pageableQuery = - new PageableQueryExecutor<>(this.copier, Criteria.createNoCriteria(), null); - return pageableQuery.execute( + () -> + pageableQuery.execute( this.domainClass, this.storage.getEntityList(this.domainClass), - new Object[]{pageable}); - } + new Object[]{pageable}) ); } @Override public Optional findOne(final Example example) { - // TODO - return Optional.empty(); + final SingleOptionalQueryExecutor query = + new SingleOptionalQueryExecutor<>(this.copier, new CriteriaByExample<>((Example)example), null); + return this.storage.getReadWriteLock().read( + () -> + (Optional)query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); } @Override public Iterable findAll(final Example example) { - // TODO - return null; + final ListQueryExecutor query = + new ListQueryExecutor<>(this.copier, new CriteriaByExample<>(example)); + return this.storage.getReadWriteLock().read( + () -> (Iterable)query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); } @Override public Iterable findAll(final Example example, final Sort sort) { - // TODO - return null; + final ListQueryExecutor query = + new ListQueryExecutor<>(this.copier, new CriteriaByExample<>(example)); + return this.storage.getReadWriteLock().read( + () -> + (Iterable)query.execute( + this.domainClass, + this.storage.getEntityList(this.domainClass), + new Object[]{sort}) + ); } @Override public Page findAll(final Example example, final Pageable pageable) { - // TODO - return null; + final PageableQueryExecutor pageableQuery = + new PageableQueryExecutor<>(this.copier, new CriteriaByExample<>(example), null); + return this.storage.getReadWriteLock().read( + () -> + (Page)pageableQuery.execute( + this.domainClass, + this.storage.getEntityList(this.domainClass), + new Object[]{pageable}) + ); } @Override public long count(final Example example) { - // TODO - return 0; + final CountQueryExecutor query = new CountQueryExecutor<>(new CriteriaByExample<>(example)); + return this.storage.getReadWriteLock().read( + () -> query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); } @Override public boolean exists(final Example example) { - // TODO - return false; + final ExistsQueryExecutor query = new ExistsQueryExecutor<>(new CriteriaByExample<>(example)); + return this.storage.getReadWriteLock().read( + () -> query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); } @Override From 3af893fc09296ff42c4eb685d8f0e742fb9ecfc3 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 13 Jun 2024 15:11:58 +0200 Subject: [PATCH 5/7] Implemented FluentQueryByExample --- .../EclipseStoreFetchableFluentQuery.java | 141 ++++++++++++++++++ .../query/criteria/CriteriaByExample.java | 4 + .../support/SimpleEclipseStoreRepository.java | 14 +- .../query/by/example/QueryByExampleTest.java | 2 +- 4 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java new file mode 100644 index 00000000..7d960b44 --- /dev/null +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java @@ -0,0 +1,141 @@ +package software.xdev.spring.data.eclipse.store.repository.query.by.example; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery; + +import software.xdev.spring.data.eclipse.store.repository.EclipseStoreStorage; +import software.xdev.spring.data.eclipse.store.repository.query.criteria.CriteriaByExample; +import software.xdev.spring.data.eclipse.store.repository.query.executors.CountQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.ExistsQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.ListQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.PageableQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.query.executors.SingleQueryExecutor; +import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier; + + +/** + * Needed to support {@link QueryByExampleExecutor}. + */ +public class EclipseStoreFetchableFluentQuery implements FluentQuery.FetchableFluentQuery +{ + private final WorkingCopier copier; + private final Example example; + private final Class domainClass; + private final EclipseStoreStorage storage; + private final Sort sort; + + public EclipseStoreFetchableFluentQuery( + final WorkingCopier copier, + final Example example, + final Class domainClass, + final EclipseStoreStorage storage, + final Sort sort + ) + { + this.copier = copier; + this.example = example; + this.domainClass = domainClass; + this.storage = storage; + this.sort = sort; + } + + @Override + public FetchableFluentQuery sortBy(final Sort sort) + { + return new EclipseStoreFetchableFluentQuery( + this.copier, + this.example, + this.domainClass, + this.storage, + sort + ); + } + + @Override + public FetchableFluentQuery as(final Class resultType) + { + throw new UnsupportedOperationException("The method as() is not yet supported"); + } + + @Override + public FetchableFluentQuery project(final Collection properties) + { + throw new UnsupportedOperationException("The method project() is not yet supported"); + } + + @Override + public S oneValue() + { + return this.firstValue(); + } + + @Override + public S firstValue() + { + final SingleQueryExecutor query = + new SingleQueryExecutor<>(this.copier, new CriteriaByExample<>((Example)this.example), this.sort); + return this.storage.getReadWriteLock().read( + () -> + (S)query.execute( + this.domainClass, + this.storage.getEntityList(this.domainClass), + new Object[]{this.sort}) + ); + } + + @Override + public List all() + { + final ListQueryExecutor query = + new ListQueryExecutor<>(this.copier, new CriteriaByExample<>(this.example)); + return this.storage.getReadWriteLock().read( + () -> (List)query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), new Object[]{ + this.sort}) + ); + } + + @Override + public Page page(final Pageable pageable) + { + final PageableQueryExecutor pageableQuery = + new PageableQueryExecutor<>(this.copier, new CriteriaByExample<>(this.example), this.sort); + return this.storage.getReadWriteLock().read( + () -> + (Page)pageableQuery.execute( + this.domainClass, + this.storage.getEntityList(this.domainClass), + new Object[]{pageable, this.sort}) + ); + } + + @Override + public Stream stream() + { + return this.all().stream(); + } + + @Override + public long count() + { + final CountQueryExecutor query = new CountQueryExecutor<>(new CriteriaByExample<>(this.example)); + return this.storage.getReadWriteLock().read( + () -> query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); + } + + @Override + public boolean exists() + { + final ExistsQueryExecutor query = new ExistsQueryExecutor<>(new CriteriaByExample<>(this.example)); + return this.storage.getReadWriteLock().read( + () -> query.execute(this.domainClass, this.storage.getEntityList(this.domainClass), null) + ); + } +} diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java index dd139c20..aa77a0fe 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java @@ -25,11 +25,15 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.repository.query.QueryByExampleExecutor; import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper; import software.xdev.spring.data.eclipse.store.repository.query.ReflectedField; +/** + * Creates a criteria from {@link Example}s. Needed to implement {@link QueryByExampleExecutor}. + */ public class CriteriaByExample implements Criteria { private final Predicate predicate; diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java index 16c0abed..0df1fd21 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java @@ -44,6 +44,7 @@ import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStorePagingAndSortingRepositoryRepository; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreQueryByExampleExecutor; import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreRepository; +import software.xdev.spring.data.eclipse.store.repository.query.by.example.EclipseStoreFetchableFluentQuery; import software.xdev.spring.data.eclipse.store.repository.query.criteria.Criteria; import software.xdev.spring.data.eclipse.store.repository.query.criteria.CriteriaByExample; import software.xdev.spring.data.eclipse.store.repository.query.executors.CountQueryExecutor; @@ -433,7 +434,16 @@ public R findBy( final Example example, final Function, R> queryFunction) { - // TODO - return null; + final EclipseStoreFetchableFluentQuery query = new EclipseStoreFetchableFluentQuery<>( + this.copier, + example, + this.domainClass, + this.storage, + null + ); + + return this.storage.getReadWriteLock().read( + () -> queryFunction.apply(query) + ); } } diff --git a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java index a72cb1c6..cf1e867e 100644 --- a/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java +++ b/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java @@ -222,7 +222,7 @@ void exampleMatcherStartsWithName() } @Test - void exampleMatcheExactBalanceAndExactName() + void exampleMatcherExactBalanceAndExactName() { TestUtil.doBeforeAndAfterRestartOfDatastore( this.configuration, From 5c7ebcb59e8bf9b430a4087a3eade5fbd3a40867 Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Thu, 13 Jun 2024 15:15:40 +0200 Subject: [PATCH 6/7] Fix for Checkstyle --- .../with/id/FindByIdCustomerWithAutoIdBenchmark.java | 1 + .../repository/query/criteria/CriteriaByExample.java | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spring-data-eclipse-store-benchmark/src/main/java/software/xdev/spring/data/eclipse/store/benchmark/benchmarks/with/id/FindByIdCustomerWithAutoIdBenchmark.java b/spring-data-eclipse-store-benchmark/src/main/java/software/xdev/spring/data/eclipse/store/benchmark/benchmarks/with/id/FindByIdCustomerWithAutoIdBenchmark.java index 57a57634..82f8c63b 100644 --- a/spring-data-eclipse-store-benchmark/src/main/java/software/xdev/spring/data/eclipse/store/benchmark/benchmarks/with/id/FindByIdCustomerWithAutoIdBenchmark.java +++ b/spring-data-eclipse-store-benchmark/src/main/java/software/xdev/spring/data/eclipse/store/benchmark/benchmarks/with/id/FindByIdCustomerWithAutoIdBenchmark.java @@ -11,6 +11,7 @@ import software.xdev.spring.data.eclipse.store.repository.config.EclipseStoreClientConfiguration; +@SuppressWarnings("checkstyle:MagicNumber") public class FindByIdCustomerWithAutoIdBenchmark { public abstract static class ExistingCustomerSpringState extends SpringState diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java index aa77a0fe..e6b4d988 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/criteria/CriteriaByExample.java @@ -103,9 +103,9 @@ private Predicate createPredicateForSpecif final Optional transformedValue = specifier.getPropertyValueTransformer().apply(Optional.ofNullable(value)); - final ExampleMatcher.StringMatcher setOrDefaultMatcher = specifier.getStringMatcher() == null ? - example.getMatcher().getDefaultStringMatcher() : - specifier.getStringMatcher(); + final ExampleMatcher.StringMatcher setOrDefaultMatcher = specifier.getStringMatcher() == null + ? example.getMatcher().getDefaultStringMatcher() + : specifier.getStringMatcher(); switch(setOrDefaultMatcher) { @@ -159,8 +159,11 @@ private Predicate createPredicateForSpecif ) .matcher(valueAsString.get()).find(); } + default -> + { + return false; + } } - return true; }; } From b4235a7a31360efec9e2f5e7e1327f9c7d8682bc Mon Sep 17 00:00:00 2001 From: JohannesRabauer Date: Fri, 14 Jun 2024 06:59:52 +0200 Subject: [PATCH 7/7] Added license to one file --- .../example/EclipseStoreFetchableFluentQuery.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java index 7d960b44..4980434a 100644 --- a/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.java +++ b/spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/query/by/example/EclipseStoreFetchableFluentQuery.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.repository.query.by.example; import java.util.Collection;