Skip to content

Commit 446ebe7

Browse files
committed
Initial draft for cursor window queries.
1 parent 5371fc8 commit 446ebe7

File tree

8 files changed

+460
-28
lines changed

8 files changed

+460
-28
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core;
17+
18+
import java.util.List;
19+
20+
import org.springframework.data.domain.CursorRequest;
21+
import org.springframework.data.domain.CursorWindow;
22+
23+
/**
24+
* @author Mark Paluch
25+
*/
26+
class CursorUtils {
27+
28+
static boolean hasMoreElements(CursorRequest request, List<?> result) {
29+
return !result.isEmpty() && result.size() > request.getSize();
30+
}
31+
32+
static boolean isLast(CursorRequest request, List<?> result) {
33+
return !hasMoreElements(request, result);
34+
}
35+
36+
static <T> List<T> getSubList(CursorRequest request, List<T> result) {
37+
38+
if (result.size() > request.getSize()) {
39+
return result.subList(0, request.getSize());
40+
}
41+
42+
return result;
43+
}
44+
45+
public static <T> CursorWindow<T> createWindow(CursorRequest cursorRequest, List<T> result) {
46+
return CursorWindow.from(cursorRequest.withLast(isLast(cursorRequest, result)), getSubList(cursorRequest, result));
47+
}
48+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717

1818
import java.util.Collection;
1919
import java.util.Iterator;
20+
import java.util.LinkedHashMap;
2021
import java.util.Map;
2122
import java.util.Optional;
2223

2324
import org.bson.Document;
2425
import org.springframework.core.convert.ConversionService;
2526
import org.springframework.dao.InvalidDataAccessApiUsageException;
2627
import org.springframework.data.convert.CustomConversions;
28+
import org.springframework.data.domain.Sort;
29+
import org.springframework.data.domain.Sort.Order;
2730
import org.springframework.data.mapping.IdentifierAccessor;
2831
import org.springframework.data.mapping.MappingException;
2932
import org.springframework.data.mapping.PersistentEntity;
@@ -44,6 +47,7 @@
4447
import org.springframework.data.mongodb.core.query.Query;
4548
import org.springframework.data.mongodb.core.timeseries.Granularity;
4649
import org.springframework.data.mongodb.core.validation.Validator;
50+
import org.springframework.data.mongodb.util.BsonUtils;
4751
import org.springframework.data.projection.EntityProjection;
4852
import org.springframework.data.projection.EntityProjectionIntrospector;
4953
import org.springframework.data.projection.ProjectionFactory;
@@ -450,6 +454,9 @@ default boolean isVersionedEntity() {
450454
* @since 2.1.2
451455
*/
452456
boolean isNew();
457+
458+
Map<String, Object> extractKeys(Sort sort);
459+
453460
}
454461

455462
/**
@@ -563,6 +570,19 @@ public T getBean() {
563570
public boolean isNew() {
564571
return map.get(ID_FIELD) != null;
565572
}
573+
574+
@Override
575+
public Map<String, Object> extractKeys(Sort sort) {
576+
577+
Map<String, Object> keyset = new LinkedHashMap<>();
578+
keyset.put(ID_FIELD, getId());
579+
580+
for (Order order : sort) {
581+
keyset.put(order.getProperty(), BsonUtils.resolveValue(map, order.getProperty()));
582+
}
583+
584+
return keyset;
585+
}
566586
}
567587

568588
private static class SimpleMappedEntity<T extends Map<String, Object>> extends UnmappedEntity<T> {
@@ -697,6 +717,22 @@ public T getBean() {
697717
public boolean isNew() {
698718
return entity.isNew(propertyAccessor.getBean());
699719
}
720+
721+
@Override
722+
public Map<String, Object> extractKeys(Sort sort) {
723+
724+
Map<String, Object> keyset = new LinkedHashMap<>();
725+
keyset.put(entity.getRequiredIdProperty().getName(), getId());
726+
727+
for (Order order : sort) {
728+
729+
// TODO: make this work for nested properties
730+
MongoPersistentProperty persistentProperty = entity.getRequiredPersistentProperty(order.getProperty());
731+
keyset.put(order.getProperty(), propertyAccessor.getProperty(persistentProperty));
732+
}
733+
734+
return keyset;
735+
}
700736
}
701737

702738
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.stream.Stream;
2424

2525
import org.bson.Document;
26+
import org.springframework.data.domain.CursorRequest;
27+
import org.springframework.data.domain.CursorWindow;
2628
import org.springframework.data.geo.GeoResults;
2729
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
2830
import org.springframework.data.mongodb.core.aggregation.Aggregation;
@@ -319,7 +321,8 @@ default MongoCollection<Document> createView(String name, Class<?> source, Aggre
319321
* @param options additional settings to apply when creating the view. Can be {@literal null}.
320322
* @since 4.0
321323
*/
322-
MongoCollection<Document> createView(String name, Class<?> source, AggregationPipeline pipeline, @Nullable ViewOptions options);
324+
MongoCollection<Document> createView(String name, Class<?> source, AggregationPipeline pipeline,
325+
@Nullable ViewOptions options);
323326

324327
/**
325328
* Create a view with the provided name. The view content is defined by the {@link AggregationPipeline pipeline} on
@@ -331,7 +334,8 @@ default MongoCollection<Document> createView(String name, Class<?> source, Aggre
331334
* @param options additional settings to apply when creating the view. Can be {@literal null}.
332335
* @since 4.0
333336
*/
334-
MongoCollection<Document> createView(String name, String source, AggregationPipeline pipeline, @Nullable ViewOptions options);
337+
MongoCollection<Document> createView(String name, String source, AggregationPipeline pipeline,
338+
@Nullable ViewOptions options);
335339

336340
/**
337341
* A set of collection names.
@@ -466,6 +470,38 @@ default MongoCollection<Document> createView(String name, Class<?> source, Aggre
466470
*/
467471
<T> List<T> findAll(Class<T> entityClass, String collectionName);
468472

473+
/**
474+
* Query for a cursor window of objects of type T from the specified collection. <br />
475+
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
476+
* configured otherwise, an instance of {@link MappingMongoConverter} will be used. <br />
477+
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
478+
* to map objects since the test for class type is done in the client and not on the server.
479+
*
480+
* @param cursorRequest the cursor request.
481+
* @param entityType the parametrized type of the returned list.
482+
* @param collectionName name of the collection to retrieve the objects from.
483+
* @return the converted cursor window.
484+
* @see org.springframework.data.domain.OffsetCursorRequest
485+
* @see org.springframework.data.domain.KeysetCursorRequest
486+
*/
487+
<T> CursorWindow<T> findWindow(CursorRequest cursorRequest, Query query, Class<T> entityTypee);
488+
489+
/**
490+
* Query for a cursor window of objects of type T from the specified collection. <br />
491+
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
492+
* configured otherwise, an instance of {@link MappingMongoConverter} will be used. <br />
493+
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
494+
* to map objects since the test for class type is done in the client and not on the server.
495+
*
496+
* @param cursorRequest the cursor request.
497+
* @param entityType the parametrized type of the returned list.
498+
* @param collectionName name of the collection to retrieve the objects from.
499+
* @return the converted cursor window.
500+
* @see org.springframework.data.domain.OffsetCursorRequest
501+
* @see org.springframework.data.domain.KeysetCursorRequest
502+
*/
503+
<T> CursorWindow<T> findWindow(CursorRequest cursorRequest, Query query, Class<T> entityType, String collectionName);
504+
469505
/**
470506
* Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the
471507
* inputCollection is derived from the inputType of the aggregation.
@@ -1175,7 +1211,7 @@ <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions option
11751211
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
11761212
* @return the count of matching documents.
11771213
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
1178-
* {@link #getCollectionName(Class) derived} from the given type.
1214+
* {@link #getCollectionName(Class) derived} from the given type.
11791215
* @see #exactCount(Query, Class)
11801216
* @see #estimatedCount(Class)
11811217
*/

0 commit comments

Comments
 (0)