@@ -35,22 +35,6 @@ describeSpec('Existence Filters:', [], () => {
35
35
. watchSnapshots ( 2000 ) ;
36
36
} ) ;
37
37
38
- // This test is only to make sure watchFilters can accept bloom filter.
39
- // TODO:(mila) update the tests when bloom filter logic is implemented.
40
- specTest ( 'Existence filter with bloom filter match' , [ ] , ( ) => {
41
- const query1 = query ( 'collection' ) ;
42
- const doc1 = doc ( 'collection/1' , 1000 , { v : 1 } ) ;
43
- return spec ( )
44
- . userListens ( query1 )
45
- . watchAcksFull ( query1 , 1000 , doc1 )
46
- . expectEvents ( query1 , { added : [ doc1 ] } )
47
- . watchFilters ( [ query1 ] , [ doc1 . key ] , {
48
- bits : { bitmap : 'a' , padding : 1 } ,
49
- hashCount : 1
50
- } )
51
- . watchSnapshots ( 2000 ) ;
52
- } ) ;
53
-
54
38
specTest ( 'Existence filter match after pending update' , [ ] , ( ) => {
55
39
const query1 = query ( 'collection' ) ;
56
40
const doc1 = doc ( 'collection/1' , 2000 , { v : 2 } ) ;
@@ -128,36 +112,6 @@ describeSpec('Existence Filters:', [], () => {
128
112
) ;
129
113
} ) ;
130
114
131
- // This test is only to make sure watchFilters can accept bloom filter.
132
- // TODO:(mila) update the tests when bloom filter logic is implemented.
133
- specTest ( 'Existence filter mismatch triggers bloom filter' , [ ] , ( ) => {
134
- const query1 = query ( 'collection' ) ;
135
- const doc1 = doc ( 'collection/1' , 1000 , { v : 1 } ) ;
136
- const doc2 = doc ( 'collection/2' , 1000 , { v : 2 } ) ;
137
- return (
138
- spec ( )
139
- . userListens ( query1 )
140
- . watchAcksFull ( query1 , 1000 , doc1 , doc2 )
141
- . expectEvents ( query1 , { added : [ doc1 , doc2 ] } )
142
- . watchFilters ( [ query1 ] , [ doc1 . key ] , {
143
- bits : { bitmap : 'a' , padding : 1 } ,
144
- hashCount : 3
145
- } ) // in the next sync doc2 was deleted
146
- . watchSnapshots ( 2000 )
147
- // query is now marked as "inconsistent" because of filter mismatch
148
- . expectEvents ( query1 , { fromCache : true } )
149
- . expectActiveTargets ( { query : query1 , resumeToken : '' } )
150
- . watchRemoves ( query1 ) // Acks removal of query
151
- . watchAcksFull ( query1 , 2000 , doc1 )
152
- . expectLimboDocs ( doc2 . key ) // doc2 is now in limbo
153
- . ackLimbo ( 2000 , deletedDoc ( 'collection/2' , 2000 ) )
154
- . expectLimboDocs ( ) // doc2 is no longer in limbo
155
- . expectEvents ( query1 , {
156
- removed : [ doc2 ]
157
- } )
158
- ) ;
159
- } ) ;
160
-
161
115
specTest ( 'Existence filter mismatch will drop resume token' , [ ] , ( ) => {
162
116
const query1 = query ( 'collection' ) ;
163
117
const doc1 = doc ( 'collection/1' , 1000 , { v : 1 } ) ;
@@ -286,4 +240,134 @@ describeSpec('Existence Filters:', [], () => {
286
240
) ;
287
241
}
288
242
) ;
243
+
244
+ /**
245
+ * ExistenceFilter with BloomFilter
246
+ * This bloomFilter bitmap size and hashCount is small enough to make false positive rate
247
+ * as high as 0.5.
248
+ * {
249
+ * bits: {
250
+ * bitmap: 'AQ=='
251
+ * padding: 6
252
+ * },
253
+ * hashCount: 1
254
+ * }
255
+ * When testing migthContain(), 'collection/a','collection/c' will return true,
256
+ * while mightContain('collection/b') will return false.
257
+ */
258
+
259
+ specTest (
260
+ 'Full requery is skipped when bloom filter can identify documents deleted' ,
261
+ [ ] ,
262
+ ( ) => {
263
+ const query1 = query ( 'collection' ) ;
264
+ const docA = doc ( 'collection/a' , 1000 , { v : 1 } ) ;
265
+ const docB = doc ( 'collection/b' , 1000 , { v : 2 } ) ;
266
+ return (
267
+ spec ( )
268
+ . userListens ( query1 )
269
+ . watchAcksFull ( query1 , 1000 , docA , docB )
270
+ . expectEvents ( query1 , { added : [ docA , docB ] } )
271
+ . watchFilters ( [ query1 ] , [ docA . key ] , {
272
+ bits : { bitmap : 'AQ==' , padding : 6 } ,
273
+ hashCount : 1
274
+ } )
275
+ . watchSnapshots ( 2000 )
276
+ // BloomFilter identify docB is deleted, skip full query and put docB
277
+ // into limbo directly.
278
+ . expectEvents ( query1 , { fromCache : true } )
279
+ . expectLimboDocs ( docB . key ) // docB is now in limbo
280
+ . ackLimbo ( 2000 , deletedDoc ( 'collection/b' , 2000 ) )
281
+ . expectLimboDocs ( ) // docB is no longer in limbo
282
+ . expectEvents ( query1 , {
283
+ removed : [ docB ]
284
+ } )
285
+ ) ;
286
+ }
287
+ ) ;
288
+
289
+ specTest (
290
+ 'Full requery is triggered when bloom filter can not identify documents deleted' ,
291
+ [ ] ,
292
+ ( ) => {
293
+ const query1 = query ( 'collection' ) ;
294
+ const docA = doc ( 'collection/a' , 1000 , { v : 1 } ) ;
295
+ const docC = doc ( 'collection/c' , 1000 , { v : 2 } ) ;
296
+
297
+ return (
298
+ spec ( )
299
+ . userListens ( query1 )
300
+ . watchAcksFull ( query1 , 1000 , docA , docC )
301
+ . expectEvents ( query1 , { added : [ docA , docC ] } )
302
+ . watchFilters ( [ query1 ] , [ docA . key ] , {
303
+ bits : { bitmap : 'AQ==' , padding : 6 } ,
304
+ hashCount : 1
305
+ } )
306
+ . watchSnapshots ( 2000 )
307
+ // BloomFilter yields false positive results, cannot identify docC
308
+ // is deleted. Re-run query is triggered.
309
+ . expectEvents ( query1 , { fromCache : true } )
310
+ . expectActiveTargets ( { query : query1 , resumeToken : '' } )
311
+ . watchRemoves ( query1 ) // Acks removal of query
312
+ . watchAcksFull ( query1 , 2000 , docA )
313
+ . expectLimboDocs ( docC . key ) // docC is now in limbo
314
+ . ackLimbo ( 2000 , deletedDoc ( 'collection/c' , 2000 ) )
315
+ . expectLimboDocs ( ) // docC is no longer in limbo
316
+ . expectEvents ( query1 , {
317
+ removed : [ docC ]
318
+ } )
319
+ ) ;
320
+ }
321
+ ) ;
322
+
323
+ specTest ( 'Bloom filter handled at global snapshot' , [ ] , ( ) => {
324
+ const query1 = query ( 'collection' ) ;
325
+ const docA = doc ( 'collection/a' , 1000 , { v : 1 } ) ;
326
+ const docB = doc ( 'collection/b' , 2000 , { v : 2 } ) ;
327
+ const docC = doc ( 'collection/c' , 3000 , { v : 3 } ) ;
328
+ return (
329
+ spec ( )
330
+ . userListens ( query1 )
331
+ . watchAcksFull ( query1 , 1000 , docA , docB )
332
+ . expectEvents ( query1 , { added : [ docA , docB ] } )
333
+ // Send a mismatching existence filter with one document, but don't
334
+ // send a new global snapshot. We should not see an event until we
335
+ // receive the snapshot.
336
+ . watchFilters ( [ query1 ] , [ docA . key ] , {
337
+ bits : { bitmap : 'AQ==' , padding : 6 } ,
338
+ hashCount : 1
339
+ } )
340
+ // BloomFilter identifies docB is removed, moves it to limbo.
341
+ . watchSends ( { affects : [ query1 ] } , docC )
342
+ . watchSnapshots ( 2000 )
343
+ . expectEvents ( query1 , { added : [ docC ] , fromCache : true } )
344
+ // re-run of the query1 is skipped, docB is in limbo.
345
+ . expectLimboDocs ( docB . key )
346
+ ) ;
347
+ } ) ;
348
+
349
+ specTest ( 'Bloom filter limbo resolution is denied' , [ ] , ( ) => {
350
+ const query1 = query ( 'collection' ) ;
351
+ const docA = doc ( 'collection/a' , 1000 , { v : 1 } ) ;
352
+ const docB = doc ( 'collection/b' , 1000 , { v : 2 } ) ;
353
+ return spec ( )
354
+ . userListens ( query1 )
355
+ . watchAcksFull ( query1 , 1000 , docA , docB )
356
+ . expectEvents ( query1 , { added : [ docA , docB ] } )
357
+ . watchFilters ( [ query1 ] , [ docA . key ] , {
358
+ bits : { bitmap : 'AQ==' , padding : 6 } ,
359
+ hashCount : 1
360
+ } )
361
+ . watchSnapshots ( 2000 )
362
+ . expectEvents ( query1 , { fromCache : true } )
363
+ . expectLimboDocs ( docB . key ) // docB is now in limbo
364
+ . watchRemoves (
365
+ newQueryForPath ( docB . key . path ) ,
366
+ new RpcError ( Code . PERMISSION_DENIED , 'no' )
367
+ )
368
+ . expectLimboDocs ( ) // docB is no longer in limbo
369
+ . expectEvents ( query1 , {
370
+ removed : [ docB ]
371
+ } ) ;
372
+ } ) ;
289
373
} ) ;
0 commit comments