Skip to content

Commit dd8ccd4

Browse files
authored
Merge 01ff39f into 320e58c
2 parents 320e58c + 01ff39f commit dd8ccd4

File tree

9 files changed

+872
-45
lines changed

9 files changed

+872
-45
lines changed

.changeset/four-kids-film.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/firestore': patch
3+
'firebase': patch
4+
---
5+
6+
Internal refactoring to improve testing support of persistent cache index auto-creation.

packages/firestore/externs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"packages/util/dist/src/environment.d.ts",
3232
"packages/util/dist/src/compat.d.ts",
3333
"packages/util/dist/src/obj.d.ts",
34+
"packages/firestore/src/api/persistent_cache_index_manager.ts",
3435
"packages/firestore/src/protos/firestore_bundle_proto.ts",
3536
"packages/firestore/src/protos/firestore_proto_api.ts",
3637
"packages/firestore/src/util/error.ts",

packages/firestore/src/api/persistent_cache_index_manager.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import {
1919
firestoreClientDeleteAllFieldIndexes,
2020
firestoreClientSetPersistentCacheIndexAutoCreationEnabled,
21-
FirestoreClient
21+
FirestoreClient,
22+
TestingHooks as FirestoreClientTestingHooks
2223
} from '../core/firestore_client';
2324
import { cast } from '../util/input_validation';
2425
import { logDebug, logWarn } from '../util/log';
26+
import { testingHooksSpi } from '../util/testing_hooks_spi';
2527

2628
import { ensureFirestoreConfigured, Firestore } from './database';
2729

@@ -108,6 +110,8 @@ export function deleteAllPersistentCacheIndexes(
108110
.catch(error =>
109111
logWarn('deleting all persistent cache indexes failed', error)
110112
);
113+
114+
testingHooksSpi?.notifyPersistentCacheDeleteAllIndexes(promise);
111115
}
112116

113117
function setPersistentCacheIndexAutoCreationEnabled(
@@ -135,6 +139,8 @@ function setPersistentCacheIndexAutoCreationEnabled(
135139
error
136140
)
137141
);
142+
143+
testingHooksSpi?.notifyPersistentCacheIndexAutoCreationToggle(promise);
138144
}
139145

140146
/**
@@ -149,3 +155,25 @@ const persistentCacheIndexManagerByFirestore = new WeakMap<
149155
Firestore,
150156
PersistentCacheIndexManager
151157
>();
158+
159+
/**
160+
* Test-only hooks into the SDK for use exclusively by tests.
161+
*/
162+
export class TestingHooks {
163+
private constructor() {
164+
throw new Error('creating instances is not supported');
165+
}
166+
167+
static setIndexAutoCreationSettings(
168+
indexManager: PersistentCacheIndexManager,
169+
settings: {
170+
indexAutoCreationMinCollectionSize?: number;
171+
relativeIndexReadCostPerDocument?: number;
172+
}
173+
): Promise<void> {
174+
return FirestoreClientTestingHooks.setPersistentCacheIndexAutoCreationSettings(
175+
indexManager._client,
176+
settings
177+
);
178+
}
179+
}

