@@ -2064,136 +2064,134 @@ apiDescribe('Queries', (persistence: boolean) => {
2064
2064
} ) ;
2065
2065
} ) ;
2066
2066
2067
- it (
2068
- 'resuming a query should use bloom filter to avoid full requery' ,
2069
- async ( ) => {
2070
- // Create 100 documents in a new collection.
2071
- const testDocs : { [ key : string ] : object } = { } ;
2072
- for ( let i = 1 ; i <= 100 ; i ++ ) {
2073
- testDocs [ 'doc' + i ] = { key : i } ;
2074
- }
2067
+ it ( 'resuming a query should use bloom filter to avoid full requery' , async ( ) => {
2068
+ // Create 100 documents in a new collection.
2069
+ const testDocs : { [ key : string ] : object } = { } ;
2070
+ for ( let i = 1 ; i <= 100 ; i ++ ) {
2071
+ testDocs [ 'doc' + i ] = { key : i } ;
2072
+ }
2075
2073
2076
- // The function that runs a single iteration of the test.
2077
- // Below this definition, there is a "while" loop that calls this
2078
- // function potentially multiple times.
2079
- const runTestIteration = async (
2080
- coll : CollectionReference ,
2081
- db : Firestore
2082
- ) : Promise < 'retry' | 'passed' > => {
2083
- // Run a query to populate the local cache with the 100 documents
2084
- // and a resume token.
2085
- const snapshot1 = await getDocs ( coll ) ;
2086
- expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2074
+ // The function that runs a single iteration of the test.
2075
+ // Below this definition, there is a "while" loop that calls this
2076
+ // function potentially multiple times.
2077
+ const runTestIteration = async (
2078
+ coll : CollectionReference ,
2079
+ db : Firestore
2080
+ ) : Promise < 'retry' | 'passed' > => {
2081
+ // Run a query to populate the local cache with the 100 documents
2082
+ // and a resume token.
2083
+ const snapshot1 = await getDocs ( coll ) ;
2084
+ expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2085
+
2086
+ // Delete 50 of the 100 documents. Do this in a transaction, rather
2087
+ // than deleteDoc(), to avoid affecting the local cache.
2088
+ await runTransaction ( db , async txn => {
2089
+ for ( let i = 1 ; i <= 50 ; i ++ ) {
2090
+ txn . delete ( doc ( coll , 'doc' + i ) ) ;
2091
+ }
2092
+ } ) ;
2087
2093
2088
- // Delete 50 of the 100 documents. Do this in a transaction, rather
2089
- // than deleteDoc(), to avoid affecting the local cache.
2090
- await runTransaction ( db , async txn => {
2091
- for ( let i = 1 ; i <= 50 ; i ++ ) {
2092
- txn . delete ( doc ( coll , 'doc' + i ) ) ;
2094
+ // Wait for 10 seconds, during which Watch will stop tracking the
2095
+ // query and will send an existence filter rather than "delete"
2096
+ // events when the query is resumed.
2097
+ await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2098
+
2099
+ // Resume the query and expect to get a snapshot with the 50
2100
+ // remaining documents. Use some internal testing hooks to "capture"
2101
+ // the existence filter mismatches to later verify that Watch sent a
2102
+ // bloom filter, and it was used to avert a full requery.
2103
+ const existenceFilterMismatches = await captureExistenceFilterMismatches (
2104
+ async ( ) => {
2105
+ const snapshot2 = await getDocs ( coll ) ;
2106
+ // TODO(b/270731363): Remove the "if" condition below once the
2107
+ // Firestore Emulator is fixed to send an existence filter. At the
2108
+ // time of writing, the Firestore emulator fails to send an
2109
+ // existence filter, resulting in the client including the deleted
2110
+ // documents in the snapshot of the resumed query.
2111
+ if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2112
+ expect ( snapshot2 . size , 'snapshot2.size' ) . to . equal ( 50 ) ;
2093
2113
}
2094
- } ) ;
2095
-
2096
- // Wait for 10 seconds, during which Watch will stop tracking the
2097
- // query and will send an existence filter rather than "delete"
2098
- // events when the query is resumed.
2099
- await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2100
-
2101
- // Resume the query and expect to get a snapshot with the 50
2102
- // remaining documents. Use some internal testing hooks to "capture"
2103
- // the existence filter mismatches to later verify that Watch sent a
2104
- // bloom filter, and it was used to avert a full requery.
2105
- const existenceFilterMismatches =
2106
- await captureExistenceFilterMismatches ( async ( ) => {
2107
- const snapshot2 = await getDocs ( coll ) ;
2108
- // TODO(b/270731363): Remove the "if" condition below once the
2109
- // Firestore Emulator is fixed to send an existence filter. At the
2110
- // time of writing, the Firestore emulator fails to send an
2111
- // existence filter, resulting in the client including the deleted
2112
- // documents in the snapshot of the resumed query.
2113
- if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2114
- expect ( snapshot2 . size , 'snapshot2.size' ) . to . equal ( 50 ) ;
2115
- }
2116
- } ) ;
2117
-
2118
- // Skip the verification of the existence filter mismatch when
2119
- // persistence is disabled because without persistence there is no
2120
- // resume token specified in the subsequent call to getDocs(), and,
2121
- // therefore, Watch will _not_ send an existence filter.
2122
- if ( ! persistence ) {
2123
- return 'passed' ;
2124
2114
}
2115
+ ) ;
2125
2116
2126
- // Skip the verification of the existence filter mismatch when
2127
- // testing against the Firestore emulator because the Firestore
2128
- // emulator does not include the `unchanged_names` bloom filter when
2129
- // it sends ExistenceFilter messages. Some day the emulator _may_
2130
- // implement this logic, at which time this short-circuit can be
2131
- // removed.
2132
- if ( USE_EMULATOR ) {
2133
- return 'passed' ;
2134
- }
2117
+ // Skip the verification of the existence filter mismatch when
2118
+ // persistence is disabled because without persistence there is no
2119
+ // resume token specified in the subsequent call to getDocs(), and,
2120
+ // therefore, Watch will _not_ send an existence filter.
2121
+ if ( ! persistence ) {
2122
+ return 'passed' ;
2123
+ }
2135
2124
2136
- // Verify that upon resuming the query that Watch sent an existence
2137
- // filter that included a bloom filter, and that the bloom filter
2138
- // was successfully used to avoid a full requery.
2139
- // TODO(b/271949433) Remove this check for "nightly" once the bloom
2140
- // filter logic is deployed to production, circa May 2023.
2141
- if ( TARGET_BACKEND === 'nightly' ) {
2142
- expect (
2143
- existenceFilterMismatches ,
2144
- 'existenceFilterMismatches'
2145
- ) . to . have . length ( 1 ) ;
2146
- const { localCacheCount, existenceFilterCount, bloomFilter } =
2147
- existenceFilterMismatches [ 0 ] ;
2148
-
2149
- expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2150
- expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2151
- if ( ! bloomFilter ) {
2152
- expect . fail (
2153
- 'The existence filter should have specified ' +
2154
- 'a bloom filter in its `unchanged_names` field.'
2155
- ) ;
2156
- throw new Error ( 'should never get here' ) ;
2157
- }
2125
+ // Skip the verification of the existence filter mismatch when
2126
+ // testing against the Firestore emulator because the Firestore
2127
+ // emulator does not include the `unchanged_names` bloom filter when
2128
+ // it sends ExistenceFilter messages. Some day the emulator _may_
2129
+ // implement this logic, at which time this short-circuit can be
2130
+ // removed.
2131
+ if ( USE_EMULATOR ) {
2132
+ return 'passed' ;
2133
+ }
2158
2134
2159
- expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above ( 0 ) ;
2160
- expect (
2161
- bloomFilter . bitmapLength ,
2162
- 'bloomFilter.bitmapLength'
2163
- ) . to . be . above ( 0 ) ;
2164
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2165
- expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2166
-
2167
- // Retry the entire test if a bloom filter false positive occurs.
2168
- // Although statistically rare, false positives are expected to
2169
- // happen occasionally. When a false positive _does_ happen, just
2170
- // retry the test with a different set of documents. If that retry
2171
- // _also_ experiences a false positive, then fail the test because
2172
- // that is so improbable that something must have gone wrong.
2173
- if ( attemptNumber > 1 && ! bloomFilter . applied ) {
2174
- return 'retry' ;
2175
- }
2176
- expect ( bloomFilter . applied , 'bloomFilter.applied' ) . to . be . true ;
2135
+ // Verify that upon resuming the query that Watch sent an existence
2136
+ // filter that included a bloom filter, and that the bloom filter
2137
+ // was successfully used to avoid a full requery.
2138
+ // TODO(b/271949433) Remove this check for "nightly" once the bloom
2139
+ // filter logic is deployed to production, circa May 2023.
2140
+ if ( TARGET_BACKEND === 'nightly' ) {
2141
+ expect (
2142
+ existenceFilterMismatches ,
2143
+ 'existenceFilterMismatches'
2144
+ ) . to . have . length ( 1 ) ;
2145
+ const { localCacheCount, existenceFilterCount, bloomFilter } =
2146
+ existenceFilterMismatches [ 0 ] ;
2147
+
2148
+ expect ( localCacheCount , 'localCacheCount' ) . to . equal ( 100 ) ;
2149
+ expect ( existenceFilterCount , 'existenceFilterCount' ) . to . equal ( 50 ) ;
2150
+ if ( ! bloomFilter ) {
2151
+ expect . fail (
2152
+ 'The existence filter should have specified ' +
2153
+ 'a bloom filter in its `unchanged_names` field.'
2154
+ ) ;
2155
+ throw new Error ( 'should never get here' ) ;
2177
2156
}
2178
2157
2179
- return 'passed' ;
2180
- } ;
2181
-
2182
- // Run the test
2183
- let attemptNumber = 0 ;
2184
- while ( true ) {
2185
- attemptNumber ++ ;
2186
- const iterationResult = await withTestCollection (
2187
- persistence ,
2188
- testDocs ,
2189
- runTestIteration
2190
- ) ;
2191
- if ( iterationResult === 'passed' ) {
2192
- break ;
2158
+ expect ( bloomFilter . hashCount , 'bloomFilter.hashCount' ) . to . be . above ( 0 ) ;
2159
+ expect (
2160
+ bloomFilter . bitmapLength ,
2161
+ 'bloomFilter.bitmapLength'
2162
+ ) . to . be . above ( 0 ) ;
2163
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . above ( 0 ) ;
2164
+ expect ( bloomFilter . padding , 'bloomFilterPadding' ) . to . be . below ( 8 ) ;
2165
+
2166
+ // Retry the entire test if a bloom filter false positive occurs.
2167
+ // Although statistically rare, false positives are expected to
2168
+ // happen occasionally. When a false positive _does_ happen, just
2169
+ // retry the test with a different set of documents. If that retry
2170
+ // _also_ experiences a false positive, then fail the test because
2171
+ // that is so improbable that something must have gone wrong.
2172
+ if ( attemptNumber > 1 && ! bloomFilter . applied ) {
2173
+ return 'retry' ;
2193
2174
}
2175
+ expect ( bloomFilter . applied , 'bloomFilter.applied' ) . to . be . true ;
2176
+ }
2177
+
2178
+ return 'passed' ;
2179
+ } ;
2180
+
2181
+ // Run the test
2182
+ let attemptNumber = 0 ;
2183
+ while ( true ) {
2184
+ attemptNumber ++ ;
2185
+ const iterationResult = await withTestCollection (
2186
+ persistence ,
2187
+ testDocs ,
2188
+ runTestIteration
2189
+ ) ;
2190
+ if ( iterationResult === 'passed' ) {
2191
+ break ;
2194
2192
}
2195
2193
}
2196
- ) . timeout ( '90s' ) ;
2194
+ } ) . timeout ( '90s' ) ;
2197
2195
} ) ;
2198
2196
2199
2197
function verifyDocumentChange < T > (
0 commit comments