Skip to content

Add support for findBy(…) using predicate that returns a Slice #4890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-GH-4889-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-GH-4889-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-GH-4889-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -87,7 +88,7 @@ public Object execute(Query query) {
int pageSize = pageable.getPageSize();

// Apply Pageable but tweak limit to peek into next page
Query modifiedQuery = query.with(pageable).limit(pageSize + 1);
Query modifiedQuery = SliceUtils.limitResult(query, pageable).with(pageable.getSort());
List result = find.matching(modifiedQuery).all();

boolean hasNext = result.size() > pageSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.MongoOperations;
Expand Down Expand Up @@ -271,6 +272,14 @@ public Page<T> page(Pageable pageable) {
return createQuery().fetchPage(pageable);
}

@Override
public Slice<T> slice(Pageable pageable) {

Assert.notNull(pageable, "Pageable must not be null");

return createQuery().fetchSlice(pageable);
}

@Override
public Stream<T> stream() {
return createQuery().stream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@

import org.bson.Document;
import org.reactivestreams.Publisher;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
Expand Down Expand Up @@ -239,6 +241,14 @@ public Mono<Page<T>> page(Pageable pageable) {
return createQuery().fetchPage(pageable);
}

@Override
public Mono<Slice<T>> slice(Pageable pageable) {

Assert.notNull(pageable, "Pageable must not be null");

return createQuery().fetchSlice(pageable);
}

@Override
public Mono<Long> count() {
return createQuery().fetchCount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.support;

import org.springframework.data.mongodb.repository.util.SliceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand All @@ -27,6 +28,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveFindOperation;
Expand Down Expand Up @@ -109,6 +111,20 @@ Mono<Page<K>> fetchPage(Pageable pageable) {
return content.flatMap(it -> ReactivePageableExecutionUtils.getPage(it, pageable, fetchCount()));
}

/**
* Fetch all matching query results as Slice.
*
* @return {@link Mono} emitting the requested Slice.
* @since 4.5
*/
Mono<Slice<K>> fetchSlice(Pageable pageable) {

Mono<List<K>> content = createQuery().map(it -> SliceUtils.limitResult(it, pageable).with(pageable.getSort()))
.flatMapMany(it -> find.matching(it).all()).collectList();

return content.map(it -> SliceUtils.sliceResult(it, pageable));
}

/**
* Fetch the one matching query result.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.ExecutableFindOperation;
Expand All @@ -42,6 +43,7 @@
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
Expand Down Expand Up @@ -136,8 +138,7 @@ public boolean existsById(ID id) {
Query query = getIdQuery(id);
getReadPreference().ifPresent(query::withReadPreference);

return mongoOperations.exists(query, entityInformation.getJavaType(),
entityInformation.getCollectionName());
return mongoOperations.exists(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
}

@Override
Expand Down Expand Up @@ -455,6 +456,16 @@ public Page<T> page(Pageable pageable) {
return PageableExecutionUtils.getPage(list, pageable, this::count);
}

@Override
public Slice<T> slice(Pageable pageable) {

Assert.notNull(pageable, "Pageable must not be null");

List<T> resultList = createQuery(q -> SliceUtils.limitResult(q, pageable).with(pageable.getSort())).all();

return SliceUtils.sliceResult(resultList, pageable);
}

@Override
public Stream<T> stream() {
return createQuery().stream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static org.springframework.data.mongodb.core.query.Criteria.*;

import org.springframework.data.mongodb.repository.util.SliceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand All @@ -37,6 +38,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.MongoTemplate;
Expand Down Expand Up @@ -592,6 +594,13 @@ public Mono<Page<T>> page(Pageable pageable) {
return items.flatMap(content -> ReactivePageableExecutionUtils.getPage(content, pageable, this.count()));
}

@Override
public Mono<Slice<T>> slice(Pageable pageable) {

return createQuery(q -> SliceUtils.limitResult(q, pageable).with(pageable.getSort())).all().collectList()
.map(it -> SliceUtils.sliceResult(it, pageable));
}

@Override
public Mono<Long> count() {
return createQuery().count();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@
import java.util.stream.Stream;

import org.bson.Document;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.util.SliceUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;

Expand Down Expand Up @@ -182,6 +185,20 @@ public Page<T> fetchPage(Pageable pageable) {
}
}

/**
* Fetch a {@link Slice}.
*
* @param pageable defines range and sort of requested slice
* @return new instance of {@link Slice} containing matching results within range.
* @since 4.5
*/
public Slice<T> fetchSlice(Pageable pageable) {

List<T> content = find.matching(SliceUtils.limitResult(createQuery(), pageable).with(pageable.getSort())).all();

return SliceUtils.sliceResult(content, pageable);
}

@Override
public T fetchFirst() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2025 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.util;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.mongodb.core.query.Query;

/**
* Utility methods for {@link Slice} handling.
*
* @author Mark Paluch
* @since 4.5
*/
public class SliceUtils {

/**
* Creates a {@link Slice} given {@link Pageable} and {@link List} of results.
*
* @param <T>
* @param resultList
* @param pageable
* @return
*/
public static <T> Slice<T> sliceResult(List<T> resultList, Pageable pageable) {

boolean hasNext = resultList.size() > pageable.getPageSize();

if (hasNext) {
resultList = resultList.subList(0, pageable.getPageSize());
}

return new SliceImpl<>(resultList, pageable, hasNext);
}

/**
* Customize query for slice retrieval.
*
* @param query the source query
* @param pageable paging to apply.
* @return new instance of {@link Query} if either {@link Pageable#isPaged() paged} or {@link Pageable#getSort()
* sorted}, the source query otherwise.
*/
public static Query limitResult(Query query, Pageable pageable) {

if (pageable.isUnpaged()) {
return query;
}

Query target = Query.of(query);
target.skip(pageable.getOffset());
target.limit(pageable.getPageSize() + 1);

return target;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,33 @@ void findByShouldApplyPagination() {
}).verifyComplete();
}

@Test // GH-4889
void findByShouldApplySlice() {

ReactivePerson probe = new ReactivePerson();
probe.setLastname(oliver.getLastname());

repository
.findBy(Example.of(probe, matching().withIgnorePaths("age")),
it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname")))) //
.as(StepVerifier::create) //
.assertNext(it -> {

assertThat(it.hasNext()).isTrue();
assertThat(it.getContent()).contains(dave);
}).verifyComplete();

repository
.findBy(Example.of(probe, matching().withIgnorePaths("age")),
it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname")))) //
.as(StepVerifier::create) //
.assertNext(it -> {

assertThat(it.hasNext()).isFalse();
assertThat(it.getContent()).contains(oliver);
}).verifyComplete();
}

@Test // GH-3757
void findByShouldCount() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.MongoDatabaseFactory;
Expand Down Expand Up @@ -314,6 +315,7 @@ public void findByShouldApplyPagination() {

Page<Person> first = repository.findBy(person.lastname.eq(oliver.getLastname()),
it -> it.page(PageRequest.of(0, 1, Sort.by("firstname"))));

assertThat(first.getTotalElements()).isEqualTo(2);
assertThat(first.getContent()).contains(dave);

Expand All @@ -324,6 +326,22 @@ public void findByShouldApplyPagination() {
assertThat(next.getContent()).contains(oliver);
}

@Test // GH-4889
public void findByShouldApplySlice() {

Slice<Person> first = repository.findBy(person.lastname.eq(oliver.getLastname()),
it -> it.slice(PageRequest.of(0, 1, Sort.by("firstname"))));

assertThat(first.hasNext()).isTrue();
assertThat(first.getContent()).contains(dave);

Slice<Person> next = repository.findBy(person.lastname.eq(oliver.getLastname()),
it -> it.slice(PageRequest.of(1, 1, Sort.by("firstname"))));

assertThat(next.hasNext()).isFalse();
assertThat(next.getContent()).contains(oliver);
}

@Test // GH-3757
public void findByShouldCount() {

Expand Down
Loading