17
17
import static com .google .firebase .firestore .testutil .TestUtil .doc ;
18
18
import static com .google .firebase .firestore .testutil .TestUtil .docSet ;
19
19
import static com .google .firebase .firestore .testutil .TestUtil .filter ;
20
+ import static com .google .firebase .firestore .testutil .TestUtil .key ;
20
21
import static com .google .firebase .firestore .testutil .TestUtil .map ;
21
22
import static com .google .firebase .firestore .testutil .TestUtil .orderBy ;
22
23
import static com .google .firebase .firestore .testutil .TestUtil .query ;
@@ -61,8 +62,6 @@ public class IndexFreeQueryEngineTest {
61
62
doc ("coll/a" , 11 , map ("matches" , true , "order" , 1 ), Document .DocumentState .SYNCED );
62
63
private static final Document MATCHING_DOC_B =
63
64
doc ("coll/b" , 1 , map ("matches" , true , "order" , 2 ), Document .DocumentState .SYNCED );
64
- private static final Document NON_MATCHING_DOC_B =
65
- doc ("coll/b" , 1 , map ("matches" , false , "order" , 2 ), Document .DocumentState .SYNCED );
66
65
private static final Document UPDATED_MATCHING_DOC_B =
67
66
doc ("coll/b" , 11 , map ("matches" , true , "order" , 2 ), Document .DocumentState .SYNCED );
68
67
@@ -102,13 +101,13 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
102
101
}
103
102
104
103
/** Adds the provided documents to the query target mapping. */
105
- private void persistQueryMapping (Document ... docs ) {
104
+ private void persistQueryMapping (DocumentKey ... documentKeys ) {
106
105
persistence .runTransaction (
107
106
"persistQueryMapping" ,
108
107
() -> {
109
108
ImmutableSortedSet <DocumentKey > remoteKeys = DocumentKey .emptyKeySet ();
110
- for (Document doc : docs ) {
111
- remoteKeys = remoteKeys .insert (doc . getKey () );
109
+ for (DocumentKey documentKey : documentKeys ) {
110
+ remoteKeys = remoteKeys .insert (documentKey );
112
111
}
113
112
queryCache .addMatchingKeys (remoteKeys , TEST_TARGET_ID );
114
113
});
@@ -162,7 +161,7 @@ public void usesTargetMappingForInitialView() throws Exception {
162
161
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
163
162
164
163
addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
165
- persistQueryMapping (MATCHING_DOC_A , MATCHING_DOC_B );
164
+ persistQueryMapping (MATCHING_DOC_A . getKey () , MATCHING_DOC_B . getKey () );
166
165
167
166
DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , queryData ));
168
167
assertEquals (docSet (query .comparator (), MATCHING_DOC_A , MATCHING_DOC_B ), docs );
@@ -174,7 +173,7 @@ public void filtersNonMatchingInitialResults() throws Exception {
174
173
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
175
174
176
175
addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
177
- persistQueryMapping (MATCHING_DOC_A , MATCHING_DOC_B );
176
+ persistQueryMapping (MATCHING_DOC_A . getKey () , MATCHING_DOC_B . getKey () );
178
177
179
178
// Add a mutated document that is not yet part of query's set of remote keys.
180
179
addDocument (PENDING_NON_MATCHING_DOC_A );
@@ -189,7 +188,7 @@ public void includesChangesSinceInitialResults() throws Exception {
189
188
QueryData originalQueryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
190
189
191
190
addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
192
- persistQueryMapping (MATCHING_DOC_A , MATCHING_DOC_B );
191
+ persistQueryMapping (MATCHING_DOC_A . getKey () , MATCHING_DOC_B . getKey () );
193
192
194
193
DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , originalQueryData ));
195
194
assertEquals (docSet (query .comparator (), MATCHING_DOC_A , MATCHING_DOC_B ), docs );
@@ -225,7 +224,7 @@ public void doesNotUseInitialResultsForLimitQueryWithDocumentRemoval() throws Ex
225
224
// While the backend would never add DocA to the set of remote keys, this allows us to easily
226
225
// simulate what would happen when a document no longer matches due to an out-of-band update.
227
226
addDocument (NON_MATCHING_DOC_A );
228
- persistQueryMapping (NON_MATCHING_DOC_A );
227
+ persistQueryMapping (NON_MATCHING_DOC_A . getKey () );
229
228
230
229
addDocument (MATCHING_DOC_B );
231
230
@@ -235,7 +234,8 @@ public void doesNotUseInitialResultsForLimitQueryWithDocumentRemoval() throws Ex
235
234
}
236
235
237
236
@ Test
238
- public void doesNotUseInitialResultsForLimitQueryWithPendingWrite () throws Exception {
237
+ public void doesNotUseInitialResultsForLimitQueryWhenLastDocumentHasPendingWrite ()
238
+ throws Exception {
239
239
Query query =
240
240
query ("coll" )
241
241
.filter (filter ("matches" , "==" , true ))
@@ -245,7 +245,7 @@ public void doesNotUseInitialResultsForLimitQueryWithPendingWrite() throws Excep
245
245
// Add a query mapping for a document that matches, but that sorts below another document due to
246
246
// a pending write.
247
247
addDocument (PENDING_MATCHING_DOC_A );
248
- persistQueryMapping (PENDING_MATCHING_DOC_A );
248
+ persistQueryMapping (PENDING_MATCHING_DOC_A . getKey () );
249
249
250
250
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
251
251
@@ -256,7 +256,7 @@ public void doesNotUseInitialResultsForLimitQueryWithPendingWrite() throws Excep
256
256
}
257
257
258
258
@ Test
259
- public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedOutOfBand ()
259
+ public void doesNotUseInitialResultsForLimitQueryWhenLastDocumentHasBeenUpdatedOutOfBand ()
260
260
throws Exception {
261
261
Query query =
262
262
query ("coll" )
@@ -267,7 +267,7 @@ public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedO
267
267
// Add a query mapping for a document that matches, but that sorts below another document based
268
268
// due to an update that the SDK received after the query's snapshot was persisted.
269
269
addDocument (UDPATED_DOC_A );
270
- persistQueryMapping (UDPATED_DOC_A );
270
+ persistQueryMapping (UDPATED_DOC_A . getKey () );
271
271
272
272
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
273
273
@@ -277,6 +277,30 @@ public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedO
277
277
assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
278
278
}
279
279
280
+ @ Test
281
+ public void limitQueriesUseInitialResultsIfLastDocumentInLimitIsUnchanged () throws Exception {
282
+ Query query = query ("coll" ).orderBy (orderBy ("order" )).limit (2 );
283
+
284
+ addDocument (doc ("coll/a" , 1 , map ("order" , 1 )));
285
+ addDocument (doc ("coll/b" , 1 , map ("order" , 3 )));
286
+ persistQueryMapping (key ("coll/a" ), key ("coll/b" ));
287
+ QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
288
+
289
+ // Update "coll/a" but make sure it still sorts before "coll/b"
290
+ addDocument (doc ("coll/a" , 1 , map ("order" , 2 ), Document .DocumentState .LOCAL_MUTATIONS ));
291
+
292
+ // Since the last document in the limit didn't change (and hence we know that all documents
293
+ // written prior to query execution still sort after "coll/b"), we should use an Index-Free
294
+ // query.
295
+ DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , queryData ));
296
+ assertEquals (
297
+ docSet (
298
+ query .comparator (),
299
+ doc ("coll/a" , 1 , map ("order" , 2 ), Document .DocumentState .LOCAL_MUTATIONS ),
300
+ doc ("coll/b" , 1 , map ("order" , 3 ))),
301
+ docs );
302
+ }
303
+
280
304
private QueryData queryData (Query query , boolean hasLimboFreeSnapshot ) {
281
305
return new QueryData (
282
306
query ,
0 commit comments