Skip to content

Commit 85ed7ab

Browse files
committed
avoid using objects for parameters since their names can get mangled and/or excluded from minified code
1 parent af53f1a commit 85ed7ab

File tree

5 files changed

+155
-135
lines changed

5 files changed

+155
-135
lines changed

packages/firestore/src/api.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,5 @@ export type { ByteString as _ByteString } from './util/byte_string';
196196
export { logWarn as _logWarn } from './util/log';
197197
export { EmptyAuthCredentialsProvider as _EmptyAuthCredentialsProvider } from './api/credentials';
198198
export { EmptyAppCheckTokenProvider as _EmptyAppCheckTokenProvider } from './api/credentials';
199-
export {
200-
TestingHooks as _TestingHooks,
201-
ExistenceFilterMismatchInfo as _TestingUtilsExistenceFilterMismatchInfo
202-
} from './util/testing_hooks';
199+
export { TestingHooks as _TestingHooks } from './util/testing_hooks';
200+
export { ExistenceFilterMismatchCallback as _TestingHooksExistenceFilterMismatchCallback } from './util/testing_hooks';

packages/firestore/src/remote/watch_change.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,11 @@ export class WatchChangeAggregator {
433433
this.resetTarget(targetId);
434434
this.pendingTargetResets = this.pendingTargetResets.add(targetId);
435435
}
436-
TestingHooks.instance?.notifyOnExistenceFilterMismatch({
437-
actualCount: currentSize,
438-
change: watchChange,
439-
bloomFilterApplied
440-
});
436+
notifyTestingHooksOnExistenceFilterMismatch(
437+
bloomFilterApplied,
438+
currentSize,
439+
watchChange.existenceFilter
440+
);
441441
}
442442
}
443443
}
@@ -795,3 +795,25 @@ function documentTargetMap(): SortedMap<DocumentKey, SortedSet<TargetId>> {
795795
function snapshotChangesMap(): SortedMap<DocumentKey, ChangeType> {
796796
return new SortedMap<DocumentKey, ChangeType>(DocumentKey.comparator);
797797
}
798+
799+
function notifyTestingHooksOnExistenceFilterMismatch(
800+
bloomFilterApplied: boolean,
801+
actualCount: number,
802+
existenceFilter: ExistenceFilter
803+
): void {
804+
const expectedCount = existenceFilter.count;
805+
const unchangedNames = existenceFilter.unchangedNames;
806+
const bloomFilterSentFromWatch = !!unchangedNames;
807+
const bloomFilterHashCount = unchangedNames?.hashCount ?? 0;
808+
const bloomFilterBitmapLength = unchangedNames?.bits?.bitmap?.length ?? 0;
809+
const bloomFilterPadding = unchangedNames?.bits?.padding ?? 0;
810+
TestingHooks.instance?.notifyOnExistenceFilterMismatch(
811+
actualCount,
812+
expectedCount,
813+
bloomFilterSentFromWatch,
814+
bloomFilterApplied,
815+
bloomFilterHashCount,
816+
bloomFilterBitmapLength,
817+
bloomFilterPadding
818+
);
819+
}

packages/firestore/src/util/testing_hooks.ts

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
export class TestingHooks {
3535
private readonly onExistenceFilterMismatchCallbacks = new Map<
3636
Symbol,
37-
(info: ExistenceFilterMismatchInfo) => void
37+
ExistenceFilterMismatchCallback
3838
>();
3939

4040
private constructor() {}
@@ -65,14 +65,16 @@ export class TestingHooks {
6565
* The relative order in which callbacks are notified is unspecified; do not
6666
* rely on any particular ordering.
6767
*
68-
* @param callback the callback to invoke upon existence filter mismatch.
68+
* @param callback the callback to invoke upon existence filter mismatch; see
69+
* the documentation for `notifyOnExistenceFilterMismatch()` for the meaning
70+
* of these arguments.
6971
*
7072
* @return a function that, when called, unregisters the given callback; only
7173
* the first invocation of the returned function does anything; all subsequent
7274
* invocations do nothing.
7375
*/
7476
onExistenceFilterMismatch(
75-
callback: (info: ExistenceFilterMismatchInfo) => void
77+
callback: ExistenceFilterMismatchCallback
7678
): () => void {
7779
const key = Symbol();
7880
this.onExistenceFilterMismatchCallbacks.set(key, callback);
@@ -81,36 +83,65 @@ export class TestingHooks {
8183

8284
/**
8385
* Invokes all currently-registered `onExistenceFilterMismatch` callbacks.
84-
* @param info the argument to specify to the callbacks.
86+
* @param bloomFilterApplied true if a full requery was averted because the
87+
* limbo documents were successfully deduced using the bloom filter, or false
88+
* if a full requery had to be performed, such as due to a false positive in
89+
* the bloom filter.
90+
* @param actualCount the number of documents that matched the query in the
91+
* local cache.
92+
* @param expectedCount the number of documents that matched the query on the
93+
* server, as specified in the ExistenceFilter message's `count` field.
94+
* @param bloomFilterSentFromWatch whether the ExistenceFilter message
95+
* included the `unchangedNames` bloom filter. If false, the remaining
96+
* arguments are not applicable and may have any values.
97+
* @param bloomFilterApplied whether a full requery was averted by using the
98+
* bloom filter; if false, then a false positive occurred, requiring a full
99+
* requery to deduce which documents should go into limbo.
100+
* @param bloomFilterHashCount the number of hash functions used in the bloom
101+
* filter.
102+
* @param bloomFilterBitmapLength the number of bytes used by the bloom
103+
* filter.
104+
* @param bloomFilterPadding the number of bits of padding in the last byte
105+
* of the bloom filter.
85106
*/
86-
notifyOnExistenceFilterMismatch(info: ExistenceFilterMismatchInfo): void {
87-
this.onExistenceFilterMismatchCallbacks.forEach(callback => callback(info));
107+
notifyOnExistenceFilterMismatch(
108+
actualCount: number,
109+
expectedCount: number,
110+
bloomFilterSentFromWatch: boolean,
111+
bloomFilterApplied: boolean,
112+
bloomFilterHashCount: number,
113+
bloomFilterBitmapLength: number,
114+
bloomFilterPadding: number
115+
): void {
116+
this.onExistenceFilterMismatchCallbacks.forEach(callback => {
117+
callback(
118+
actualCount,
119+
expectedCount,
120+
bloomFilterSentFromWatch,
121+
bloomFilterApplied,
122+
bloomFilterHashCount,
123+
bloomFilterBitmapLength,
124+
bloomFilterPadding
125+
);
126+
});
88127
}
89128
}
90129

91130
/**
92-
* The shape of the object specified to
93-
* `TestingUtils.onExistenceFilterMismatch()` callbacks.
131+
* The signature of callbacks registered with
132+
* `TestingUtils.onExistenceFilterMismatch()`.
94133
*
95134
* @internal
96135
*/
97-
export interface ExistenceFilterMismatchInfo {
98-
actualCount: number;
99-
bloomFilterApplied: boolean;
100-
change: {
101-
targetId: number;
102-
existenceFilter: {
103-
count: number;
104-
unchangedNames?: {
105-
bits?: {
106-
bitmap?: string | Uint8Array;
107-
padding?: number;
108-
};
109-
hashCount?: number;
110-
};
111-
};
112-
};
113-
}
136+
export type ExistenceFilterMismatchCallback = (
137+
actualCount: number,
138+
expectedCount: number,
139+
bloomFilterSentFromWatch: boolean,
140+
bloomFilterApplied: boolean,
141+
bloomFilterHashCount: number,
142+
bloomFilterBitmapLength: number,
143+
bloomFilterPadding: number
144+
) => void;
114145

115146
/** The global singleton instance of `TestingHooks`. */
116147
let gTestingHooksSingletonInstance: TestingHooks | null = null;

packages/firestore/test/integration/api/query.test.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,7 +2085,7 @@ apiDescribe('Queries', (persistence: boolean) => {
20852085
// Run a query to populate the local cache with the 100 documents
20862086
// and a resume token.
20872087
const snapshot1 = await getDocs(coll);
2088-
expect(snapshot1.size).to.equal(100);
2088+
expect(snapshot1.size, 'snapshot1.size').to.equal(100);
20892089

20902090
// Delete 50 of the 100 documents. Do this in a transaction, rather
20912091
// than deleteDoc(), to avoid affecting the local cache.
@@ -2107,47 +2107,56 @@ apiDescribe('Queries', (persistence: boolean) => {
21072107
const existenceFilterMismatches =
21082108
await captureExistenceFilterMismatches(async () => {
21092109
const snapshot2 = await getDocs(coll);
2110-
expect(snapshot2.size).to.equal(50);
2110+
expect(snapshot2.size, 'snapshot2.size').to.equal(50);
21112111
});
21122112

21132113
// Verify that upon resuming the query that Watch sent an existence
2114-
// filter that included a bloom filter, and that that bloom filter
2114+
// filter that included a bloom filter, and that the bloom filter
21152115
// was successfully used to avoid a full requery.
21162116
// TODO(b/271949433) Replace this "if" condition with !USE_EMULATOR
21172117
// once the feature has been deployed to production. Note that there
2118-
// are no plans to implement the bloom filter in the existence filter
2119-
// responses sent from the Firestore emulator.
2118+
// are no plans to implement the bloom filter in the existence
2119+
// filter responses sent from the Firestore *emulator*.
21202120
if (TARGET_BACKEND === 'nightly') {
2121-
expect(existenceFilterMismatches).to.have.length(1);
2122-
const existenceFilterMismatch = existenceFilterMismatches[0];
2123-
expect(existenceFilterMismatch.actualCount).to.equal(100);
2124-
expect(existenceFilterMismatch.expectedCount).to.equal(50);
2125-
const bloomFilter = existenceFilterMismatch.bloomFilter;
2126-
if (!bloomFilter) {
2127-
expect.fail('existence filter should have had a bloom filter');
2128-
throw new Error('should never get here');
2121+
expect(
2122+
existenceFilterMismatches,
2123+
'existenceFilterMismatches'
2124+
).to.have.length(1);
2125+
const {
2126+
actualCount,
2127+
expectedCount,
2128+
bloomFilterSentFromWatch,
2129+
bloomFilterApplied,
2130+
bloomFilterHashCount,
2131+
bloomFilterBitmapLength,
2132+
bloomFilterPadding
2133+
} = existenceFilterMismatches[0];
2134+
2135+
expect(actualCount, 'actualCount').to.equal(100);
2136+
expect(expectedCount, 'expectedCount').to.equal(50);
2137+
expect(bloomFilterSentFromWatch, 'bloomFilterSentFromWatch').to.be
2138+
.true;
2139+
2140+
// Retry the entire test if a bloom filter false positive occurs.
2141+
// Although statistically rare, false positives are expected to
2142+
// happen occasionally. When a false positive _does_ happen, just
2143+
// retry the test with a different set of documents. If that retry
2144+
// _also_ experiences a false positive, then fail the test because
2145+
// that is so improbable that something must have gone wrong.
2146+
if (attemptNumber > 1 && !bloomFilterApplied) {
2147+
return 'retry';
21292148
}
21302149

2131-
// Although statistically rare, it _is_ possible to get a legitimate
2132-
// false positive when checking the bloom filter. If this happens,
2133-
// then retry the test with a different set of documents, and only
2134-
// fail if the retry _also_ experiences a false positive.
2135-
if (!bloomFilter.applied) {
2136-
if (attemptNumber < 2) {
2137-
return 'retry';
2138-
} else {
2139-
expect.fail(
2140-
'bloom filter false positive occurred ' +
2141-
'multiple times; this is statistically ' +
2142-
'improbable and should be investigated.'
2143-
);
2144-
}
2145-
}
2146-
2147-
expect(bloomFilter.bitmapLength).to.be.above(0);
2148-
expect(bloomFilter.hashCount).to.be.above(0);
2149-
expect(bloomFilter.padding).to.be.above(0);
2150-
expect(bloomFilter.padding).to.be.below(8);
2150+
expect(bloomFilterApplied, 'bloomFilterApplied').to.be.true;
2151+
expect(bloomFilterHashCount, 'bloomFilterHashCount').to.be.above(
2152+
0
2153+
);
2154+
expect(
2155+
bloomFilterBitmapLength,
2156+
'bloomFilterBitmapLength'
2157+
).to.be.above(0);
2158+
expect(bloomFilterPadding, 'bloomFilterPadding').to.be.above(0);
2159+
expect(bloomFilterPadding, 'bloomFilterPadding').to.be.below(8);
21512160
}
21522161

21532162
return 'passed';

packages/firestore/test/integration/util/testing_hooks_util.ts

Lines changed: 28 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import {
19-
_TestingHooks as TestingHooks,
20-
_TestingUtilsExistenceFilterMismatchInfo as RawExistenceFilterMismatchInfo,
21-
_logWarn
22-
} from './firebase_export';
18+
import { _TestingHooks as TestingHooks } from './firebase_export';
2319

2420
/**
2521
* Captures all existence filter mismatches in the Watch 'Listen' stream that
@@ -32,16 +28,28 @@ export async function captureExistenceFilterMismatches(
3228
callback: () => Promise<void>
3329
): Promise<ExistenceFilterMismatchInfo[]> {
3430
const results: ExistenceFilterMismatchInfo[] = [];
35-
const callbackWrapper = (arg: unknown): void => {
36-
const existenceFilterMismatchInfo = existenceFilterMismatchInfoFromRaw(
37-
arg as RawExistenceFilterMismatchInfo
38-
);
39-
results.push(existenceFilterMismatchInfo);
40-
};
31+
const onExistenceFilterMismatchCallback = (
32+
actualCount: number,
33+
expectedCount: number,
34+
bloomFilterSentFromWatch: boolean,
35+
bloomFilterApplied: boolean,
36+
bloomFilterHashCount: number,
37+
bloomFilterBitmapLength: number,
38+
bloomFilterPadding: number
39+
) =>
40+
results.push({
41+
actualCount,
42+
expectedCount,
43+
bloomFilterSentFromWatch,
44+
bloomFilterApplied,
45+
bloomFilterHashCount,
46+
bloomFilterBitmapLength,
47+
bloomFilterPadding
48+
});
4149

4250
const unregister =
4351
TestingHooks.getOrCreateInstance().onExistenceFilterMismatch(
44-
callbackWrapper
52+
onExistenceFilterMismatchCallback
4553
);
4654

4755
try {
@@ -56,64 +64,16 @@ export async function captureExistenceFilterMismatches(
5664
/**
5765
* Information about an existence filter mismatch, capturing during an
5866
* invocation of `captureExistenceFilterMismatches()`.
67+
*
68+
* See the documentation of `TestingHooks.notifyOnExistenceFilterMismatch()`
69+
* for the meaning of these values.
5970
*/
6071
export interface ExistenceFilterMismatchInfo {
61-
/** The number of documents that match the query in the local cache. */
6272
actualCount: number;
63-
/** The number of documents that matched the query on the server. */
6473
expectedCount: number;
65-
/** The bloom filter provided by the server, or null if not provided. */
66-
bloomFilter: ExistenceFilterBloomFilter | null;
67-
}
68-
69-
/**
70-
* Information about a bloom filter in an existence filter.
71-
*/
72-
export interface ExistenceFilterBloomFilter {
73-
/** Whether the bloom filter was used to avert a full requery. */
74-
applied: boolean;
75-
/** The number of hash functions used in the bloom filter. */
76-
hashCount: number;
77-
/** The number of bytes in the bloom filter. */
78-
bitmapLength: number;
79-
/** The number of bits of "padding" in the last byte of the bloom filter. */
80-
padding: number;
81-
}
82-
83-
/**
84-
* Creates a `ExistenceFilterMismatchInfo` object from the raw object given
85-
* by `TestingUtils`.
86-
*/
87-
function existenceFilterMismatchInfoFromRaw(
88-
raw: RawExistenceFilterMismatchInfo
89-
): ExistenceFilterMismatchInfo {
90-
_logWarn(
91-
'zzyzx existenceFilterMismatchInfoFromRaw() start; raw:',
92-
JSON.stringify(raw, null, 2)
93-
);
94-
return {
95-
actualCount: raw.actualCount,
96-
expectedCount: raw.change.existenceFilter.count,
97-
bloomFilter: bloomFilterFromRawExistenceFilterMismatchInfo(raw)
98-
};
99-
}
100-
101-
/**
102-
* Creates an `ExistenceFilterBloomFilter` object from the raw object given
103-
* by `TestingUtils`, returning null if the given object does not define a
104-
* bloom filter.
105-
*/
106-
function bloomFilterFromRawExistenceFilterMismatchInfo(
107-
raw: RawExistenceFilterMismatchInfo
108-
): null | ExistenceFilterBloomFilter {
109-
const unchangedNames = raw.change.existenceFilter?.unchangedNames;
110-
if (!unchangedNames) {
111-
return null;
112-
}
113-
return {
114-
applied: raw.bloomFilterApplied,
115-
hashCount: unchangedNames.hashCount ?? 0,
116-
bitmapLength: unchangedNames.bits?.bitmap?.length ?? 0,
117-
padding: unchangedNames.bits?.padding ?? 0
118-
};
74+
bloomFilterSentFromWatch: boolean;
75+
bloomFilterApplied: boolean;
76+
bloomFilterHashCount: number;
77+
bloomFilterBitmapLength: number;
78+
bloomFilterPadding: number;
11979
}

0 commit comments

Comments
 (0)