diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e2e44fe8573..cd92b6cb330 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,6 +85,7 @@ tree-sitter-sql = "gh-pages-a" tree-sitter-hocon = "master-a" otel = "1.45.0" spring-boot = "3.4.1" +spring-data = "3.4.0" #Tools pmdTool = "6.55.0" @@ -275,3 +276,4 @@ opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporte spring-boot = { module = "org.springframework.boot:spring-boot", version.ref = "spring-boot" } spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure", version.ref = "spring-boot" } spring-boot-test = { module = "org.springframework.boot:spring-boot-test", version.ref = "spring-boot" } +spring-data-jdbc = { module = "org.springframework.data:spring-data-jdbc", version.ref = "spring-data"} diff --git a/modules/spring/spring-data-ignite/build.gradle b/modules/spring/spring-data-ignite/build.gradle new file mode 100644 index 00000000000..bcb62488001 --- /dev/null +++ b/modules/spring/spring-data-ignite/build.gradle @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +apply from: "$rootDir/buildscripts/java-core.gradle" +apply from: "$rootDir/buildscripts/publishing.gradle" +apply from: "$rootDir/buildscripts/java-junit5.gradle" + +description = "spring-data-ignite" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + implementation project(':ignite-client') + implementation project(":ignite-jdbc") + implementation libs.spring.boot + implementation libs.spring.boot.autoconfigure + implementation libs.spring.data.jdbc + + testImplementation libs.spring.boot.test + testImplementation libs.assertj.core + testImplementation testFixtures(project(':ignite-runner')) + testImplementation testFixtures(project(':ignite-core')) +} diff --git a/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialect.java b/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialect.java new file mode 100644 index 00000000000..d5b9f27a2f2 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialect.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data; + +import java.util.Collections; +import java.util.Set; +import org.springframework.data.relational.core.dialect.AbstractDialect; +import org.springframework.data.relational.core.dialect.ArrayColumns; +import org.springframework.data.relational.core.dialect.LimitClause; +import org.springframework.data.relational.core.dialect.LockClause; +import org.springframework.data.relational.core.sql.IdentifierProcessing; +import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; +import org.springframework.data.relational.core.sql.LockOptions; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Implementation of Ignite-specific dialect. + */ +public class IgniteDialect extends AbstractDialect { + + /** + * Singleton instance. + */ + public static final IgniteDialect INSTANCE = new IgniteDialect(); + + private IgniteDialect() {} + + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { + @Override + public String getLimit(long limit) { + return "LIMIT " + limit; + } + + @Override + public String getOffset(long offset) { + return "OFFSET " + offset; + } + + @Override + public String getLimitOffset(long limit, long offset) { + return String.format("OFFSET %d ROWS FETCH FIRST %d ROWS ONLY", offset, limit); + } + + @Override + public Position getClausePosition() { + return Position.AFTER_ORDER_BY; + } + }; + + static class IgniteArrayColumns implements ArrayColumns { + @Override + public boolean isSupported() { + return true; + } + + @Override + public Class getArrayType(Class userType) { + Assert.notNull(userType, "Array component type must not be null"); + + return ClassUtils.resolvePrimitiveIfNecessary(userType); + } + } + + @Override + public LimitClause limit() { + return LIMIT_CLAUSE; + } + + static final LockClause LOCK_CLAUSE = new LockClause() { + + @Override + public String getLock(LockOptions lockOptions) { + return ""; + } + + @Override + public Position getClausePosition() { + return Position.AFTER_ORDER_BY; + } + }; + + @Override + public LockClause lock() { + return LOCK_CLAUSE; + } + + private final IgniteArrayColumns arrayColumns = new IgniteArrayColumns(); + + @Override + public ArrayColumns getArraySupport() { + return arrayColumns; + } + + @Override + public IdentifierProcessing getIdentifierProcessing() { + return IdentifierProcessing.create(Quoting.ANSI, LetterCasing.UPPER_CASE); + } + + @Override + public Set> simpleTypes() { + return Collections.emptySet(); + } + + @Override + public boolean supportsSingleQueryLoading() { + return false; + } +} diff --git a/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialectProvider.java b/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialectProvider.java new file mode 100644 index 00000000000..900aed50c93 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/main/java/org/apache/ignite/data/IgniteDialectProvider.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data; + +import java.util.Optional; +import org.springframework.data.jdbc.repository.config.DialectResolver; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.jdbc.core.JdbcOperations; + +/** + * Provider for Ignite-specific dialect. + */ +public class IgniteDialectProvider implements DialectResolver.JdbcDialectProvider { + + @Override public Optional getDialect(JdbcOperations operations) { + return Optional.of(IgniteDialect.INSTANCE); + } +} diff --git a/modules/spring/spring-data-ignite/src/main/resources/META-INF/spring.factories b/modules/spring/spring-data-ignite/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..ee5bcab7a48 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=org.apache.ignite.data.IgniteDialectProvider diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/SpringDataJdbcTest.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/SpringDataJdbcTest.java new file mode 100644 index 00000000000..669233e5e0c --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/SpringDataJdbcTest.java @@ -0,0 +1,815 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import org.apache.ignite.data.repository.Person; +import org.apache.ignite.data.repository.Person.Direction; +import org.apache.ignite.data.repository.PersonRepository; +import org.apache.ignite.data.repository.Root; +import org.apache.ignite.data.repository.RootRepository; +import org.apache.ignite.internal.Cluster; +import org.apache.ignite.internal.ClusterConfiguration; +import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; +import org.apache.ignite.internal.testframework.WorkDirectory; +import org.apache.ignite.internal.testframework.WorkDirectoryExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtendWith; +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.boot.test.context.SpringBootTest; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.Limit; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Window; +import org.springframework.data.jdbc.core.mapping.AggregateReference; +import org.springframework.data.support.WindowIterator; +import org.springframework.data.util.Streamable; + +/** + * This is a subset of Spring Data JDBC tests adapted from + * + * Spring Data JDBC repo. + */ +@SpringBootTest(classes = TestApplication.class) +@ExtendWith(WorkDirectoryExtension.class) +public class SpringDataJdbcTest extends BaseIgniteAbstractTest { + + @WorkDirectory + private static Path workDir; + private static Cluster cluster; + + @Autowired + PersonRepository repository; + + @Autowired + RootRepository rootRepository; + + @BeforeAll + static void setUp(TestInfo testInfo) { + ClusterConfiguration clusterConfiguration = ClusterConfiguration.builder(testInfo, workDir).build(); + + cluster = new Cluster(clusterConfiguration); + cluster.startAndInit(1); + + cluster.aliveNode().sql().execute(null, "CREATE TABLE IF NOT EXISTS Person (" + + " id INT," + + " name VARCHAR," + + " flag BOOLEAN," + + " ref BIGINT," + + " direction VARCHAR," + + " Primary key(id)" + + ");"); + + cluster.aliveNode().sql().execute(null, "CREATE TABLE IF NOT EXISTS ROOT (" + + " ID BIGINT PRIMARY KEY," + + " NAME VARCHAR(100)" + + ");"); + cluster.aliveNode().sql().execute(null, "CREATE TABLE IF NOT EXISTS INTERMEDIATE (" + + " ID BIGINT PRIMARY KEY," + + " NAME VARCHAR(100)," + + " ROOT BIGINT," + + " ROOT_ID BIGINT," + + " ROOT_KEY INTEGER" + + ");"); + cluster.aliveNode().sql().execute(null, "CREATE TABLE IF NOT EXISTS LEAF (" + + " ID BIGINT PRIMARY KEY," + + " NAME VARCHAR(100)," + + " INTERMEDIATE BIGINT," + + " INTERMEDIATE_ID BIGINT," + + " INTERMEDIATE_KEY INTEGER" + + ");"); + } + + @BeforeEach + void setupEach() { + repository.deleteAll(); + rootRepository.deleteAll(); + } + + @Test + public void savesAnEntity() { + repository.save(Person.create()); + + assertThat(repository.count()).isEqualTo(1); + } + + @Test + public void saveAndLoadAnEntity() { + Person p1 = repository.save(Person.create()); + + assertThat(repository.findById(p1.getId())).hasValueSatisfying(it -> { + + assertThat(it.getId()).isEqualTo(p1.getId()); + assertThat(it.getName()).isEqualTo(p1.getName()); + }); + } + + @Test + public void insertsManyEntities() { + Person p1 = Person.create(); + Person p2 = Person.create(); + + repository.saveAll(asList(p1, p2)); + + assertThat(repository.findAll()) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p1.getId(), p2.getId()); + } + + @Test + public void existsReturnsTrueIffEntityExists() { + Person p1 = repository.save(Person.create()); + + assertThat(repository.existsById(p1.getId())).isTrue(); + assertThat(repository.existsById(p1.getId() + 1)).isFalse(); + } + + @Test + public void findAllFindsAllEntities() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + + Iterable all = repository.findAll(); + + assertThat(all) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p1.getId(), p2.getId()); + } + + @Test + public void findAllFindsAllSpecifiedEntities() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + + assertThat(repository.findAllById(asList(p1.getId(), p2.getId()))) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p1.getId(), p2.getId()); + } + + @Test + public void countsEntities() { + repository.save(Person.create()); + repository.save(Person.create()); + repository.save(Person.create()); + + assertThat(repository.count()).isEqualTo(3L); + } + + @Test + public void deleteById() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + Person p3 = repository.save(Person.create()); + + repository.deleteById(p2.getId()); + + assertThat(repository.findAll()) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p1.getId(), p3.getId()); + } + + @Test + public void deleteByEntity() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + Person p3 = repository.save(Person.create()); + + repository.delete(p1); + + assertThat(repository.findAll()) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p2.getId(), p3.getId()); + } + + @Test + public void deleteByList() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + Person p3 = repository.save(Person.create()); + + repository.deleteAll(asList(p1, p3)); + + assertThat(repository.findAll()) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p2.getId()); + } + + @Test + public void deleteByIdList() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + Person p3 = repository.save(Person.create()); + + repository.deleteAllById(asList(p1.getId(), p3.getId())); + + assertThat(repository.findAll()) + .extracting(Person::getId) + .containsExactlyInAnyOrder(p2.getId()); + } + + @Test + public void deleteAll() { + repository.save(Person.create()); + repository.save(Person.create()); + repository.save(Person.create()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + @Test + public void update() { + Person p1 = repository.save(Person.create()); + + p1.setName("something else"); + p1.setNew(false); + Person saved = repository.save(p1); + + assertThat(repository.findById(p1.getId())).hasValueSatisfying(it -> { + assertThat(it.getName()).isEqualTo(saved.getName()); + }); + } + + @Test + public void updateMany() { + Person p1 = repository.save(Person.create()); + Person p2 = repository.save(Person.create()); + + p1.setName("something else"); + p1.setNew(false); + p2.setName("others Name"); + p2.setNew(false); + + repository.saveAll(asList(p1, p2)); + + assertThat(repository.findAll()) + .extracting(Person::getName) + .containsExactlyInAnyOrder(p1.getName(), p2.getName()); + } + + @Test + void insertsOrUpdatesManyEntities() { + Person p1 = repository.save(Person.create()); + p1.setName("something else"); + p1.setNew(false); + Person p2 = Person.create(); + p2.setName("others name"); + repository.saveAll(asList(p2, p1)); + + assertThat(repository.findAll()) + .extracting(Person::getName) + .containsExactlyInAnyOrder(p1.getName(), p2.getName()); + } + + @Test + public void findByIdReturnsEmptyWhenNoneFound() { + // NOT saving anything, so DB is empty + + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test + public void existsWorksAsExpected() { + Person p1 = repository.save(Person.create()); + + assertSoftly(softly -> { + + softly.assertThat(repository.existsByName(p1.getName())) + .describedAs("Positive") + .isTrue(); + softly.assertThat(repository.existsByName("not an existing name")) + .describedAs("Positive") + .isFalse(); + }); + } + + @Test + public void existsInWorksAsExpected() { + Person p1 = repository.save(Person.create()); + + assertSoftly(softly -> { + + softly.assertThat(repository.existsByNameIn(p1.getName())) + .describedAs("Positive") + .isTrue(); + softly.assertThat(repository.existsByNameIn()) + .describedAs("Negative") + .isFalse(); + }); + } + + @Test + public void existsNotInWorksAsExpected() { + Person dummy = repository.save(Person.create()); + + assertSoftly(softly -> { + + softly.assertThat(repository.existsByNameNotIn(dummy.getName())) + .describedAs("Positive") + .isFalse(); + softly.assertThat(repository.existsByNameNotIn()) + .describedAs("Negative") + .isTrue(); + }); + } + + @Test + public void countByQueryDerivation() { + Person p1 = Person.create(); + Person p2 = Person.create(); + p2.setName("other"); + Person three = Person.create(); + + repository.saveAll(asList(p1, p2, three)); + + assertThat(repository.countByName(p1.getName())).isEqualTo(2); + } + + @Test + public void pageByNameShouldReturnCorrectResult() { + repository.saveAll(asList(Person.create("a1"), Person.create("a2"), Person.create("a3"))); + + Page page = repository.findPageByNameContains("a", PageRequest.of(0, 5)); + + assertThat(page.getContent()).hasSize(3); + assertThat(page.getTotalElements()).isEqualTo(3); + assertThat(page.getTotalPages()).isEqualTo(1); + + assertThat(repository.findPageByNameContains("a", PageRequest.of(0, 2)).getContent()).hasSize(2); + assertThat(repository.findPageByNameContains("a", PageRequest.of(1, 2)).getContent()).hasSize(1); + } + + @Test + public void selectWithLimitShouldReturnCorrectResult() { + repository.saveAll(asList(Person.create("a1"), Person.create("a2"), Person.create("a3"))); + + List page = repository.findByNameContains("a", Limit.of(3)); + assertThat(page).hasSize(3); + + assertThat(repository.findByNameContains("a", Limit.of(2))).hasSize(2); + assertThat(repository.findByNameContains("a", Limit.unlimited())).hasSize(3); + } + + @Test + public void sliceByNameShouldReturnCorrectResult() { + repository.saveAll(asList(Person.create("a1"), Person.create("a2"), Person.create("a3"))); + + Slice slice = repository.findSliceByNameContains("a", PageRequest.of(0, 5)); + + assertThat(slice.getContent()).hasSize(3); + assertThat(slice.hasNext()).isFalse(); + + slice = repository.findSliceByNameContains("a", PageRequest.of(0, 2)); + + assertThat(slice.getContent()).hasSize(2); + assertThat(slice.hasNext()).isTrue(); + } + + @Test + void derivedQueryWithBooleanLiteralFindsCorrectValues() { + repository.save(Person.create()); + Person p1 = Person.create(); + p1.setFlag(true); + p1 = repository.save(p1); + + List result = repository.findByFlagTrue(); + + assertThat(result).extracting(Person::getId).containsExactly(p1.getId()); + } + + @Test + void queryBySimpleReference() { + Person p1 = repository.save(Person.create()); + Person p2 = Person.create(); + p2.setRef(AggregateReference.to(p1.getId())); + p2 = repository.save(p2); + + List result = repository.findByRef(p1.getId().intValue()); + + assertThat(result).extracting(Person::getId).containsExactly(p2.getId()); + } + + @Test + void queryByAggregateReference() { + Person p1 = repository.save(Person.create()); + Person p2 = Person.create(); + p2.setRef(AggregateReference.to(p1.getId())); + p2 = repository.save(p2); + + List result = repository.findByRef(p2.getRef()); + + assertThat(result).extracting(Person::getId).containsExactly(p2.getId()); + } + + + @Test + void queryByEnumTypeIn() { + Person p1 = Person.create("p1"); + p1.setDirection(Direction.LEFT); + Person p2 = Person.create("p2"); + p2.setDirection(Direction.CENTER); + Person p3 = Person.create("p3"); + p3.setDirection(Direction.RIGHT); + repository.saveAll(asList(p1, p2, p3)); + + assertThat(repository.findByEnumTypeIn(Set.of(Direction.LEFT, Direction.RIGHT))) + .extracting(Person::getDirection).containsExactlyInAnyOrder(Direction.LEFT, Direction.RIGHT); + } + + @Test + void queryByEnumTypeEqual() { + Person p1 = Person.create("p1"); + p1.setDirection(Direction.LEFT); + Person p2 = Person.create("p2"); + p2.setDirection(Direction.CENTER); + Person p3 = Person.create("p3"); + p3.setDirection(Direction.RIGHT); + repository.saveAll(asList(p1, p2, p3)); + + assertThat(repository.findByEnumType(Direction.CENTER)).extracting(Person::getDirection) + .containsExactlyInAnyOrder(Direction.CENTER); + } + + @Test + void manyInsertsWithNestedEntities() { + Root root1 = Root.create("root1"); + Root root2 = Root.create("root2"); + + List savedRoots = rootRepository.saveAll(asList(root1, root2)); + + List reloadedRoots = rootRepository.findAllByOrderByIdAsc(); + assertThat(reloadedRoots).isEqualTo(savedRoots); + assertThat(reloadedRoots).hasSize(2); + } + + @Test + void findOneByExampleShouldGetOne() { + Person p1 = Person.create(); + p1.setFlag(true); + repository.save(p1); + + Person p2 = Person.create(); + p2.setName("Diego"); + repository.save(p2); + + Example diegoExample = Example.of(p2); + Optional foundExampleDiego = repository.findOne(diegoExample); + + assertThat(foundExampleDiego.get().getName()).isEqualTo("Diego"); + } + + @Test + void findOneByExampleMultipleMatchShouldGetOne() { + repository.save(Person.create()); + repository.save(Person.create()); + + Example example = Example.of(new Person()); + + assertThatThrownBy(() -> repository.findOne(example)).isInstanceOf(IncorrectResultSizeDataAccessException.class) + .hasMessageContaining("expected 1, actual 2"); + } + + @Test + void findOneByExampleShouldGetNone() { + Person p1 = Person.create(); + p1.setFlag(true); + repository.save(p1); + + Example diegoExample = Example.of(Person.create("NotExisting")); + + Optional foundExampleDiego = repository.findOne(diegoExample); + + assertThat(foundExampleDiego).isNotPresent(); + } + + @Test + void findAllByExampleShouldGetOne() { + Person p1 = Person.create(); + p1.setFlag(true); + repository.save(p1); + + Person p2 = Person.create(); + p2.setName("Diego"); + repository.save(p2); + + Example example = Example.of(Person.create(null, "Diego")); + + Iterable allFound = repository.findAll(example); + + assertThat(allFound).extracting(Person::getName) + .containsExactly(example.getProbe().getName()); + } + + @Test + void findAllByExampleMultipleMatchShouldGetOne() { + repository.save(Person.create()); + repository.save(Person.create()); + + Example example = Example.of(new Person(null, "Name")); + + Iterable allFound = repository.findAll(example); + + assertThat(allFound) + .hasSize(2) + .extracting(Person::getName) + .containsOnly(example.getProbe().getName()); + } + + @Test + void findAllByExampleShouldGetNone() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Example example = Example.of(Person.create("NotExisting")); + + Iterable allFound = repository.findAll(example); + + assertThat(allFound).isEmpty(); + } + + @Test + void findAllByExamplePageableShouldGetOne() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Person p2 = Person.create(); + p2.setName("Diego"); + + repository.save(p2); + + Example example = Example.of(p2); + Pageable pageRequest = PageRequest.of(0, 10); + + Iterable allFound = repository.findAll(example, pageRequest); + + assertThat(allFound).extracting(Person::getName) + .containsExactly(example.getProbe().getName()); + } + + @Test + void findAllByExamplePageableMultipleMatchShouldGetOne() { + repository.save(Person.create()); + repository.save(Person.create()); + + Example example = Example.of(new Person(null, "Name")); + Pageable pageRequest = PageRequest.of(0, 10); + + Iterable allFound = repository.findAll(example, pageRequest); + + assertThat(allFound) + .hasSize(2) + .extracting(Person::getName) + .containsOnly(example.getProbe().getName()); + } + + @Test + void findAllByExamplePageableShouldGetNone() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Example example = Example.of(Person.create("NotExisting")); + Pageable pageRequest = PageRequest.of(0, 10); + + Iterable allFound = repository.findAll(example, pageRequest); + + assertThat(allFound).isEmpty(); + } + + @Test + void findAllByExamplePageableOutsidePageShouldGetNone() { + repository.save(Person.create()); + repository.save(Person.create()); + + Example example = Example.of(Person.create()); + Pageable pageRequest = PageRequest.of(10, 10); + + Iterable allFound = repository.findAll(example, pageRequest); + + assertThat(allFound) + .isNotNull() + .isEmpty(); + } + + @ParameterizedTest + @MethodSource("findAllByExamplePageableSource") + void findAllByExamplePageable(Pageable pageRequest, int size, int totalPages, List notContains) { + for (int i = 0; i < 100; i++) { + Person p = Person.create(); + p.setFlag(true); + p.setName("" + i); + + repository.save(p); + } + + Person p = Person.create(); + p.setId(null); + p.setName(null); + p.setFlag(true); + + Example example = Example.of(p); + + Page allFound = repository.findAll(example, pageRequest); + + // page has correct size + assertThat(allFound) + .isNotNull() + .hasSize(size); + + // correct number of total + assertThat(allFound.getTotalElements()).isEqualTo(100); + + assertThat(allFound.getTotalPages()).isEqualTo(totalPages); + + if (!notContains.isEmpty()) { + assertThat(allFound) + .extracting(Person::getName) + .doesNotContain(notContains.toArray(new String[0])); + } + } + + static Stream findAllByExamplePageableSource() { + return Stream.of( + Arguments.of(PageRequest.of(0, 3), 3, 34, asList("3", "4", "100")), + Arguments.of(PageRequest.of(1, 10), 10, 10, asList("9", "20", "30")), + Arguments.of(PageRequest.of(2, 10), 10, 10, asList("1", "2", "3")), + Arguments.of(PageRequest.of(33, 3), 1, 34, Collections.emptyList()), + Arguments.of(PageRequest.of(36, 3), 0, 34, Collections.emptyList()), + Arguments.of(PageRequest.of(0, 10000), 100, 1, Collections.emptyList()), + Arguments.of(PageRequest.of(100, 10000), 0, 1, Collections.emptyList()) + ); + } + + @Test + void existsByExampleShouldGetOne() { + Person p1 = Person.create(); + p1.setFlag(true); + repository.save(p1); + + Person p2 = Person.create(); + p2.setName("Diego"); + repository.save(p2); + + Example example = Example.of(Person.create(null, "Diego")); + + boolean exists = repository.exists(example); + + assertThat(exists).isTrue(); + } + + @Test + void existsByExampleMultipleMatchShouldGetOne() { + Person p1 = Person.create(); + repository.save(p1); + + Person p2 = Person.create(); + repository.save(p2); + + Example example = Example.of(new Person()); + + boolean exists = repository.exists(example); + assertThat(exists).isTrue(); + } + + @Test + void existsByExampleShouldGetNone() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Example example = Example.of(Person.create("NotExisting")); + + boolean exists = repository.exists(example); + + assertThat(exists).isFalse(); + } + + @Test + void countByExampleShouldGetOne() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Person p2 = Person.create(); + p2.setName("Diego"); + + repository.save(p2); + + Example example = Example.of(p2); + + long count = repository.count(example); + + assertThat(count).isOne(); + } + + @Test + void countByExampleMultipleMatchShouldGetOne() { + Person p1 = Person.create(); + repository.save(p1); + + Person p2 = Person.create(); + repository.save(p2); + + Example example = Example.of(new Person()); + + long count = repository.count(example); + assertThat(count).isEqualTo(2); + } + + @Test + void countByExampleShouldGetNone() { + Person p1 = Person.create(); + p1.setFlag(true); + + repository.save(p1); + + Example example = Example.of(Person.create("NotExisting")); + + long count = repository.count(example); + + assertThat(count).isNotNull().isZero(); + } + + @Test + void findByScrollPosition() { + Person p1 = Person.create("p1"); + p1.setFlag(true); + + Person p2 = Person.create("p2"); + p2.setFlag(true); + + Person p3 = Person.create("p3"); + p3.setFlag(true); + + Person p4 = Person.create("p4"); + p4.setFlag(false); + + repository.saveAll(asList(p1, p2, p3, p4)); + + Example example = Example.of(p1, ExampleMatcher.matching().withIgnorePaths("name", "id")); + + Window first = repository.findBy(example, q -> q.limit(2).sortBy(Sort.by("name"))) + .scroll(ScrollPosition.offset()); + assertThat(first.map(Person::getName)).containsExactly("p1", "p2"); + + Window second = repository.findBy(example, q -> q.limit(2).sortBy(Sort.by("name"))) + .scroll(ScrollPosition.offset(1)); + assertThat(second.map(Person::getName)).containsExactly("p3"); + + WindowIterator iterator = WindowIterator.of( + scrollPosition -> repository.findBy(example, q -> q.limit(2).sortBy(Sort.by("name")).scroll(scrollPosition))) + .startingAt(ScrollPosition.offset()); + + List result = Streamable.of(() -> iterator).stream().map(Person::getName).toList(); + + assertThat(result).hasSize(3).containsExactly("p1", "p2", "p3"); + } +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/TestApplication.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/TestApplication.java new file mode 100644 index 00000000000..becfa6e545d --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/TestApplication.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; + +/** + * Test Application. + */ +@EnableJdbcRepositories +@SpringBootApplication +public class TestApplication { + +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Intermediate.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Intermediate.java new file mode 100644 index 00000000000..151e2bc7874 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Intermediate.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.List; +import java.util.Objects; +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; +import org.springframework.data.relational.core.mapping.MappedCollection; + +/** + * A class to be a part of nested entities. Root -> Intermediate -> Leaf. + */ +public class Intermediate implements Persistable { + + @Id + private Long id; + private String name; + private Leaf leaf; + @MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") private List leaves; + + public Intermediate() {} + + /** + * Constructor. + * + * @param id Id. + * @param name Name. + * @param leaf Leaf. + * @param leaves list of leaves. + */ + public Intermediate(Long id, String name, Leaf leaf, List leaves) { + this.id = id; + this.name = name; + this.leaf = leaf; + this.leaves = leaves; + } + + @Override + public Long getId() { + return this.id; + } + + @Override + public boolean isNew() { + return true; + } + + public String getName() { + return this.name; + } + + public Leaf getLeaf() { + return this.leaf; + } + + public List getLeaves() { + return this.leaves; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Intermediate that = (Intermediate) o; + return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(leaf, that.leaf) + && Objects.equals(leaves, that.leaves); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, leaf, leaves); + } + + private static long idCounter = 1; + + public static Intermediate createWithLeaf(String namePrefix) { + return new Intermediate(idCounter++, namePrefix + "Intermediate", Leaf.create(namePrefix), emptyList()); + } + + public static Intermediate createWithLeaves(String namePrefix) { + return new Intermediate(idCounter++, namePrefix + "Intermediate", null, singletonList(Leaf.create(namePrefix))); + } +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Leaf.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Leaf.java new file mode 100644 index 00000000000..07e233d3c12 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Leaf.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import java.util.Objects; +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; + +/** + * A class to be a part of nested entities. Root -> Intermediate -> Leaf. + */ +public class Leaf implements Persistable { + + @Id + private Long id; + private String name; + + public Leaf(Long id, String name) { + this.id = id; + this.name = name; + } + + public Leaf() { + + } + + @Override + public Long getId() { + return this.id; + } + + @Override + public boolean isNew() { + return true; + } + + public String getName() { + return this.name; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Leaf leaf = (Leaf) o; + return Objects.equals(id, leaf.id) && Objects.equals(name, leaf.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + private static long idCounter = 1; + + public static Leaf create(String namePrefix) { + return new Leaf(idCounter++, namePrefix + "QualifiedLeaf"); + } +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Person.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Person.java new file mode 100644 index 00000000000..3630bbeefcd --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Person.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.domain.Persistable; +import org.springframework.data.jdbc.core.mapping.AggregateReference; + +/** + * Test class. + */ +public class Person implements Persistable { + @Id + private Long id; + + private String name; + + private Boolean flag; + + private Direction direction; + + @Transient + private boolean isNew = true; + + AggregateReference ref; + + public Person() {} + + /** + * Constructor. + * + * @param id Id. + * @param name Name. + */ + public Person(Long id, String name) { + this.id = id; + this.name = name; + this.direction = Direction.LEFT; + this.flag = false; + this.ref = new AggregateReference() { + @Override + public Long getId() { + return 0L; + } + }; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return isNew; + } + + public void setNew(boolean newValue) { + isNew = newValue; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getFlag() { + return flag; + } + + public void setFlag(Boolean flag) { + this.flag = flag; + } + + public AggregateReference getRef() { + return ref; + } + + public void setRef(AggregateReference ref) { + this.ref = ref; + } + + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + /** + * Enum for testing purposes. Doesn't have semantic meaning. + */ + public enum Direction { + LEFT, CENTER, RIGHT + } + + private static long idCounter = 1; + + /** + * Constructor. + * Id will be generated. + */ + public static Person create() { + return new Person(idCounter++, "Name"); + } + + /** + * Constructor. + * Id will be generated. + * + * @param name Name. + */ + public static Person create(String name) { + Person person = new Person(idCounter++, name); + person.setName(name); + return person; + } + + /** + * Constructor. + * + * @param id Id. + * @param name Name. + */ + public static Person create(Long id, String name) { + Person person = new Person(id, name); + person.setName(name); + return person; + } +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/PersonRepository.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/PersonRepository.java new file mode 100644 index 00000000000..a7805a0112e --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/PersonRepository.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import java.util.List; +import java.util.Set; +import org.apache.ignite.data.repository.Person.Direction; +import org.springframework.data.domain.Limit; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jdbc.core.mapping.AggregateReference; +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.query.QueryByExampleExecutor; +import org.springframework.stereotype.Repository; + +/** + * Repository for {@link Person}. + */ +@Repository +public interface PersonRepository extends CrudRepository, QueryByExampleExecutor { + + boolean existsByName(String name); + + boolean existsByNameIn(String... names); + + boolean existsByNameNotIn(String... names); + + int countByName(String name); + + Page findPageByNameContains(String name, Pageable pageable); + + List findByNameContains(String name, Limit limit); + + Slice findSliceByNameContains(String name, Pageable pageable); + + List findByFlagTrue(); + + List findByRef(int ref); + + List findByRef(AggregateReference ref); + + @Query("SELECT * FROM PERSON WHERE DIRECTION IN (:directions)") + List findByEnumTypeIn(@Param("directions") Set directions); + + @Query("SELECT * FROM PERSON WHERE DIRECTION = :direction") + List findByEnumType(@Param("direction")Direction direction); +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Root.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Root.java new file mode 100644 index 00000000000..471a7a6f1e6 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/Root.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import static java.util.Collections.singletonList; + +import java.util.List; +import java.util.Objects; +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; +import org.springframework.data.relational.core.mapping.MappedCollection; + +/** + * A class to be a part of nested entities. Root -> Intermediate -> Leaf. + */ +public class Root implements Persistable { + + @Id + private Long id; + private String name; + private Intermediate intermediate; + @MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") private List intermediates; + + public Root() {} + + private Root(Long id, String name, Intermediate intermediate, List intermediates) { + this.id = id; + this.name = name; + this.intermediate = intermediate; + this.intermediates = intermediates; + } + + @Override + public Long getId() { + return this.id; + } + + @Override + public boolean isNew() { + return true; + } + + public String getName() { + return this.name; + } + + public Intermediate getIntermediate() { + return this.intermediate; + } + + public List getIntermediates() { + return this.intermediates; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Root root = (Root) o; + return Objects.equals(id, root.id) && Objects.equals(name, root.name) && Objects.equals(intermediate, + root.intermediate) && Objects.equals(intermediates, root.intermediates); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, intermediate, intermediates); + } + + private static long idCounter = 1; + + /** + * Creates {@link Root}. + * + * @param namePrefix Name prefix. + */ + public static Root create(String namePrefix) { + return new Root(idCounter++, namePrefix, + Intermediate.createWithLeaf(namePrefix), + singletonList(Intermediate.createWithLeaves(namePrefix))); + } +} diff --git a/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/RootRepository.java b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/RootRepository.java new file mode 100644 index 00000000000..b031877d9a5 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/java/org/apache/ignite/data/repository/RootRepository.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.data.repository; + +import java.util.List; +import org.springframework.data.repository.ListCrudRepository; + +/** + * Repository for {@link Root}. + */ +public interface RootRepository extends ListCrudRepository { + List findAllByOrderByIdAsc(); +} diff --git a/modules/spring/spring-data-ignite/src/test/resources/META-INF/spring.factories b/modules/spring/spring-data-ignite/src/test/resources/META-INF/spring.factories new file mode 100644 index 00000000000..ee5bcab7a48 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=org.apache.ignite.data.IgniteDialectProvider diff --git a/modules/spring/spring-data-ignite/src/test/resources/application.properties b/modules/spring/spring-data-ignite/src/test/resources/application.properties new file mode 100644 index 00000000000..0339ab8d9a4 --- /dev/null +++ b/modules/spring/spring-data-ignite/src/test/resources/application.properties @@ -0,0 +1,4 @@ +ignite.client.addresses=127.0.0.1:10800 + +spring.datasource.url=jdbc:ignite:thin://localhost +spring.datasource.driver-class-name=org.apache.ignite.jdbc.IgniteJdbcDriver diff --git a/settings.gradle b/settings.gradle index a286ef494f2..31b079d83ca 100644 --- a/settings.gradle +++ b/settings.gradle @@ -180,10 +180,12 @@ if (JavaVersion.current() >= JavaVersion.VERSION_17) { include(':spring-boot-ignite-client-autoconfigure') include(':spring-boot-starter-ignite-client') include(':spring-boot-starter-ignite-client-example') + include(':spring-data-ignite') project(":spring-boot-starter-ignite-client-example").projectDir = file('modules/spring/spring-boot-starter-ignite-client-example') project(":spring-boot-ignite-client-autoconfigure").projectDir = file('modules/spring/spring-boot-ignite-client-autoconfigure') project(":spring-boot-starter-ignite-client").projectDir = file('modules/spring/spring-boot-starter-ignite-client') + project(":spring-data-ignite").projectDir = file('modules/spring/spring-data-ignite') } ext.isCiServer = System.getenv().containsKey("IGNITE_CI")