Skip to content

Commit 1abd82c

Browse files
authored
Merge acff0f4 into 5c2ec00
2 parents 5c2ec00 + acff0f4 commit 1abd82c

File tree

9 files changed

+390
-41
lines changed

9 files changed

+390
-41
lines changed

.github/workflows/check-changeset.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ name: Check Changeset
22

33
on:
44
pull_request:
5-
branches-ignore:
6-
- release
5+
branches:
6+
- master
77

88
env:
99
GITHUB_PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }}

integration/firestore/gulpfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function copyTests() {
4545
testBase + '/integration/util/events_accumulator.ts',
4646
testBase + '/integration/util/helpers.ts',
4747
testBase + '/integration/util/settings.ts',
48+
testBase + '/integration/util/testing_hooks_util.ts',
4849
testBase + '/util/equality_matcher.ts',
4950
testBase + '/util/promise.ts'
5051
],

packages/firestore/src/api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,4 @@ 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 { TestingHooks as _TestingHooks } from './util/testing_hooks';

packages/firestore/src/remote/watch_change.ts

+34
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ import { logDebug, logWarn } from '../util/log';
3737
import { primitiveComparator } from '../util/misc';
3838
import { SortedMap } from '../util/sorted_map';
3939
import { SortedSet } from '../util/sorted_set';
40+
import {
41+
ExistenceFilterMismatchInfo as TestingHooksExistenceFilterMismatchInfo,
42+
TestingHooks
43+
} from '../util/testing_hooks';
4044

