Skip to content

Introduce Limit type #2836

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 5 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-commons</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.x-2827-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down
38 changes: 32 additions & 6 deletions src/main/asciidoc/repositories-paging-sorting.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[[repositories.special-parameters]]
=== Paging, Iterating Large Results, Sorting
=== Paging, Iterating Large Results, Sorting & Limiting

To handle parameters in your query, define method parameters as already seen in the preceding examples.
Besides that, the infrastructure recognizes certain specific types like `Pageable` and `Sort`, to apply pagination and sorting to your queries dynamically.
Besides that, the infrastructure recognizes certain specific types like `Pageable`, `Sort` and `Limit`, to apply pagination, sorting and limiting to your queries dynamically.
The following example demonstrates these features:

ifdef::feature-scroll[]
.Using `Pageable`, `Slice`, `ScrollPosition`, and `Sort` in query methods
.Using `Pageable`, `Slice`, `ScrollPosition`, `Sort` and `Limit` in query methods
====
[source,java]
----
Expand All @@ -18,13 +18,15 @@ Window<User> findTop10ByLastname(String lastname, ScrollPosition position, Sort

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Sort sort, Limit limit);

List<User> findByLastname(String lastname, Pageable pageable);
----
====
endif::[]

ifndef::feature-scroll[]
.Using `Pageable`, `Slice`, and `Sort` in query methods
.Using `Pageable`, `Slice`, `Sort` and `Limit` in query methods
====
[source,java]
----
Expand All @@ -34,13 +36,15 @@ Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Sort sort, Limit limit);

List<User> findByLastname(String lastname, Pageable pageable);
----
====
endif::[]

IMPORTANT: APIs taking `Sort` and `Pageable` expect non-`null` values to be handed into methods.
If you do not want to apply any sorting or pagination, use `Sort.unsorted()` and `Pageable.unpaged()`.
IMPORTANT: APIs taking `Sort`, `Pageable` and `Limit` expect non-`null` values to be handed into methods.
If you do not want to apply any sorting or pagination, use `Sort.unsorted()`, `Pageable.unpaged()` and `Limit.unlimited()`.

The first method lets you pass an `org.springframework.data.domain.Pageable` instance to the query method to dynamically add paging to your statically defined query.
A `Page` knows about the total number of elements and pages available.
Expand All @@ -57,6 +61,28 @@ Rather, it restricts the query to look up only the given range of entities.
NOTE: To find out how many pages you get for an entire query, you have to trigger an additional count query.
By default, this query is derived from the query you actually trigger.

[IMPORTANT]
====
Special parameters may only be used once within a query method. +
Some of the special parameters described above are mutually exclusive.
Please consider the following list of invalid parameter combinations.

|===
| Parameters | Example | Reason

| `Pageable` & `Sort`
| `findBy...(Pageable page, Sort sort)`
| `Pageable` already defines `Sort`

| `Pageable` & `Limit`
| `findBy...(Pageable page, Limit limit)`
| `Pageable` already defines a limit.

|===

The `Top` keyword used to limit results can be used to along with `Pageable` whereas `Top` defines the total maximum of results, whereas the Pageable parameter may reduce this this number.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double "this this"?

====

[[repositories.scrolling.guidance]]
==== Which Method is Appropriate?

Expand Down
121 changes: 121 additions & 0 deletions src/main/java/org/springframework/data/domain/Limit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2023 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.domain;

import org.springframework.data.domain.Limit.Limited;
import org.springframework.data.domain.Limit.Unlimited;
import org.springframework.util.ClassUtils;

/**
* {@link Limit} represents the maximum value up to which an operation should continue processing. It may be used for
* defining the {@link #max() maximum} number of results within a repository finder method or if applicable a template
* operation.
* <p>
* A {@link Limit#isUnlimited()} is used to indicate that there is no {@link Limit} defined, which should be favoured
* over using {@literal null} or {@link java.util.Optional#empty()} to indicate the absence of an actual {@link Limit}.
* </p>
* {@link Limit} itself does not make assumptions about the actual {@link #max()} value sign. This means that a negative
* value may be valid within a defined context. Implementations should override {@link #isUnlimited()} to fit their
* needs and enforce restrictions if needed.
*
* @author Christoph Strobl
* @since 3.2
*/
public sealed interface Limit permits Limited, Unlimited {

/**
* @return the max number of potential results.
*/
int max();

/**
* @return {@literal true} if no limiting (maximum value) should be applied.
*/
default boolean isUnlimited() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also have a isLimited() method.

return Unlimited.INSTANCE.equals(this);
}

