Skip to content

Commit e9e1548

Browse files
authored
fix: Fix limitToLast queries with cursors (#1072)
1 parent a7ddc9e commit e9e1548

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,10 +1289,12 @@ public Query endAt(@Nonnull DocumentSnapshot snapshot) {
12891289
StructuredQuery.Builder buildQuery() {
12901290
StructuredQuery.Builder structuredQuery = buildWithoutClientTranslation();
12911291
if (options.getLimitType().equals(LimitType.Last)) {
1292-
// Apply client translation for limitToLast.
1292+
structuredQuery.clearOrderBy();
1293+
structuredQuery.clearStartAt();
1294+
structuredQuery.clearEndAt();
12931295

1296+
// Apply client translation for limitToLast.
12941297
if (!options.getFieldOrders().isEmpty()) {
1295-
structuredQuery.clearOrderBy();
12961298
for (FieldOrder order : options.getFieldOrders()) {
12971299
// Flip the orderBy directions since we want the last results
12981300
order =
@@ -1306,7 +1308,6 @@ StructuredQuery.Builder buildQuery() {
13061308
}
13071309

13081310
if (options.getStartCursor() != null) {
1309-
structuredQuery.clearEndAt();
13101311
// Swap the cursors to match the flipped query ordering.
13111312
Cursor cursor =
13121313
options
@@ -1318,7 +1319,6 @@ StructuredQuery.Builder buildQuery() {
13181319
}
13191320

13201321
if (options.getEndCursor() != null) {
1321-
structuredQuery.clearStartAt();
13221322
// Swap the cursors to match the flipped query ordering.
13231323
Cursor cursor =
13241324
options

google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.firestore.it;
1818

1919
import static com.google.cloud.firestore.LocalFirestoreHelper.map;
20+
import static com.google.common.collect.ImmutableList.toImmutableList;
2021
import static com.google.common.truth.Truth.assertThat;
2122
import static com.google.common.truth.Truth.assertWithMessage;
2223
import static java.util.Arrays.asList;
@@ -26,6 +27,7 @@
2627
import com.google.cloud.firestore.CollectionReference;
2728
import com.google.cloud.firestore.DocumentChange;
2829
import com.google.cloud.firestore.DocumentReference;
30+
import com.google.cloud.firestore.DocumentSnapshot;
2931
import com.google.cloud.firestore.EventListener;
3032
import com.google.cloud.firestore.Firestore;
3133
import com.google.cloud.firestore.FirestoreException;
@@ -39,6 +41,7 @@
3941
import com.google.common.base.Joiner;
4042
import com.google.common.base.Joiner.MapJoiner;
4143
import com.google.common.base.Preconditions;
44+
import com.google.common.collect.ImmutableList;
4245
import com.google.common.collect.Range;
4346
import com.google.common.truth.Truth;
4447
import java.util.ArrayList;
@@ -381,6 +384,112 @@ public void limitToLast() throws Exception {
381384
listenerAssertions.addedIdsIsAnyOf(emptyList(), asList("doc2", "doc3"));
382385
}
383386

387+
/**
388+
* Verifies that QuerySnapshot for limitToLast() queries work with startAt when the full limit is
389+
* used in the result set.
390+
*/
391+
@Test
392+
public void limitToLastWithStartAtFullLimit() throws Exception {
393+
for (int i = 0; i < 10; i++) {
394+
setDocument("doc" + i, Collections.singletonMap("counter", i));
395+
}
396+
Query query = randomColl.orderBy("counter").startAt(5).limitToLast(3);
397+
assertQueryResultContainsDocsInOrder(query, "doc7", "doc8", "doc9");
398+
}
399+
400+
/**
401+
* Verifies that QuerySnapshot for limitToLast() queries work with startAt when the partial limit
402+
* is used in the result set.
403+
*/
404+
@Test
405+
public void limitToLastWithStartAtPartialLimit() throws Exception {
406+
for (int i = 0; i < 10; i++) {
407+
setDocument("doc" + i, Collections.singletonMap("counter", i));
408+
}
409+
Query query = randomColl.orderBy("counter").startAt(8).limitToLast(3);
410+
assertQueryResultContainsDocsInOrder(query, "doc8", "doc9");
411+
}
412+
413+
/**
414+
* Verifies that QuerySnapshot for limitToLast() queries work with startAfter when the full limit
415+
* is used in the result set.
416+
*/
417+
@Test
418+
public void limitToLastWithStartAfterFullLimit() throws Exception {
419+
for (int i = 0; i < 10; i++) {
420+
setDocument("doc" + i, Collections.singletonMap("counter", i));
421+
}
422+
Query query = randomColl.orderBy("counter").startAfter(5).limitToLast(3);
423+
assertQueryResultContainsDocsInOrder(query, "doc7", "doc8", "doc9");
424+
}
425+
426+
/**
427+
* Verifies that QuerySnapshot for limitToLast() queries work with startAfter when the partial
428+
* limit is used in the result set.
429+
*/
430+
@Test
431+
public void limitToLastWithStartAfterPartialLimit() throws Exception {
432+
for (int i = 0; i < 10; i++) {
433+
setDocument("doc" + i, Collections.singletonMap("counter", i));
434+
}
435+
Query query = randomColl.orderBy("counter").startAfter(7).limitToLast(3);
436+
assertQueryResultContainsDocsInOrder(query, "doc8", "doc9");
437+
}
438+
439+
/** Verifies that QuerySnapshot for limitToLast() queries work with endAt. */
440+
@Test
441+
public void limitToLastWithEndAt() throws Exception {
442+
for (int i = 0; i < 10; i++) {
443+
setDocument("doc" + i, Collections.singletonMap("counter", i));
444+
}
445+
Query query = randomColl.orderBy("counter").endAt(5).limitToLast(3);
446+
assertQueryResultContainsDocsInOrder(query, "doc3", "doc4", "doc5");
447+
}
448+
449+
/** Verifies that QuerySnapshot for limitToLast() queries work with endBefore. */
450+
@Test
451+
public void limitToLastWithEndBefore() throws Exception {
452+
for (int i = 0; i < 10; i++) {
453+
setDocument("doc" + i, Collections.singletonMap("counter", i));
454+
}
455+
Query query = randomColl.orderBy("counter").endBefore(5).limitToLast(3);
456+
assertQueryResultContainsDocsInOrder(query, "doc2", "doc3", "doc4");
457+
}
458+
459+
/**
460+
* Verifies that QuerySnapshot for limitToLast() queries work with both startAt and endAt when the
461+
* full limit is used in the result set.
462+
*/
463+
@Test
464+
public void limitToLastWithStartAtAndEndAtFullLimit() throws Exception {
465+
for (int i = 0; i < 10; i++) {
466+
setDocument("doc" + i, Collections.singletonMap("counter", i));
467+
}
468+
Query query = randomColl.orderBy("counter").startAt(3).endAt(6).limitToLast(3);
469+
assertQueryResultContainsDocsInOrder(query, "doc4", "doc5", "doc6");
470+
}
471+
472+
/**
473+
* Verifies that QuerySnapshot for limitToLast() queries work with both startAt and endAt when the
474+
* partial limit is used in the result set.
475+
*/
476+
@Test
477+
public void limitToLastWithStartAtAndEndAtPartialLimit() throws Exception {
478+
for (int i = 0; i < 10; i++) {
479+
setDocument("doc" + i, Collections.singletonMap("counter", i));
480+
}
481+
Query query = randomColl.orderBy("counter").startAt(5).endAt(6).limitToLast(3);
482+
assertQueryResultContainsDocsInOrder(query, "doc5", "doc6");
483+
}
484+
485+
private static void assertQueryResultContainsDocsInOrder(Query query, String... docIds)
486+
throws ExecutionException, InterruptedException {
487+
QuerySnapshot snapshot = query.get().get();
488+
ImmutableList<String> actualDocIds =
489+
snapshot.getDocuments().stream().map(DocumentSnapshot::getId).collect(toImmutableList());
490+
assertThat(actualDocIds).containsExactlyElementsIn(docIds).inOrder();
491+
}
492+
384493
@Test
385494
public void shutdownNowTerminatesActiveListener() throws Exception {
386495
Query query = randomColl.whereEqualTo("foo", "bar");

0 commit comments

Comments
 (0)