Skip to content

Commit 78da030

Browse files
committed
Add support for Limit.
Closes #1407
1 parent 05ef8c8 commit 78da030

File tree

9 files changed

+93
-17
lines changed

9 files changed

+93
-17
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/StatementFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ private StatementBuilder<Select> createSelect(Query query, CassandraPersistentEn
632632

633633
StatementBuilder<Select> select = createSelectAndOrder(selectors, tableName, filter, sort);
634634

635+
// TODO: Bind marker
635636
if (query.getLimit() > 0) {
636637
select.apply(it -> it.limit(Math.toIntExact(query.getLimit())));
637638
}

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Query.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.stream.StreamSupport;
2828

2929
import org.springframework.data.cassandra.core.cql.QueryOptions;
30+
import org.springframework.data.domain.Limit;
3031
import org.springframework.data.domain.PageRequest;
3132
import org.springframework.data.domain.Pageable;
3233
import org.springframework.data.domain.Sort;
@@ -46,15 +47,15 @@
4647
public class Query implements Filter {
4748

4849
private static final Query EMPTY = new Query(Collections.emptyList(), Columns.empty(), Sort.unsorted(),
49-
Optional.empty(), Optional.empty(), Optional.empty(), false);
50+
Optional.empty(), Optional.empty(), Limit.unlimited(), false);
5051

5152
private final boolean allowFiltering;
5253

5354
private final Columns columns;
5455

5556
private final List<CriteriaDefinition> criteriaDefinitions;
5657

57-
private final Optional<Long> limit;
58+
private final Limit limit;
5859

5960
private final Optional<ByteBuffer> pagingState;
6061

@@ -63,8 +64,9 @@ public class Query implements Filter {
6364
private final Sort sort;
6465

6566
private Query(List<CriteriaDefinition> criteriaDefinitions, Columns columns, Sort sort,
66-
Optional<ByteBuffer> pagingState, Optional<QueryOptions> queryOptions, Optional<Long> limit,
67-
boolean allowFiltering) {
67+
Optional<ByteBuffer> pagingState, Optional<QueryOptions> queryOptions, Limit limit, boolean allowFiltering) {
68+
69+
Assert.notNull(limit, "Limit must not be null");
6870

6971
this.criteriaDefinitions = criteriaDefinitions;
7072
this.columns = columns;
@@ -110,7 +112,7 @@ public static Query query(Iterable<? extends CriteriaDefinition> criteriaDefinit
110112
List<CriteriaDefinition> collect = StreamSupport.stream(criteriaDefinitions.spliterator(), false)
111113
.collect(Collectors.toList());
112114

113-
return new Query(collect, Columns.empty(), Sort.unsorted(), Optional.empty(), Optional.empty(), Optional.empty(),
115+
return new Query(collect, Columns.empty(), Sort.unsorted(), Optional.empty(), Optional.empty(), Limit.unlimited(),
114116
false);
115117
}
116118

@@ -194,7 +196,7 @@ public Sort getSort() {
194196
/**
195197
* Create a {@link Query} initialized with a {@link PageRequest} to fetch the first page of results or advance in
196198
* paging along with sorting. Reads (and overrides, if set) {@link Pageable#getPageSize() page size} into
197-
* {@link QueryOptions#getPageSize()} and sets {@code pagingState} and {@link Sort}.
199+
* {@code QueryOptions#getPageSize()} and sets {@code pagingState} and {@link Sort}.
198200
*
199201
* @param pageable must not be {@literal null}.
200202
* @return a new {@link Query} object containing the former settings with {@link PageRequest} applied.
@@ -269,14 +271,32 @@ public Optional<QueryOptions> getQueryOptions() {
269271
*/
270272
public Query limit(long limit) {
271273
return new Query(this.criteriaDefinitions, this.columns, this.sort, this.pagingState, this.queryOptions,
272-
Optional.of(limit), this.allowFiltering);
274+
Limit.of(Math.toIntExact(limit)), this.allowFiltering);
275+
}
276+
277+
/**
278+
* Limit the number of returned rows to {@link Limit}.
279+
*
280+
* @param limit
281+
* @return a new {@link Query} object containing the former settings with {@code limit} applied.
282+
*/
283+
public Query limit(Limit limit) {
284+
return new Query(this.criteriaDefinitions, this.columns, this.sort, this.pagingState, this.queryOptions, limit,
285+
this.allowFiltering);
273286
}
274287

275288
/**
276289
* @return the maximum number of rows to be returned.
277290
*/
278291
public long getLimit() {
279-
return this.limit.orElse(0L);
292+
return this.limit.isLimited() ? this.limit.max() : 0;
293+
}
294+
295+
/**
296+
* @return {@code true} if the query is limited.
297+
*/
298+
public boolean isLimited() {
299+
return this.limit.isLimited();
280300
}
281301

282302
/**

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParametersParameterAccessor.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.data.cassandra.core.cql.QueryOptions;
1919
import org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder;
2020
import org.springframework.data.cassandra.core.mapping.CassandraType;
21+
import org.springframework.data.domain.Limit;
2122
import org.springframework.data.repository.query.ParameterAccessor;
2223
import org.springframework.data.repository.query.ParametersParameterAccessor;
2324
import org.springframework.lang.Nullable;
@@ -75,6 +76,16 @@ public Object[] getValues() {
7576
return super.getValues();
7677
}
7778

79+
@Override
80+
public Limit getLimit() {
81+
82+
if (!getParameters().hasLimitParameter()) {
83+
return Limit.unlimited();
84+
}
85+
86+
return super.getLimit();
87+
}
88+
7889
@Nullable
7990
@Override
8091
public QueryOptions getQueryOptions() {

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.data.cassandra.core.convert.CassandraConverter;
2121
import org.springframework.data.cassandra.core.cql.QueryOptions;
2222
import org.springframework.data.cassandra.core.mapping.CassandraType;
23+
import org.springframework.data.domain.Limit;
2324
import org.springframework.data.domain.Pageable;
2425
import org.springframework.data.domain.Range;
2526
import org.springframework.data.domain.ScrollPosition;
@@ -64,6 +65,11 @@ public Sort getSort() {
6465
return this.delegate.getSort();
6566
}
6667

68+
@Override
69+
public Limit getLimit() {
70+
return this.delegate.getLimit();
71+
}
72+
6773
@Nullable
6874
@Override
6975
public Class<?> findDynamicProjection() {

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/QueryStatementCreator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.cassandra.core.query.Columns;
3131
import org.springframework.data.cassandra.core.query.Query;
3232
import org.springframework.data.cassandra.repository.Query.Idempotency;
33+
import org.springframework.data.domain.Limit;
3334
import org.springframework.data.mapping.context.MappingContext;
3435
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
3536
import org.springframework.data.repository.query.QueryCreationException;
@@ -199,6 +200,11 @@ <T> T doWithQuery(CassandraParameterAccessor parameterAccessor, PartTree tree,
199200
query = query.limit(tree.getMaxResults());
200201
}
201202

203+
Limit limit = parameterAccessor.getLimit();
204+
if (limit.isLimited()) {
205+
query = query.limit(limit);
206+
}
207+
202208
if (allowsFiltering()) {
203209
query = query.withAllowFiltering();
204210
}

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/QueryDerivationIntegrationTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.data.cassandra.repository.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
5353
import org.springframework.data.cassandra.repository.support.IntegrationTestConfig;
5454
import org.springframework.data.cassandra.support.CassandraVersion;
55+
import org.springframework.data.domain.Limit;
5556
import org.springframework.data.domain.Pageable;
5657
import org.springframework.data.domain.Slice;
5758
import org.springframework.data.domain.Sort;
@@ -340,6 +341,14 @@ public void shouldSelectSliced() {
340341
assertThat(result).contains(walter, skyler, flynn);
341342
}
342343

344+
@Test // GH-1407
345+
public void shouldSelectWithLimit() {
346+
347+
List<Person> result = personRepository.findAllLimitedByLastname("White", Limit.of(2));
348+
349+
assertThat(result).hasSize(2);
350+
}
351+
343352
@Test // DATACASS-512
344353
public void shouldCountRecords() {
345354

@@ -427,6 +436,8 @@ static interface PersonRepository extends MapIdCassandraRepository<Person> {
427436

428437
Slice<Person> findAllSlicedByLastname(String lastname, Pageable pageable);
429438

439+
List<Person> findAllLimitedByLastname(String lastname, Limit limit);
440+
430441
Collection<PersonProjection> findPersonProjectedBy();
431442

432443
Collection<PersonDto> findPersonDtoBy();

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/ReactiveCassandraRepositoryIntegrationTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.data.cassandra.repository.support.IntegrationTestConfig;
4343
import org.springframework.data.cassandra.repository.support.ReactiveCassandraRepositoryFactory;
4444
import org.springframework.data.cassandra.repository.support.SimpleReactiveCassandraRepository;
45+
import org.springframework.data.domain.Limit;
4546
import org.springframework.data.domain.Pageable;
4647
import org.springframework.data.domain.Slice;
4748
import org.springframework.data.domain.Sort;
@@ -135,6 +136,18 @@ void shouldFindSliceByLastName() {
135136
.expectNextMatches(users -> users.getSize() == 1 && users.hasNext()).verifyComplete();
136137
}
137138

139+
@Test // GH-1407
140+
void shouldFindWithLimitByLastName() {
141+
repository.findByLastname(dave.getLastname(), Limit.of(1)).as(StepVerifier::create).expectNextCount(1)
142+
.verifyComplete();
143+
144+
repository.findByLastname(dave.getLastname(), Limit.of(2)).as(StepVerifier::create).expectNextCount(2)
145+
.verifyComplete();
146+
147+
repository.findByLastname(dave.getLastname(), Limit.unlimited()).as(StepVerifier::create).expectNextCount(2)
148+
.verifyComplete();
149+
}
150+
138151
@Test // DATACASS-529
139152
void shouldFindEmptySliceByLastName() {
140153
repository.findByLastname("foo", CassandraPageRequest.first(1)).as(StepVerifier::create)
@@ -235,6 +248,8 @@ interface UserRepository extends ReactiveCassandraRepository<User, String> {
235248

236249
Mono<Slice<User>> findByLastname(String firstname, Pageable pageable);
237250

251+
Flux<User> findByLastname(String lastname, Limit limit);
252+
238253
Mono<User> findFirstByLastname(String lastname);
239254

240255
Mono<User> findOneByLastname(String lastname);

src/main/asciidoc/reference/cassandra-repositories.adoc

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,19 @@ public interface PersonRepository extends CrudRepository<Person, String> {
186186
187187
List<Person> findByFirstname(String firstname, Sort sort); <4>
188188
189-
Person findByShippingAddress(Address address); <5>
189+
List<Person> findByFirstname(String firstname, Limit limit); <5>
190190
191-
Person findFirstByShippingAddress(Address address); <6>
191+
Person findByShippingAddress(Address address); <6>
192192
193-
Stream<Person> findAllBy(); <7>
193+
Person findFirstByShippingAddress(Address address); <7>
194+
195+
Stream<Person> findAllBy(); <8>
194196
195197
@AllowFiltering
196-
List<Person> findAllByAge(int age); <8>
198+
List<Person> findAllByAge(int age); <9>
197199
}
198200
----
201+
199202
<1> The method shows a query for all people with the given `lastname`.
200203
The query is derived from parsing the method name for constraints, which can be concatenated with `And`.
201204
Thus, the method name results in a query expression of `SELECT * FROM person WHERE lastname = 'lastname'`.
@@ -204,12 +207,14 @@ You can equip your method signature with a `Pageable` parameter and let the meth
204207
<3> Passing a `QueryOptions` object applies the query options to the resulting query before its execution.
205208
<4> Applies dynamic sorting to a query.
206209
You can add a `Sort` parameter to your method signature, and Spring Data automatically applies ordering to the query.
207-
<5> Shows that you can query based on properties that are not a primitive type by using `Converter` instances registered in `CustomConversions`.
210+
<5> Applies dynamic result limiting to a query.
211+
Query results can be limited using `SELECT … LIMIT`.
212+
<6> Shows that you can query based on properties that are not a primitive type by using `Converter` instances registered in `CustomConversions`.
208213
Throws `IncorrectResultSizeDataAccessException` if more than one match is found.
209-
<6> Uses the `First` keyword to restrict the query to only the first result.
214+
<7> Uses the `First` keyword to restrict the query to only the first result.
210215
Unlike the preceding method, this method does not throw an exception if more than one match is found.
211-
<7> Uses a Java 8 `Stream` to read and convert individual elements while iterating the stream.
212-
<8> Shows a query method annotated with `@AllowFiltering`, to allow server-side filtering.
216+
<8> Uses a Java 8 `Stream` to read and convert individual elements while iterating the stream.
217+
<9> Shows a query method annotated with `@AllowFiltering`, to allow server-side filtering.
213218
====
214219

215220
NOTE: Querying non-primary key properties requires secondary indexes.

src/main/asciidoc/reference/cassandra.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,8 @@ The `Query` class has some additional methods that you can use to provide option
10631063
* `Query` *by* `(CriteriaDefinition... criteria)`: Used to create a `Query` object.
10641064
* `Query` *and* `(CriteriaDefinition criteria)`: Used to add additional criteria to the query.
10651065
* `Query` *columns* `(Columns columns)`: Used to define columns to be included in the query results.
1066-
* `Query` *limit* `(long limit)`: Used to limit the size of the returned results to the provided limit (used for paging).
1066+
* `Query` *limit* `(Limit limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting).
1067+
* `Query` *limit* `(long limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting).
10671068
* `Query` *pageRequest* `(Pageable pageRequest)`: Used to associate `Sort`, `PagingState`, and `fetchSize` with the query (used for paging).
10681069
* `Query` *pagingState* `(ByteBuffer pagingState)`: Used to associate a `ByteBuffer` with the query (used for paging).
10691070
* `Query` *queryOptions* `(QueryOptions queryOptions)`: Used to associate `QueryOptions` with the query.

0 commit comments

Comments
 (0)