|
|
|
@ -17,7 +17,6 @@ package org.springframework.data.mongodb.repository.query; |
|
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.function.LongUnaryOperator; |
|
|
|
|
|
|
|
import java.util.stream.Stream; |
|
|
|
import java.util.stream.Stream; |
|
|
|
|
|
|
|
|
|
|
|
import org.bson.Document; |
|
|
|
import org.bson.Document; |
|
|
|
@ -26,11 +25,7 @@ import org.springframework.data.domain.Pageable; |
|
|
|
import org.springframework.data.domain.SliceImpl; |
|
|
|
import org.springframework.data.domain.SliceImpl; |
|
|
|
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; |
|
|
|
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; |
|
|
|
import org.springframework.data.mongodb.core.MongoOperations; |
|
|
|
import org.springframework.data.mongodb.core.MongoOperations; |
|
|
|
import org.springframework.data.mongodb.core.aggregation.Aggregation; |
|
|
|
|
|
|
|
import org.springframework.data.mongodb.core.aggregation.AggregationOptions; |
|
|
|
|
|
|
|
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; |
|
|
|
|
|
|
|
import org.springframework.data.mongodb.core.aggregation.AggregationResults; |
|
|
|
import org.springframework.data.mongodb.core.aggregation.AggregationResults; |
|
|
|
import org.springframework.data.mongodb.core.aggregation.TypedAggregation; |
|
|
|
|
|
|
|
import org.springframework.data.mongodb.core.convert.MongoConverter; |
|
|
|
import org.springframework.data.mongodb.core.convert.MongoConverter; |
|
|
|
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; |
|
|
|
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; |
|
|
|
import org.springframework.data.mongodb.core.query.Query; |
|
|
|
import org.springframework.data.mongodb.core.query.Query; |
|
|
|
@ -41,7 +36,6 @@ import org.springframework.data.repository.query.ValueExpressionDelegate; |
|
|
|
import org.springframework.data.util.ReflectionUtils; |
|
|
|
import org.springframework.data.util.ReflectionUtils; |
|
|
|
import org.springframework.expression.ExpressionParser; |
|
|
|
import org.springframework.expression.ExpressionParser; |
|
|
|
import org.springframework.lang.Nullable; |
|
|
|
import org.springframework.lang.Nullable; |
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* {@link AbstractMongoQuery} implementation to run string-based aggregations using |
|
|
|
* {@link AbstractMongoQuery} implementation to run string-based aggregations using |
|
|
|
@ -103,87 +97,67 @@ public class StringBasedAggregation extends AbstractMongoQuery { |
|
|
|
this.mongoConverter = mongoOperations.getConverter(); |
|
|
|
this.mongoConverter = mongoOperations.getConverter(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@Nullable |
|
|
|
@Nullable |
|
|
|
protected Object doExecute(MongoQueryMethod method, ResultProcessor resultProcessor, |
|
|
|
protected Object doExecute(MongoQueryMethod method, ResultProcessor processor, ConvertingParameterAccessor accessor, |
|
|
|
ConvertingParameterAccessor accessor, @Nullable Class<?> typeToRead) { |
|
|
|
@Nullable Class<?> ignore) { |
|
|
|
|
|
|
|
|
|
|
|
Class<?> sourceType = method.getDomainClass(); |
|
|
|
return AggregationUtils.doAggregate(AggregationUtils.computePipeline(this, method, accessor), method, processor, |
|
|
|
Class<?> targetType = typeToRead; |
|
|
|
accessor, this::getExpressionEvaluatorFor, |
|
|
|
|
|
|
|
(aggregation, sourceType, typeToRead, elementType, simpleType, rawResult) -> { |
|
|
|
|
|
|
|
|
|
|
|
AggregationPipeline pipeline = computePipeline(method, accessor); |
|
|
|
if (method.isStreamQuery()) { |
|
|
|
AggregationUtils.appendSortIfPresent(pipeline, accessor, typeToRead); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (method.isSliceQuery()) { |
|
|
|
Stream<?> stream = mongoOperations.aggregateStream(aggregation, typeToRead); |
|
|
|
AggregationUtils.appendLimitAndOffsetIfPresent(pipeline, accessor, LongUnaryOperator.identity(), |
|
|
|
|
|
|
|
limit -> limit + 1); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
AggregationUtils.appendLimitAndOffsetIfPresent(pipeline, accessor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
boolean isSimpleReturnType = typeToRead != null && isSimpleReturnType(typeToRead); |
|
|
|
|
|
|
|
boolean isRawAggregationResult = typeToRead != null && ClassUtils.isAssignable(AggregationResults.class, typeToRead); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isSimpleReturnType) { |
|
|
|
if (!simpleType || elementType.equals(Document.class)) { |
|
|
|
targetType = Document.class; |
|
|
|
return stream; |
|
|
|
} else if (isRawAggregationResult) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 🙈
|
|
|
|
return stream |
|
|
|
targetType = method.getReturnType().getRequiredActualType().getRequiredComponentType().getType(); |
|
|
|
.map(it -> AggregationUtils.extractSimpleTypeResult((Document) it, elementType, mongoConverter)); |
|
|
|
} else if (resultProcessor.getReturnedType().isProjecting()) { |
|
|
|
} |
|
|
|
targetType = resultProcessor.getReturnedType().getReturnedType().isInterface() ? Document.class :resultProcessor.getReturnedType().getReturnedType(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AggregationOptions options = computeOptions(method, accessor, pipeline); |
|
|
|
AggregationResults<Object> result = (AggregationResults<Object>) mongoOperations.aggregate(aggregation, |
|
|
|
TypedAggregation<?> aggregation = new TypedAggregation<>(sourceType, pipeline.getOperations(), options); |
|
|
|
typeToRead); |
|
|
|
|
|
|
|
|
|
|
|
if (method.isStreamQuery()) { |
|
|
|
if (ReflectionUtils.isVoid(elementType)) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Stream<?> stream = mongoOperations.aggregateStream(aggregation, targetType); |
|
|
|
if (rawResult) { |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (isSimpleReturnType) { |
|
|
|
List<?> results = result.getMappedResults(); |
|
|
|
return stream.map(it -> AggregationUtils.extractSimpleTypeResult((Document) it, typeToRead, mongoConverter)); |
|
|
|
if (method.isCollectionQuery()) { |
|
|
|
} |
|
|
|
return simpleType ? convertResults(elementType, (List<Document>) results) : results; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return stream; |
|
|
|
if (method.isSliceQuery()) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AggregationResults<Object> result = (AggregationResults<Object>) mongoOperations.aggregate(aggregation, targetType); |
|
|
|
Pageable pageable = accessor.getPageable(); |
|
|
|
if (typeToRead != null && ReflectionUtils.isVoid(typeToRead)) { |
|
|
|
int pageSize = pageable.getPageSize(); |
|
|
|
return null; |
|
|
|
List<Object> resultsToUse = simpleType ? convertResults(elementType, (List<Document>) results) |
|
|
|
} |
|
|
|
: (List<Object>) results; |
|
|
|
|
|
|
|
boolean hasNext = resultsToUse.size() > pageSize; |
|
|
|
|
|
|
|
return new SliceImpl<>(hasNext ? resultsToUse.subList(0, pageSize) : resultsToUse, pageable, hasNext); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (isRawAggregationResult) { |
|
|
|
Object uniqueResult = result.getUniqueMappedResult(); |
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<Object> results = result.getMappedResults(); |
|
|
|
|
|
|
|
if (method.isCollectionQuery()) { |
|
|
|
|
|
|
|
return isSimpleReturnType ? convertResults(typeToRead, results) : results; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (method.isSliceQuery()) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Pageable pageable = accessor.getPageable(); |
|
|
|
|
|
|
|
int pageSize = pageable.getPageSize(); |
|
|
|
|
|
|
|
List<Object> resultsToUse = isSimpleReturnType ? convertResults(typeToRead, results) : results; |
|
|
|
|
|
|
|
boolean hasNext = resultsToUse.size() > pageSize; |
|
|
|
|
|
|
|
return new SliceImpl<>(hasNext ? resultsToUse.subList(0, pageSize) : resultsToUse, pageable, hasNext); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object uniqueResult = result.getUniqueMappedResult(); |
|
|
|
return simpleType |
|
|
|
|
|
|
|
? AggregationUtils.extractSimpleTypeResult((Document) uniqueResult, elementType, mongoConverter) |
|
|
|
return isSimpleReturnType |
|
|
|
: uniqueResult; |
|
|
|
? AggregationUtils.extractSimpleTypeResult((Document) uniqueResult, typeToRead, mongoConverter) |
|
|
|
}); |
|
|
|
: uniqueResult; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private List<Object> convertResults(Class<?> typeToRead, List<Object> mappedResults) { |
|
|
|
private List<Object> convertResults(Class<?> targetType, List<Document> mappedResults) { |
|
|
|
|
|
|
|
|
|
|
|
List<Object> list = new ArrayList<>(mappedResults.size()); |
|
|
|
List<Object> list = new ArrayList<>(mappedResults.size()); |
|
|
|
for (Object it : mappedResults) { |
|
|
|
for (Document it : mappedResults) { |
|
|
|
Object extractSimpleTypeResult = AggregationUtils.extractSimpleTypeResult((Document) it, typeToRead, |
|
|
|
Object extractSimpleTypeResult = AggregationUtils.extractSimpleTypeResult(it, targetType, mongoConverter); |
|
|
|
mongoConverter); |
|
|
|
|
|
|
|
list.add(extractSimpleTypeResult); |
|
|
|
list.add(extractSimpleTypeResult); |
|
|
|
} |
|
|
|
} |
|
|
|
return list; |
|
|
|
return list; |
|
|
|
@ -193,28 +167,6 @@ public class StringBasedAggregation extends AbstractMongoQuery { |
|
|
|
return MongoSimpleTypes.HOLDER.isSimpleType(targetType); |
|
|
|
return MongoSimpleTypes.HOLDER.isSimpleType(targetType); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AggregationPipeline computePipeline(MongoQueryMethod method, ConvertingParameterAccessor accessor) { |
|
|
|
|
|
|
|
return new AggregationPipeline(parseAggregationPipeline(method.getAnnotatedAggregation(), accessor)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private AggregationOptions computeOptions(MongoQueryMethod method, ConvertingParameterAccessor accessor, |
|
|
|
|
|
|
|
AggregationPipeline pipeline) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AggregationOptions.Builder builder = Aggregation.newAggregationOptions(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, |
|
|
|
|
|
|
|
getExpressionEvaluatorFor(accessor)); |
|
|
|
|
|
|
|
AggregationUtils.applyMeta(builder, method); |
|
|
|
|
|
|
|
AggregationUtils.applyHint(builder, method); |
|
|
|
|
|
|
|
AggregationUtils.applyReadPreference(builder, method); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ReflectionUtils.isVoid(method.getReturnType().getType()) && pipeline.isOutOrMerge()) { |
|
|
|
|
|
|
|
builder.skipOutput(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return builder.build(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected Query createQuery(ConvertingParameterAccessor accessor) { |
|
|
|
protected Query createQuery(ConvertingParameterAccessor accessor) { |
|
|
|
throw new UnsupportedOperationException("No query support for aggregation"); |
|
|
|
throw new UnsupportedOperationException("No query support for aggregation"); |
|
|
|
|