Skip to content

Commit edbda8a

Browse files
fix: Use contextual Cypher-DSL configuration for rendering statements in various Cypher-DSL based query extension.
Fixes #2927.
1 parent 092e189 commit edbda8a

17 files changed

+173
-27
lines changed

Diff for: src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ default TerminatingFind<T> matching(String query) {
136136
* @return new instance of {@link TerminatingFind}.
137137
* @throws IllegalArgumentException if statement is {@literal null}.
138138
*/
139-
default TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter) {
140-
141-
return matching(statement.getCypher(), TemplateSupport.mergeParameters(statement, parameter));
142-
}
139+
TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter);
143140

144141
/**
145142
* Set the filter {@link Statement statement} to be used.

Diff for: src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020
import java.util.Map;
2121

22+
import org.neo4j.cypherdsl.core.Statement;
2223
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2324
import org.springframework.util.Assert;
2425

@@ -87,7 +88,6 @@ public <T1> FindWithQuery<T1> as(Class<T1> returnType) {
8788
public TerminatingFind<T> matching(String query, Map<String, Object> parameters) {
8889

8990
Assert.notNull(query, "Query must not be null");
90-
9191
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
9292
}
9393

@@ -100,6 +100,12 @@ public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAnd
100100
return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
101101
}
102102

103+
@Override
104+
public TerminatingFind<T> matching(Statement statement, Map<String, Object> parameter) {
105+
106+
return matching(template.render(statement), TemplateSupport.mergeParameters(statement, parameter));
107+
}
108+
103109
@Override
104110
public T oneValue() {
105111

Diff for: src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+4
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,10 @@ <T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
12481248
});
12491249
}
12501250

1251+
String render(Statement statement) {
1252+
return renderer.render(statement);
1253+
}
1254+
12511255
final class DefaultExecutableQuery<T> implements ExecutableQuery<T> {
12521256

12531257
private final PreparedQuery<T> preparedQuery;

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,7 @@ default TerminatingFind<T> matching(String query) {
126126
* @return new instance of {@link TerminatingFind}.
127127
* @throws IllegalArgumentException if statement is {@literal null}.
128128
*/
129-
default TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter) {
130-
return matching(statement.getCypher(), TemplateSupport.mergeParameters(statement, parameter));
131-
}
129+
TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter);
132130

