diff --git a/spring-data-neo4j/pom.xml b/spring-data-neo4j/pom.xml index da8b69fad..ad36913a8 100644 --- a/spring-data-neo4j/pom.xml +++ b/spring-data-neo4j/pom.xml @@ -125,7 +125,7 @@ org.jetbrains.kotlin - kotlin-stdlib + kotlin-stdlib-jdk8 true diff --git a/spring-data-neo4j/src/main/java/org/neo4j/springframework/data/core/Neo4jClient.java b/spring-data-neo4j/src/main/java/org/neo4j/springframework/data/core/Neo4jClient.java index cd75eb2b4..25f4aa206 100644 --- a/spring-data-neo4j/src/main/java/org/neo4j/springframework/data/core/Neo4jClient.java +++ b/spring-data-neo4j/src/main/java/org/neo4j/springframework/data/core/Neo4jClient.java @@ -41,9 +41,6 @@ import org.springframework.lang.Nullable; /** * Definition of a modern Neo4j client. * - * TODO Create examples how to use the callbacks etc. with Springs TransactionTemplate to deal with rollbacks etc. - * TODO database selection - * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 @@ -51,6 +48,8 @@ import org.springframework.lang.Nullable; @API(status = API.Status.STABLE, since = "1.0") public interface Neo4jClient { + // TODO Create examples how to use the callbacks etc. with Springs TransactionTemplate to deal with rollbacks etc. + LogAccessor cypherLog = new LogAccessor(LogFactory.getLog("org.neo4j.springframework.data.cypher")); static Neo4jClient create(Driver driver) { diff --git a/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensions.kt b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensions.kt index 53b432672..40d828ebd 100644 --- a/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensions.kt +++ b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensions.kt @@ -18,13 +18,19 @@ */ package org.neo4j.springframework.data.core +import org.neo4j.driver.Record +import org.neo4j.driver.types.TypeSystem +import java.util.* +import java.util.function.BiFunction + /** * Extension for [Neo4jClient.RunnableSpec.in] providing an `inDatabase` alias since `in` is a reserved keyword in Kotlin. * * @author Michael J. Simons * @since 1.0 */ -fun Neo4jClient.RunnableSpec.inDatabase(targetDatabase: String): Neo4jClient.RunnableSpecTightToDatabase = `in`(targetDatabase) +fun Neo4jClient.RunnableSpec.inDatabase(targetDatabase: String): Neo4jClient.RunnableSpecTightToDatabase = + `in`(targetDatabase) /** * Extension for [Neo4jClient.OngoingDelegation.in] providing an `inDatabase` alias since `in` is a reserved keyword in Kotlin. @@ -32,4 +38,49 @@ fun Neo4jClient.RunnableSpec.inDatabase(targetDatabase: String): Neo4jClient.Run * @author Michael J. Simons * @since 1.0 */ -fun Neo4jClient.OngoingDelegation.inDatabase(targetDatabase: String): Neo4jClient.RunnableDelegation = `in`(targetDatabase) +fun Neo4jClient.OngoingDelegation.inDatabase(targetDatabase: String): Neo4jClient.RunnableDelegation = + `in`(targetDatabase) + +/** + * An implementation of a fetch spec that replaces Java's Optional with a nullable. + * @author Michael J. Simons + */ +class DelegatingFetchSpec(private val delegate: Neo4jClient.RecordFetchSpec, Collection, T>) : Neo4jClient.RecordFetchSpec, T> { + override fun one(): T? = delegate.one().orElse(null) + + override fun first(): T = delegate.first().orElse(null) + + override fun all(): Collection = delegate.all() +} + +/** + * An implementation of a mapping spec that replaces Java's Optional with a nullable. + * @author Michael J. Simons + */ +class DelegatingMappingSpec(private val delegate: Neo4jClient.MappingSpec, Collection, T>) : Neo4jClient.MappingSpec, T> { + override fun mappedBy(mappingFunction: BiFunction): Neo4jClient.RecordFetchSpec, T> = + DelegatingFetchSpec(delegate.mappedBy(mappingFunction)) + + override fun one(): T? = delegate.one().orElse(null) + + override fun first(): T = delegate.first().orElse(null) + + override fun all(): Collection = delegate.all() +} + +/** + * Extension for [Neo4jClient.RunnableSpecTightToDatabase.fetchAs] leveraging reified type parameters. + * @author Michael J. Simons + * @since 1.0 + */ +inline fun Neo4jClient.RunnableSpecTightToDatabase.fetchAs(): Neo4jClient.MappingSpec, T> + = DelegatingMappingSpec(fetchAs(T::class.java)) + +/** + * Extension for [Neo4jClient.RunnableSpecTightToDatabase.mappedBy] leveraging reified type parameters and removing + * the need for an explicit `fetchAs`. + * @author Michael J. Simons + * @since 1.0 + */ +inline fun Neo4jClient.RunnableSpecTightToDatabase.mappedBy(noinline mappingFunction: (TypeSystem, Record) -> T): Neo4jClient.RecordFetchSpec, T> + = DelegatingFetchSpec(fetchAs(T::class.java).mappedBy(mappingFunction)) diff --git a/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensions.kt b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensions.kt new file mode 100644 index 000000000..fc050ca3d --- /dev/null +++ b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensions.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.springframework.data.core + +import kotlin.reflect.KClass + +/** + * Helper class for [PreparedQuery.queryFor] that removes the need of adding `::class.java` manually. + * + * @author Michael J. Simons + * @since 1.0 + */ +class PreparedQueryFactory(val c: KClass) { + fun withCypherQuery(cypherQuery: String): PreparedQuery.OptionalBuildSteps = c.javaObjectType.let { PreparedQuery.queryFor(it) } + .withCypherQuery(cypherQuery) +} diff --git a/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensions.kt b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensions.kt index a7f1ab6bb..864d37e54 100644 --- a/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensions.kt +++ b/spring-data-neo4j/src/main/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensions.kt @@ -18,13 +18,19 @@ */ package org.neo4j.springframework.data.core +import org.neo4j.driver.Record +import org.neo4j.driver.types.TypeSystem +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + /** * Extension for [ReactiveNeo4jClient.ReactiveRunnableSpec.in] providing an `inDatabase` alias since `in` is a reserved keyword in Kotlin. * * @author Michael J. Simons * @since 1.0 */ -fun ReactiveNeo4jClient.ReactiveRunnableSpec.inDatabase(targetDatabase: String): ReactiveNeo4jClient.ReactiveRunnableSpecTightToDatabase = `in`(targetDatabase) +fun ReactiveNeo4jClient.ReactiveRunnableSpec.inDatabase(targetDatabase: String): ReactiveNeo4jClient.ReactiveRunnableSpecTightToDatabase = + `in`(targetDatabase) /** * Extension for [ReactiveNeo4jClient.OngoingReactiveDelegation.in] providing an `inDatabase` alias since `in` is a reserved keyword in Kotlin. @@ -32,4 +38,22 @@ fun ReactiveNeo4jClient.ReactiveRunnableSpec.inDatabase(targetDatabase: String): * @author Michael J. Simons * @since 1.0 */ -fun ReactiveNeo4jClient.OngoingReactiveDelegation.inDatabase(targetDatabase: String): ReactiveNeo4jClient.ReactiveRunnableDelegation = `in`(targetDatabase) +fun ReactiveNeo4jClient.OngoingReactiveDelegation.inDatabase(targetDatabase: String): ReactiveNeo4jClient.ReactiveRunnableDelegation + = `in`(targetDatabase) + +/** + * Extension for [ReactiveNeo4jClient.RunnableSpecTightToDatabase.fetchAs] leveraging reified type parameters. + * @author Michael J. Simons + * @since 1.0 + */ +inline fun ReactiveNeo4jClient.ReactiveRunnableSpecTightToDatabase.fetchAs(): Neo4jClient.MappingSpec, Flux, T> + = fetchAs(T::class.java) + +/** + * Extension for [ReactiveNeo4jClient.RunnableSpecTightToDatabase.mappedBy] leveraging reified type parameters and removing + * the need for an explicit `fetchAs`. + * @author Michael J. Simons + * @since 1.0 + */ +inline fun ReactiveNeo4jClient.ReactiveRunnableSpecTightToDatabase.mappedBy(noinline mappingFunction: (TypeSystem, Record) -> T): Neo4jClient.RecordFetchSpec, Flux, T> + = fetchAs(T::class.java).mappedBy(mappingFunction) diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java index d44348c71..d6437f94e 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java @@ -31,6 +31,7 @@ import org.neo4j.driver.Transaction; import org.neo4j.driver.Values; import org.neo4j.springframework.data.config.AbstractNeo4jConfig; import org.neo4j.springframework.data.integration.shared.KotlinPerson; +import org.neo4j.springframework.data.integration.shared.KotlinRepository; import org.neo4j.springframework.data.repository.config.EnableNeo4jRepositories; import org.neo4j.springframework.data.test.Neo4jExtension.Neo4jConnectionSupport; import org.neo4j.springframework.data.test.Neo4jIntegrationTest; diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensionsTest.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensionsTest.kt index 0c84724af..8949d4f5f 100644 --- a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensionsTest.kt +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/Neo4jClientExtensionsTest.kt @@ -46,4 +46,15 @@ class Neo4jClientExtensionsTest { verify(exactly = 1) { ongoingDelegation.`in`("foobar") } } -} \ No newline at end of file + + @Test + fun `RunnableSpecTightToDatabase#fetchAs() extension should call its Java counterpart`() { + + val runnableSpec = mockk(relaxed = true) + + val mappingSpec: Neo4jClient.RecordFetchSpec, String> = + runnableSpec.mappedBy { _, record -> "Foo" }; + + verify(exactly = 1) { runnableSpec.fetchAs(String::class.java) } + } +} diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensionsTest.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensionsTest.kt new file mode 100644 index 000000000..eb1014765 --- /dev/null +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/PreparedQueryExtensionsTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.springframework.data.core + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +/** + * @author Michael J. Simons + */ +class PreparedQueryExtensionsTest { + + @Test + fun `PreparedQueryFactory call its Java counterpart`() { + + val preparedQuery = PreparedQueryFactory(String::class) + .withCypherQuery("RETURN 'Hallo'"). + build(); + + assertThat(preparedQuery.resultType).isEqualTo(String::class.java) + } +} diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensionsTest.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensionsTest.kt index 85f72a511..fbde43f8a 100644 --- a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensionsTest.kt +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/core/ReactiveNeo4jClientExtensionsTest.kt @@ -21,6 +21,8 @@ package org.neo4j.springframework.data.core import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.Test +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono /** * @author Michael J. Simons @@ -46,4 +48,15 @@ class ReactiveNeo4jClientExtensionsTest { verify(exactly = 1) { ongoingDelegation.`in`("foobar") } } -} \ No newline at end of file + + @Test + fun `ReactiveRunnableDelegation#fetchAs() extension should call its Java counterpart`() { + + val runnableSpec = mockk(relaxed = true) + + val mappingSpec : Neo4jClient.MappingSpec, Flux, String> = + runnableSpec.fetchAs(); + + verify(exactly = 1) { runnableSpec.fetchAs(String::class.java) } + } +} diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/imperative/Neo4jClientKotlinInteropIT.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/imperative/Neo4jClientKotlinInteropIT.kt new file mode 100644 index 000000000..e5bda3ba7 --- /dev/null +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/imperative/Neo4jClientKotlinInteropIT.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.springframework.data.integration.imperative + +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.neo4j.driver.Driver +import org.neo4j.driver.Values +import org.neo4j.springframework.data.config.AbstractNeo4jConfig +import org.neo4j.springframework.data.core.Neo4jClient +import org.neo4j.springframework.data.core.fetchAs +import org.neo4j.springframework.data.core.mappedBy +import org.neo4j.springframework.data.test.Neo4jExtension +import org.neo4j.springframework.data.test.Neo4jIntegrationTest +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.transaction.annotation.EnableTransactionManagement + +/** + * Integration tests for using the Neo4j client in a Kotlin program. + * + * @author Michael J. Simons + */ +@Neo4jIntegrationTest +class Neo4jClientKotlinInteropIT @Autowired constructor( + private val driver: Driver, + private val neo4jClient: Neo4jClient +) { + + companion object { + @JvmStatic + private lateinit var neo4jConnectionSupport: Neo4jExtension.Neo4jConnectionSupport + } + + @BeforeEach + fun prepareData() { + + driver.session().use { + val bands = mapOf( + Pair("Queen", listOf("Brian", "Roger", "John", "Freddie")), + Pair("Die Ärzte", listOf("Farin", "Rod", "Bela")) + ) + + bands.forEach { b, m -> + val summary = it.run(""" + CREATE (b:Band {name: ${'$'}band}) + WITH b + UNWIND ${'$'}names AS name CREATE (n:Member {name: name}) <- [:HAS_MEMBER] - (b) + """.trimIndent(), Values.parameters("band", b, "names", m)).summary() + assertThat(summary.counters().nodesCreated()).isGreaterThan(0) + } + } + } + + @AfterEach + fun purgeData() { + + driver.session().use { it.run("MATCH (n) DETACH DELETE n").consume() } + } + + data class Artist(val name: String) + + data class Band(val name: String, val member: Collection) + + @Test + fun `The Neo4j client should be usable from idiomatic Kotlin code`() { + + val dieAerzte = neo4jClient + .query(" MATCH (b:Band {name: \$name}) - [:HAS_MEMBER] -> (m) RETURN b as band, collect(m.name) as members") + .bind("Die Ärzte").to("name") + .mappedBy { _, r -> + val members = r["members"].asList { v -> Artist(v.asString()) } + Band(r["band"]["name"].asString(), members) + } + .one() + + assertThat(dieAerzte).isNotNull + assertThat(dieAerzte!!.member).hasSize(3) + + if (neo4jClient.query("MATCH (n:IDontExists) RETURN id(n)").fetchAs().one() != null) { + Assertions.fail("The record does not exist, the optional had to be null") + } + } + + @Configuration + @EnableTransactionManagement + open class Config : AbstractNeo4jConfig() { + + @Bean + override fun driver(): Driver { + return neo4jConnectionSupport.driver + } + } +} diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/reactive/ReactiveNeo4jClientKotlinInteropIT.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/reactive/ReactiveNeo4jClientKotlinInteropIT.kt new file mode 100644 index 000000000..df9645281 --- /dev/null +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/reactive/ReactiveNeo4jClientKotlinInteropIT.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.springframework.data.integration.reactive + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.neo4j.driver.Driver +import org.neo4j.driver.Values +import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig +import org.neo4j.springframework.data.core.ReactiveNeo4jClient +import org.neo4j.springframework.data.core.fetchAs +import org.neo4j.springframework.data.core.mappedBy +import org.neo4j.springframework.data.test.Neo4jExtension +import org.neo4j.springframework.data.test.Neo4jIntegrationTest +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.transaction.annotation.EnableTransactionManagement +import reactor.test.StepVerifier + +/** + * Integration tests for using the Neo4j client in a Kotlin program. + * + * @author Michael J. Simons + */ +@Neo4jIntegrationTest +class ReactiveNeo4jClientKotlinInteropIT @Autowired constructor( + private val driver: Driver, + private val neo4jClient: ReactiveNeo4jClient +) { + + companion object { + @JvmStatic + private lateinit var neo4jConnectionSupport: Neo4jExtension.Neo4jConnectionSupport + } + + @BeforeEach + fun prepareData() { + driver.session().use { + val bands = mapOf( + Pair("Queen", listOf("Brian", "Roger", "John", "Freddie")), + Pair("Die Ärzte", listOf("Farin", "Rod", "Bela")) + ) + + bands.forEach { b, m -> + val summary = it.run(""" + CREATE (b:Band {name: ${'$'}band}) + WITH b + UNWIND ${'$'}names AS name CREATE (n:Member {name: name}) <- [:HAS_MEMBER] - (b) + """.trimIndent(), Values.parameters("band", b, "names", m)).summary() + assertThat(summary.counters().nodesCreated()).isGreaterThan(0) + } + } + } + + @AfterEach + fun purgeData() { + + driver.session().use { it.run("MATCH (n) DETACH DELETE n").consume() } + } + + data class Artist(val name: String) + + data class Band(val name: String, val member: Collection) + + @Test + fun `The reactive Neo4j client should be usable from idiomatic Kotlin code`() { + + val queen = neo4jClient + .query("MATCH (b:Band {name: \$name}) - [:HAS_MEMBER] -> (m) RETURN b as band, collect(m.name) as members") + .bind("Queen").to("name") + .mappedBy { _, r -> + val members = r["members"].asList { v -> Artist(v.asString()) } + Band(r["band"]["name"].asString(), members) + }.one() + + StepVerifier.create(queen) + .expectNextMatches { it.name == "Queen" && it.member.size == 4 } + .verifyComplete() + + StepVerifier.create(neo4jClient.query("MATCH (n:IDontExists) RETURN id(n)").fetchAs().one()) + .verifyComplete(); + } + + @Configuration + @EnableTransactionManagement + open class Config : AbstractReactiveNeo4jConfig() { + + @Bean + override fun driver(): Driver { + return neo4jConnectionSupport.driver + } + } +} diff --git a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/kotlin/KotlinRepository.kt b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/shared/KotlinRepository.kt similarity index 86% rename from spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/kotlin/KotlinRepository.kt rename to spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/shared/KotlinRepository.kt index 786a945b8..baf791bff 100644 --- a/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/kotlin/KotlinRepository.kt +++ b/spring-data-neo4j/src/test/kotlin/org/neo4j/springframework/data/integration/shared/KotlinRepository.kt @@ -17,9 +17,8 @@ * limitations under the License. */ -package org.neo4j.springframework.data.integration.kotlin +package org.neo4j.springframework.data.integration.shared -import org.neo4j.springframework.data.integration.shared.KotlinPerson import org.neo4j.springframework.data.repository.Neo4jRepository /**