4145
import { BloomFilter, BloomFilterError } from './bloom_filter';
4246
import { ExistenceFilter } from './existence_filter';
@@ -445,6 +449,13 @@ export class WatchChangeAggregator {
445449
purpose
446450
);
447451
}
452+
TestingHooks.instance?.notifyOnExistenceFilterMismatch(
453+
createExistenceFilterMismatchInfoForTestingHooks(
454+
status,
455+
currentSize,
456+
watchChange.existenceFilter
457+
)
458+
);
448459
}
449460
}
450461
}
@@ -815,3 +826,26 @@ function documentTargetMap(): SortedMap<DocumentKey, SortedSet<TargetId>> {
815826
function snapshotChangesMap(): SortedMap<DocumentKey, ChangeType> {
816827
return new SortedMap<DocumentKey, ChangeType>(DocumentKey.comparator);
817828
}
829+
830+
function createExistenceFilterMismatchInfoForTestingHooks(
831+
status: BloomFilterApplicationStatus,
832+
localCacheCount: number,
833+
existenceFilter: ExistenceFilter
834+
): TestingHooksExistenceFilterMismatchInfo {
835+
const result: TestingHooksExistenceFilterMismatchInfo = {
836+
localCacheCount,
837+
existenceFilterCount: existenceFilter.count
838+
};
839+
840+
const unchangedNames = existenceFilter.unchangedNames;
841+
if (unchangedNames) {
842+
result.bloomFilter = {
843+
applied: status === BloomFilterApplicationStatus.Success,
844+
hashCount: unchangedNames?.hashCount ?? 0,
845+
bitmapLength: unchangedNames?.bits?.bitmap?.length ?? 0,
846+
padding: unchangedNames?.bits?.padding ?? 0
847+
};
848+
}
849+
850+
return result;
851+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* Manages "testing hooks", hooks into the internals of the SDK to verify
20+
* internal state and events during integration tests. Do not use this class
21+
* except for testing purposes.
22+
*
23+
* There are two ways to retrieve the global singleton instance of this class:
24+
* 1. The `instance` property, which returns null if the global singleton
25+
* instance has not been created. Use this property if the caller should
26+
* "do nothing" if there are no testing hooks registered, such as when
27+
* delivering an event to notify registered callbacks.
28+
* 2. The `getOrCreateInstance()` method, which creates the global singleton
29+
* instance if it has not been created. Use this method if the instance is
30+
* needed to, for example, register a callback.
31+
*
32+
* @internal
33+
*/
34+
export class TestingHooks {
35+
private readonly onExistenceFilterMismatchCallbacks = new Map<
36+
Symbol,
37+
ExistenceFilterMismatchCallback
38+
>();
39+
40+
private constructor() {}
41+
42+
/**
43+
* Returns the singleton instance of this class, or null if it has not been
44+
* initialized.
45+
*/
46+
static get instance(): TestingHooks | null {
47+
return gTestingHooksSingletonInstance;
48+
}
49+
50+
/**
51+
* Returns the singleton instance of this class, creating it if is has never
52+
* been created before.
53+
*/
54+
static getOrCreateInstance(): TestingHooks {
55+
if (gTestingHooksSingletonInstance === null) {
56+
gTestingHooksSingletonInstance = new TestingHooks();
57+
}
58+
return gTestingHooksSingletonInstance;
59+
}
60+
61+
/**
62+
* Registers a callback to be notified when an existence filter mismatch
63+
* occurs in the Watch listen stream.
64+
*
65+
* The relative order in which callbacks are notified is unspecified; do not
66+
* rely on any particular ordering. If a given callback is registered multiple
67+
* times then it will be notified multiple times, once per registration.
68+
*
69+
* @param callback the callback to invoke upon existence filter mismatch.
70+
*
71+
* @return a function that, when called, unregisters the given callback; only
72+
* the first invocation of the returned function does anything; all subsequent
73+
* invocations do nothing.
74+
*/
75+
onExistenceFilterMismatch(
76+
callback: ExistenceFilterMismatchCallback
77+
): () => void {
78+
const key = Symbol();
79+
this.onExistenceFilterMismatchCallbacks.set(key, callback);
80+
return () => this.onExistenceFilterMismatchCallbacks.delete(key);
81+
}
82+
83+
/**
84+
* Invokes all currently-registered `onExistenceFilterMismatch` callbacks.
85+
* @param info Information about the existence filter mismatch.
86+
*/
87+
notifyOnExistenceFilterMismatch(info: ExistenceFilterMismatchInfo): void {
88+
this.onExistenceFilterMismatchCallbacks.forEach(callback => callback(info));
89+
}
90+
}
91+
92+
/**
93+
* Information about an existence filter mismatch, as specified to callbacks
94+
* registered with `TestingUtils.onExistenceFilterMismatch()`.
95+
*/
96+
export interface ExistenceFilterMismatchInfo {
97+
/** The number of documents that matched the query in the local cache. */
98+
localCacheCount: number;
99+
100+
/**
101+
* The number of documents that matched the query on the server, as specified
102+
* in the ExistenceFilter message's `count` field.
103+
*/
104+
existenceFilterCount: number;
105+
106+
/**
107+
* Information about the bloom filter provided by Watch in the ExistenceFilter
108+
* message's `unchangedNames` field. If this property is omitted or undefined
109+
* then that means that Watch did _not_ provide a bloom filter.
110+
*/
111+
bloomFilter?: {
112+
/**
113+
* Whether a full requery was averted by using the bloom filter. If false,
114+
* then something happened, such as a false positive, to prevent using the
115+
* bloom filter to avoid a full requery.
116+
*/
117+
applied: boolean;
118+
119+
/** The number of hash functions used in the bloom filter. */
120+
hashCount: number;
121+
122+
/** The number of bytes in the bloom filter's bitmask. */
123+
bitmapLength: number;
124+
125+
/** The number of bits of padding in the last byte of the bloom filter. */
126+
padding: number;
127+
};
128+
}
129+
130+
/**
131+
* The signature of callbacks registered with
132+
* `TestingUtils.onExistenceFilterMismatch()`.
133+
*/
134+
export type ExistenceFilterMismatchCallback = (
135+
info: ExistenceFilterMismatchInfo
136+
) => void;
137+
138+
/** The global singleton instance of `TestingHooks`. */
139+
let gTestingHooksSingletonInstance: TestingHooks | null = null;

0 commit comments

Comments
 (0)