23
23
import static com .google .firebase .firestore .testutil .TestUtil .version ;
24
24
import static org .junit .Assert .assertEquals ;
25
25
26
+ import com .google .android .gms .common .internal .Preconditions ;
26
27
import com .google .firebase .database .collection .ImmutableSortedMap ;
27
28
import com .google .firebase .database .collection .ImmutableSortedSet ;
28
29
import com .google .firebase .firestore .auth .User ;
34
35
import com .google .firebase .firestore .model .SnapshotVersion ;
35
36
import com .google .protobuf .ByteString ;
36
37
import java .util .Collections ;
38
+ import java .util .concurrent .Callable ;
39
+ import javax .annotation .Nullable ;
37
40
import org .junit .Before ;
38
41
import org .junit .Test ;
39
42
import org .junit .runner .RunWith ;
@@ -68,11 +71,11 @@ public class IndexFreeQueryEngineTest {
68
71
private QueryCache queryCache ;
69
72
private QueryEngine queryEngine ;
70
73
71
- private boolean expectIndexFreeExecution ;
74
+ private @ Nullable Boolean expectIndexFreeExecution ;
72
75
73
76
@ Before
74
77
public void setUp () {
75
- expectIndexFreeExecution = false ;
78
+ expectIndexFreeExecution = null ;
76
79
77
80
persistence = MemoryPersistence .createEagerGcMemoryPersistence ();
78
81
queryCache = new MemoryQueryCache (persistence );
@@ -98,24 +101,52 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
98
101
queryEngine .setLocalDocumentsView (localDocuments );
99
102
}
100
103
101
- /** Add a document to local cache and the remote key mapping. */
102
- private void addDocumentToRemoteResult (Document doc ) {
104
+ /** Adds the provided documents to the query target mapping. */
105
+ private void persistQueryMapping (Document ... docs ) {
103
106
persistence .runTransaction (
104
- "addDocumentToRemoteResult " ,
107
+ "persistQueryMapping " ,
105
108
() -> {
106
- queryCache .addMatchingKeys (
107
- DocumentKey .emptyKeySet ().insert (doc .getKey ()), TEST_TARGET_ID );
108
- remoteDocumentCache .add (doc , doc .getVersion ());
109
+ ImmutableSortedSet <DocumentKey > remoteKeys = DocumentKey .emptyKeySet ();
110
+ for (Document doc : docs ) {
111
+ remoteKeys = remoteKeys .insert (doc .getKey ());
112
+ }
113
+ queryCache .addMatchingKeys (remoteKeys , TEST_TARGET_ID );
114
+ });
115
+ }
116
+
117
+ /** Adds the provided documents to the remote document cache. */
118
+ private void addDocument (Document ... docs ) {
119
+ persistence .runTransaction (
120
+ "addDocument" ,
121
+ () -> {
122
+ for (Document doc : docs ) {
123
+ remoteDocumentCache .add (doc , doc .getVersion ());
124
+ }
109
125
});
110
126
}
111
127
112
- /** Add a document to local cache but not the remote key mapping. */
113
- private void addDocumentToLocalResult (Document doc ) {
114
- remoteDocumentCache .add (doc , doc .getVersion ());
128
+ private <T > T expectIndexFreeQuery (Callable <T > c ) throws Exception {
129
+ try {
130
+ expectIndexFreeExecution = true ;
131
+ return c .call ();
132
+ } finally {
133
+ expectIndexFreeExecution = null ;
134
+ }
115
135
}
116
136
117
- private DocumentSet runQuery (Query query , QueryData queryData , boolean expectIndexFree ) {
118
- expectIndexFreeExecution = expectIndexFree ;
137
+ private <T > T expectFullCollectionQuery (Callable <T > c ) throws Exception {
138
+ try {
139
+ expectIndexFreeExecution = false ;
140
+ return c .call ();
141
+ } finally {
142
+ expectIndexFreeExecution = null ;
143
+ }
144
+ }
145
+
146
+ private DocumentSet runQuery (Query query , QueryData queryData ) {
147
+ Preconditions .checkNotNull (
148
+ expectIndexFreeExecution ,
149
+ "Encountered runQuery() call not wrapped in expectIndexFreeQuery()/expectFullCollectionQuery()" );
119
150
ImmutableSortedMap <DocumentKey , Document > docs =
120
151
queryEngine .getDocumentsMatchingQuery (
121
152
query , queryData , queryCache .getMatchingKeysForTargetId (TEST_TARGET_ID ));
@@ -126,80 +157,85 @@ private DocumentSet runQuery(Query query, QueryData queryData, boolean expectInd
126
157
}
127
158
128
159
@ Test
129
- public void usesTargetMappingForInitialView () {
160
+ public void usesTargetMappingForInitialView () throws Exception {
130
161
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
131
162
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
132
163
133
- addDocumentToRemoteResult (MATCHING_DOC_A );
134
- addDocumentToRemoteResult ( MATCHING_DOC_B );
164
+ addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
165
+ persistQueryMapping ( MATCHING_DOC_A , MATCHING_DOC_B );
135
166
136
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ true );
167
+ DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , queryData ) );
137
168
assertEquals (docSet (query .comparator (), MATCHING_DOC_A , MATCHING_DOC_B ), docs );
138
169
}
139
170
140
171
@ Test
141
- public void filtersNonMatchingInitialResults () {
172
+ public void filtersNonMatchingInitialResults () throws Exception {
142
173
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
143
174
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
144
175
145
- addDocumentToRemoteResult (MATCHING_DOC_A );
146
- addDocumentToRemoteResult ( MATCHING_DOC_B );
176
+ addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
177
+ persistQueryMapping ( MATCHING_DOC_A , MATCHING_DOC_B );
147
178
148
- addDocumentToLocalResult (PENDING_NON_MATCHING_DOC_A );
179
+ // Add a mutated document that is not yet part of query's set of remote keys.
180
+ addDocument (PENDING_NON_MATCHING_DOC_A );
149
181
150
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ true );
182
+ DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , queryData ) );
151
183
assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
152
184
}
153
185
154
186
@ Test
155
- public void includesChangesSinceInitialResults () {
187
+ public void includesChangesSinceInitialResults () throws Exception {
156
188
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
157
189
QueryData originalQueryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
158
190
159
- addDocumentToRemoteResult (MATCHING_DOC_A );
160
- addDocumentToRemoteResult ( NON_MATCHING_DOC_B );
191
+ addDocument (MATCHING_DOC_A , MATCHING_DOC_B );
192
+ persistQueryMapping ( MATCHING_DOC_A , MATCHING_DOC_B );
161
193
162
- DocumentSet docs = runQuery (query , originalQueryData , /* expectIndexFree= */ true );
163
- assertEquals (docSet (query .comparator (), MATCHING_DOC_A ), docs );
194
+ DocumentSet docs = expectIndexFreeQuery (() -> runQuery (query , originalQueryData ) );
195
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_A , MATCHING_DOC_B ), docs );
164
196
165
- addDocumentToLocalResult (UPDATED_MATCHING_DOC_B );
197
+ addDocument (UPDATED_MATCHING_DOC_B );
166
198
167
- docs = runQuery (query , originalQueryData , /* expectIndexFree= */ true );
199
+ docs = expectIndexFreeQuery (() -> runQuery (query , originalQueryData ) );
168
200
assertEquals (docSet (query .comparator (), MATCHING_DOC_A , UPDATED_MATCHING_DOC_B ), docs );
169
201
}
170
202
171
203
@ Test
172
- public void doesNotUseInitialResultsWithoutLimboFreeSnapshotVersion () {
204
+ public void doesNotUseInitialResultsWithoutLimboFreeSnapshotVersion () throws Exception {
173
205
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
174
206
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ false );
175
207
176
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
208
+ DocumentSet docs = expectFullCollectionQuery (() -> runQuery (query , queryData ) );
177
209
assertEquals (docSet (query .comparator ()), docs );
178
210
}
179
211
180
212
@ Test
181
- public void doesNotUseInitialResultsForUnfilteredCollectionQuery () {
213
+ public void doesNotUseInitialResultsForUnfilteredCollectionQuery () throws Exception {
182
214
Query query = query ("coll" );
183
215
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
184
216
185
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
217
+ DocumentSet docs = expectFullCollectionQuery (() -> runQuery (query , queryData ) );
186
218
assertEquals (docSet (query .comparator ()), docs );
187
219
}
188
220
189
221
@ Test
190
- public void doesNotUseInitialResultsForLimitQueryWithDocumentRemoval () {
222
+ public void doesNotUseInitialResultsForLimitQueryWithDocumentRemoval () throws Exception {
191
223
Query query = query ("coll" ).filter (filter ("matches" , "==" , true )).limit (1 );
192
224
193
- addDocumentToRemoteResult (NON_MATCHING_DOC_A );
194
- QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
195
- addDocumentToLocalResult (MATCHING_DOC_B );
225
+ // While the backend would never add DocA to the set of remote keys, this allows us to easily
226
+ // simulate what would happen when a document no longer matches due to an out-of-band update.
227
+ addDocument (NON_MATCHING_DOC_A );
228
+ persistQueryMapping (NON_MATCHING_DOC_A );
229
+
230
+ addDocument (MATCHING_DOC_B );
196
231
197
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
232
+ QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
233
+ DocumentSet docs = expectFullCollectionQuery (() -> runQuery (query , queryData ));
198
234
assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
199
235
}
200
236
201
237
@ Test
202
- public void doesNotUseInitialResultsForLimitQueryWithPendingWrite () {
238
+ public void doesNotUseInitialResultsForLimitQueryWithPendingWrite () throws Exception {
203
239
Query query =
204
240
query ("coll" )
205
241
.filter (filter ("matches" , "==" , true ))
@@ -208,18 +244,20 @@ public void doesNotUseInitialResultsForLimitQueryWithPendingWrite() {
208
244
209
245
// Add a query mapping for a document that matches, but that sorts below another document due to
210
246
// a pending write.
211
- addDocumentToRemoteResult (PENDING_MATCHING_DOC_A );
247
+ addDocument (PENDING_MATCHING_DOC_A );
248
+ persistQueryMapping (PENDING_MATCHING_DOC_A );
212
249
213
250
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
214
251
215
- addDocumentToLocalResult (MATCHING_DOC_B );
252
+ addDocument (MATCHING_DOC_B );
216
253
217
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
254
+ DocumentSet docs = expectFullCollectionQuery (() -> runQuery (query , queryData ) );
218
255
assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
219
256
}
220
257
221
258
@ Test
222
- public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedOutOfBand () {
259
+ public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedOutOfBand ()
260
+ throws Exception {
223
261
Query query =
224
262
query ("coll" )
225
263
.filter (filter ("matches" , "==" , true ))
@@ -228,13 +266,14 @@ public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedO
228
266
229
267
// Add a query mapping for a document that matches, but that sorts below another document based
230
268
// due to an update that the SDK received after the query's snapshot was persisted.
231
- addDocumentToRemoteResult (UDPATED_DOC_A );
269
+ addDocument (UDPATED_DOC_A );
270
+ persistQueryMapping (UDPATED_DOC_A );
232
271
233
272
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
234
273
235
- addDocumentToLocalResult (MATCHING_DOC_B );
274
+ addDocument (MATCHING_DOC_B );
236
275
237
- DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
276
+ DocumentSet docs = expectFullCollectionQuery (() -> runQuery (query , queryData ) );
238
277
assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
239
278
}
240
279
0 commit comments