133131
/**
134132
* Set the filter {@link Statement statement} to be used.

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Collections;
2222
import java.util.Map;
2323

24+
import org.neo4j.cypherdsl.core.Statement;
2425
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2526
import org.springframework.util.Assert;
2627

@@ -101,6 +102,12 @@ public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAnd
101102
return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
102103
}
103104

105+
@Override
106+
public TerminatingFind<T> matching(Statement statement, Map<String, Object> parameter) {
107+
108+
return matching(template.render(statement), TemplateSupport.mergeParameters(statement, parameter));
109+
}
110+
104111
@Override
105112
public Mono<T> one() {
106113
return doFind(TemplateSupport.FetchType.ONE).single();

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+4
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,10 @@ public <T> ExecutableSave<T> save(Class<T> domainType) {
13001300
return new ReactiveFluentOperationSupport(this).save(domainType);
13011301
}
13021302

1303+
String render(Statement statement) {
1304+
return this.renderer.render(statement);
1305+
}
1306+
13031307
final class DefaultReactiveExecutableQuery<T> implements ExecutableQuery<T> {
13041308

13051309
private final PreparedQuery<T> preparedQuery;

Diff for: src/main/java/org/springframework/data/neo4j/repository/query/CypherdslBasedQuery.java

+11-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020
import java.util.Optional;
2121
import java.util.function.BiFunction;
22+
import java.util.function.Function;
2223
import java.util.function.Supplier;
2324
import java.util.function.UnaryOperator;
2425

@@ -44,15 +45,20 @@
4445
final class CypherdslBasedQuery extends AbstractNeo4jQuery {
4546

4647
static CypherdslBasedQuery create(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
47-
Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory) {
48+
Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory,
49+
Function<Statement, String> renderer) {
4850

49-
return new CypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory);
51+
return new CypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory, renderer);
5052
}
5153

54+
private final Function<Statement, String> renderer;
55+
5256
private CypherdslBasedQuery(Neo4jOperations neo4jOperations,
5357
Neo4jMappingContext mappingContext,
54-
Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory) {
58+
Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory,
59+
Function<Statement, String> renderer) {
5560
super(neo4jOperations, mappingContext, queryMethod, queryType, projectionFactory);
61+
this.renderer = renderer;
5662
}
5763

5864
@Override
@@ -86,7 +92,7 @@ protected <T> PreparedQuery<T> prepareQuery(Class<T> returnedType,
8692

8793
Map<String, Object> boundParameters = statement.getCatalog().getParameters();
8894
return PreparedQuery.queryFor(returnedType)
89-
.withCypherQuery(statement.getCypher())
95+
.withCypherQuery(renderer.apply(statement))
9096
.withParameters(boundParameters)
9197
.usingMappingFunction(mappingFunction)
9298
.build();
@@ -98,7 +104,7 @@ protected Optional<PreparedQuery<Long>> getCountQuery(Neo4jParameterAccessor par
98104
// We verified this above
99105
Statement countStatement = (Statement) parameterAccessor.getValues()[1];
100106
return Optional.of(PreparedQuery.queryFor(Long.class)
101-
.withCypherQuery(countStatement.getCypher())
107+
.withCypherQuery(renderer.apply(countStatement))
102108
.withParameters(countStatement.getCatalog().getParameters()).build());
103109
}
104110
}

Diff for: src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.lang.reflect.Method;
1919

2020
import org.apiguardian.api.API;
21+
import org.neo4j.cypherdsl.core.renderer.Configuration;
22+
import org.neo4j.cypherdsl.core.renderer.Renderer;
2123
import org.springframework.data.neo4j.core.Neo4jOperations;
2224
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
2325
import org.springframework.data.projection.ProjectionFactory;
@@ -40,12 +42,14 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy {
4042
private final Neo4jMappingContext mappingContext;
4143
private final Neo4jOperations neo4jOperations;
4244
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
45+
private final Configuration configuration;
4346

4447
public Neo4jQueryLookupStrategy(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
45-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
48+
QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) {
4649
this.neo4jOperations = neo4jOperations;
4750
this.mappingContext = mappingContext;
4851
this.evaluationContextProvider = evaluationContextProvider;
52+
this.configuration = configuration;
4953
}
5054

5155
/* (non-Javadoc)
@@ -65,7 +69,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
6569
return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
6670
factory);
6771
} else if (queryMethod.isCypherBasedProjection()) {
68-
return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory);
72+
return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory, Renderer.getRenderer(configuration)::render);
6973
} else {
7074
return PartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, factory);
7175
}

Diff for: src/main/java/org/springframework/data/neo4j/repository/query/ReactiveCypherdslBasedQuery.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Collection;
1919
import java.util.Map;
2020
import java.util.function.BiFunction;
21+
import java.util.function.Function;
2122
import java.util.function.Supplier;
2223
import java.util.function.UnaryOperator;
2324

@@ -42,15 +43,19 @@
4243
final class ReactiveCypherdslBasedQuery extends AbstractReactiveNeo4jQuery {
4344

4445
static ReactiveCypherdslBasedQuery create(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
45-
Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory) {
46+
Neo4jQueryMethod queryMethod, ProjectionFactory projectionFactory, Function<Statement, String> renderer) {
4647

47-
return new ReactiveCypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory);
48+
return new ReactiveCypherdslBasedQuery(neo4jOperations, mappingContext, queryMethod, Neo4jQueryType.DEFAULT, projectionFactory, renderer);
4849
}
4950

51+
private final Function<Statement, String> renderer;
52+
5053
private ReactiveCypherdslBasedQuery(ReactiveNeo4jOperations neo4jOperations,
5154
Neo4jMappingContext mappingContext,
52-
Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory) {
55+
Neo4jQueryMethod queryMethod, Neo4jQueryType queryType, ProjectionFactory projectionFactory,
56+
Function<Statement, String> renderer) {
5357
super(neo4jOperations, mappingContext, queryMethod, queryType, projectionFactory);
58+
this.renderer = renderer;
5459
}
5560

5661
@Override
@@ -68,7 +73,7 @@ protected <T> PreparedQuery<T> prepareQuery(Class<T> returnedType, Collection<Pr
6873

6974
Map<String, Object> boundParameters = statement.getCatalog().getParameters();
7075
return PreparedQuery.queryFor(returnedType)
71-
.withCypherQuery(statement.getCypher())
76+
.withCypherQuery(renderer.apply(statement))
7277
.withParameters(boundParameters)
7378
.usingMappingFunction(mappingFunction)
7479
.build();

Diff for: src/main/java/org/springframework/data/neo4j/repository/query/ReactiveNeo4jQueryLookupStrategy.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.lang.reflect.Method;
1919

2020
import org.apiguardian.api.API;
21+
import org.neo4j.cypherdsl.core.renderer.Configuration;
22+
import org.neo4j.cypherdsl.core.renderer.Renderer;
2123
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
2224
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
2325
import org.springframework.data.projection.ProjectionFactory;
@@ -40,12 +42,14 @@ public final class ReactiveNeo4jQueryLookupStrategy implements QueryLookupStrate
4042
private final ReactiveNeo4jOperations neo4jOperations;
4143
private final Neo4jMappingContext mappingContext;
4244
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
45+
private final Configuration configuration;
4346

4447
public ReactiveNeo4jQueryLookupStrategy(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
45-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
48+
QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) {
4649
this.neo4jOperations = neo4jOperations;
4750
this.mappingContext = mappingContext;
4851
this.evaluationContextProvider = evaluationContextProvider;
52+
this.configuration = configuration;
4953
}
5054

5155
/* (non-Javadoc)
@@ -65,7 +69,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
6569
return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider,
6670
queryMethod, projectionFactory);
6771
} else if (queryMethod.isCypherBasedProjection()) {
68-
return ReactiveCypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory);
72+
return ReactiveCypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory, Renderer.getRenderer(configuration)::render);
6973
} else {
7074
return ReactivePartTreeNeo4jQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory);
7175
}

Diff for: src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import java.util.Optional;
1919

20+
import org.neo4j.cypherdsl.core.renderer.Configuration;
21+
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.BeanFactory;
2023
import org.springframework.data.neo4j.core.Neo4jOperations;
2124
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
2225
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
@@ -51,6 +54,8 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
5154

5255
private final Neo4jMappingContext mappingContext;
5356

57+
private Configuration cypherDSLConfiguration = Configuration.defaultConfig();
58+
5459
Neo4jRepositoryFactory(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext) {
5560

5661
this.neo4jOperations = neo4jOperations;
@@ -122,6 +127,14 @@ protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
122127
return SimpleNeo4jRepository.class;
123128
}
124129

130+
@Override
131+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
132+
super.setBeanFactory(beanFactory);
133+
this.cypherDSLConfiguration = beanFactory
134+
.getBeanProvider(Configuration.class)
135+
.getIfAvailable(Configuration::defaultConfig);
136+
}
137+
125138
/*
126139
* (non-Javadoc)
127140
* @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 @@ protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
130143
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
131144
QueryMethodEvaluationContextProvider evaluationContextProvider) {
132145

133-
return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider));
146+
return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration));
134147
}
135148

136149
@Override

Diff for: src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Optional;
1919

20+
import org.neo4j.cypherdsl.core.renderer.Configuration;
2021
import org.springframework.beans.BeansException;
2122
import org.springframework.beans.factory.BeanFactory;
2223
import org.springframework.beans.factory.ListableBeanFactory;
@@ -55,6 +56,8 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp
5556

5657
private final Neo4jMappingContext mappingContext;
5758

59+
private Configuration cypherDSLConfiguration = Configuration.defaultConfig();
60+
5861
ReactiveNeo4jRepositoryFactory(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext) {
5962

6063
this.neo4jOperations = neo4jOperations;
@@ -133,7 +136,7 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
133136
QueryMethodEvaluationContextProvider evaluationContextProvider) {
134137

135138
return Optional
136-
.of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider));
139+
.of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration));
137140
}
138141

139142
@Override
@@ -148,6 +151,10 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
148151
factory.addAdvice(advice);
149152
});
150153
}
154+
155+
this.cypherDSLConfiguration = beanFactory
156+
.getBeanProvider(Configuration.class)
157+
.getIfAvailable(Configuration::defaultConfig);
151158
}
152159

153160
@Override

Diff for: src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ abstract class AbstractElementIdTestBase {
4141
void setupData(LogbackCapture logbackCapture, @Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {
4242

4343
logbackCapture.addLogger("org.springframework.data.neo4j.cypher.deprecation", Level.WARN);
44+
logbackCapture.addLogger("org.springframework.data.neo4j.cypher", Level.DEBUG);
4445
try (Session session = driver.session()) {
4546
session.run("MATCH (n) DETACH DELETE n").consume();
4647
bookmarkCapture.seedWith(session.lastBookmarks());
@@ -78,6 +79,7 @@ static void assertThatLogMessageDoNotIndicateIDUsage(LogbackCapture logbackCaptu
7879
List<String> formattedMessages = logbackCapture.getFormattedMessages();
7980
assertThat(formattedMessages)
8081
.noneMatch(s -> s.contains("Neo.ClientNotification.Statement.FeatureDeprecationWarning") ||
81-
s.contains("The query used a deprecated function. ('id' is no longer supported)"));
82+
s.contains("The query used a deprecated function. ('id' is no longer supported)") ||
83+
s.matches("(?s).*toString\\(id\\(.*")); // No deprecations are logged when deprecated function call is nested. Anzeige ist raus.
8284
}
8385
}

Diff for: src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/ImperativeElementIdIT.java

+26
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23+
import org.junit.jupiter.api.Tag;
2324
import org.junit.jupiter.api.Test;
2425
import org.junit.jupiter.api.extension.ExtendWith;
26+
import org.neo4j.cypherdsl.core.Cypher;
27+
import org.neo4j.cypherdsl.core.Statement;
2528
import org.neo4j.driver.Driver;
2629
import org.springframework.beans.factory.annotation.Autowired;
2730
import org.springframework.context.annotation.Bean;
2831
import org.springframework.context.annotation.Configuration;
2932
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
33+
import org.springframework.data.neo4j.core.Neo4jTemplate;
3034
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
3135
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
3236
import org.springframework.data.neo4j.repository.Neo4jRepository;
@@ -351,6 +355,28 @@ RETURN count(*)"""),
351355
}
352356
}
353357

358+
@Test
359+
@Tag("GH-2927")
360+
void fluentOpsMustUseCypherDSLConfig(
361+
LogbackCapture logbackCapture,
362+
@Autowired Driver driver,
363+
@Autowired BookmarkCapture bookmarkCapture,
364+
@Autowired Neo4jTemplate neo4jTemplate) {
365+
366+
try (var session = driver.session(bookmarkCapture.createSessionConfig())) {
367+
session.run("MERGE (n:" + Thing.THING_LABEL + "{foo: 'bar'})").consume();
368+
}
369+
370+
var thingNode = Cypher.node(Thing.THING_LABEL);
371+
var cypherStatement = Statement.builder()
372+
.match(thingNode)
373+
.where(Cypher.elementId(thingNode).eq(Cypher.literalOf("test")))
374+
.returning(thingNode)
375+
.build();
376+
neo4jTemplate.find(Thing.class).matching(cypherStatement).one();
377+
assertThatLogMessageDoNotIndicateIDUsage(logbackCapture);
378+
}
379+
354380
@Configuration
355381
@EnableTransactionManagement
356382
@EnableNeo4jRepositories(considerNestedRepositories = true)

0 commit comments

Comments
 (0)