diff --git a/pom.xml b/pom.xml
index 9a1889723d..1cf8761bdc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 5.0.0-SNAPSHOT
+ 5.0.x-GH-5027-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index fc88571622..fb60c00423 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
- 5.0.0-SNAPSHOT
+ 5.0.x-GH-5027-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 595e5a4250..c41ad60ea5 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 5.0.0-SNAPSHOT
+ 5.0.x-GH-5027-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index a49cd4ae98..895288d889 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -111,9 +111,11 @@
import org.springframework.data.mongodb.core.timeseries.Granularity;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.projection.EntityProjection;
+import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Optionals;
+import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -2272,8 +2274,17 @@ protected AggregationResults doAggregate(Aggregation aggregation, String
AggregationResults doAggregate(Aggregation aggregation, String collectionName, Class outputType,
QueryResultConverter super T, ? extends O> resultConverter, AggregationOperationContext context) {
- DocumentCallback callback = new QueryResultConverterCallback<>(resultConverter,
+ final DocumentCallback callback;
+ if(aggregation instanceof TypedAggregation> ta && outputType.isInterface()) {
+ EntityProjection projection = operations.introspectProjection(outputType, ta.getInputType());
+ ProjectingReadCallback cb = new ProjectingReadCallback(mongoConverter, projection, collectionName);
+ callback = new QueryResultConverterCallback<>(resultConverter,
+ cb);
+ } else {
+
+ callback = new QueryResultConverterCallback<>(resultConverter,
new ReadDocumentCallback<>(mongoConverter, outputType, collectionName));
+ }
AggregationOptions options = aggregation.getOptions();
AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java
index 602713cb77..24e2c99a63 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java
@@ -377,6 +377,9 @@ Document getMappedFields(@Nullable MongoPersistentEntity> entity, EntityProjec
mappedFields = queryMapper.getMappedFields(fields, entity);
} else {
mappedFields = propertyOperations.computeMappedFieldsForProjection(projection, fields);
+ if(projection.getMappedType().getType().isInterface()) {
+ mappedFields = queryMapper.getMappedFields(mappedFields, entity);
+ }
mappedFields = queryMapper.addMetaAttributes(mappedFields, entity);
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java
index e8dbffb19a..c376fb11fd 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java
@@ -22,13 +22,13 @@
import org.bson.Document;
import org.jspecify.annotations.NullUnmarked;
-
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
@@ -80,12 +80,7 @@ CodeBlock build() {
builder.add("\n");
- Class> outputType = queryMethod.getReturnedObjectType();
- if (MongoSimpleTypes.HOLDER.isSimpleType(outputType)) {
- outputType = Document.class;
- } else if (ClassUtils.isAssignable(AggregationResults.class, outputType)) {
- outputType = queryMethod.getReturnType().getComponentType().getType();
- }
+ Class> outputType = getOutputType(queryMethod);
if (ReflectionUtils.isVoid(queryMethod.getReturnedObjectType())) {
builder.addStatement("$L.aggregate($L, $T.class)", mongoOpsRef, aggregationVariableName, outputType);
@@ -146,7 +141,6 @@ CodeBlock build() {
builder.addStatement("return $L.aggregateStream($L, $T.class)", mongoOpsRef, aggregationVariableName,
outputType);
} else {
-
builder.addStatement("return $L.aggregate($L, $T.class).getMappedResults()", mongoOpsRef,
aggregationVariableName, outputType);
}
@@ -155,6 +149,17 @@ CodeBlock build() {
return builder.build();
}
+
+ }
+
+ private static Class> getOutputType(MongoQueryMethod queryMethod) {
+ Class> outputType = queryMethod.getReturnedObjectType();
+ if (MongoSimpleTypes.HOLDER.isSimpleType(outputType)) {
+ outputType = Document.class;
+ } else if (ClassUtils.isAssignable(AggregationResults.class, outputType) && queryMethod.getReturnType().getComponentType() != null) {
+ outputType = queryMethod.getReturnType().getComponentType().getType();
+ }
+ return outputType;
}
@NullUnmarked
@@ -173,13 +178,7 @@ static class AggregationCodeBlockBuilder {
this.context = context;
this.queryMethod = queryMethod;
- String parameterNames = StringUtils.collectionToDelimitedString(context.getAllParameterNames(), ", ");
-
- if (StringUtils.hasText(parameterNames)) {
- this.parameterNames = ", " + parameterNames;
- } else {
- this.parameterNames = "";
- }
+ this.parameterNames = StringUtils.collectionToDelimitedString(context.getAllParameterNames(), ", ");
}
AggregationCodeBlockBuilder stages(AggregationInteraction aggregation) {
@@ -231,7 +230,8 @@ private CodeBlock pipeline(String pipelineVariableName) {
builder.add(aggregationStages(context.localVariable("stages"), source.stages()));
if (StringUtils.hasText(sortParameter)) {
- builder.add(sortingStage(sortParameter));
+ Class> outputType = getOutputType(queryMethod);
+ builder.add(sortingStage(sortParameter, outputType));
}
if (StringUtils.hasText(limitParameter)) {
@@ -244,6 +244,7 @@ private CodeBlock pipeline(String pipelineVariableName) {
builder.addStatement("$T $L = createPipeline($L)", AggregationPipeline.class, pipelineVariableName,
context.localVariable("stages"));
+
return builder.build();
}
@@ -303,7 +304,7 @@ private CodeBlock aggregationStages(String stageListVariableName, Collection outputType) {
Builder builder = CodeBlock.builder();
@@ -322,8 +323,17 @@ private CodeBlock sortingStage(String sortProvider) {
builder.addStatement("$1L.append($2L.getProperty(), $2L.isAscending() ? 1 : -1);",
context.localVariable("sortDocument"), context.localVariable("order"));
builder.endControlFlow();
- builder.addStatement("stages.add(new $T($S, $L))", Document.class, "$sort",
- context.localVariable("sortDocument"));
+
+ if (outputType == Document.class || MongoSimpleTypes.HOLDER.isSimpleType(outputType)
+ || ClassUtils.isAssignable(context.getRepositoryInformation().getDomainType(), outputType)) {
+ builder.addStatement("$L.add(new $T($S, $L))", context.localVariable("stages"), Document.class, "$sort",
+ context.localVariable("sortDocument"));
+ } else {
+ builder.addStatement("$L.add(($T) _ctx -> new $T($S, _ctx.getMappedObject($L, $T.class)))",
+ context.localVariable("stages"), AggregationOperation.class, Document.class, "$sort",
+ context.localVariable("sortDocument"), outputType);
+ }
+
builder.endControlFlow();
return builder.build();
@@ -333,7 +343,7 @@ private CodeBlock pagingStage(String pageableProvider, boolean slice) {
Builder builder = CodeBlock.builder();
- builder.add(sortingStage(pageableProvider + ".getSort()"));
+ builder.add(sortingStage(pageableProvider + ".getSort()", getOutputType(queryMethod)));
builder.beginControlFlow("if ($L.isPaged())", pageableProvider);
builder.beginControlFlow("if ($L.getOffset() > 0)", pageableProvider);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java
index be30dcf357..b6cbe50833 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java
@@ -17,6 +17,7 @@
import java.util.List;
+import org.jspecify.annotations.Nullable;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
@@ -52,7 +53,7 @@ static Placeholder indexed(int position) {
* @param type
* @return
*/
- public static Shape geoJson(int index, String type) {
+ static Shape geoJson(int index, String type) {
return new GeoJsonPlaceholder(index, type);
}
@@ -62,7 +63,7 @@ public static Shape geoJson(int index, String type) {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
- public static Point point(int index) {
+ static Point point(int index) {
return new PointPlaceholder(index);
}
@@ -72,7 +73,7 @@ public static Point point(int index) {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
- public static Shape circle(int index) {
+ static Shape circle(int index) {
return new CirclePlaceholder(index);
}
@@ -82,7 +83,7 @@ public static Shape circle(int index) {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
- public static Shape box(int index) {
+ static Shape box(int index) {
return new BoxPlaceholder(index);
}
@@ -92,7 +93,7 @@ public static Shape box(int index) {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
- public static Shape sphere(int index) {
+ static Shape sphere(int index) {
return new SpherePlaceholder(index);
}
@@ -102,20 +103,23 @@ public static Shape sphere(int index) {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
- public static Shape polygon(int index) {
+ static Shape polygon(int index) {
return new PolygonPlaceholder(index);
}
+ static RegexPlaceholder regex(int index, @Nullable String options) {
+ return new RegexPlaceholder(index, options);
+ }
+
/**
* A placeholder expression used when rending queries to JSON.
*
* @since 5.0
* @author Christoph Strobl
*/
- public interface Placeholder {
+ interface Placeholder {
String getValue();
-
}
/**
@@ -139,7 +143,7 @@ private static class PointPlaceholder extends Point implements Placeholder {
private final int index;
- public PointPlaceholder(int index) {
+ PointPlaceholder(int index) {
super(Double.NaN, Double.NaN);
this.index = index;
}
@@ -184,7 +188,7 @@ private static class CirclePlaceholder extends Circle implements Placeholder {
private final int index;
- public CirclePlaceholder(int index) {
+ CirclePlaceholder(int index) {
super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
this.index = index;
}
@@ -205,7 +209,7 @@ private static class BoxPlaceholder extends Box implements Placeholder {
private final int index;
- public BoxPlaceholder(int index) {
+ BoxPlaceholder(int index) {
super(new PointPlaceholder(index), new PointPlaceholder(index));
this.index = index;
}
@@ -226,7 +230,7 @@ private static class SpherePlaceholder extends Sphere implements Placeholder {
private final int index;
- public SpherePlaceholder(int index) {
+ SpherePlaceholder(int index) {
super(new PointPlaceholder(index), Distance.of(1, Metrics.NEUTRAL)); //
this.index = index;
}
@@ -247,7 +251,7 @@ private static class PolygonPlaceholder extends Polygon implements Placeholder {
private final int index;
- public PolygonPlaceholder(int index) {
+ PolygonPlaceholder(int index) {
super(new PointPlaceholder(index), new PointPlaceholder(index), new PointPlaceholder(index),
new PointPlaceholder(index));
this.index = index;
@@ -265,4 +269,29 @@ public String toString() {
}
+ static class RegexPlaceholder implements Placeholder {
+
+ private final int index;
+ private final @Nullable String options;
+
+ RegexPlaceholder(int index, @Nullable String options) {
+ this.index = index;
+ this.options = options;
+ }
+
+ @Nullable String regexOptions() {
+ return options;
+ }
+
+ @Override
+ public String getValue() {
+ return "?" + index;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+ }
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java
index d48983ba7e..b0c998e652 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java
@@ -19,11 +19,11 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.regex.Pattern;
import org.bson.conversions.Bson;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
-
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Score;
@@ -47,6 +47,7 @@
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.repository.VectorSearch;
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.Placeholder;
+import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
import org.springframework.data.mongodb.repository.query.MongoParameterAccessor;
import org.springframework.data.mongodb.repository.query.MongoQueryCreator;
@@ -55,6 +56,8 @@
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.Part;
+import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
+import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ClassUtils;
@@ -81,14 +84,14 @@ AotStringQuery createQuery(PartTree partTree, QueryMethod queryMethod, Method so
? mqm.isSearchQuery() || source.isAnnotationPresent(VectorSearch.class)
: source.isAnnotationPresent(VectorSearch.class);
- Query query = new AotMongoQueryCreator(partTree,
- new PlaceholderConvertingParameterAccessor(new PlaceholderParameterAccessor(queryMethod)), mappingContext,
- geoNear, searchQuery).createQuery();
+ PlaceholderParameterAccessor placeholderAccessor = new PlaceholderParameterAccessor(partTree, queryMethod);
+ Query query = new AotMongoQueryCreator(partTree, new PlaceholderConvertingParameterAccessor(placeholderAccessor),
+ mappingContext, geoNear, searchQuery).createQuery();
if (partTree.isLimiting()) {
query.limit(partTree.getMaxResults());
}
- return new AotStringQuery(query);
+ return new AotStringQuery(query, placeholderAccessor.getPlaceholders());
}
static class AotMongoQueryCreator extends MongoQueryCreator {
@@ -117,6 +120,25 @@ protected Criteria regex(Criteria criteria, Object param) {
protected Criteria exists(Criteria criteria, Object param) {
return param instanceof Placeholder p ? criteria.raw("$exists", p) : super.exists(criteria, param);
}
+
+ @Override
+ protected Criteria createContainingCriteria(Part part, MongoPersistentProperty property, Criteria criteria,
+ Object param) {
+
+ if (part.getType().equals(Type.LIKE)) {
+ return criteria.is(param);
+ }
+
+ if(part.getType().equals(Type.NOT_LIKE)) {
+ return criteria.raw("$not", param);
+ }
+
+ if (param instanceof RegexPlaceholder) {
+ return criteria.raw("$regex", param);
+ }
+
+ return super.createContainingCriteria(part, property, criteria, param);
+ }
}
static class PlaceholderConvertingParameterAccessor extends ConvertingParameterAccessor {
@@ -157,12 +179,34 @@ static class PlaceholderParameterAccessor implements MongoParameterAccessor {
private final List