From 31bb6d7ead029eb6ef750f1f1fa3c78048662652 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Fri, 19 Jul 2024 12:40:51 +0200 Subject: [PATCH] fix: Use contextual Cypher-DSL configuration for rendering statements in various Cypher-DSL based query extension. Fixes #2927. # Conflicts: # src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java --- .../data/neo4j/core/FluentFindOperation.java | 5 +-- .../neo4j/core/FluentOperationSupport.java | 8 ++++- .../data/neo4j/core/Neo4jTemplate.java | 4 +++ .../core/ReactiveFluentFindOperation.java | 4 +-- .../core/ReactiveFluentOperationSupport.java | 7 ++++ .../neo4j/core/ReactiveNeo4jTemplate.java | 6 +++- .../repository/query/CypherdslBasedQuery.java | 16 ++++++--- .../query/Neo4jQueryLookupStrategy.java | 8 +++-- .../query/ReactiveCypherdslBasedQuery.java | 13 ++++--- .../ReactiveNeo4jQueryLookupStrategy.java | 8 +++-- .../support/Neo4jRepositoryFactory.java | 15 +++++++- .../ReactiveNeo4jRepositoryFactory.java | 9 ++++- .../AbstractElementIdTestBase.java | 4 ++- .../ImperativeElementIdIT.java | 27 +++++++++++++++ .../pure_element_id/ReactiveElementIdIT.java | 29 ++++++++++++++++ .../issues/pure_element_id/Thing.java | 34 +++++++++++++++++++ .../repository/query/RepositoryQueryTest.java | 7 ++-- 17 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/Thing.java diff --git a/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java b/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java index a4b8f7097..bd3efc123 100644 --- a/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java +++ b/src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java @@ -136,10 +136,7 @@ public interface FluentFindOperation { * @return new instance of {@link TerminatingFind}. * @throws IllegalArgumentException if statement is {@literal null}. */ - default TerminatingFind matching(Statement statement, @Nullable Map parameter) { - - return matching(statement.getCypher(), TemplateSupport.mergeParameters(statement, parameter)); - } + TerminatingFind matching(Statement statement, @Nullable Map parameter); /** * Set the filter {@link Statement statement} to be used. diff --git a/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java b/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java index 1dae566bf..44b82f756 100644 --- a/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java +++ b/src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import org.neo4j.cypherdsl.core.Statement; import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters; import org.springframework.util.Assert; @@ -87,7 +88,6 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe public TerminatingFind matching(String query, Map parameters) { Assert.notNull(query, "Query must not be null"); - return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters); } @@ -100,6 +100,12 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters); } + @Override + public TerminatingFind matching(Statement statement, Map parameter) { + + return matching(template.render(statement), TemplateSupport.mergeParameters(statement, parameter)); + } + @Override public T oneValue() { diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index 5e3b76de9..46c590b5d 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -1099,6 +1099,10 @@ public final class Neo4jTemplate implements return results; } + String render(Statement statement) { + return renderer.render(statement); + } + final class DefaultExecutableQuery implements ExecutableQuery { private final PreparedQuery preparedQuery; diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java index a49a85a4f..ae48f7804 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java @@ -126,9 +126,7 @@ public interface ReactiveFluentFindOperation { * @return new instance of {@link TerminatingFind}. * @throws IllegalArgumentException if statement is {@literal null}. */ - default TerminatingFind matching(Statement statement, @Nullable Map parameter) { - return matching(statement.getCypher(), TemplateSupport.mergeParameters(statement, parameter)); - } + TerminatingFind matching(Statement statement, @Nullable Map parameter); /** * Set the filter {@link Statement statement} to be used. diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java index 7daa1fa98..eef8b731e 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java @@ -21,6 +21,7 @@ import reactor.core.publisher.Mono; import java.util.Collections; import java.util.Map; +import org.neo4j.cypherdsl.core.Statement; import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters; import org.springframework.util.Assert; @@ -101,6 +102,12 @@ final class ReactiveFluentOperationSupport implements ReactiveFluentFindOperatio return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters); } + @Override + public TerminatingFind matching(Statement statement, Map parameter) { + + return matching(template.render(statement), TemplateSupport.mergeParameters(statement, parameter)); + } + @Override public Mono one() { return doFind(TemplateSupport.FetchType.ONE).single(); diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index b0f3a45d6..a302cd06b 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -1172,7 +1172,11 @@ public final class ReactiveNeo4jTemplate implements return new ReactiveFluentOperationSupport(this).save(domainType); } - static final class DefaultReactiveExecutableQuery implements ExecutableQuery { + String render(Statement statement) { + return this.renderer.render(statement); + } + + final class DefaultReactiveExecutableQuery implements ExecutableQuery { private final PreparedQuery preparedQuery; private final ReactiveNeo4jClient.RecordFetchSpec fetchSpec; diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/CypherdslBasedQuery.java b/src/main/java/org/springframework/data/neo4j/repository/query/CypherdslBasedQuery.java index 530a7f003..448568c38 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/CypherdslBasedQuery.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/CypherdslBasedQuery.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -44,15 +45,20 @@ import org.springframework.util.Assert; final class CypherdslBasedQuery extends AbstractNeo4jQuery { static CypherdslBasedQuery create(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory) { + Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory, + Function renderer) { - return new CypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory); + return new CypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory, renderer); } + private final Function renderer; + private CypherdslBasedQuery(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory) { + Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory, + Function renderer) { super(neo4jOperations, mappingContext, queryMethod, queryType, projectionFactory); + this.renderer = renderer; } @Override @@ -86,7 +92,7 @@ final class CypherdslBasedQuery extends AbstractNeo4jQuery { Map boundParameters = statement.getCatalog().getParameters(); return PreparedQuery.queryFor(returnedType) - .withCypherQuery(statement.getCypher()) + .withCypherQuery(renderer.apply(statement)) .withParameters(boundParameters) .usingMappingFunction(mappingFunction) .build(); @@ -98,7 +104,7 @@ final class CypherdslBasedQuery extends AbstractNeo4jQuery { // We verified this above Statement countStatement = (Statement) parameterAccessor.getValues()[1]; return Optional.of(PreparedQuery.queryFor(Long.class) - .withCypherQuery(countStatement.getCypher()) + .withCypherQuery(renderer.apply(countStatement)) .withParameters(countStatement.getCatalog().getParameters()).build()); } } diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java index bd4085440..73d740240 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java @@ -18,6 +18,8 @@ package org.springframework.data.neo4j.repository.query; import java.lang.reflect.Method; import org.apiguardian.api.API; +import org.neo4j.cypherdsl.core.renderer.Configuration; +import org.neo4j.cypherdsl.core.renderer.Renderer; import org.springframework.data.neo4j.core.Neo4jOperations; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.projection.ProjectionFactory; @@ -40,12 +42,14 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy { private final Neo4jMappingContext mappingContext; private final Neo4jOperations neo4jOperations; private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final Configuration configuration; public Neo4jQueryLookupStrategy(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) { this.neo4jOperations = neo4jOperations; this.mappingContext = mappingContext; this.evaluationContextProvider = evaluationContextProvider; + this.configuration = configuration; } /* (non-Javadoc) @@ -65,7 +69,7 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy { return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod, factory); } else if (queryMethod.isCypherBasedProjection()) { - return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory); + return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory, Renderer.getRenderer(configuration)::render); } else { return PartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, factory); } diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveCypherdslBasedQuery.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveCypherdslBasedQuery.java index 16cd03041..318c3b9de 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveCypherdslBasedQuery.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveCypherdslBasedQuery.java @@ -18,6 +18,7 @@ package org.springframework.data.neo4j.repository.query; import java.util.Collection; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -42,15 +43,19 @@ import org.springframework.util.Assert; final class ReactiveCypherdslBasedQuery extends AbstractReactiveNeo4jQuery { static ReactiveCypherdslBasedQuery create(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory) { + Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory, Function renderer) { - return new ReactiveCypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory); + return new ReactiveCypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory, renderer); } + private final Function renderer; + private ReactiveCypherdslBasedQuery(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory) { + Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory, + Function renderer) { super(neo4jOperations, mappingContext, queryMethod, queryType, projectionFactory); + this.renderer = renderer; } @Override @@ -68,7 +73,7 @@ final class ReactiveCypherdslBasedQuery extends AbstractReactiveNeo4jQuery { Map boundParameters = statement.getCatalog().getParameters(); return PreparedQuery.queryFor(returnedType) - .withCypherQuery(statement.getCypher()) + .withCypherQuery(renderer.apply(statement)) .withParameters(boundParameters) .usingMappingFunction(mappingFunction) .build(); diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveNeo4jQueryLookupStrategy.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveNeo4jQueryLookupStrategy.java index 218d067c6..ef8e7b4e9 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveNeo4jQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveNeo4jQueryLookupStrategy.java @@ -18,6 +18,8 @@ package org.springframework.data.neo4j.repository.query; import java.lang.reflect.Method; import org.apiguardian.api.API; +import org.neo4j.cypherdsl.core.renderer.Configuration; +import org.neo4j.cypherdsl.core.renderer.Renderer; import org.springframework.data.neo4j.core.ReactiveNeo4jOperations; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.projection.ProjectionFactory; @@ -40,12 +42,14 @@ public final class ReactiveNeo4jQueryLookupStrategy implements QueryLookupStrate private final ReactiveNeo4jOperations neo4jOperations; private final Neo4jMappingContext mappingContext; private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final Configuration configuration; public ReactiveNeo4jQueryLookupStrategy(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) { this.neo4jOperations = neo4jOperations; this.mappingContext = mappingContext; this.evaluationContextProvider = evaluationContextProvider; + this.configuration = configuration; } /* (non-Javadoc) @@ -65,7 +69,7 @@ public final class ReactiveNeo4jQueryLookupStrategy implements QueryLookupStrate return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod, projectionFactory); } else if (queryMethod.isCypherBasedProjection()) { - return ReactiveCypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory); + return ReactiveCypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory, Renderer.getRenderer(configuration)::render); } else { return ReactivePartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory); } diff --git a/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java b/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java index f8e5a6803..1efd3d1fe 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java +++ b/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java @@ -17,6 +17,9 @@ package org.springframework.data.neo4j.repository.support; import java.util.Optional; +import org.neo4j.cypherdsl.core.renderer.Configuration; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.data.neo4j.core.Neo4jOperations; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; @@ -51,6 +54,8 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { private final Neo4jMappingContext mappingContext; + private Configuration cypherDSLConfiguration = Configuration.defaultConfig(); + Neo4jRepositoryFactory(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext) { this.neo4jOperations = neo4jOperations; @@ -122,6 +127,14 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { return SimpleNeo4jRepository.class; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + this.cypherDSLConfiguration = beanFactory + .getBeanProvider(Configuration.class) + .getIfAvailable(Configuration::defaultConfig); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider) @@ -130,7 +143,7 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { protected Optional getQueryLookupStrategy(Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider)); + return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration)); } @Override diff --git a/src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java b/src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java index 4dc4a4d65..54ff4db0a 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java +++ b/src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java @@ -17,6 +17,7 @@ package org.springframework.data.neo4j.repository.support; import java.util.Optional; +import org.neo4j.cypherdsl.core.renderer.Configuration; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; @@ -55,6 +56,8 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp private final Neo4jMappingContext mappingContext; + private Configuration cypherDSLConfiguration = Configuration.defaultConfig(); + ReactiveNeo4jRepositoryFactory(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext) { this.neo4jOperations = neo4jOperations; @@ -133,7 +136,7 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp QueryMethodEvaluationContextProvider evaluationContextProvider) { return Optional - .of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider)); + .of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration)); } @Override @@ -148,6 +151,10 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp factory.addAdvice(advice); }); } + + this.cypherDSLConfiguration = beanFactory + .getBeanProvider(Configuration.class) + .getIfAvailable(Configuration::defaultConfig); } @Override diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java index b8d04a7b9..00402dd42 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java @@ -41,6 +41,7 @@ abstract class AbstractElementIdTestBase { void setupData(LogbackCapture logbackCapture, @Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) { logbackCapture.addLogger("org.springframework.data.neo4j.cypher.deprecation", Level.WARN); + logbackCapture.addLogger("org.springframework.data.neo4j.cypher", Level.DEBUG); try (Session session = driver.session()) { session.run("MATCH (n) DETACH DELETE n").consume(); bookmarkCapture.seedWith(session.lastBookmarks()); @@ -78,6 +79,7 @@ abstract class AbstractElementIdTestBase { List formattedMessages = logbackCapture.getFormattedMessages(); assertThat(formattedMessages) .noneMatch(s -> s.contains("Neo.ClientNotification.Statement.FeatureDeprecationWarning") || - s.contains("The query used a deprecated function. ('id' is no longer supported)")); + s.contains("The query used a deprecated function. ('id' is no longer supported)") || + s.matches("(?s).*toString\\(id\\(.*")); // No deprecations are logged when deprecated function call is nested. Anzeige ist raus. } } diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ImperativeElementIdIT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ImperativeElementIdIT.java index 713090eb9..908338b64 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ImperativeElementIdIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ImperativeElementIdIT.java @@ -20,13 +20,18 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.neo4j.cypherdsl.core.Cypher; +import org.neo4j.cypherdsl.core.Functions; +import org.neo4j.cypherdsl.core.Statement; import org.neo4j.driver.Driver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.repository.Neo4jRepository; @@ -351,6 +356,28 @@ public class ImperativeElementIdIT extends AbstractElementIdTestBase { } } + @Test + @Tag("GH-2927") + void fluentOpsMustUseCypherDSLConfig( + LogbackCapture logbackCapture, + @Autowired Driver driver, + @Autowired BookmarkCapture bookmarkCapture, + @Autowired Neo4jTemplate neo4jTemplate) { + + try (var session = driver.session(bookmarkCapture.createSessionConfig())) { + session.run("MERGE (n:" + Thing.THING_LABEL + "{foo: 'bar'})").consume(); + } + + var thingNode = Cypher.node(Thing.THING_LABEL); + var cypherStatement = Statement.builder() + .match(thingNode) + .where(Functions.elementId(thingNode).eq(Cypher.literalOf("test"))) + .returning(thingNode) + .build(); + neo4jTemplate.find(Thing.class).matching(cypherStatement).one(); + assertThatLogMessageDoNotIndicateIDUsage(logbackCapture); + } + @Configuration @EnableTransactionManagement @EnableNeo4jRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ReactiveElementIdIT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ReactiveElementIdIT.java index 935be06d5..59643b736 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ReactiveElementIdIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ReactiveElementIdIT.java @@ -19,13 +19,18 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.neo4j.cypherdsl.core.Cypher; +import org.neo4j.cypherdsl.core.Functions; +import org.neo4j.cypherdsl.core.Statement; import org.neo4j.driver.Driver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; +import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate; import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager; import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository; @@ -39,6 +44,7 @@ import org.springframework.lang.NonNull; import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import static org.assertj.core.api.Assertions.assertThat; @@ -363,6 +369,29 @@ public class ReactiveElementIdIT extends AbstractElementIdTestBase { } } + @Test + @Tag("GH-2927") + void fluentOpsMustUseCypherDSLConfig( + LogbackCapture logbackCapture, + @Autowired Driver driver, + @Autowired BookmarkCapture bookmarkCapture, + @Autowired ReactiveNeo4jTemplate neo4jTemplate) { + + try (var session = driver.session(bookmarkCapture.createSessionConfig())) { + session.run("MERGE (n:" + Thing.THING_LABEL + "{foo: 'bar'})").consume(); + } + + var thingNode = Cypher.node(Thing.THING_LABEL); + var cypherStatement = Statement.builder() + .match(thingNode) + .where(Functions.elementId(thingNode).eq(Cypher.literalOf("test"))) + .returning(thingNode) + .build(); + neo4jTemplate.find(Thing.class).matching(cypherStatement).all().as(StepVerifier::create) + .verifyComplete(); + assertThatLogMessageDoNotIndicateIDUsage(logbackCapture); + } + @Configuration @EnableTransactionManagement @EnableReactiveNeo4jRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/Thing.java b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/Thing.java new file mode 100644 index 000000000..0263ce2cd --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/Thing.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.pure_element_id; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * Just a random annotated entity. + */ +@Node(primaryLabel = Thing.THING_LABEL) +public class Thing { + + public static final String THING_LABEL = "THING"; + + @Id + @GeneratedValue + String id; + String name; +} diff --git a/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java b/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java index 960df2819..d790fa266 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java +++ b/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java @@ -45,6 +45,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.driver.Values; import org.neo4j.driver.types.Point; import org.springframework.context.ConfigurableApplicationContext; @@ -199,7 +200,7 @@ final class RepositoryQueryTest { final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations, neo4jMappingContext, - QueryMethodEvaluationContextProvider.DEFAULT); + QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig()); RepositoryQuery query = lookupStrategy.resolveQuery(queryMethod("findById", Object.class), TEST_REPOSITORY_METADATA, PROJECTION_FACTORY, namedQueries); @@ -210,7 +211,7 @@ final class RepositoryQueryTest { void shouldSelectStringBasedNeo4jQuery() { final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations, - neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT); + neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig()); RepositoryQuery query = lookupStrategy.resolveQuery(queryMethod("annotatedQueryWithValidTemplate"), TEST_REPOSITORY_METADATA, PROJECTION_FACTORY, namedQueries); @@ -225,7 +226,7 @@ final class RepositoryQueryTest { when(namedQueries.getQuery(namedQueryName)).thenReturn("MATCH (n) RETURN n"); final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations, - neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT); + neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig()); RepositoryQuery query = lookupStrategy .resolveQuery(queryMethod("findAllByANamedQuery"), TEST_REPOSITORY_METADATA,