diff --git a/pom.xml b/pom.xml
index 252650fca3..e6e4e32ad9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
org.springframework.data
spring-data-neo4j
- 7.4.0-SNAPSHOT
+ 7.4.0-GH-2954-SNAPSHOT
Spring Data Neo4j
Next generation Object-Graph-Mapping for Spring Data.
diff --git a/src/main/antora/modules/ROOT/pages/appendix/custom-queries.adoc b/src/main/antora/modules/ROOT/pages/appendix/custom-queries.adoc
index b1842e7772..55171bc4c0 100644
--- a/src/main/antora/modules/ROOT/pages/appendix/custom-queries.adoc
+++ b/src/main/antora/modules/ROOT/pages/appendix/custom-queries.adoc
@@ -367,8 +367,11 @@ If an entity has a relationship with the same type to different types of others
If you need such a mapping and also have the need to work with those custom parameters, you have to unroll it accordingly.
One way to do this are correlated subqueries (Neo4j 4.1+ required).
+[[custom-queries.expressions]]
+== Value Expressions in custom queries
+
[[custom-queries.spel]]
-== Spring Expression Language in custom queries
+=== Spring Expression Language in custom queries
{springdocsurl}/core/expressions.html[Spring Expression Language (SpEL)] can be used in custom queries inside `:#{}`.
The colon here refers to a parameter and such an expression should be used where parameters make sense.
@@ -502,3 +505,18 @@ MATCH (n:`Bike`:`Gravel`:`Easy Trail`) WHERE n.id = $nameOrId OR n.name = $nameO
Notice how we used standard parameter for the `nameOrId`: In most cases there is no need to complicate things here by
adding a SpEL expression.
+
+
+[[custom-queries.propertyplaceholder]]
+=== Property Placeholder resolution in custom queries
+
+Spring's property placeholders can be used in custom queries inside `${}`.
+
+[source,java,indent=0]
+[[custom-queries-with-property-placeholder-example]]
+.ARepository.java
+----
+include::example$documentation/repositories/domain_events/ARepository.java[tags=property-placeholder]
+----
+
+In the example above, if the property `foo` would be set to `bar` then the `${foo}` block would be resolved to `bar`.
\ No newline at end of file
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 73d740240f..469703f1d4 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
@@ -26,8 +26,8 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
/**
* Lookup strategy for queries. This is the internal api of the {@code query package}.
@@ -41,14 +41,14 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy {
private final Neo4jMappingContext mappingContext;
private final Neo4jOperations neo4jOperations;
- private final QueryMethodEvaluationContextProvider evaluationContextProvider;
+ private final ValueExpressionDelegate delegate;
private final Configuration configuration;
public Neo4jQueryLookupStrategy(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) {
+ ValueExpressionDelegate delegate, Configuration configuration) {
this.neo4jOperations = neo4jOperations;
this.mappingContext = mappingContext;
- this.evaluationContextProvider = evaluationContextProvider;
+ this.delegate = delegate;
this.configuration = configuration;
}
@@ -63,10 +63,10 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
- return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, delegate, queryMethod,
namedQueries.getQuery(namedQueryName), factory);
} else if (queryMethod.hasQueryAnnotation()) {
- return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return StringBasedNeo4jQuery.create(neo4jOperations, mappingContext, delegate, queryMethod,
factory);
} else if (queryMethod.isCypherBasedProjection()) {
return CypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, factory, Renderer.getRenderer(configuration)::render);
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java
index 385e37a459..4d1d74d898 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java
@@ -44,6 +44,7 @@
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.ScrollPosition.Direction;
import org.springframework.data.domain.Window;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
@@ -60,7 +61,6 @@
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.TypeInformation;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -74,7 +74,7 @@
*/
abstract class Neo4jQuerySupport {
- protected static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser();
+ protected static final ValueExpressionParser SPEL_EXPRESSION_PARSER = ValueExpressionParser.create();
protected final Neo4jMappingContext mappingContext;
protected final Neo4jQueryMethod queryMethod;
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupport.java b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupport.java
index 837f6277e4..61426179ea 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupport.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupport.java
@@ -29,13 +29,13 @@
import org.neo4j.cypherdsl.support.schema_name.SchemaNames;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.data.expression.ValueEvaluationContext;
+import org.springframework.data.expression.ValueExpression;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.repository.core.EntityMetadata;
-import org.springframework.expression.Expression;
-import org.springframework.expression.ParserContext;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -233,7 +233,7 @@ public Target getTarget() {
* @return A query in which some SpEL expression have been replaced with the result of evaluating the expression
*/
public static String renderQueryIfExpressionOrReturnQuery(String query, Neo4jMappingContext mappingContext, EntityMetadata> metadata,
- SpelExpressionParser parser) {
+ ValueExpressionParser parser) {
Assert.notNull(query, "query must not be null");
Assert.notNull(metadata, "metadata must not be null");
@@ -243,10 +243,10 @@ public static String renderQueryIfExpressionOrReturnQuery(String query, Neo4jMap
return query;
}
- StandardEvaluationContext evalContext = new StandardEvaluationContext();
+ ValueEvaluationContext evalContext = ValueEvaluationContext.of(null, new StandardEvaluationContext());
Neo4jPersistentEntity> requiredPersistentEntity = mappingContext
.getRequiredPersistentEntity(metadata.getJavaType());
- evalContext.setVariable(ENTITY_NAME, requiredPersistentEntity.getStaticLabels()
+ evalContext.getEvaluationContext().setVariable(ENTITY_NAME, requiredPersistentEntity.getStaticLabels()
.stream()
.map(l -> {
Matcher matcher = LABEL_AND_TYPE_QUOTATION.matcher(l);
@@ -256,9 +256,9 @@ public static String renderQueryIfExpressionOrReturnQuery(String query, Neo4jMap
query = potentiallyQuoteExpressionsParameter(query);
- Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);
+ ValueExpression expr = parser.parse(query);
- String result = expr.getValue(evalContext, String.class);
+ String result = (String) expr.evaluate(evalContext);
if (result == null) {
return query;
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 ef8e7b4e9c..9ba53e8bb9 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
@@ -26,8 +26,8 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
/**
* Lookup strategy for queries. This is the internal api of the {@code query package}.
@@ -41,14 +41,14 @@ public final class ReactiveNeo4jQueryLookupStrategy implements QueryLookupStrate
private final ReactiveNeo4jOperations neo4jOperations;
private final Neo4jMappingContext mappingContext;
- private final QueryMethodEvaluationContextProvider evaluationContextProvider;
+ private final ValueExpressionDelegate delegate;
private final Configuration configuration;
public ReactiveNeo4jQueryLookupStrategy(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Configuration configuration) {
+ ValueExpressionDelegate delegate, Configuration configuration) {
this.neo4jOperations = neo4jOperations;
this.mappingContext = mappingContext;
- this.evaluationContextProvider = evaluationContextProvider;
+ this.delegate = delegate;
this.configuration = configuration;
}
@@ -63,10 +63,10 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
- return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider,
+ return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, delegate,
queryMethod, namedQueries.getQuery(namedQueryName), projectionFactory);
} else if (queryMethod.hasQueryAnnotation()) {
- return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, evaluationContextProvider,
+ return ReactiveStringBasedNeo4jQuery.create(neo4jOperations, mappingContext, delegate,
queryMethod, projectionFactory);
} else if (queryMethod.isCypherBasedProjection()) {
return ReactiveCypherdslBasedQuery.create(neo4jOperations, mappingContext, queryMethod, projectionFactory, Renderer.getRenderer(configuration)::render);
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveStringBasedNeo4jQuery.java b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveStringBasedNeo4jQuery.java
index 4d499ed945..8316ee0948 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveStringBasedNeo4jQuery.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/ReactiveStringBasedNeo4jQuery.java
@@ -33,11 +33,10 @@
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
-import org.springframework.data.repository.query.SpelEvaluator;
import org.springframework.data.repository.query.SpelQueryContext;
-import org.springframework.data.repository.query.SpelQueryContext.SpelExtractor;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
+import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -65,11 +64,9 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
static final SpelQueryContext SPEL_QUERY_CONTEXT = SpelQueryContext
.of(ReactiveStringBasedNeo4jQuery::parameterNameSource, ReactiveStringBasedNeo4jQuery::replacementSource);
- /**
- * Used to evaluate the expression found while parsing the cypher template of this query against the actual parameters
- * with the help of the formal parameters during the building of the {@link PreparedQuery}.
- */
- private final SpelEvaluator spelEvaluator;
+ private final ValueExpressionQueryRewriter.EvaluatingValueExpressionQueryRewriter queryRewriter;
+
+ private final ValueExpressionQueryRewriter.QueryExpressionEvaluator parsedQuery;
/**
* Create a {@link ReactiveStringBasedNeo4jQuery} for a query method that is annotated with {@link Query @Query}. The
@@ -77,12 +74,12 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
*
* @param neo4jOperations reactive Neo4j operations
* @param mappingContext a Neo4jMappingContext instance
- * @param evaluationContextProvider a QueryMethodEvaluationContextProvider instance
+ * @param delegate a ValueExpressionDelegate instance
* @param queryMethod the query method
* @return A new instance of a String based Neo4j query.
*/
static ReactiveStringBasedNeo4jQuery create(ReactiveNeo4jOperations neo4jOperations,
- Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider,
+ Neo4jMappingContext mappingContext, ValueExpressionDelegate delegate,
Neo4jQueryMethod queryMethod, ProjectionFactory factory) {
Query queryAnnotation = queryMethod.getQueryAnnotation()
@@ -91,7 +88,7 @@ static ReactiveStringBasedNeo4jQuery create(ReactiveNeo4jOperations neo4jOperati
String cypherTemplate = Optional.ofNullable(queryAnnotation.value()).filter(StringUtils::hasText)
.orElseThrow(() -> new MappingException("Expected @Query annotation to have a value, but it did not"));
- return new ReactiveStringBasedNeo4jQuery(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return new ReactiveStringBasedNeo4jQuery(neo4jOperations, mappingContext, delegate, queryMethod,
cypherTemplate, Neo4jQueryType.fromDefinition(queryAnnotation), factory);
}
@@ -100,30 +97,30 @@ static ReactiveStringBasedNeo4jQuery create(ReactiveNeo4jOperations neo4jOperati
*
* @param neo4jOperations reactive Neo4j operations
* @param mappingContext a Neo4jMappingContext instance
- * @param evaluationContextProvider a QueryMethodEvaluationContextProvider instance
+ * @param delegate a ValueExpressionDelegate instance
* @param queryMethod the query method
* @param cypherTemplate The template to use.
* @return A new instance of a String based Neo4j query.
*/
static ReactiveStringBasedNeo4jQuery create(ReactiveNeo4jOperations neo4jOperations,
- Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider,
+ Neo4jMappingContext mappingContext, ValueExpressionDelegate delegate,
Neo4jQueryMethod queryMethod, String cypherTemplate, ProjectionFactory factory) {
Assert.hasText(cypherTemplate, "Cannot create String based Neo4j query without a cypher template");
- return new ReactiveStringBasedNeo4jQuery(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return new ReactiveStringBasedNeo4jQuery(neo4jOperations, mappingContext, delegate, queryMethod,
cypherTemplate, Neo4jQueryType.DEFAULT, factory);
}
private ReactiveStringBasedNeo4jQuery(ReactiveNeo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod,
+ ValueExpressionDelegate delegate, Neo4jQueryMethod queryMethod,
String cypherTemplate, Neo4jQueryType queryType, ProjectionFactory factory) {
super(neo4jOperations, mappingContext, queryMethod, queryType, factory);
- cypherTemplate = Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(cypherTemplate, mappingContext, queryMethod.getEntityInformation(), SPEL_EXPRESSION_PARSER);
- SpelExtractor spelExtractor = SPEL_QUERY_CONTEXT.parse(cypherTemplate);
- this.spelEvaluator = new SpelEvaluator(evaluationContextProvider, queryMethod.getParameters(), spelExtractor);
+ this.queryRewriter = ValueExpressionQueryRewriter.of(delegate,
+ StringBasedNeo4jQuery::parameterNameSource, StringBasedNeo4jQuery::replacementSource);
+ this.parsedQuery = queryRewriter.parse(cypherTemplate, queryMethod.getParameters());
}
@Override
@@ -134,7 +131,7 @@ protected PreparedQuery prepareQuery(Class returnedType
Map boundParameters = bindParameters(parameterAccessor);
QueryContext queryContext = new QueryContext(
queryMethod.getRepositoryName() + "." + queryMethod.getName(),
- spelEvaluator.getQueryString(),
+ parsedQuery.getQueryString(),
boundParameters
);
@@ -153,7 +150,7 @@ Map bindParameters(Neo4jParameterAccessor parameterAccessor) {
Map resolvedParameters = new HashMap<>();
// Values from the parameter accessor can only get converted after evaluation
- for (Map.Entry evaluatedParam : spelEvaluator.evaluate(parameterAccessor.getValues()).entrySet()) {
+ for (Map.Entry evaluatedParam : parsedQuery.evaluate(parameterAccessor.getValues()).entrySet()) {
Object value = evaluatedParam.getValue();
if (!(evaluatedParam.getValue() instanceof Neo4jSpelSupport.LiteralReplacement)) {
Neo4jQuerySupport.logParameterIfNull(evaluatedParam.getKey(), value);
diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java b/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java
index 906722fa3b..0d7fa92d77 100644
--- a/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java
+++ b/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java
@@ -36,10 +36,9 @@
import org.springframework.data.neo4j.repository.query.Neo4jSpelSupport.LiteralReplacement;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.query.Parameters;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
-import org.springframework.data.repository.query.SpelEvaluator;
-import org.springframework.data.repository.query.SpelQueryContext;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
+import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -61,12 +60,6 @@
*/
final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
- /**
- * Used for extracting SpEL expressions inside Cypher query templates.
- */
- static final SpelQueryContext SPEL_QUERY_CONTEXT = SpelQueryContext.of(StringBasedNeo4jQuery::parameterNameSource,
- StringBasedNeo4jQuery::replacementSource);
-
private final static String COMMENT_OR_WHITESPACE_GROUP = "(?:\\s|/\\\\*.*?\\\\*/|//.*?$)";
static final Pattern SKIP_AND_LIMIT_WITH_PLACEHOLDER_PATTERN = Pattern
.compile(""
@@ -81,12 +74,14 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
* Used to evaluate the expression found while parsing the cypher template of this query against the actual parameters
* with the help of the formal parameters during the building of the {@link PreparedQuery}.
*/
- private final SpelEvaluator spelEvaluator;
+ private final ValueExpressionQueryRewriter.QueryExpressionEvaluator parsedQuery;
/**
* An optional evaluator for a count query if such a query is present.
*/
- private final Optional spelEvaluatorForCountQuery;
+ private final Optional parsedCountQuery;
+
+ private final ValueExpressionQueryRewriter.EvaluatingValueExpressionQueryRewriter queryRewriter;
/**
* Create a {@link StringBasedNeo4jQuery} for a query method that is annotated with {@link Query @Query}. The
@@ -94,12 +89,12 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
*
* @param neo4jOperations the Neo4j operations
* @param mappingContext a Neo4jMappingContext instance
- * @param evaluationContextProvider a QueryMethodEvaluationContextProvider instance
+ * @param delegate a ValueExpressionDelegate instance
* @param queryMethod the query method
* @return A new instance of a String based Neo4j query.
*/
static StringBasedNeo4jQuery create(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod,
+ ValueExpressionDelegate delegate, Neo4jQueryMethod queryMethod,
ProjectionFactory factory) {
Query queryAnnotation = queryMethod.getQueryAnnotation()
@@ -134,7 +129,7 @@ static StringBasedNeo4jQuery create(Neo4jOperations neo4jOperations, Neo4jMappin
cypherTemplate, queryMethod.getRepositoryName(), queryMethod.getName()));
}
- return new StringBasedNeo4jQuery(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return new StringBasedNeo4jQuery(neo4jOperations, mappingContext, delegate, queryMethod,
cypherTemplate, Neo4jQueryType.fromDefinition(queryAnnotation), factory);
}
@@ -143,35 +138,35 @@ static StringBasedNeo4jQuery create(Neo4jOperations neo4jOperations, Neo4jMappin
*
* @param neo4jOperations the Neo4j operations
* @param mappingContext a Neo4jMappingContext instance
- * @param evaluationContextProvider a QueryMethodEvaluationContextProvider instance
+ * @param delegate a ValueExpressionDelegate instance
* @param queryMethod the query method
* @param cypherTemplate The template to use.
* @return A new instance of a String based Neo4j query.
*/
static StringBasedNeo4jQuery create(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod,
+ ValueExpressionDelegate delegate, Neo4jQueryMethod queryMethod,
String cypherTemplate, ProjectionFactory factory) {
Assert.hasText(cypherTemplate, "Cannot create String based Neo4j query without a cypher template");
- return new StringBasedNeo4jQuery(neo4jOperations, mappingContext, evaluationContextProvider, queryMethod,
+ return new StringBasedNeo4jQuery(neo4jOperations, mappingContext, delegate, queryMethod,
cypherTemplate, Neo4jQueryType.DEFAULT, factory);
}
private StringBasedNeo4jQuery(Neo4jOperations neo4jOperations, Neo4jMappingContext mappingContext,
- QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod,
+ ValueExpressionDelegate delegate, Neo4jQueryMethod queryMethod,
String cypherTemplate, Neo4jQueryType queryType, ProjectionFactory factory) {
super(neo4jOperations, mappingContext, queryMethod, queryType, factory);
- Parameters, ?> methodParameters = queryMethod.getParameters();
cypherTemplate = Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(cypherTemplate, mappingContext, queryMethod.getEntityInformation(), SPEL_EXPRESSION_PARSER);
- this.spelEvaluator = new SpelEvaluator(
- evaluationContextProvider, methodParameters, SPEL_QUERY_CONTEXT.parse(cypherTemplate));
- this.spelEvaluatorForCountQuery = queryMethod.getQueryAnnotation()
+ this.queryRewriter = ValueExpressionQueryRewriter.of(delegate,
+ StringBasedNeo4jQuery::parameterNameSource, StringBasedNeo4jQuery::replacementSource);
+ this.parsedQuery = queryRewriter.parse(cypherTemplate, queryMethod.getParameters());
+ this.parsedCountQuery = queryMethod.getQueryAnnotation()
.map(Query::countQuery)
- .map(q -> Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(q, mappingContext, queryMethod.getEntityInformation(), SPEL_EXPRESSION_PARSER))
- .map(countQuery -> new SpelEvaluator(evaluationContextProvider, methodParameters, SPEL_QUERY_CONTEXT.parse(countQuery)));
+ .map(q -> Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(q, mappingContext, queryMethod.getEntityInformation(), delegate))
+ .map(string -> queryRewriter.parse(string, queryMethod.getParameters()));
}
@Override
@@ -184,7 +179,7 @@ protected PreparedQuery prepareQuery(Class returnedType
Map boundParameters = bindParameters(parameterAccessor, true, limitModifier);
QueryContext queryContext = new QueryContext(
queryMethod.getRepositoryName() + "." + queryMethod.getName(),
- spelEvaluator.getQueryString(),
+ parsedQuery.getQueryString(),
boundParameters
);
@@ -204,7 +199,7 @@ Map bindParameters(Neo4jParameterAccessor parameterAccessor, boo
Map resolvedParameters = new HashMap<>();
// Values from the parameter accessor can only get converted after evaluation
- for (Entry evaluatedParam : spelEvaluator.evaluate(parameterAccessor.getValues()).entrySet()) {
+ for (Entry evaluatedParam : parsedQuery.evaluate(parameterAccessor.getValues()).entrySet()) {
Object value = evaluatedParam.getValue();
if (!(evaluatedParam.getValue() instanceof LiteralReplacement)) {
Neo4jQuerySupport.logParameterIfNull(evaluatedParam.getKey(), value);
@@ -237,7 +232,7 @@ Map bindParameters(Neo4jParameterAccessor parameterAccessor, boo
@Override
protected Optional> getCountQuery(Neo4jParameterAccessor parameterAccessor) {
- return spelEvaluatorForCountQuery.map(SpelEvaluator::getQueryString)
+ return parsedCountQuery.map(ValueExpressionQueryRewriter.QueryExpressionEvaluator::getQueryString)
.map(countQuery -> {
Map boundParameters = bindParameters(parameterAccessor, false, UnaryOperator.identity());
QueryContext queryContext = new QueryContext(
@@ -258,7 +253,7 @@ protected Optional> getCountQuery(Neo4jParameterAccessor par
* @param originalSpelExpression Not used for configuring parameter names atm.
* @return A new parameter name for the given index.
*/
- private static String parameterNameSource(int index, @SuppressWarnings("unused") String originalSpelExpression) {
+ static String parameterNameSource(int index, @SuppressWarnings("unused") String originalSpelExpression) {
return "__SpEL__" + index;
}
@@ -268,7 +263,7 @@ private static String parameterNameSource(int index, @SuppressWarnings("unused")
* @param parameterName name of the parameter
* @return The name of the parameter in its native Cypher form.
*/
- private static String replacementSource(@SuppressWarnings("unused") String originalPrefix, String parameterName) {
+ static String replacementSource(@SuppressWarnings("unused") String originalPrefix, String parameterName) {
return "$" + parameterName;
}
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 1efd3d1fe8..8bea468ad9 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
@@ -40,6 +40,7 @@
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
/**
* Factory to create {@link Neo4jRepository} instances.
@@ -135,15 +136,10 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
.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)
- */
@Override
protected Optional getQueryLookupStrategy(Key key,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
-
- return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration));
+ ValueExpressionDelegate valueExpressionDelegate) {
+ return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, mappingContext, valueExpressionDelegate, 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 54ff4db0ad..7c774bbdfd 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
@@ -40,7 +40,7 @@
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
/**
* Factory to create {@link ReactiveNeo4jRepository} instances.
@@ -127,16 +127,11 @@ protected Class> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleReactiveNeo4jRepository.class;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
- */
- @Override
- protected Optional getQueryLookupStrategy(Key key,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ @Override protected Optional getQueryLookupStrategy(Key key,
+ ValueExpressionDelegate valueExpressionDelegate) {
return Optional
- .of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration));
+ .of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, valueExpressionDelegate, cypherDSLConfiguration));
}
@Override
diff --git a/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/ARepository.java b/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/ARepository.java
index ff2944236f..71497bc3b8 100644
--- a/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/ARepository.java
+++ b/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/ARepository.java
@@ -36,6 +36,11 @@ public interface ARepository extends Neo4jRepository {
Optional findByCustomQuery(String name);
// end::standard-parameter[]
+ // tag::property-placeholder[]
+ @Query("MATCH (a:AnAggregateRoot) WHERE a.name = :${foo} RETURN a")
+ Optional findByCustomQueryWithPropertyPlaceholder();
+ // end::property-placeholder[]
+
// tag::spel[]
@Query("MATCH (a:AnAggregateRoot) WHERE a.name = :#{#pt1 + #pt2} RETURN a")
Optional findByCustomQueryWithSpEL(String pt1, String pt2);
diff --git a/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/DomainEventsTest.java b/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/DomainEventsTest.java
index 80e47483f7..46a75f1550 100644
--- a/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/DomainEventsTest.java
+++ b/src/test/java/org/springframework/data/neo4j/documentation/repositories/domain_events/DomainEventsTest.java
@@ -22,11 +22,13 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
/**
* Basic tests for domain events (and documentation).
*/
@Disabled
+@TestPropertySource("foo=The Root")
// tag::domain-events[]
public class DomainEventsTest {
@@ -57,6 +59,9 @@ void customQueryShouldWork() {
optionalAggregate = aRepository.findByCustomQueryWithSpEL("The ", "Root");
assertThat(optionalAggregate).isPresent();
+
+ optionalAggregate = aRepository.findByCustomQueryWithPropertyPlaceholder();
+ assertThat(optionalAggregate).isPresent();
}
// tag::domain-events[]
diff --git a/src/test/java/org/springframework/data/neo4j/integration/conversion_reactive/ReactiveCustomTypesIT.java b/src/test/java/org/springframework/data/neo4j/integration/conversion_reactive/ReactiveCustomTypesIT.java
index c0d4c672cf..fa92ade14a 100644
--- a/src/test/java/org/springframework/data/neo4j/integration/conversion_reactive/ReactiveCustomTypesIT.java
+++ b/src/test/java/org/springframework/data/neo4j/integration/conversion_reactive/ReactiveCustomTypesIT.java
@@ -23,6 +23,7 @@
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
import org.springframework.data.neo4j.test.BookmarkCapture;
import org.springframework.data.neo4j.test.Neo4jReactiveTestConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.ReactiveTransactionManager;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -66,6 +67,7 @@
*/
@Neo4jIntegrationTest
@Tag(Neo4jExtension.NEEDS_REACTIVE_SUPPORT)
+@TestPropertySource(properties = {"foo=XYZ"})
public class ReactiveCustomTypesIT {
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
@@ -117,6 +119,15 @@ void findByConvertedCustomTypeWithSpELPropertyAccessQuery(
.expectNextCount(1).verifyComplete();
}
+ @Test
+ void findByConvertedCustomTypeWithPropertyPlaceholderAccessQuery(
+ @Autowired EntityWithCustomTypePropertyRepository repository) {
+
+ StepVerifier
+ .create(repository.findByCustomTypeCustomPropertyPlaceholderAccessQuery())
+ .expectNextCount(1).verifyComplete();
+ }
+
@Test
void findByConvertedCustomTypeWithSpELObjectQuery(@Autowired EntityWithCustomTypePropertyRepository repository) {
StepVerifier.create(repository.findByCustomTypeSpELObjectQuery(ThingWithCustomTypes.CustomType.of("XYZ")))
@@ -182,6 +193,10 @@ Mono findByCustomTypeCustomQuery(
Mono findByCustomTypeCustomSpELPropertyAccessQuery(
@Param("customType") ThingWithCustomTypes.CustomType customType);
+
+ @Query("MATCH (c:CustomTypes) WHERE c.customType = :${foo} return c")
+ Mono findByCustomTypeCustomPropertyPlaceholderAccessQuery();
+
@Query("MATCH (c:CustomTypes) WHERE c.customType = :#{#customType} return c")
Mono findByCustomTypeSpELObjectQuery(
@Param("customType") ThingWithCustomTypes.CustomType customType);
diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java
index e0a555a208..c61117fdf3 100644
--- a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java
+++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java
@@ -169,6 +169,7 @@
import org.springframework.data.repository.query.Param;
import org.springframework.data.support.WindowIterator;
import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -212,6 +213,7 @@ static PersonWithAllConstructor personExample(String sameValue) {
PersonWithAllConstructor person2;
@Nested
+ @TestPropertySource(properties = "foo=Test")
class Find extends IntegrationTestBase {
@Override
@@ -546,6 +548,15 @@ void loadOptionalPersonWithAllConstructorWithSpelParameters(@Autowired PersonRep
assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME);
}
+ @Test
+ void loadOptionalPersonWithAllConstructorWithPropertyPlacholder(@Autowired PersonRepository repository) {
+
+ Optional person = repository
+ .getOptionalPersonViaPropertyPlaceholder();
+ assertThat(person).isPresent();
+ assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME);
+ }
+
@Test
void loadOptionalPersonWithAllConstructorWithSpelParametersAndDynamicSort(@Autowired PersonRepository repository) {
diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/repositories/PersonRepository.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/repositories/PersonRepository.java
index 66f59fad41..f446e8af47 100644
--- a/src/test/java/org/springframework/data/neo4j/integration/imperative/repositories/PersonRepository.java
+++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/repositories/PersonRepository.java
@@ -99,6 +99,9 @@ public CustomAggregation(Streamable delegate) {
Optional getOptionalPersonViaQuery(@Param("part1") String part1,
@Param("part2") String part2);
+ @Query("MATCH (n:PersonWithAllConstructor{name::${foo}}) return n")
+ Optional getOptionalPersonViaPropertyPlaceholder();
+
@Query("MATCH (n:PersonWithAllConstructor{name::#{#part1 + #part2}}) return n :#{orderBy(#sort)}")
Optional getOptionalPersonViaQueryWithSort(@Param("part1") String part1,
@Param("part2") String part2, Sort sort);
diff --git a/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupportTest.java b/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupportTest.java
index a2f20cc611..10d2a93a72 100644
--- a/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupportTest.java
+++ b/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jSpelSupportTest.java
@@ -39,13 +39,14 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.repository.query.Neo4jSpelSupport.LiteralReplacement;
import org.springframework.data.repository.core.EntityMetadata;
-import org.springframework.data.repository.query.SpelQueryContext;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
+import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
import org.springframework.util.ReflectionUtils;
/**
@@ -188,11 +189,9 @@ void shouldUnquoteParameterExpressionsCorrectly(String quoted, String expected)
@Test
void moreThan10SpelEntriesShouldWork() {
- SpelQueryContext spelQueryContext = StringBasedNeo4jQuery.SPEL_QUERY_CONTEXT;
-
StringBuilder template = new StringBuilder("MATCH (user:User) WHERE ");
String query;
- SpelQueryContext.SpelExtractor spelExtractor;
+ ValueExpressionQueryRewriter.ParsedQuery spelExtractor;
class R implements LiteralReplacement {
private final String value;
@@ -218,7 +217,8 @@ public Target getTarget() {
parameters.put("__SpEL__" + i, new R("'x" + i + "'"));
}
template.delete(template.length() - 4, template.length());
- spelExtractor = spelQueryContext.parse(template.toString());
+ spelExtractor = ValueExpressionQueryRewriter.of(ValueExpressionDelegate.create(),
+ StringBasedNeo4jQuery::parameterNameSource, StringBasedNeo4jQuery::replacementSource).parse(template.toString());
query = spelExtractor.getQueryString();
Neo4jQuerySupport.QueryContext qc = new Neo4jQuerySupport.QueryContext("n/a", query, parameters);
assertThat(qc.query).isEqualTo(
@@ -240,7 +240,7 @@ void shouldReplaceStaticLabels() {
String query = Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(
"MATCH (n:#{#staticLabels}) WHERE n.name = ?#{#name} OR n.name = :?#{#name} RETURN n",
- new Neo4jMappingContext(), (EntityMetadata) () -> BikeNode.class, new SpelExpressionParser());
+ new Neo4jMappingContext(), (EntityMetadata) () -> BikeNode.class, ValueExpressionParser.create());
assertThat(query).isEqualTo("MATCH (n:`Bike`:`Gravel`:`Easy Trail`) WHERE n.name = ?#{#name} OR n.name = :?#{#name} RETURN n");
diff --git a/src/test/java/org/springframework/data/neo4j/repository/query/ReactiveRepositoryQueryTest.java b/src/test/java/org/springframework/data/neo4j/repository/query/ReactiveRepositoryQueryTest.java
index b1edd730d5..467e6e0c83 100644
--- a/src/test/java/org/springframework/data/neo4j/repository/query/ReactiveRepositoryQueryTest.java
+++ b/src/test/java/org/springframework/data/neo4j/repository/query/ReactiveRepositoryQueryTest.java
@@ -53,9 +53,9 @@
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.Param;
-import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
-import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.SpelQueryContext;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.util.ReflectionUtils;
@@ -124,7 +124,7 @@ void shouldDetectInvalidAnnotation() {
Neo4jQueryMethod method = reactiveNeo4jQueryMethod("annotatedQueryWithoutTemplate");
assertThatExceptionOfType(MappingException.class)
.isThrownBy(() -> ReactiveStringBasedNeo4jQuery
- .create(neo4jOperations, neo4jMappingContext, ReactiveQueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory))
+ .create(neo4jOperations, neo4jMappingContext, ValueExpressionDelegate.create(), method, projectionFactory))
.withMessage("Expected @Query annotation to have a value, but it did not");
}
@@ -135,7 +135,7 @@ void shouldWarnWhenUsingSortedAndCustomQuery(LogbackCapture logbackCapture) {
ReactiveStringBasedNeo4jQuery query =
ReactiveStringBasedNeo4jQuery
.create(neo4jOperations, neo4jMappingContext,
- ReactiveQueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
@@ -167,11 +167,10 @@ void orderBySpelShouldWork(LogbackCapture logbackCapture) {
context.refresh();
Neo4jQueryMethod method = reactiveNeo4jQueryMethod("orderBySpel", Pageable.class);
+ ValueExpressionDelegate delegate = getValueExpressionDelegate(context);
ReactiveStringBasedNeo4jQuery query =
ReactiveStringBasedNeo4jQuery
- .create(neo4jOperations, neo4jMappingContext,
- new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(
- context.getBeanFactory()), method, projectionFactory);
+ .create(neo4jOperations, neo4jMappingContext, delegate, method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
@@ -205,7 +204,7 @@ void literalReplacementsShouldWork() {
String.class, String.class, Sort.class);
ReactiveStringBasedNeo4jQuery query =
ReactiveStringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(context.getBeanFactory()),
+ getValueExpressionDelegate(context),
method, projectionFactory);
String s = Mono.fromSupplier(() -> {
@@ -236,7 +235,7 @@ void shouldBindParameters() {
ReactiveStringBasedNeo4jQuery repositoryQuery = spy(
ReactiveStringBasedNeo4jQuery.create(neo4jOperations,
- neo4jMappingContext, ReactiveQueryMethodEvaluationContextProvider.DEFAULT,
+ neo4jMappingContext, ValueExpressionDelegate.create(),
method, projectionFactory));
// skip conversion
@@ -258,7 +257,7 @@ void shouldResolveNamedParameters() {
ReactiveStringBasedNeo4jQuery repositoryQuery = spy(
ReactiveStringBasedNeo4jQuery.create(neo4jOperations,
- neo4jMappingContext, ReactiveQueryMethodEvaluationContextProvider.DEFAULT,
+ neo4jMappingContext, ValueExpressionDelegate.create(),
method, projectionFactory));
// skip conversion
@@ -277,6 +276,12 @@ void shouldResolveNamedParameters() {
}
}
+ private static ValueExpressionDelegate getValueExpressionDelegate(ConfigurableApplicationContext context) {
+ QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(context.getEnvironment(), context.getBeanFactory());
+ ValueExpressionDelegate delegate = new ValueExpressionDelegate(accessor, ValueExpressionDelegate.create());
+ return delegate;
+ }
+
private static Method queryMethod(String name, Class>... parameters) {
return ReflectionUtils.findMethod(TestRepository.class, name, parameters);
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 d790fa266c..108bb86766 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
@@ -57,6 +57,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.PreparedQuery;
@@ -71,12 +72,12 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
-import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.Param;
-import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ReturnedType;
-import org.springframework.data.repository.query.SpelQueryContext;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
+import org.springframework.data.repository.query.ValueExpressionQueryRewriter;
import org.springframework.util.ReflectionUtils;
/**
@@ -200,7 +201,7 @@ void shouldSelectPartTreeNeo4jQuery() {
final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations,
neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig());
+ ValueExpressionDelegate.create(), Configuration.defaultConfig());
RepositoryQuery query = lookupStrategy.resolveQuery(queryMethod("findById", Object.class),
TEST_REPOSITORY_METADATA, PROJECTION_FACTORY, namedQueries);
@@ -211,7 +212,7 @@ void shouldSelectPartTreeNeo4jQuery() {
void shouldSelectStringBasedNeo4jQuery() {
final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations,
- neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig());
+ neo4jMappingContext, ValueExpressionDelegate.create(), Configuration.defaultConfig());
RepositoryQuery query = lookupStrategy.resolveQuery(queryMethod("annotatedQueryWithValidTemplate"),
TEST_REPOSITORY_METADATA, PROJECTION_FACTORY, namedQueries);
@@ -226,7 +227,7 @@ void shouldSelectStringBasedNeo4jQueryForNamedQuery() {
when(namedQueries.getQuery(namedQueryName)).thenReturn("MATCH (n) RETURN n");
final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(neo4jOperations,
- neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, Configuration.defaultConfig());
+ neo4jMappingContext, ValueExpressionDelegate.create(), Configuration.defaultConfig());
RepositoryQuery query = lookupStrategy
.resolveQuery(queryMethod("findAllByANamedQuery"), TEST_REPOSITORY_METADATA,
@@ -241,12 +242,13 @@ class StringBasedNeo4jQueryTest {
@Test
void spelQueryContextShouldBeConfiguredCorrectly() {
-
- SpelQueryContext spelQueryContext = StringBasedNeo4jQuery.SPEL_QUERY_CONTEXT;
+ ValueExpressionQueryRewriter.EvaluatingValueExpressionQueryRewriter spelQueryContext = ValueExpressionQueryRewriter.of(
+ ValueExpressionDelegate.create(), StringBasedNeo4jQuery::parameterNameSource,
+ StringBasedNeo4jQuery::replacementSource);
String template;
String query;
- SpelQueryContext.SpelExtractor spelExtractor;
+ ValueExpressionQueryRewriter.ParsedQuery spelExtractor;
template = "MATCH (user:User) WHERE user.name = :#{#searchUser.name} and user.middleName = ?#{#searchUser.middleName} RETURN user";
@@ -272,7 +274,7 @@ void shouldDetectInvalidAnnotation() {
assertThatExceptionOfType(MappingException.class)
.isThrownBy(() -> StringBasedNeo4jQuery
.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory))
+ ValueExpressionDelegate.create(), method, projectionFactory))
.withMessage("Expected @Query annotation to have a value, but it did not");
}
@@ -283,7 +285,7 @@ void shouldDetectMissingCountQuery() {
assertThatExceptionOfType(MappingException.class)
.isThrownBy(() -> StringBasedNeo4jQuery
.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory))
+ ValueExpressionDelegate.create(), method, projectionFactory))
.withMessage("Expected paging query method to have a count query");
}
@@ -292,7 +294,7 @@ void shouldAllowMissingCountOnSlicedQuery(LogbackCapture logbackCapture) {
Neo4jQueryMethod method = neo4jQueryMethod("missingCountQueryOnSlice", Pageable.class);
StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
assertThat(logbackCapture.getFormattedMessages())
.anyMatch(s -> s.matches(
"(?s)You provided a string based query returning a slice for '.*\\.missingCountQueryOnSlice'\\. You might want to consider adding a count query if more slices than you expect are returned\\."));
@@ -303,7 +305,7 @@ void shouldDetectMissingPlaceHoldersOnPagedQuery(LogbackCapture logbackCapture)
Neo4jQueryMethod method = neo4jQueryMethod("missingPlaceHoldersOnPage", Pageable.class);
StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
assertThat(logbackCapture.getFormattedMessages())
.anyMatch(s -> s.matches(
"(?s)The custom query.*MATCH \\(n:Page\\) return n.*for '.*\\.missingPlaceHoldersOnPage' is supposed to work with a page or slicing query but does not have the required parameter placeholders `\\$skip` and `\\$limit`\\..*"));
@@ -314,7 +316,7 @@ void shouldDetectMissingPlaceHoldersOnSlicedQuery(LogbackCapture logbackCapture)
Neo4jQueryMethod method = neo4jQueryMethod("missingPlaceHoldersOnSlice", Pageable.class);
StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
assertThat(logbackCapture.getFormattedMessages())
.anyMatch(s -> s.matches(
"(?s)The custom query.*MATCH \\(n:Slice\\) return n.*is supposed to work with a page or slicing query but does not have the required parameter placeholders `\\$skip` and `\\$limit`\\..*"));
@@ -326,7 +328,7 @@ void shouldWarnWhenUsingSortedAndCustomQuery(LogbackCapture logbackCapture) {
Neo4jQueryMethod method = neo4jQueryMethod("findAllExtendedEntitiesWithCustomQuery", Sort.class);
AbstractNeo4jQuery query =
StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
@@ -353,7 +355,7 @@ void shouldWarnWhenUsingSortedPageable(LogbackCapture logbackCapture) {
Neo4jQueryMethod method = neo4jQueryMethod("noWarningsPerSe", Pageable.class);
AbstractNeo4jQuery query =
StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory);
+ ValueExpressionDelegate.create(), method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
new Object[] { PageRequest.of(1, 1, Sort.by("name").ascending()) });
@@ -383,9 +385,11 @@ void orderBySpelShouldWork(LogbackCapture logbackCapture) {
new Neo4jEvaluationContextExtension());
context.refresh();
+ ValueExpressionDelegate delegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(context), ValueExpressionParser.create());
+
Neo4jQueryMethod method = neo4jQueryMethod("orderBySpel", Pageable.class);
StringBasedNeo4jQuery query = StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- new ExtensionAwareQueryMethodEvaluationContextProvider(context.getBeanFactory()), method, projectionFactory);
+ delegate, method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
@@ -415,9 +419,11 @@ void literalReplacementsShouldWork() {
new Neo4jEvaluationContextExtension());
context.refresh();
+ ValueExpressionDelegate delegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(context), ValueExpressionParser.create());
+
Neo4jQueryMethod method = neo4jQueryMethod("makeStaticThingsDynamic", String.class, String.class, String.class, String.class, Sort.class);
StringBasedNeo4jQuery query = StringBasedNeo4jQuery.create(neo4jOperations, neo4jMappingContext,
- new ExtensionAwareQueryMethodEvaluationContextProvider(context.getBeanFactory()), method, projectionFactory);
+ delegate, method, projectionFactory);
Neo4jParameterAccessor parameterAccessor = new Neo4jParameterAccessor(
(Neo4jQueryMethod.Neo4jParameters) method.getParameters(),
@@ -442,7 +448,7 @@ void shouldBindParameters() {
String.class);
StringBasedNeo4jQuery repositoryQuery = spy(StringBasedNeo4jQuery.create(neo4jOperations,
- neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory));
+ neo4jMappingContext, ValueExpressionDelegate.create(), method, projectionFactory));
// skip conversion
doAnswer(invocation -> invocation.getArgument(0)).when(repositoryQuery).convertParameter(any());
@@ -461,7 +467,7 @@ void shouldResolveNamedParameters() {
org.neo4j.driver.types.Point.class, String.class, String.class);
StringBasedNeo4jQuery repositoryQuery = spy(StringBasedNeo4jQuery.create(neo4jOperations,
- neo4jMappingContext, QueryMethodEvaluationContextProvider.DEFAULT, method, projectionFactory));
+ neo4jMappingContext, ValueExpressionDelegate.create(), method, projectionFactory));
// skip conversion
doAnswer(invocation -> invocation.getArgument(0)).when(repositoryQuery).convertParameter(any());