/**
* @return a {@link Limit} instance that does not define {@link #max()} and answers {@link #isUnlimited()} with
* {@literal true}.
*/
static Limit unlimited() {
return Unlimited.INSTANCE;
}

/**
* Create a new {@link Limit} from the given {@literal max} value.
*
* @param max the maximum value.
* @return new instance of {@link Limit}.
*/
static Limit of(int max) {
return new Limited(max);
}

final class Limited implements Limit {

private final int max;

Limited(int max) {
this.max = max;
}

@Override
public int max() {
return max;
}

@Override
public boolean equals(Object obj) {

if (obj == null) {
return false;
}
if (!ClassUtils.isAssignable(Limit.class, obj.getClass())) {
return false;
}
Limit that = (Limit) obj;
if (this.isUnlimited() && that.isUnlimited()) {
return true;
}
return max() == that.max();
}

@Override
public int hashCode() {
return (int) (max ^ (max >>> 32));
}
}

final class Unlimited implements Limit {

static final Limit INSTANCE = new Unlimited();

Unlimited() {}

@Override
public int max() {
throw new IllegalStateException(
"Unlimited does not define 'max'. Please check 'isUnlimited' before attempting to read 'max'");
}

@Override
public boolean isUnlimited() {
return true;
}
}
}
14 changes: 13 additions & 1 deletion src/main/java/org/springframework/data/domain/Pageable.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
*/
public interface Pageable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a toLimit() method as well, similar to toScrollPosition()?


Expand All @@ -33,7 +34,18 @@ public interface Pageable {
* @return
*/
static Pageable unpaged() {
return Unpaged.INSTANCE;
return unpaged(Sort.unsorted());
}

/**
* Returns a {@link Pageable} instance representing no pagination setup having a defined result {@link Sort order}.
*
* @param sort must not be {@literal null}, use {@link Sort#unsorted()} if needed.
* @return never {@literal null}.
* @since 3.2
*/
static Pageable unpaged(Sort sort) {
return Unpaged.sorted(sort);
}

/**
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/org/springframework/data/domain/Unpaged.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@
* {@link Pageable} implementation to represent the absence of pagination information.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
enum Unpaged implements Pageable {
final class Unpaged implements Pageable {

INSTANCE;
private static final Pageable UNSORTED = new Unpaged(Sort.unsorted());

private final Sort sort;

Unpaged(Sort sort) {
this.sort = sort;
}

static Pageable sorted(Sort sort) {
return sort.isSorted() ? new Unpaged(sort) : UNSORTED;
}

@Override
public boolean isPaged() {
Expand All @@ -46,7 +57,7 @@ public boolean hasPrevious() {

@Override
public Sort getSort() {
return Sort.unsorted();
return sort;
}

@Override
Expand Down Expand Up @@ -78,5 +89,4 @@ public Pageable withPage(int pageNumber) {

throw new UnsupportedOperationException();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -58,7 +59,7 @@ public class Parameter {

static {

List<Class<?>> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class));
List<Class<?>> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class, Limit.class));

// consider Kotlin Coroutines Continuation a special parameter. That parameter is synthetic and should not get
// bound to any query.
Expand Down Expand Up @@ -221,6 +222,16 @@ boolean isSort() {
return Sort.class.isAssignableFrom(getType());
}

/**
* Returns whether the {@link Parameter} is a {@link Limit} parameter.
*
* @return
* @since 3.2
*/
boolean isLimit() {
return Limit.class.isAssignableFrom(getType());
}

/**
* Returns whether the given {@link MethodParameter} is a dynamic projection parameter, which means it carries a
* dynamic type parameter which is identical to the type parameter of the actually returned type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Iterator;

import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -54,6 +55,22 @@ public interface ParameterAccessor extends Iterable<Object> {
*/
Sort getSort();

/**
* Returns the {@link Limit} instance to be used for query creation. If no {@link java.lang.reflect.Parameter}
* assignable to {@link Limit} can be found {@link Limit} will be created out of {@link Pageable#getPageSize()} if
* present.
*
* @return {@link Limit#unlimited()} by default.
* @since 3.2
*/
default Limit getLimit() {

if (getPageable().isUnpaged()) {
return Limit.unlimited();
}
return Limit.of(getPageable().getPageSize());
}

/**
* Returns the dynamic projection type to be used when executing the query or {@literal null} if none is defined.
*
Expand Down
Loading