packages/firestore/src/core/firestore_client.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
CredentialsProvider
2424
} from '../api/credentials';
2525
import { User } from '../auth/user';
26+
import { IndexType } from '../local/index_manager';
2627
import { LocalStore } from '../local/local_store';
2728
import {
2829
localStoreConfigureFieldIndexes,
@@ -31,7 +32,8 @@ import {
3132
localStoreGetNamedQuery,
3233
localStoreHandleUserChange,
3334
localStoreReadDocument,
34-
localStoreSetIndexAutoCreationEnabled
35+
localStoreSetIndexAutoCreationEnabled,
36+
TestingHooks as LocalStoreTestingHooks
3537
} from '../local/local_store_impl';
3638
import { Persistence } from '../local/persistence';
3739
import { Document } from '../model/document';
@@ -850,3 +852,39 @@ export function firestoreClientDeleteAllFieldIndexes(
850852
return localStoreDeleteAllFieldIndexes(await getLocalStore(client));
851853
});
852854
}
855+
856+
/**
857+
* Test-only hooks into the SDK for use exclusively by tests.
858+
*/
859+
export class TestingHooks {
860+
private constructor() {
861+
throw new Error('creating instances is not supported');
862+
}
863+
864+
static getQueryIndexType(
865+
client: FirestoreClient,
866+
query: Query
867+
): Promise<IndexType> {
868+
return client.asyncQueue.enqueue(async () => {
869+
const localStore = await getLocalStore(client);
870+
return LocalStoreTestingHooks.getQueryIndexType(localStore, query);
871+
});
872+
}
873+
874+
static setPersistentCacheIndexAutoCreationSettings(
875+
client: FirestoreClient,
876+
settings: {
877+
indexAutoCreationMinCollectionSize?: number;
878+
relativeIndexReadCostPerDocument?: number;
879+
}
880+
): Promise<void> {
881+
const settingsCopy = { ...settings };
882+
return client.asyncQueue.enqueue(async () => {
883+
const localStore = await getLocalStore(client);
884+
LocalStoreTestingHooks.setIndexAutoCreationSettings(
885+
localStore,
886+
settingsCopy
887+
);
888+
});
889+
}
890+
}

packages/firestore/src/local/local_store_impl.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import { BATCHID_UNKNOWN } from '../util/types';
7070

7171
import { BundleCache } from './bundle_cache';
7272
import { DocumentOverlayCache } from './document_overlay_cache';
73-
import { IndexManager } from './index_manager';
73+
import { IndexManager, IndexType } from './index_manager';
7474
import { IndexedDbMutationQueue } from './indexeddb_mutation_queue';
7575
import { IndexedDbPersistence } from './indexeddb_persistence';
7676
import { IndexedDbTargetCache } from './indexeddb_target_cache';
@@ -1572,4 +1572,17 @@ export class TestingHooks {
15721572
settings.relativeIndexReadCostPerDocument;
15731573
}
15741574
}
1575+
1576+
static getQueryIndexType(
1577+
localStore: LocalStore,
1578+
query: Query
1579+
): Promise<IndexType> {
1580+
const localStoreImpl = debugCast(localStore, LocalStoreImpl);
1581+
const target = queryToTarget(query);
1582+
return localStoreImpl.persistence.runTransaction(
1583+
'local_store_impl TestingHooks getQueryIndexType',
1584+
'readonly',
1585+
txn => localStoreImpl.indexManager.getIndexType(txn, target)
1586+
);
1587+
}
15751588
}

packages/firestore/src/util/testing_hooks.ts

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { ensureFirestoreConfigured, Firestore } from '../api/database';
19+
import {
20+
PersistentCacheIndexManager,
21+
TestingHooks as PersistentCacheIndexManagerTestingHooks
22+
} from '../api/persistent_cache_index_manager';
1823
import { Unsubscribe } from '../api/reference_impl';
24+
import { TestingHooks as FirestoreClientTestingHooks } from '../core/firestore_client';
25+
import { Query } from '../lite-api/reference';
26+
import { IndexType } from '../local/index_manager';
1927

