diff --git a/pom.xml b/pom.xml
index 0ce76b516..03142c67c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-cassandra-parent
- 4.4.0-SNAPSHOT
+ 4.4.0-GH-1522-SNAPSHOT
pom
Spring Data for Apache Cassandra
@@ -97,7 +97,7 @@
0.5.4
1.01
multi
- 3.4.0-SNAPSHOT
+ 3.4.0-GH-3049-SNAPSHOT
diff --git a/spring-data-cassandra-distribution/pom.xml b/spring-data-cassandra-distribution/pom.xml
index 3086d4bc7..04a109521 100644
--- a/spring-data-cassandra-distribution/pom.xml
+++ b/spring-data-cassandra-distribution/pom.xml
@@ -8,7 +8,7 @@
org.springframework.data
spring-data-cassandra-parent
- 4.4.0-SNAPSHOT
+ 4.4.0-GH-1522-SNAPSHOT
../pom.xml
diff --git a/spring-data-cassandra/pom.xml b/spring-data-cassandra/pom.xml
index 5b3ec2954..e2e24834a 100644
--- a/spring-data-cassandra/pom.xml
+++ b/spring-data-cassandra/pom.xml
@@ -8,7 +8,7 @@
org.springframework.data
spring-data-cassandra-parent
- 4.4.0-SNAPSHOT
+ 4.4.0-GH-1522-SNAPSHOT
../pom.xml
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/BindingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/BindingContext.java
index d13732e38..c52561507 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/BindingContext.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/BindingContext.java
@@ -19,7 +19,7 @@
import java.util.Collections;
import java.util.List;
-import org.springframework.data.mapping.model.SpELExpressionEvaluator;
+import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.lang.Nullable;
@@ -39,13 +39,13 @@ class BindingContext {
private final List bindings;
- private final SpELExpressionEvaluator evaluator;
+ private final ValueExpressionEvaluator evaluator;
/**
* Create new {@link BindingContext}.
*/
- public BindingContext(CassandraParameters parameters, ParameterAccessor parameterAccessor,
- List bindings, SpELExpressionEvaluator evaluator) {
+ BindingContext(CassandraParameters parameters, ParameterAccessor parameterAccessor,
+ List bindings, ValueExpressionEvaluator evaluator) {
this.parameters = parameters;
this.parameterAccessor = parameterAccessor;
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java
new file mode 100644
index 000000000..258a21bd8
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.cassandra.repository.query;
+
+import org.springframework.data.expression.ValueEvaluationContext;
+import org.springframework.data.expression.ValueExpression;
+import org.springframework.data.expression.ValueExpressionParser;
+import org.springframework.data.mapping.model.ValueExpressionEvaluator;
+
+/**
+ * @author Marcin Grzejszczak
+ * @author Mark Paluch
+ */
+class ContextualValueExpressionEvaluator implements ValueExpressionEvaluator {
+
+ private final ValueExpressionParser parser;
+
+ public ContextualValueExpressionEvaluator(ValueExpressionParser parser, ValueEvaluationContext evaluationContext) {
+ this.parser = parser;
+ this.evaluationContext = evaluationContext;
+ }
+
+ private final ValueEvaluationContext evaluationContext;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T evaluate(String expressionString) {
+ ValueExpression expression = parser.parse(expressionString);
+ return (T) expression.evaluate(evaluationContext);
+ }
+}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java
deleted file mode 100644
index e3a25b719..000000000
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2020-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.data.cassandra.repository.query;
-
-import org.springframework.data.mapping.model.SpELExpressionEvaluator;
-import org.springframework.expression.EvaluationContext;
-import org.springframework.expression.ExpressionParser;
-
-/**
- * Simple {@link SpELExpressionEvaluator} implementation using {@link ExpressionParser} and {@link EvaluationContext}.
- *
- * @author Mark Paluch
- * @since 3.1
- */
-class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator {
-
- private final ExpressionParser parser;
-
- private final EvaluationContext context;
-
- DefaultSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
- this.parser = parser;
- this.context = context;
- }
-
- /**
- * Return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
- *
- * @return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
- */
- public static SpELExpressionEvaluator unsupported() {
- return NoOpExpressionEvaluator.INSTANCE;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public T evaluate(String expression) {
- return (T) parser.parseExpression(expression).getValue(context, Object.class);
- }
-
- /**
- * {@link SpELExpressionEvaluator} that does not support SpEL evaluation.
- *
- * @author Mark Paluch
- */
- enum NoOpExpressionEvaluator implements SpELExpressionEvaluator {
-
- INSTANCE;
-
- @Override
- public T evaluate(String expression) {
- throw new UnsupportedOperationException("Expression evaluation not supported");
- }
- }
-}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/QueryStatementCreator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/QueryStatementCreator.java
index 77ad3e3bd..523f45b1c 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/QueryStatementCreator.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/QueryStatementCreator.java
@@ -32,7 +32,7 @@
import org.springframework.data.cassandra.repository.Query.Idempotency;
import org.springframework.data.domain.Limit;
import org.springframework.data.mapping.context.MappingContext;
-import org.springframework.data.mapping.model.SpELExpressionEvaluator;
+import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
@@ -249,7 +249,7 @@ private boolean allowsFiltering() {
* @return the {@link Statement}.
*/
SimpleStatement select(StringBasedQuery stringBasedQuery, CassandraParameterAccessor parameterAccessor,
- SpELExpressionEvaluator evaluator) {
+ ValueExpressionEvaluator evaluator) {
try {
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java
index b00510aac..b112e567c 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java
@@ -17,11 +17,17 @@
import reactor.core.publisher.Mono;
+import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.Query;
-import org.springframework.data.mapping.model.SpELExpressionEvaluator;
+import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
+import org.springframework.data.expression.ValueEvaluationContextProvider;
+import org.springframework.data.expression.ValueExpressionParser;
+import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -37,6 +43,7 @@
* index-based and expression parameters that are resolved during query execution.
*
* @author Mark Paluch
+ * @author Marcin Grzejszczak
* @see org.springframework.data.cassandra.repository.Query
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery
* @since 2.0
@@ -51,8 +58,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
private final boolean isExistsQuery;
- private final ExpressionParser expressionParser;
- private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
+ private final ValueExpressionDelegate delegate;
+
+ private final ReactiveValueEvaluationContextProvider valueEvaluationContextProvider;
/**
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
@@ -66,7 +74,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
+ * @deprecated since 4.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
+ @Deprecated(since = "4.4")
public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMethod,
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
@@ -86,19 +96,59 @@ public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMetho
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
+ * @deprecated since 4.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
+ @Deprecated(since = "4.4")
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
+ this(query, method, operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser)));
+ }
+
+ /**
+ * Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
+ * {@link ReactiveCassandraOperations}, {@link ValueExpressionDelegate}
+ *
+ * @param queryMethod {@link ReactiveCassandraQueryMethod} on which this query is based.
+ * @param operations {@link ReactiveCassandraOperations} used to perform data access in Cassandra.
+ * @param delegate {@link ValueExpressionDelegate} used to parse expressions in the query.
+ * @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
+ * @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
+ * @since 4.4
+ */
+ public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMethod,
+ ReactiveCassandraOperations operations, ValueExpressionDelegate delegate) {
+
+ this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, delegate);
+ }
+
+ /**
+ * Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
+ * {@link ReactiveCassandraOperations}, {@link ValueExpressionDelegate}
+ *
+ * @param method {@link ReactiveCassandraQueryMethod} on which this query is based.
+ * @param operations {@link ReactiveCassandraOperations} used to perform data access in Cassandra.
+ * @param delegate {@link SpelExpressionParser} used to parse expressions in the query.
+ * @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
+ * @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
+ * @since 4.4
+ */
+ public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
+ ReactiveCassandraOperations operations, ValueExpressionDelegate delegate) {
+
super(method, operations);
Assert.hasText(query, "Query must not be empty");
- this.expressionParser = expressionParser;
- this.evaluationContextProvider = evaluationContextProvider;
+ this.delegate = delegate;
- this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
+ this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), delegate);
+
+ ValueEvaluationContextProvider valueContextProvider = delegate.createValueContextProvider(
+ method.getParameters());
+ Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
+ this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
if (method.hasAnnotatedQuery()) {
@@ -126,10 +176,9 @@ public Mono createQuery(CassandraParameterAccessor parameterAcc
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(
getReactiveCassandraOperations().getConverter(), parameterAccessor);
- Mono spelEvaluator = getSpelEvaluatorFor(query.getExpressionDependencies(),
- parameterAccessorToUse);
- return spelEvaluator.map(it -> getQueryStatementCreator().select(query, parameterAccessorToUse, it));
+ return getValueExpressionEvaluatorLater(query.getExpressionDependencies(), parameterAccessor)
+ .map(it -> getQueryStatementCreator().select(query, parameterAccessorToUse, it));
}
@Override
@@ -152,21 +201,9 @@ protected boolean isModifyingQuery() {
return false;
}
- /**
- * Obtain a {@link Mono publisher} emitting the {@link SpELExpressionEvaluator} suitable to evaluate expressions
- * backed by the given dependencies.
- *
- * @param dependencies must not be {@literal null}.
- * @param accessor must not be {@literal null}.
- * @return a {@link Mono} emitting the {@link SpELExpressionEvaluator} when ready.
- */
- private Mono getSpelEvaluatorFor(ExpressionDependencies dependencies,
+ private Mono getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
CassandraParameterAccessor accessor) {
-
- return evaluationContextProvider
- .getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
- .map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
- evaluationContext))
- .defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
+ return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
+ .map(evaluationContext -> new ContextualValueExpressionEvaluator(delegate, evaluationContext));
}
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java
index c2ef4a655..d7309c2cd 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java
@@ -15,10 +15,14 @@
*/
package org.springframework.data.cassandra.repository.query;
+import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.Query;
+import org.springframework.data.expression.ValueEvaluationContext;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
-import org.springframework.expression.EvaluationContext;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -33,6 +37,7 @@
*
* @author Matthew Adams
* @author Mark Paluch
+ * @author Marcin Grzejszczak
* @see org.springframework.data.cassandra.repository.Query
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery
*/
@@ -46,8 +51,7 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
private final boolean isExistsQuery;
- private final ExpressionParser expressionParser;
- private final QueryMethodEvaluationContextProvider evaluationContextProvider;
+ private final ValueExpressionDelegate valueExpressionDelegate;
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
@@ -60,35 +64,52 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
+ * @deprecated use the constructor version with {@link ValueExpressionDelegate}
*/
+ @Deprecated(since = "4.4")
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
}
+ /**
+ * Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
+ * {@link CassandraOperations}, {@link ValueExpressionDelegate}.
+ *
+ * @param queryMethod {@link CassandraQueryMethod} on which this query is based.
+ * @param operations {@link CassandraOperations} used to perform data access in Cassandra.
+ * @param valueExpressionDelegate {@link ValueExpressionDelegate} used to parse expressions in the query.
+ * @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
+ * @see org.springframework.data.cassandra.core.CassandraOperations
+ * @since 4.4
+ */
+ public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
+ ValueExpressionDelegate valueExpressionDelegate) {
+
+ this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, valueExpressionDelegate);
+ }
+
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
- * {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
+ * {@link CassandraOperations}, {@link ValueExpressionDelegate}.
*
* @param query {@link String} containing the Apache Cassandra CQL query to execute.
* @param method {@link CassandraQueryMethod} on which this query is based.
* @param operations {@link CassandraOperations} used to perform data access in Cassandra.
- * @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
- * @param evaluationContextProvider {@link QueryMethodEvaluationContextProvider} used to access the potentially shared
- * {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
+ * @param valueExpressionDelegate {@link ValueExpressionDelegate} used to parse expressions in the query.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
+ * @since 4.4
*/
public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
- ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ ValueExpressionDelegate valueExpressionDelegate) {
super(method, operations);
- this.expressionParser = expressionParser;
- this.evaluationContextProvider = evaluationContextProvider;
+ this.valueExpressionDelegate = valueExpressionDelegate;
- this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
+ this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), valueExpressionDelegate);
if (method.hasAnnotatedQuery()) {
@@ -106,6 +127,26 @@ public StringBasedCassandraQuery(String query, CassandraQueryMethod method, Cass
}
}
+ /**
+ * Create a new {@link StringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
+ * {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
+ *
+ * @param query {@link String} containing the Apache Cassandra CQL query to execute.
+ * @param method {@link CassandraQueryMethod} on which this query is based.
+ * @param operations {@link CassandraOperations} used to perform data access in Cassandra.
+ * @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
+ * @param evaluationContextProvider {@link QueryMethodEvaluationContextProvider} used to access the potentially shared
+ * {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
+ * @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
+ * @see org.springframework.data.cassandra.core.CassandraOperations
+ * @deprecated use the constructor version with {@link ValueExpressionDelegate}
+ */
+ @Deprecated(since = "4.4")
+ public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
+ ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ this(query, method, operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser)));
+ }
+
protected StringBasedQuery getStringBasedQuery() {
return this.stringBasedQuery;
}
@@ -116,11 +157,11 @@ public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor)
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(getOperations().getConverter(),
parameterAccessor);
- EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(
- getQueryMethod().getParameters(), parameterAccessorToUse.getValues(), query.getExpressionDependencies());
+ ValueEvaluationContext evaluationContext = valueExpressionDelegate.createValueContextProvider(
+ getQueryMethod().getParameters()).getEvaluationContext(parameterAccessorToUse.getValues(), query.getExpressionDependencies());
return getQueryStatementCreator().select(query, parameterAccessorToUse,
- new DefaultSpELExpressionEvaluator(expressionParser, evaluationContext));
+ new ContextualValueExpressionEvaluator(valueExpressionDelegate, evaluationContext));
}
@Override
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java
index 14f1a9968..c34f348aa 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java
@@ -17,14 +17,16 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.cassandra.repository.query.BindingContext.ParameterBinding;
-import org.springframework.data.mapping.model.SpELExpressionEvaluator;
+import org.springframework.data.mapping.model.ValueExpressionEvaluator;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.ExpressionDependencies;
-import org.springframework.expression.ExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -35,6 +37,7 @@
* String-based Query abstracting a CQL query with parameter bindings.
*
* @author Mark Paluch
+ * @author Marcin Grzejszczak
* @since 2.0
*/
class StringBasedQuery {
@@ -43,21 +46,20 @@ class StringBasedQuery {
private final CassandraParameters parameters;
- private final ExpressionParser expressionParser;
+ private final ValueExpressionDelegate expressionParser;
private final List queryParameterBindings = new ArrayList<>();
private final ExpressionDependencies expressionDependencies;
/**
- * Create a new {@link StringBasedQuery} given {@code query}, {@link CassandraParameters} and
- * {@link ExpressionParser}.
+ * Create a new {@link StringBasedQuery} given {@code query}, {@link CassandraParameters} and {@link ValueExpressionDelegate}.
*
* @param query must not be empty.
* @param parameters must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
*/
- StringBasedQuery(String query, CassandraParameters parameters, ExpressionParser expressionParser) {
+ StringBasedQuery(String query, CassandraParameters parameters, ValueExpressionDelegate expressionParser) {
this.query = ParameterBindingParser.INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
this.queryParameterBindings);
@@ -77,7 +79,7 @@ private ExpressionDependencies createExpressionDependencies() {
for (ParameterBinding binding : queryParameterBindings) {
if (binding.isExpression()) {
dependencies
- .add(ExpressionDependencies.discover(expressionParser.parseExpression(binding.getRequiredExpression())));
+ .add(expressionParser.parse(binding.getRequiredExpression()).getExpressionDependencies());
}
}
@@ -100,7 +102,7 @@ public ExpressionDependencies getExpressionDependencies() {
* @param evaluator must not be {@literal null}.
* @return the bound String query containing formatted parameters.
*/
- public SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, SpELExpressionEvaluator evaluator) {
+ SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, ValueExpressionEvaluator evaluator) {
Assert.notNull(parameterAccessor, "CassandraParameterAccessor must not be null");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
@@ -176,6 +178,10 @@ enum ParameterBindingParser {
private static final Pattern NAMED_PARAMETER_BINDING_PATTERN = Pattern.compile("\\:(\\w+)");
private static final Pattern INDEX_BASED_EXPRESSION_PATTERN = Pattern.compile("\\?\\#\\{");
private static final Pattern NAME_BASED_EXPRESSION_PATTERN = Pattern.compile("\\:\\#\\{");
+ private static final Pattern INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN = Pattern.compile("\\?\\$\\{");
+ private static final Pattern NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN = Pattern.compile("\\:\\$\\{");
+
+ private static final Set VALUE_EXPRESSION_PATTERNS = Set.of(INDEX_BASED_EXPRESSION_PATTERN, NAME_BASED_EXPRESSION_PATTERN, INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN, NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN);
private static final String ARGUMENT_PLACEHOLDER = "?_param_?";
@@ -217,7 +223,7 @@ private static String transformQueryAndCollectExpressionParametersIntoBindings(S
int exprStart = matcher.start();
currentPosition = exprStart;
- if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
+ if (isValueExpression(matcher)) {
// eat parameter expression
int curlyBraceOpenCount = 1;
currentPosition += 3;
@@ -233,7 +239,6 @@ private static String transformQueryAndCollectExpressionParametersIntoBindings(S
default:
}
}
-
result.append(input.subSequence(startIndex, exprStart));
} else {
result.append(input.subSequence(startIndex, exprStart));
@@ -241,10 +246,10 @@ private static String transformQueryAndCollectExpressionParametersIntoBindings(S
result.append(ARGUMENT_PLACEHOLDER);
- if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
+ if (isValueExpression(matcher)) {
bindings.add(
BindingContext.ParameterBinding
- .expression(input.substring(exprStart + 3, currentPosition - 1), true));
+ .expression(input.substring(exprStart + 1, currentPosition), true));
} else {
if (matcher.pattern() == INDEX_PARAMETER_BINDING_PATTERN) {
bindings
@@ -262,20 +267,26 @@ private static String transformQueryAndCollectExpressionParametersIntoBindings(S
return result.append(input.subSequence(currentPosition, input.length())).toString();
}
+ private static boolean isValueExpression(Matcher matcher) {
+ return VALUE_EXPRESSION_PATTERNS.contains(matcher.pattern());
+ }
+
@Nullable
- private static Matcher findNextBindingOrExpression(String input, int position) {
+ private static Matcher findNextBindingOrExpression(String input, int startPosition) {
- List matchers = new ArrayList<>();
+ List matchers = new ArrayList<>(6);
matchers.add(INDEX_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(NAMED_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(INDEX_BASED_EXPRESSION_PATTERN.matcher(input));
matchers.add(NAME_BASED_EXPRESSION_PATTERN.matcher(input));
+ matchers.add(INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
+ matchers.add(NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
- TreeMap matcherMap = new TreeMap<>();
+ Map matcherMap = new TreeMap<>();
for (Matcher matcher : matchers) {
- if (matcher.find(position)) {
+ if (matcher.find(startPosition)) {
matcherMap.put(matcher.start(), matcher);
}
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
index e235dbd62..49ce905aa 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
@@ -33,13 +33,11 @@
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
+import org.springframework.data.repository.query.CachingValueExpressionDelegate;
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.RepositoryQuery;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.lang.Nullable;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.util.Assert;
/**
@@ -53,8 +51,6 @@
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
- private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
-
private final MappingContext extends CassandraPersistentEntity>, CassandraPersistentProperty> mappingContext;
private final CassandraOperations operations;
@@ -100,30 +96,18 @@ public CassandraEntityInformation getEntityInformation(Class d
}
@Override
- protected Optional getQueryLookupStrategy(@Nullable Key key,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
-
- return Optional.of(new CassandraQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
+ protected Optional getQueryLookupStrategy(Key key,
+ ValueExpressionDelegate valueExpressionDelegate) {
+ return Optional.of(new CassandraQueryLookupStrategy(operations,
+ new CachingValueExpressionDelegate(valueExpressionDelegate), mappingContext));
}
- private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
-
- private final QueryMethodEvaluationContextProvider evaluationContextProvider;
-
- private final MappingContext extends CassandraPersistentEntity>, CassandraPersistentProperty> mappingContext;
- private final CassandraOperations operations;
-
- private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
-
- CassandraQueryLookupStrategy(CassandraOperations operations,
- QueryMethodEvaluationContextProvider evaluationContextProvider,
- MappingContext extends CassandraPersistentEntity>, CassandraPersistentProperty> mappingContext) {
-
- this.operations = operations;
- this.evaluationContextProvider = evaluationContextProvider;
- this.mappingContext = mappingContext;
- }
+ private record CassandraQueryLookupStrategy(CassandraOperations operations,
+ ValueExpressionDelegate valueExpressionDelegate,
+ MappingContext extends CassandraPersistentEntity>, CassandraPersistentProperty> mappingContext)
+ implements
+ QueryLookupStrategy {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
@@ -134,10 +118,9 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
- return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
- evaluationContextProvider);
+ return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, valueExpressionDelegate);
} else if (queryMethod.hasAnnotatedQuery()) {
- return new StringBasedCassandraQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
+ return new StringBasedCassandraQuery(queryMethod, operations, valueExpressionDelegate);
} else {
return new PartTreeCassandraQuery(queryMethod, operations);
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java
index f95efb244..25d7a5868 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java
@@ -32,26 +32,22 @@
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
+import org.springframework.data.repository.query.CachingValueExpressionDelegate;
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.ReactiveQueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.lang.Nullable;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.util.Assert;
/**
* Factory to create {@link org.springframework.data.cassandra.repository.ReactiveCassandraRepository} instances.
*
* @author Mark Paluch
+ * @author Marcin Grzejszczak
* @since 2.0
*/
public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactorySupport {
- private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
-
private final ReactiveCassandraOperations operations;
private final MappingContext extends CassandraPersistentEntity>, ? extends CassandraPersistentProperty> mappingContext;
@@ -67,8 +63,6 @@ public ReactiveCassandraRepositoryFactory(ReactiveCassandraOperations cassandraO
this.operations = cassandraOperations;
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
-
- setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
}
@Override
@@ -90,10 +84,10 @@ protected Object getTargetRepository(RepositoryInformation information) {
}
@Override
- protected Optional getQueryLookupStrategy(@Nullable Key key,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ protected Optional getQueryLookupStrategy(Key key,
+ ValueExpressionDelegate valueExpressionDelegate) {
return Optional.of(new CassandraQueryLookupStrategy(operations,
- (ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, mappingContext));
+ new CachingValueExpressionDelegate(valueExpressionDelegate), mappingContext));
}
@SuppressWarnings("unchecked")
@@ -110,24 +104,10 @@ public CassandraEntityInformation getEntityInformation(Class d
*
* @author Mark Paluch
*/
- private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
-
- private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
-
- private final ReactiveCassandraOperations operations;
-
- private final MappingContext extends CassandraPersistentEntity>, ? extends CassandraPersistentProperty> mappingContext;
-
- private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
-
- CassandraQueryLookupStrategy(ReactiveCassandraOperations operations,
- ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
- MappingContext extends CassandraPersistentEntity>, ? extends CassandraPersistentProperty> mappingContext) {
-
- this.evaluationContextProvider = evaluationContextProvider;
- this.operations = operations;
- this.mappingContext = mappingContext;
- }
+ private record CassandraQueryLookupStrategy(ReactiveCassandraOperations operations, ValueExpressionDelegate delegate,
+ MappingContext extends CassandraPersistentEntity>, ? extends CassandraPersistentProperty> mappingContext)
+ implements
+ QueryLookupStrategy {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
@@ -141,11 +121,9 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
- return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
- evaluationContextProvider);
+ return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, delegate);
} else if (queryMethod.hasAnnotatedQuery()) {
- return new ReactiveStringBasedCassandraQuery(queryMethod, operations, expressionParser,
- evaluationContextProvider);
+ return new ReactiveStringBasedCassandraQuery(queryMethod, operations, delegate);
} else {
return new ReactivePartTreeCassandraQuery(queryMethod, operations);
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java
index 9ff84fd38..41c9fa139 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java
@@ -28,25 +28,27 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.data.cassandra.ReactiveSession;
+
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
-import org.springframework.data.cassandra.core.cql.ReactiveCqlOperations;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.repository.Consistency;
import org.springframework.data.cassandra.repository.Query;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
-import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
+import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ReflectionUtils;
import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
@@ -56,19 +58,19 @@
* Unit tests for {@link StringBasedCassandraQuery}.
*
* @author Mark Paluch
+ * @author Marcin Grzejszczak
*/
@ExtendWith(MockitoExtension.class)
class ReactiveStringBasedCassandraQueryUnitTests {
- private static final SpelExpressionParser PARSER = new SpelExpressionParser();
+ private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
@Mock private ReactiveCassandraOperations operations;
- @Mock private ReactiveCqlOperations cqlOperations;
- @Mock private ReactiveSession reactiveSession;
private MappingCassandraConverter converter;
private ProjectionFactory factory;
private RepositoryMetadata metadata;
+ private MockEnvironment mockEnvironment = new MockEnvironment();
@BeforeEach
@SuppressWarnings("unchecked")
@@ -143,6 +145,22 @@ void shouldUseSpelExtension() {
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
+ @Test // GH-1522
+ void shouldUsePropertyPlaceholder() {
+
+ mockEnvironment.withProperty("someProp", "Walter");
+
+ ReactiveStringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");
+
+ CassandraParametersParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(
+ cassandraQuery.getQueryMethod());
+
+ SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor).block();
+
+ assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname=?;");
+ assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
+ }
+
private ReactiveStringBasedCassandraQuery getQueryMethod(String name, Class>... args) {
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
@@ -150,11 +168,10 @@ private ReactiveStringBasedCassandraQuery getQueryMethod(String name, Class>..
ReactiveCassandraQueryMethod queryMethod = new ReactiveCassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
- ReactiveExtensionAwareQueryMethodEvaluationContextProvider provider = new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(
- Arrays.asList(MyReactiveExtension.INSTANCE, MyDefunctExtension.INSTANCE));
+ QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
+ mockEnvironment, Arrays.asList(MyReactiveExtension.INSTANCE, MyDefunctExtension.INSTANCE));
- return new ReactiveStringBasedCassandraQuery(queryMethod, operations, PARSER,
- provider);
+ return new ReactiveStringBasedCassandraQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER));
}
@SuppressWarnings("unused")
@@ -170,6 +187,9 @@ private interface SampleRepository extends Repository {
@Query("SELECT * FROM person WHERE lastname=:#{getName()};")
Person findBySpel();
+ @Query("SELECT * FROM person WHERE lastname=:${someProp};")
+ Person findByPropertyPlaceholder();
+
}
public static class MyReactiveExtensionObject implements EvaluationContextExtension {
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java
index 71df5439a..abfb8ef6f 100755
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java
@@ -25,6 +25,7 @@
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -33,6 +34,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
@@ -43,15 +45,18 @@
import org.springframework.data.cassandra.repository.Consistency;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
+import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
-import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.QueryCreationException;
+import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ReflectionUtils;
import com.datastax.oss.driver.api.core.CqlIdentifier;
@@ -67,11 +72,12 @@
* @author Matthew T. Adams
* @author Oliver Gierke
* @author Mark Paluch
+ * @author Marcin Grzejszczak
*/
@ExtendWith(MockitoExtension.class)
class StringBasedCassandraQueryUnitTests {
- private static final SpelExpressionParser PARSER = new SpelExpressionParser();
+ private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
@Mock private CassandraOperations operations;
@Mock private UdtValue udtValue;
@@ -80,6 +86,7 @@ class StringBasedCassandraQueryUnitTests {
private RepositoryMetadata metadata;
private MappingCassandraConverter converter;
private ProjectionFactory factory;
+ private MockEnvironment environment = new MockEnvironment();
@BeforeEach
void setUp() {
@@ -293,6 +300,21 @@ void bindsConditionalExpressionParameterCorrectly() {
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
+ @Test // GH-1522
+ void bindsPropertyPlaceholderParameterCorrectly() {
+
+ environment.withProperty("someParam", "Walter");
+
+ StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");
+ CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
+ cassandraQuery.getQueryMethod());
+
+ SimpleStatement actual = cassandraQuery.createQuery(accessor);
+
+ assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname = ?;");
+ assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
+ }
+
@Test // DATACASS-117
void bindsReusedParametersCorrectly() {
@@ -431,8 +453,10 @@ private StringBasedCassandraQuery getQueryMethod(String name, Class>... args)
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
- return new StringBasedCassandraQuery(queryMethod, operations, PARSER,
- ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
+ QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
+ environment, Collections.emptySet());
+
+ return new StringBasedCassandraQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER));
}
@SuppressWarnings("unused")
@@ -480,6 +504,9 @@ private interface SampleRepository extends Repository {
@Query("SELECT * FROM person WHERE lastname = :#{#lastname == 'Matthews' ? 'Woohoo' : #lastname};")
Person findByConditionalExpressionParameter(@Param("lastname") String lastname);
+ @Query("SELECT * FROM person WHERE lastname = :${someParam};")
+ Person findByPropertyPlaceholder();
+
@Query("SELECT * FROM person WHERE lastname=?0 AND firstname=?1;")
Person findByLastnameAndFirstname(String lastname, String firstname);