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"); + } +}