feat: Add support for Value Expressions for Repository Query methods (#2956)
This commit is contained in:
committed by
GitHub
parent
640429b8db
commit
359e248042
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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`.
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user