15
15
package com .google .firebase .firestore .local ;
16
16
17
17
import static com .google .firebase .firestore .testutil .TestUtil .doc ;
18
+ import static com .google .firebase .firestore .testutil .TestUtil .docSet ;
18
19
import static com .google .firebase .firestore .testutil .TestUtil .filter ;
19
20
import static com .google .firebase .firestore .testutil .TestUtil .map ;
20
21
import static com .google .firebase .firestore .testutil .TestUtil .orderBy ;
21
22
import static com .google .firebase .firestore .testutil .TestUtil .query ;
22
- import static com .google .firebase .firestore .testutil .TestUtil .values ;
23
23
import static com .google .firebase .firestore .testutil .TestUtil .version ;
24
- import static java .util .Arrays .asList ;
25
24
import static org .junit .Assert .assertEquals ;
26
- import static org .mockito .ArgumentMatchers .any ;
27
- import static org .mockito .ArgumentMatchers .anyInt ;
28
- import static org .mockito .Mockito .doAnswer ;
29
25
30
26
import com .google .firebase .database .collection .ImmutableSortedMap ;
31
27
import com .google .firebase .database .collection .ImmutableSortedSet ;
28
+ import com .google .firebase .firestore .auth .User ;
32
29
import com .google .firebase .firestore .core .Query ;
30
+ import com .google .firebase .firestore .core .View ;
33
31
import com .google .firebase .firestore .model .Document ;
34
- import com .google .firebase .firestore .model .DocumentCollections ;
35
32
import com .google .firebase .firestore .model .DocumentKey ;
36
- import com .google .firebase .firestore .model .MaybeDocument ;
33
+ import com .google .firebase .firestore .model .DocumentSet ;
37
34
import com .google .firebase .firestore .model .SnapshotVersion ;
38
35
import com .google .protobuf .ByteString ;
39
- import java .util .ArrayList ;
40
- import java .util .HashMap ;
41
- import java .util .Map ;
42
- import org .junit .Assert ;
36
+ import java .util .Collections ;
43
37
import org .junit .Before ;
44
38
import org .junit .Test ;
45
39
import org .junit .runner .RunWith ;
46
- import org .mockito .Mock ;
47
- import org .mockito .MockitoAnnotations ;
48
40
import org .robolectric .RobolectricTestRunner ;
49
41
import org .robolectric .annotation .Config ;
50
42
@@ -60,6 +52,8 @@ public class IndexFreeQueryEngineTest {
60
52
doc ("coll/a" , 1 , map ("matches" , false , "order" , 1 ), Document .DocumentState .SYNCED );
61
53
private static final Document PENDING_MATCHING_DOC_A =
62
54
doc ("coll/a" , 1 , map ("matches" , true , "order" , 1 ), Document .DocumentState .LOCAL_MUTATIONS );
55
+ private static final Document PENDING_NON_MATCHING_DOC_A =
56
+ doc ("coll/a" , 1 , map ("matches" , false , "order" , 1 ), Document .DocumentState .LOCAL_MUTATIONS );
63
57
private static final Document UDPATED_DOC_A =
64
58
doc ("coll/a" , 11 , map ("matches" , true , "order" , 1 ), Document .DocumentState .SYNCED );
65
59
private static final Document MATCHING_DOC_B =
@@ -69,203 +63,179 @@ public class IndexFreeQueryEngineTest {
69
63
private static final Document UPDATED_MATCHING_DOC_B =
70
64
doc ("coll/b" , 11 , map ("matches" , true , "order" , 2 ), Document .DocumentState .SYNCED );
71
65
72
- @ Mock private LocalDocumentsView localDocumentsView ;
73
- @ Mock private QueryCache queryCache ;
66
+ private MemoryPersistence persistence ;
67
+ private MemoryRemoteDocumentCache remoteDocumentCache ;
68
+ private QueryCache queryCache ;
74
69
private QueryEngine queryEngine ;
75
70
76
- private final Map <DocumentKey , Document > existingQueryResults = new HashMap <>();
77
- private final Map <DocumentKey , Document > updatedQueryResults = new HashMap <>();
78
71
private boolean expectIndexFreeExecution ;
79
72
80
73
@ Before
81
74
public void setUp () {
82
- MockitoAnnotations .initMocks (this );
83
-
84
75
expectIndexFreeExecution = false ;
85
- existingQueryResults .clear ();
86
- updatedQueryResults .clear ();
87
76
77
+ persistence = MemoryPersistence .createEagerGcMemoryPersistence ();
78
+ queryCache = new MemoryQueryCache (persistence );
88
79
queryEngine = new IndexFreeQueryEngine ();
89
- queryEngine .setLocalDocumentsView (localDocumentsView );
90
-
91
- doAnswer (
92
- getMatchingKeysForTargetIdInvocation -> {
93
- int targetId = getMatchingKeysForTargetIdInvocation .getArgument (0 );
94
- Assert .assertEquals (TEST_TARGET_ID , targetId );
95
- return existingQueryResults .keySet ();
96
- })
97
- .when (queryCache )
98
- .getMatchingKeysForTargetId (anyInt ());
99
-
100
- doAnswer (
101
- getDocumentsInvocation -> {
102
- Iterable <DocumentKey > keys = getDocumentsInvocation .getArgument (0 );
103
-
104
- ImmutableSortedMap <DocumentKey , MaybeDocument > docs =
105
- DocumentCollections .emptyMaybeDocumentMap ();
106
- for (DocumentKey key : keys ) {
107
- docs = docs .insert (key , existingQueryResults .get (key ));
108
- }
109
-
110
- return docs ;
111
- })
112
- .when (localDocumentsView )
113
- .getDocuments (any ());
114
-
115
- doAnswer (
116
- getDocumentsMatchingQueryInvocation -> {
117
- Query query = getDocumentsMatchingQueryInvocation .getArgument (0 );
118
- SnapshotVersion snapshotVersion = getDocumentsMatchingQueryInvocation .getArgument (1 );
119
-
120
- assertEquals (
121
- "Observed query execution mode did not match expectation" ,
122
- expectIndexFreeExecution ,
123
- !SnapshotVersion .NONE .equals (snapshotVersion ));
124
-
125
- ImmutableSortedMap <DocumentKey , MaybeDocument > matchingDocs =
126
- DocumentCollections .emptyMaybeDocumentMap ();
127
-
128
- for (Document doc : updatedQueryResults .values ()) {
129
- if (query .matches (doc )) {
130
- matchingDocs = matchingDocs .insert (doc .getKey (), doc );
131
- }
132
- }
133
-
134
- return matchingDocs ;
135
- })
136
- .when (localDocumentsView )
137
- .getDocumentsMatchingQuery (any (), any ());
80
+
81
+ remoteDocumentCache = persistence .getRemoteDocumentCache ();
82
+
83
+ LocalDocumentsView localDocuments =
84
+ new LocalDocumentsView (
85
+ remoteDocumentCache ,
86
+ persistence .getMutationQueue (User .UNAUTHENTICATED ),
87
+ new MemoryIndexManager ()) {
88
+ @ Override
89
+ public ImmutableSortedMap <DocumentKey , Document > getDocumentsMatchingQuery (
90
+ Query query , SnapshotVersion sinceReadTime ) {
91
+ assertEquals (
92
+ "Observed query execution mode did not match expectation" ,
93
+ expectIndexFreeExecution ,
94
+ !SnapshotVersion .NONE .equals (sinceReadTime ));
95
+ return super .getDocumentsMatchingQuery (query , sinceReadTime );
96
+ }
97
+ };
98
+ queryEngine .setLocalDocumentsView (localDocuments );
138
99
}
139
100
140
101
/** Add a document to local cache and the remote key mapping. */
141
- private void addExistingResult (Document doc ) {
142
- existingQueryResults .put (doc .getKey (), doc );
102
+ private void addDocumentToRemoteResult (Document doc ) {
103
+ persistence .runTransaction (
104
+ "addDocumentToRemoteResult" ,
105
+ () -> {
106
+ queryCache .addMatchingKeys (
107
+ DocumentKey .emptyKeySet ().insert (doc .getKey ()), TEST_TARGET_ID );
108
+ remoteDocumentCache .add (doc , doc .getVersion ());
109
+ });
143
110
}
144
111
145
112
/** Add a document to local cache but not the remote key mapping. */
146
- private void addUpdatedResult (Document doc ) {
147
- updatedQueryResults . put (doc . getKey () , doc );
113
+ private void addDocumentToLocalResult (Document doc ) {
114
+ remoteDocumentCache . add (doc , doc . getVersion () );
148
115
}
149
116
150
- private ImmutableSortedMap <DocumentKey , Document > runQuery (
151
- Query query , QueryData queryData , boolean expectIndexFree ) {
117
+ private DocumentSet runQuery (Query query , QueryData queryData , boolean expectIndexFree ) {
152
118
expectIndexFreeExecution = expectIndexFree ;
153
- ImmutableSortedSet <DocumentKey > remoteKeys =
154
- new ImmutableSortedSet <>(
155
- new ArrayList <>(existingQueryResults .keySet ()), DocumentKey .comparator ());
156
- return queryEngine .getDocumentsMatchingQuery (query , queryData , remoteKeys );
119
+ ImmutableSortedMap <DocumentKey , Document > docs =
120
+ queryEngine .getDocumentsMatchingQuery (
121
+ query , queryData , queryCache .getMatchingKeysForTargetId (TEST_TARGET_ID ));
122
+ View view =
123
+ new View (query , new ImmutableSortedSet <>(Collections .emptyList (), DocumentKey ::compareTo ));
124
+ View .DocumentChanges viewDocChanges = view .computeDocChanges (docs );
125
+ return view .applyChanges (viewDocChanges ).getSnapshot ().getDocuments ();
157
126
}
158
127
159
128
@ Test
160
129
public void usesTargetMappingForInitialView () {
161
130
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
162
131
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
163
132
164
- addExistingResult (MATCHING_DOC_A );
165
- addExistingResult (MATCHING_DOC_B );
133
+ addDocumentToRemoteResult (MATCHING_DOC_A );
134
+ addDocumentToRemoteResult (MATCHING_DOC_B );
166
135
167
- ImmutableSortedMap <DocumentKey , Document > docs =
168
- runQuery (query , queryData , /* expectIndexFree= */ true );
169
- assertEquals (asList (MATCHING_DOC_A , MATCHING_DOC_B ), values (docs ));
136
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ true );
137
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_A , MATCHING_DOC_B ), docs );
170
138
}
171
139
172
140
@ Test
173
141
public void filtersNonMatchingInitialResults () {
174
142
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
175
143
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
176
144
177
- addExistingResult ( NON_MATCHING_DOC_A );
178
- addExistingResult (MATCHING_DOC_B );
145
+ addDocumentToRemoteResult ( MATCHING_DOC_A );
146
+ addDocumentToRemoteResult (MATCHING_DOC_B );
179
147
180
- ImmutableSortedMap <DocumentKey , Document > docs =
181
- runQuery (query , queryData , /* expectIndexFree= */ true );
182
- assertEquals (asList (MATCHING_DOC_B ), values (docs ));
148
+ addDocumentToLocalResult (PENDING_NON_MATCHING_DOC_A );
149
+
150
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ true );
151
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
183
152
}
184
153
185
154
@ Test
186
155
public void includesChangesSinceInitialResults () {
187
156
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
188
157
QueryData originalQueryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
189
158
190
- addExistingResult (MATCHING_DOC_A );
191
- addExistingResult (NON_MATCHING_DOC_B );
159
+ addDocumentToRemoteResult (MATCHING_DOC_A );
160
+ addDocumentToRemoteResult (NON_MATCHING_DOC_B );
192
161
193
- ImmutableSortedMap <DocumentKey , Document > docs =
194
- runQuery (query , originalQueryData , /* expectIndexFree= */ true );
195
- assertEquals (asList (MATCHING_DOC_A ), values (docs ));
162
+ DocumentSet docs = runQuery (query , originalQueryData , /* expectIndexFree= */ true );
163
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_A ), docs );
196
164
197
- addUpdatedResult (UPDATED_MATCHING_DOC_B );
165
+ addDocumentToLocalResult (UPDATED_MATCHING_DOC_B );
198
166
199
167
docs = runQuery (query , originalQueryData , /* expectIndexFree= */ true );
200
- assertEquals (asList ( MATCHING_DOC_A , UPDATED_MATCHING_DOC_B ), values ( docs ) );
168
+ assertEquals (docSet ( query . comparator (), MATCHING_DOC_A , UPDATED_MATCHING_DOC_B ), docs );
201
169
}
202
170
203
171
@ Test
204
172
public void doesNotUseInitialResultsWithoutLimboFreeSnapshotVersion () {
205
173
Query query = query ("coll" ).filter (filter ("matches" , "==" , true ));
206
174
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ false );
207
175
208
- ImmutableSortedMap <DocumentKey , Document > docs =
209
- runQuery (query , queryData , /* expectIndexFree= */ false );
210
- assertEquals (asList (), values (docs ));
176
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
177
+ assertEquals (docSet (query .comparator ()), docs );
211
178
}
212
179
213
180
@ Test
214
181
public void doesNotUseInitialResultsForUnfilteredCollectionQuery () {
215
182
Query query = query ("coll" );
216
183
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
217
184
218
- ImmutableSortedMap <DocumentKey , Document > docs =
219
- runQuery (query , queryData , /* expectIndexFree= */ false );
220
- assertEquals (asList (), values (docs ));
185
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
186
+ assertEquals (docSet (query .comparator ()), docs );
221
187
}
222
188
223
189
@ Test
224
190
public void doesNotUseInitialResultsForLimitQueryWithDocumentRemoval () {
225
- Query query =
226
- query ("coll" ).filter (filter ("matches" , "==" , true )).orderBy (orderBy ("order" )).limit (1 );
191
+ Query query = query ("coll" ).filter (filter ("matches" , "==" , true )).limit (1 );
227
192
228
- addExistingResult (NON_MATCHING_DOC_A );
193
+ addDocumentToRemoteResult (NON_MATCHING_DOC_A );
229
194
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
230
- addUpdatedResult (MATCHING_DOC_B );
195
+ addDocumentToLocalResult (MATCHING_DOC_B );
231
196
232
- ImmutableSortedMap <DocumentKey , Document > docs =
233
- runQuery (query , queryData , /* expectIndexFree= */ false );
234
- assertEquals (asList (MATCHING_DOC_B ), values (docs ));
197
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
198
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
235
199
}
236
200
237
201
@ Test
238
202
public void doesNotUseInitialResultsForLimitQueryWithPendingWrite () {
239
- Query query = query ("coll" ).filter (filter ("matches" , "==" , true )).limit (1 );
203
+ Query query =
204
+ query ("coll" )
205
+ .filter (filter ("matches" , "==" , true ))
206
+ .orderBy (orderBy ("order" , "desc" ))
207
+ .limit (1 );
240
208
241
209
// Add a query mapping for a document that matches, but that sorts below another document due to
242
210
// a pending write.
243
- addExistingResult (PENDING_MATCHING_DOC_A );
211
+ addDocumentToRemoteResult (PENDING_MATCHING_DOC_A );
244
212
245
213
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
246
214
247
- addUpdatedResult (MATCHING_DOC_B );
215
+ addDocumentToLocalResult (MATCHING_DOC_B );
248
216
249
- ImmutableSortedMap <DocumentKey , Document > docs =
250
- runQuery (query , queryData , /* expectIndexFree= */ false );
251
- assertEquals (asList (MATCHING_DOC_B ), values (docs ));
217
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
218
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
252
219
}
253
220
254
221
@ Test
255
222
public void doesNotUseInitialResultsForLimitQueryWithDocumentThatHasBeenUpdatedOutOfBand () {
256
- Query query = query ("coll" ).filter (filter ("matches" , "==" , true )).limit (1 );
223
+ Query query =
224
+ query ("coll" )
225
+ .filter (filter ("matches" , "==" , true ))
226
+ .orderBy (orderBy ("order" , "desc" ))
227
+ .limit (1 );
257
228
258
229
// Add a query mapping for a document that matches, but that sorts below another document based
259
230
// due to an update that the SDK received after the query's snapshot was persisted.
260
- addExistingResult (UDPATED_DOC_A );
231
+ addDocumentToRemoteResult (UDPATED_DOC_A );
261
232
262
233
QueryData queryData = queryData (query , /* hasLimboFreeSnapshot= */ true );
263
234
264
- addUpdatedResult (MATCHING_DOC_B );
235
+ addDocumentToLocalResult (MATCHING_DOC_B );
265
236
266
- ImmutableSortedMap <DocumentKey , Document > docs =
267
- runQuery (query , queryData , /* expectIndexFree= */ false );
268
- assertEquals (asList (MATCHING_DOC_B ), values (docs ));
237
+ DocumentSet docs = runQuery (query , queryData , /* expectIndexFree= */ false );
238
+ assertEquals (docSet (query .comparator (), MATCHING_DOC_B ), docs );
269
239
}
270
240
271
241
private QueryData queryData (Query query , boolean hasLimboFreeSnapshot ) {
0 commit comments