28+
import { cast } from './input_validation';
2029
import {
2130
setTestingHooksSpi,
2231
ExistenceFilterMismatchInfo,
@@ -54,6 +63,106 @@ export class TestingHooks {
5463
): Unsubscribe {
5564
return TestingHooksSpiImpl.instance.onExistenceFilterMismatch(callback);
5665
}
66+
67+
/**
68+
* Registers a callback to be notified when
69+
* `enablePersistentCacheIndexAutoCreation()` or
70+
* `disablePersistentCacheIndexAutoCreation()` is invoked.
71+
*
72+
* The relative order in which callbacks are notified is unspecified; do not
73+
* rely on any particular ordering. If a given callback is registered multiple
74+
* times then it will be notified multiple times, once per registration.
75+
*
76+
* @param callback the callback to invoke when
77+
* `enablePersistentCacheIndexAutoCreation()` or
78+
* `disablePersistentCacheIndexAutoCreation()` is invoked.
79+
*
80+
* @return a function that, when called, unregisters the given callback; only
81+
* the first invocation of the returned function does anything; all subsequent
82+
* invocations do nothing.
83+
*/
84+
static onPersistentCacheIndexAutoCreationToggle(
85+
callback: PersistentCacheIndexAutoCreationToggleCallback
86+
): Unsubscribe {
87+
return TestingHooksSpiImpl.instance.onPersistentCacheIndexAutoCreationToggle(
88+
callback
89+
);
90+
}
91+
92+
/**
93+
* Registers a callback to be notified when
94+
* `deleteAllPersistentCacheIndexes()` is invoked.
95+
*
96+
* The relative order in which callbacks are notified is unspecified; do not
97+
* rely on any particular ordering. If a given callback is registered multiple
98+
* times then it will be notified multiple times, once per registration.
99+
*
100+
* @param callback the callback to invoke when
101+
* `deleteAllPersistentCacheIndexes()` is invoked.
102+
*
103+
* @return a function that, when called, unregisters the given callback; only
104+
* the first invocation of the returned function does anything; all subsequent
105+
* invocations do nothing.
106+
*/
107+
static onPersistentCacheDeleteAllIndexes(
108+
callback: PersistentCacheDeleteAllIndexesCallback
109+
): Unsubscribe {
110+
return TestingHooksSpiImpl.instance.onPersistentCacheDeleteAllIndexes(
111+
callback
112+
);
113+
}
114+
115+
/**
116+
* Determines the type of client-side index that will be used when executing the
117+
* given query against the local cache.
118+
*
119+
* @param query The query whose client-side index type to get; it is typed as
120+
* `unknown` so that it is usable in the minified, bundled code, but it should
121+
* be a `Query` object.
122+
*/
123+
static async getQueryIndexType(
124+
query: unknown
125+
): Promise<'full' | 'partial' | 'none'> {
126+
const query_ = cast<Query>(query as Query, Query);
127+
const firestore = cast(query_.firestore, Firestore);
128+
const client = ensureFirestoreConfigured(firestore);
129+
130+
const indexType = await FirestoreClientTestingHooks.getQueryIndexType(
131+
client,
132+
query_._query
133+
);
134+
135+
switch (indexType) {
136+
case IndexType.NONE:
137+
return 'none';
138+
case IndexType.PARTIAL:
139+
return 'partial';
140+
case IndexType.FULL:
141+
return 'full';
142+
default:
143+
throw new Error(`unrecognized IndexType: ${indexType}`);
144+
}
145+
}
146+
147+
/**
148+
* Sets the persistent cache index auto-creation settings for the given
149+
* Firestore instance.
150+
*
151+
* @return a Promise that is fulfilled when the settings are successfully
152+
* applied, or rejected if applying the settings fails.
153+
*/
154+
static setPersistentCacheIndexAutoCreationSettings(
155+
indexManager: PersistentCacheIndexManager,
156+
settings: {
157+
indexAutoCreationMinCollectionSize?: number;
158+
relativeIndexReadCostPerDocument?: number;
159+
}
160+
): Promise<void> {
161+
return PersistentCacheIndexManagerTestingHooks.setIndexAutoCreationSettings(
162+
indexManager,
163+
settings
164+
);
165+
}
57166
}
58167

