@@ -2075,129 +2075,128 @@ apiDescribe('Queries', persistence => {
2075
2075
} ) ;
2076
2076
} ) ;
2077
2077
2078
- it ( 'resuming a query should use bloom filter to avoid full requery' , async ( ) => {
2079
- // Prepare the names and contents of the 100 documents to create.
2080
- const testDocs : { [ key : string ] : object } = { } ;
2081
- for ( let i = 0 ; i < 100 ; i ++ ) {
2082
- testDocs [ 'doc' + ( 1000 + i ) ] = { key : 42 } ;
2083
- }
2084
-
2085
- // Ensure that the local cache is configured to use LRU garbage
2086
- // collection (rather than eager garbage collection) so that the resume
2087
- // token and document data does not get prematurely evicted.
2088
- const lruPersistence = persistence . toLruGc ( ) ;
2089
-
2090
- return withRetry ( async attemptNumber => {
2091
- return withTestCollection ( lruPersistence , testDocs , async ( coll , db ) => {
2092
- // Run a query to populate the local cache with the 100 documents and a
2093
- // resume token.
2094
- const snapshot1 = await getDocs ( coll ) ;
2095
- expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2096
- const createdDocuments = snapshot1 . docs . map ( snapshot => snapshot . ref ) ;
2097
-
2098
- // Delete 50 of the 100 documents. Use a WriteBatch, rather than
2099
- // deleteDoc(), to avoid affecting the local cache.
2100
- const deletedDocumentIds = new Set < string > ( ) ;
2101
- const writeBatchForDocumentDeletes = writeBatch ( db ) ;
2102
- for ( let i = 0 ; i < createdDocuments . length ; i += 2 ) {
2103
- const documentToDelete = createdDocuments [ i ] ;
2104
- writeBatchForDocumentDeletes . delete ( documentToDelete ) ;
2105
- deletedDocumentIds . add ( documentToDelete . id ) ;
2106
- }
2107
- await writeBatchForDocumentDeletes . commit ( ) ;
2108
-
2109
- // Wait for 10 seconds, during which Watch will stop tracking the query
2110
- // and will send an existence filter rather than "delete" events when
2111
- // the query is resumed.
2112
- await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2113
-
2114
- // Resume the query and save the resulting snapshot for verification.
2115
- // Use some internal testing hooks to "capture" the existence filter
2116
- // mismatches to verify that Watch sent a bloom filter, and it was used
2117
- // to avert a full requery.
2118
- const [ existenceFilterMismatches , snapshot2 ] =
2119
- await captureExistenceFilterMismatches ( ( ) => getDocs ( coll ) ) ;
2120
-
2121
- // Verify that the snapshot from the resumed query contains the expected
2122
- // documents; that is, that it contains the 50 documents that were _not_
2123
- // deleted.
2124
- // TODO(b/270731363): Remove the "if" condition below once the
2125
- // Firestore Emulator is fixed to send an existence filter. At the time
2126
- // of writing, the Firestore emulator fails to send an existence filter,
2127
- // resulting in the client including the deleted documents in the
2128
- // snapshot of the resumed query.
2129
- if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2130
- const actualDocumentIds = snapshot2 . docs
2131
- . map ( documentSnapshot => documentSnapshot . ref . id )
2132
- . sort ( ) ;
2133
- const expectedDocumentIds = createdDocuments
2134
- . filter ( documentRef => ! deletedDocumentIds . has ( documentRef . id ) )
2135
- . map ( documentRef => documentRef . id )
2136
- . sort ( ) ;
2137
- expect ( actualDocumentIds , 'snapshot2.docs' ) . to . deep . equal (
2138
- expectedDocumentIds
2139
- ) ;
2140
- }
2078
+ // TODO(b/291365820): Stop skipping this test when running against the
2079
+ // Firestore emulator once the emulator is improved to include a bloom filter
2080
+ // in the existence filter messages that it sends.
2081
+ // eslint-disable-next-line no-restricted-properties
2082
+ ( USE_EMULATOR ? it . skip : it ) (
2083
+ 'resuming a query should use bloom filter to avoid full requery' ,
2084
+ async ( ) => {
2085
+ // Prepare the names and contents of the 100 documents to create.
2086
+ const testDocs : { [ key : string ] : object } = { } ;
2087
+ for ( let i = 0 ; i < 100 ; i ++ ) {
2088
+ testDocs [ 'doc' + ( 1000 + i ) ] = { key : 42 } ;
2089
+ }
2141
2090
2142
- // Skip the verification of the existence filter mismatch when testing
2143
- // against the Firestore emulator because the Firestore emulator fails
2144
- // to to send an existence filter at all.
2145
- // TODO(b/270731363): Enable the verification of the existence filter
2146
- // mismatch once the Firestore emulator is fixed to send an existence
2147
- // filter.
2148
- if ( USE_EMULATOR ) {
2149
- return ;
2150
- }
2091
+ // Ensure that the local cache is configured to use LRU garbage
2092
+ // collection (rather than eager garbage collection) so that the resume
2093
+ // token and document data does not get prematurely evicted.
2094
+ const lruPersistence = persistence . toLruGc ( ) ;
2151
2095
2152
- // Verify that Watch sent an existence filter with the correct counts
2153
- // when the query was resumed.
2154
- expect (
2155
- existenceFilterMismatches ,
2156
- 'existenceFilterMismatches'
2157
- ) . to . have . length ( 1 ) ;
2158
- const { localCacheCount, existenceFilterCount, bloomFilter } =
2159
- existenceFilterMismatches [ 0 ] ;
2160
- expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2161
- expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2162
-
2163
- // Verify that Watch sent a valid bloom filter.
2164
- if ( ! bloomFilter ) {
2165
- expect . fail (
2166
- 'The existence filter should have specified a bloom filter in its ' +
2167
- '`unchanged_names` field.'
2168
- ) ;
2169
- throw new Error ( 'should never get here' ) ;
2170
- }
2096
+ return withRetry ( async attemptNumber => {
2097
+ return withTestCollection (
2098
+ lruPersistence ,
2099
+ testDocs ,
2100
+ async ( coll , db ) => {
2101
+ // Run a query to populate the local cache with the 100 documents
2102
+ // and a resume token.
2103
+ const snapshot1 = await getDocs ( coll ) ;
2104
+ expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2105
+ const createdDocuments = snapshot1 . docs . map (
2106
+ snapshot => snapshot . ref
2107
+ ) ;
2171
2108
2172
- expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above ( 0 ) ;
2173
- expect (
2174
- bloomFilter . bitmapLength ,
2175
- 'bloomFilter.bitmapLength'
2176
- ) . to . be . above ( 0 ) ;
2177
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2178
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2179
-
2180
- // Verify that the bloom filter was successfully used to avert a full
2181
- // requery. If a false positive occurred then retry the entire test.
2182
- // Although statistically rare, false positives are expected to happen
2183
- // occasionally. When a false positive _does_ happen, just retry the
2184
- // test with a different set of documents. If that retry _also_
2185
- // experiences a false positive, then fail the test because that is so
2186
- // improbable that something must have gone wrong.
2187
- if ( attemptNumber === 1 && ! bloomFilter . applied ) {
2188
- throw new RetryError ( ) ;
2189
- }
2109
+ // Delete 50 of the 100 documents. Use a WriteBatch, rather than
2110
+ // deleteDoc(), to avoid affecting the local cache.
2111
+ const deletedDocumentIds = new Set < string > ( ) ;
2112
+ const writeBatchForDocumentDeletes = writeBatch ( db ) ;
2113
+ for ( let i = 0 ; i < createdDocuments . length ; i += 2 ) {
2114
+ const documentToDelete = createdDocuments [ i ] ;
2115
+ writeBatchForDocumentDeletes . delete ( documentToDelete ) ;
2116
+ deletedDocumentIds . add ( documentToDelete . id ) ;
2117
+ }
2118
+ await writeBatchForDocumentDeletes . commit ( ) ;
2119
+
2120
+ // Wait for 10 seconds, during which Watch will stop tracking the
2121
+ // query and will send an existence filter rather than "delete"
2122
+ // events when the query is resumed.
2123
+ await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2124
+
2125
+ // Resume the query and save the resulting snapshot for
2126
+ // verification. Use some internal testing hooks to "capture" the
2127
+ // existence filter mismatches to verify that Watch sent a bloom
2128
+ // filter, and it was used to avert a full requery.
2129
+ const [ existenceFilterMismatches , snapshot2 ] =
2130
+ await captureExistenceFilterMismatches ( ( ) => getDocs ( coll ) ) ;
2131
+
2132
+ // Verify that the snapshot from the resumed query contains the
2133
+ // expected documents; that is, that it contains the 50 documents
2134
+ // that were _not_ deleted.
2135
+ const actualDocumentIds = snapshot2 . docs
2136
+ . map ( documentSnapshot => documentSnapshot . ref . id )
2137
+ . sort ( ) ;
2138
+ const expectedDocumentIds = createdDocuments
2139
+ . filter ( documentRef => ! deletedDocumentIds . has ( documentRef . id ) )
2140
+ . map ( documentRef => documentRef . id )
2141
+ . sort ( ) ;
2142
+ expect ( actualDocumentIds , 'snapshot2.docs' ) . to . deep . equal (
2143
+ expectedDocumentIds
2144
+ ) ;
2190
2145
2191
- expect (
2192
- bloomFilter . applied ,
2193
- `bloomFilter.applied with attemptNumber=${ attemptNumber } `
2194
- ) . to . be . true ;
2146
+ // Verify that Watch sent an existence filter with the correct
2147
+ // counts when the query was resumed.
2148
+ expect (
2149
+ existenceFilterMismatches ,
2150
+ 'existenceFilterMismatches'
2151
+ ) . to . have . length ( 1 ) ;
2152
+ const { localCacheCount, existenceFilterCount, bloomFilter } =
2153
+ existenceFilterMismatches [ 0 ] ;
2154
+ expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2155
+ expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2156
+
2157
+ // Verify that Watch sent a valid bloom filter.
2158
+ if ( ! bloomFilter ) {
2159
+ expect . fail (
2160
+ 'The existence filter should have specified a bloom filter ' +
2161
+ 'in its `unchanged_names` field.'
2162
+ ) ;
2163
+ throw new Error ( 'should never get here' ) ;
2164
+ }
2165
+
2166
+ expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above (
2167
+ 0
2168
+ ) ;
2169
+ expect (
2170
+ bloomFilter . bitmapLength ,
2171
+ 'bloomFilter.bitmapLength'
2172
+ ) . to . be . above ( 0 ) ;
2173
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2174
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2175
+
2176
+ // Verify that the bloom filter was successfully used to avert a
2177
+ // full requery. If a false positive occurred then retry the entire
2178
+ // test. Although statistically rare, false positives are expected
2179
+ // to happen occasionally. When a false positive _does_ happen, just
2180
+ // retry the test with a different set of documents. If that retry
2181
+ // also_ experiences a false positive, then fail the test because
2182
+ // that is so improbable that something must have gone wrong.
2183
+ if ( attemptNumber === 1 && ! bloomFilter . applied ) {
2184
+ throw new RetryError ( ) ;
2185
+ }
2186
+
2187
+ expect (
2188
+ bloomFilter . applied ,
2189
+ `bloomFilter.applied with attemptNumber=${ attemptNumber } `
2190
+ ) . to . be . true ;
2191
+ }
2192
+ ) ;
2195
2193
} ) ;
2196
- } ) ;
2197
- } ) . timeout ( '90s' ) ;
2194
+ }
2195
+ ) . timeout ( '90s' ) ;
2198
2196
2199
- // TODO(b/270731363): Re-enable this test once the Firestore emulator is fixed
2200
- // to send an existence filter.
2197
+ // TODO(b/291365820): Stop skipping this test when running against the
2198
+ // Firestore emulator once the emulator is improved to include a bloom filter
2199
+ // in the existence filter messages that it sends.
2201
2200
// eslint-disable-next-line no-restricted-properties
2202
2201
( USE_EMULATOR ? it . skip : it ) (
2203
2202
'bloom filter should correctly encode complex Unicode characters' ,
0 commit comments