diff --git a/pom.xml b/pom.xml
index de66da1866..0b1a999a37 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.4.0-SNAPSHOT
+ 4.4.x-GH-4712-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index a3dc49f892..6dd55a88bf 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.4.0-SNAPSHOT
+ 4.4.x-GH-4712-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index e33930bfd2..9a02236aad 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.4.0-SNAPSHOT
+ 4.4.x-GH-4712-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index fafe9c8793..7f6ecd1259 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.4.0-SNAPSHOT
+ 4.4.x-GH-4712-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
index 81d1ccb49d..52eef58340 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
@@ -323,7 +323,8 @@ protected List parseAggregationPipeline(String[] sourcePip
}
private AggregationOperation computePipelineStage(String source, ConvertingParameterAccessor accessor) {
- return ctx -> ctx.getMappedObject(bindParameters(source, accessor), getQueryMethod().getDomainClass());
+ return new StringAggregationOperation(source, getQueryMethod().getDomainClass(),
+ (it) -> bindParameters(it, accessor));
}
protected Document decode(String source, ParameterBindingContext bindingContext) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
index ec71ece7c3..e38168cbc6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
@@ -376,10 +376,8 @@ protected Mono> parseAggregationPipeline(String[] pip
private Mono computePipelineStage(String source, MongoParameterAccessor accessor,
ParameterBindingDocumentCodec codec) {
- return expressionEvaluator(source, accessor, codec).map(it -> {
- return ctx -> ctx.getMappedObject(decode(it.getT1(), source, accessor, it.getT2()),
- getQueryMethod().getDomainClass());
- });
+ return expressionEvaluator(source, accessor, codec).map(
+ it -> new StringAggregationOperation(source, AbstractReactiveMongoQuery.this.getQueryMethod().getDomainClass(), bsonString -> AbstractReactiveMongoQuery.this.decode(it.getT1(), bsonString, accessor, it.getT2())));
}
private Mono> expressionEvaluator(String source,
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringAggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringAggregationOperation.java
new file mode 100644
index 0000000000..45f66f9726
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringAggregationOperation.java
@@ -0,0 +1,58 @@
+/*
+ * 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.mongodb.repository.query;
+
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.bson.Document;
+import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
+import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
+
+/**
+ * @author Christoph Strobl
+ */
+class StringAggregationOperation implements AggregationOperation {
+
+ private static final Pattern OPERATOR_PATTERN = Pattern.compile("\\$\\w+");
+
+ private final String source;
+ private final Class> domainType;
+ private final Function bindFunction;
+
+ StringAggregationOperation(String source, Class> domainType, Function bindFunction) {
+
+ this.source = source;
+ this.domainType = domainType;
+ this.bindFunction = bindFunction;
+ }
+
+ @Override
+ public Document toDocument(AggregationOperationContext context) {
+ return context.getMappedObject(bindFunction.apply(source), domainType);
+ }
+
+ @Override
+ public String getOperator() {
+
+ Matcher matcher = OPERATOR_PATTERN.matcher(source);
+ if (matcher.find()) {
+ return matcher.group();
+ }
+ return AggregationOperation.super.getOperator();
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java
index 7169ba0c97..4ef61835f1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java
@@ -401,8 +401,9 @@ private BindableValue bindableValueFor(JsonToken token) {
if (matcher.find()) {
int index = computeParameterIndex(matcher.group());
- bindableValue.setValue(getBindableValueForIndex(index));
- bindableValue.setType(bsonTypeForValue(getBindableValueForIndex(index)));
+ Object bindableValueForIndex = getBindableValueForIndex(index);
+ bindableValue.setValue(bindableValueForIndex);
+ bindableValue.setType(bsonTypeForValue(bindableValueForIndex));
return bindableValue;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationOperationUnitTests.java
new file mode 100644
index 0000000000..61da176240
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationOperationUnitTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.mongodb.repository.query;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.assertj.core.api.Assertions;
+import org.bson.Document;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * @author Christoph Strobl
+ */
+public class StringBasedAggregationOperationUnitTests {
+
+ @ParameterizedTest // GH-4712
+ @ValueSource(strings = { "$project", "'$project'", "\"$project\"" })
+ void extractsAggregationOperatorFromAggregationStringWithoutBindingParameters(String operator) {
+
+ StringAggregationOperation agg = new StringAggregationOperation("{ %s : { 'fn' : 1 } }".formatted(operator),
+ Object.class, (it) -> Assertions.fail("o_O Parameter binding"));
+
+ assertThat(agg.getOperator()).isEqualTo("$project");
+ }
+
+ @Test
+ // GH-4712
+ void fallbackToParameterBindingIfAggregationOperatorCannotBeExtractedFromAggregationStringWithoutBindingParameters() {
+
+ StringAggregationOperation agg = new StringAggregationOperation("{ happy-madison : { 'fn' : 1 } }", Object.class,
+ (it) -> new Document("$project", ""));
+
+ assertThat(agg.getOperator()).isEqualTo("$project");
+ }
+}