59168
/**
@@ -68,6 +177,39 @@ export type ExistenceFilterMismatchCallback = (
68177
info: ExistenceFilterMismatchInfo
69178
) => unknown;
70179

180+
/**
181+
* The signature of callbacks registered with
182+
* `TestingHooks.onPersistentCacheIndexAutoCreationToggle()`.
183+
*
184+
* The `promise` argument will be fulfilled when the asynchronous work started
185+
* by the call to `enablePersistentCacheIndexAutoCreation()` or
186+
* `disablePersistentCacheIndexAutoCreation()` completes successfully, or will
187+
* be rejected if it fails.
188+
*
189+
* The return value, if any, is ignored.
190+
*
191+
* @internal
192+
*/
193+
export type PersistentCacheIndexAutoCreationToggleCallback = (
194+
promise: Promise<void>
195+
) => unknown;
196+
197+
/**
198+
* The signature of callbacks registered with
199+
* `TestingHooks.onPersistentCacheDeleteAllIndexes()`.
200+
*
201+
* The `promise` argument will be fulfilled when the asynchronous work started
202+
* by the call to `deleteAllPersistentCacheIndexes()` completes successfully, or
203+
* will be rejected if it fails.
204+
*
205+
* The return value of the callback, if any, is ignored.
206+
*
207+
* @internal
208+
*/
209+
export type PersistentCacheDeleteAllIndexesCallback = (
210+
promise: Promise<void>
211+
) => unknown;
212+
71213
/**
72214
* The implementation of `TestingHooksSpi`.
73215
*/
@@ -77,6 +219,14 @@ class TestingHooksSpiImpl implements TestingHooksSpi {
77219
ExistenceFilterMismatchCallback
78220
>();
79221

222+
private readonly persistentCacheIndexAutoCreationToggleCallbacksById =
223+
new Map<Symbol, PersistentCacheIndexAutoCreationToggleCallback>();
224+
225+
private readonly persistentCacheDeleteAllIndexesCallbacksById = new Map<
226+
Symbol,
227+
PersistentCacheDeleteAllIndexesCallback
228+
>();
229+
80230
private constructor() {}
81231

82232
static get instance(): TestingHooksSpiImpl {
@@ -96,11 +246,50 @@ class TestingHooksSpiImpl implements TestingHooksSpi {
96246
onExistenceFilterMismatch(
97247
callback: ExistenceFilterMismatchCallback
98248
): Unsubscribe {
99-
const id = Symbol();
100-
const callbacks = this.existenceFilterMismatchCallbacksById;
101-
callbacks.set(id, callback);
102-
return () => callbacks.delete(id);
249+
return registerCallback(
250+
callback,
251+
this.existenceFilterMismatchCallbacksById
252+
);
103253
}
254+
255+
notifyPersistentCacheIndexAutoCreationToggle(promise: Promise<void>): void {
256+
this.persistentCacheIndexAutoCreationToggleCallbacksById.forEach(callback =>
257+
callback(promise)
258+
);
259+
}
260+
261+
onPersistentCacheIndexAutoCreationToggle(
262+
callback: PersistentCacheIndexAutoCreationToggleCallback
263+
): Unsubscribe {
264+
return registerCallback(
265+
callback,
266+
this.persistentCacheIndexAutoCreationToggleCallbacksById
267+
);
268+
}
269+
270+
notifyPersistentCacheDeleteAllIndexes(promise: Promise<void>): void {
271+
this.persistentCacheDeleteAllIndexesCallbacksById.forEach(callback =>
272+
callback(promise)
273+
);
274+
}
275+
276+
onPersistentCacheDeleteAllIndexes(
277+
callback: PersistentCacheDeleteAllIndexesCallback
278+
): Unsubscribe {
279+
return registerCallback(
280+
callback,
281+
this.persistentCacheDeleteAllIndexesCallbacksById
282+
);
283+
}
284+
}
285+
286+
function registerCallback<T>(
287+
callback: T,
288+
callbacks: Map<Symbol, T>
289+
): Unsubscribe {
290+
const id = Symbol();
291+
callbacks.set(id, callback);
292+
return () => callbacks.delete(id);
104293
}
105294

106295
let testingHooksSpiImplInstance: TestingHooksSpiImpl | null = null;

0 commit comments

Comments
 (0)