feat: Add support for Value Expressions for Repository Query methods (#2956)

This commit is contained in:
Marcin Grzejszczak
2024-10-10 18:01:50 +02:00
committed by GitHub
parent 640429b8db
commit 359e248042
18 changed files with 177 additions and 126 deletions

View File

@@ -24,7 +24,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>7.4.0-SNAPSHOT</version>
<version>7.4.0-GH-2954-SNAPSHOT</version>
<name>Spring Data Neo4j</name>
<description>Next generation Object-Graph-Mapping for Spring Data.</description>

View File

@@ -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`.

View File

@@ -26,8 +26,8 @@ import org.springframework.data.projection.ProjectionFactory;
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 final class Neo4jQueryLookupStrategy implements QueryLookupStrategy {
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);

View File

@@ -44,6 +44,7 @@ import org.springframework.data.domain.Range;
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.QueryMethod;
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 @@ import org.springframework.util.Assert;
*/
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;

View File

@@ -29,13 +29,13 @@ import org.apiguardian.api.API;
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 final class Neo4jSpelSupport {
* @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 final class Neo4jSpelSupport {
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 final class Neo4jSpelSupport {
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;

View File

@@ -26,8 +26,8 @@ import org.springframework.data.projection.ProjectionFactory;
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 final class ReactiveNeo4jQueryLookupStrategy implements QueryLookupStrate
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);

View File

@@ -33,11 +33,10 @@ import org.springframework.data.neo4j.core.mapping.PropertyFilter;
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 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
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 @@ 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
* @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 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
Map<String, Object> boundParameters = bindParameters(parameterAccessor);
QueryContext queryContext = new QueryContext(
queryMethod.getRepositoryName() + "." + queryMethod.getName(),
spelEvaluator.getQueryString(),
parsedQuery.getQueryString(),
boundParameters
);
@@ -153,7 +150,7 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
Map<String, Object> resolvedParameters = new HashMap<>();
// Values from the parameter accessor can only get converted after evaluation
for (Map.Entry<String, Object> evaluatedParam : spelEvaluator.evaluate(parameterAccessor.getValues()).entrySet()) {
for (Map.Entry<String, Object> evaluatedParam : parsedQuery.evaluate(parameterAccessor.getValues()).entrySet()) {
Object value = evaluatedParam.getValue();
if (!(evaluatedParam.getValue() instanceof Neo4jSpelSupport.LiteralReplacement)) {
Neo4jQuerySupport.logParameterIfNull(evaluatedParam.getKey(), value);

View File

@@ -36,10 +36,9 @@ import org.springframework.data.neo4j.core.mapping.PropertyFilter;
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 @@ import org.springframework.util.StringUtils;
*/
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<SpelEvaluator> spelEvaluatorForCountQuery;
private final Optional<ValueExpressionQueryRewriter.QueryExpressionEvaluator> 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 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
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 @@ 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
* @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 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
Map<String, Object> boundParameters = bindParameters(parameterAccessor, true, limitModifier);
QueryContext queryContext = new QueryContext(
queryMethod.getRepositoryName() + "." + queryMethod.getName(),
spelEvaluator.getQueryString(),
parsedQuery.getQueryString(),
boundParameters
);
@@ -204,7 +199,7 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
Map<String, Object> resolvedParameters = new HashMap<>();
// Values from the parameter accessor can only get converted after evaluation
for (Entry<String, Object> evaluatedParam : spelEvaluator.evaluate(parameterAccessor.getValues()).entrySet()) {
for (Entry<String, Object> evaluatedParam : parsedQuery.evaluate(parameterAccessor.getValues()).entrySet()) {
Object value = evaluatedParam.getValue();
if (!(evaluatedParam.getValue() instanceof LiteralReplacement)) {
Neo4jQuerySupport.logParameterIfNull(evaluatedParam.getKey(), value);
@@ -237,7 +232,7 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
@Override
protected Optional<PreparedQuery<Long>> getCountQuery(Neo4jParameterAccessor parameterAccessor) {
return spelEvaluatorForCountQuery.map(SpelEvaluator::getQueryString)
return parsedCountQuery.map(ValueExpressionQueryRewriter.QueryExpressionEvaluator::getQueryString)
.map(countQuery -> {
Map<String, Object> boundParameters = bindParameters(parameterAccessor, false, UnaryOperator.identity());
QueryContext queryContext = new QueryContext(
@@ -258,7 +253,7 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
* @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 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
* @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;
}

View File

@@ -40,6 +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 Neo4jRepository} instances.
@@ -135,15 +136,10 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
.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<QueryLookupStrategy> 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

View File

@@ -40,7 +40,7 @@ import org.springframework.data.repository.core.support.RepositoryComposition.Re
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 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp
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<QueryLookupStrategy> getQueryLookupStrategy(Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
ValueExpressionDelegate valueExpressionDelegate) {
return Optional
.of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, evaluationContextProvider, cypherDSLConfiguration));
.of(new ReactiveNeo4jQueryLookupStrategy(neo4jOperations, mappingContext, valueExpressionDelegate, cypherDSLConfiguration));
}
@Override

View File

@@ -36,6 +36,11 @@ public interface ARepository extends Neo4jRepository<AnAggregateRoot, String> {
Optional<AnAggregateRoot> findByCustomQuery(String name);
// end::standard-parameter[]
// tag::property-placeholder[]
@Query("MATCH (a:AnAggregateRoot) WHERE a.name = :${foo} RETURN a")
Optional<AnAggregateRoot> findByCustomQueryWithPropertyPlaceholder();
// end::property-placeholder[]
// tag::spel[]
@Query("MATCH (a:AnAggregateRoot) WHERE a.name = :#{#pt1 + #pt2} RETURN a")
Optional<AnAggregateRoot> findByCustomQueryWithSpEL(String pt1, String pt2);

View File

@@ -22,11 +22,13 @@ import java.util.Optional;
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 @@ public class DomainEventsTest {
optionalAggregate = aRepository.findByCustomQueryWithSpEL("The ", "Root");
assertThat(optionalAggregate).isPresent();
optionalAggregate = aRepository.findByCustomQueryWithPropertyPlaceholder();
assertThat(optionalAggregate).isPresent();
}
// tag::domain-events[]

View File

@@ -23,6 +23,7 @@ import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
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 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*/
@Neo4jIntegrationTest
@Tag(Neo4jExtension.NEEDS_REACTIVE_SUPPORT)
@TestPropertySource(properties = {"foo=XYZ"})
public class ReactiveCustomTypesIT {
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
@@ -117,6 +119,15 @@ public class ReactiveCustomTypesIT {
.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 @@ public class ReactiveCustomTypesIT {
Mono<ThingWithCustomTypes> findByCustomTypeCustomSpELPropertyAccessQuery(
@Param("customType") ThingWithCustomTypes.CustomType customType);
@Query("MATCH (c:CustomTypes) WHERE c.customType = :${foo} return c")
Mono<ThingWithCustomTypes> findByCustomTypeCustomPropertyPlaceholderAccessQuery();
@Query("MATCH (c:CustomTypes) WHERE c.customType = :#{#customType} return c")
Mono<ThingWithCustomTypes> findByCustomTypeSpELObjectQuery(
@Param("customType") ThingWithCustomTypes.CustomType customType);

View File

@@ -169,6 +169,7 @@ import org.springframework.data.repository.query.FluentQuery;
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 @@ class RepositoryIT {
PersonWithAllConstructor person2;
@Nested
@TestPropertySource(properties = "foo=Test")
class Find extends IntegrationTestBase {
@Override
@@ -546,6 +548,15 @@ class RepositoryIT {
assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME);
}
@Test
void loadOptionalPersonWithAllConstructorWithPropertyPlacholder(@Autowired PersonRepository repository) {
Optional<PersonWithAllConstructor> person = repository
.getOptionalPersonViaPropertyPlaceholder();
assertThat(person).isPresent();
assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME);
}
@Test
void loadOptionalPersonWithAllConstructorWithSpelParametersAndDynamicSort(@Autowired PersonRepository repository) {

View File

@@ -99,6 +99,9 @@ public interface PersonRepository extends Neo4jRepository<PersonWithAllConstruct
Optional<PersonWithAllConstructor> getOptionalPersonViaQuery(@Param("part1") String part1,
@Param("part2") String part2);
@Query("MATCH (n:PersonWithAllConstructor{name::${foo}}) return n")
Optional<PersonWithAllConstructor> getOptionalPersonViaPropertyPlaceholder();
@Query("MATCH (n:PersonWithAllConstructor{name::#{#part1 + #part2}}) return n :#{orderBy(#sort)}")
Optional<PersonWithAllConstructor> getOptionalPersonViaQueryWithSort(@Param("part1") String part1,
@Param("part2") String part2, Sort sort);

View File

@@ -39,13 +39,14 @@ import org.junit.jupiter.params.ParameterizedTest;
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 @@ class Neo4jSpelSupportTest {
@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 @@ class Neo4jSpelSupportTest {
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 @@ class Neo4jSpelSupportTest {
String query = Neo4jSpelSupport.renderQueryIfExpressionOrReturnQuery(
"MATCH (n:#{#staticLabels}) WHERE n.name = ?#{#name} OR n.name = :?#{#name} RETURN n",
new Neo4jMappingContext(), (EntityMetadata<BikeNode>) () -> BikeNode.class, new SpelExpressionParser());
new Neo4jMappingContext(), (EntityMetadata<BikeNode>) () -> BikeNode.class, ValueExpressionParser.create());
assertThat(query).isEqualTo("MATCH (n:`Bike`:`Gravel`:`Easy Trail`) WHERE n.name = ?#{#name} OR n.name = :?#{#name} RETURN n");

View File

@@ -53,9 +53,9 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
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 @@ final class ReactiveRepositoryQueryTest {
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 @@ final class ReactiveRepositoryQueryTest {
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 @@ final class ReactiveRepositoryQueryTest {
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 @@ final class ReactiveRepositoryQueryTest {
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 @@ final class ReactiveRepositoryQueryTest {
ReactiveStringBasedNeo4jQuery repositoryQuery = spy(
ReactiveStringBasedNeo4jQuery.create(neo4jOperations,
neo4jMappingContext, ReactiveQueryMethodEvaluationContextProvider.DEFAULT,
neo4jMappingContext, ValueExpressionDelegate.create(),
method, projectionFactory));
// skip conversion
@@ -258,7 +257,7 @@ final class ReactiveRepositoryQueryTest {
ReactiveStringBasedNeo4jQuery repositoryQuery = spy(
ReactiveStringBasedNeo4jQuery.create(neo4jOperations,
neo4jMappingContext, ReactiveQueryMethodEvaluationContextProvider.DEFAULT,
neo4jMappingContext, ValueExpressionDelegate.create(),
method, projectionFactory));
// skip conversion
@@ -277,6 +276,12 @@ final class ReactiveRepositoryQueryTest {
}
}
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);

View File

@@ -57,6 +57,7 @@ import org.springframework.data.domain.PageRequest;
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.CrudRepository;
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
@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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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 @@ final class RepositoryQueryTest {
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());