Skip to content

Commit 705722a

Browse files
authored
Merge 62d28ba into b2f6084
2 parents b2f6084 + 62d28ba commit 705722a

23 files changed

+1601
-90
lines changed

.changeset/cool-games-care.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+
Implemented internal logic to auto-create client-side indexes

packages/firestore/src/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ export {
202202
setIndexConfiguration
203203
} from './api/index_configuration';
204204

205+
export {
206+
PersistentCacheIndexManager,
207+
getPersistentCacheIndexManager,
208+
enablePersistentCacheIndexAutoCreation,
209+
disablePersistentCacheIndexAutoCreation
210+
} from './api/persistent_cache_index_manager';
211+
205212
/**
206213
* Internal exports
207214
*/
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
import {
19+
firestoreClientSetPersistentCacheIndexAutoCreationEnabled,
20+
FirestoreClient
21+
} from '../core/firestore_client';
22+
import { cast } from '../util/input_validation';
23+
import { logDebug, logWarn } from '../util/log';
24+
25+
import { ensureFirestoreConfigured, Firestore } from './database';
26+
27+
/**
28+
* A `PersistentCacheIndexManager` which you can config persistent cache indexes
29+
* used for local query execution.
30+
*
31+
* To use, call `getPersistentCacheIndexManager()` to get an instance.
32+
*
33+
* TODO(CSI) Remove @internal to make the API publicly available.
34+
* @internal
35+
*/
36+
export class PersistentCacheIndexManager {
37+
readonly type: 'PersistentCacheIndexManager' = 'PersistentCacheIndexManager';
38+
39+
/** @hideconstructor */
40+
constructor(readonly _client: FirestoreClient) {}
41+
}
42+
43+
/**
44+
* Returns the PersistentCache Index Manager used by the given `Firestore`
45+
* object.
46+
*
47+
* @return The `PersistentCacheIndexManager` instance, or `null` if local
48+
* persistent storage is not in use.
49+
*
50+
* TODO(CSI) Remove @internal to make the API publicly available.
51+
* @internal
52+
*/
53+
export function getPersistentCacheIndexManager(
54+
firestore: Firestore
55+
): PersistentCacheIndexManager | null {
56+
firestore = cast(firestore, Firestore);
57+
58+
const cachedInstance = persistentCacheIndexManagerByFirestore.get(firestore);
59+
if (cachedInstance) {
60+
return cachedInstance;
61+
}
62+
63+
const client = ensureFirestoreConfigured(firestore);
64+
if (client._uninitializedComponentsProvider?._offlineKind !== 'persistent') {
65+
return null;
66+
}
67+
68+
const instance = new PersistentCacheIndexManager(client);
69+
persistentCacheIndexManagerByFirestore.set(firestore, instance);
70+
return instance;
71+
}
72+
73+
/**
74+
* Enables SDK to create persistent cache indexes automatically for local query
75+
* execution when SDK believes cache indexes can help improves performance.
76+
*
77+
* This feature is disabled by default.
78+
*
79+
* TODO(CSI) Remove @internal to make the API publicly available.
80+
* @internal
81+
*/
82+
export function enablePersistentCacheIndexAutoCreation(
83+
indexManager: PersistentCacheIndexManager
84+
): void {
85+
setPersistentCacheIndexAutoCreationEnabled(indexManager, true);
86+
}
87+
88+
/**
89+
* Stops creating persistent cache indexes automatically for local query
90+
* execution. The indexes which have been created by calling
91+
* `enablePersistentCacheIndexAutoCreation()` still take effect.
92+
*
93+
* TODO(CSI) Remove @internal to make the API publicly available.
94+
* @internal
95+
*/
96+
export function disablePersistentCacheIndexAutoCreation(
97+
indexManager: PersistentCacheIndexManager
98+
): void {
99+
setPersistentCacheIndexAutoCreationEnabled(indexManager, false);
100+
}
101+
102+
function setPersistentCacheIndexAutoCreationEnabled(
103+
indexManager: PersistentCacheIndexManager,
104+
isEnabled: boolean
105+
): void {
106+
indexManager._client.verifyNotTerminated();
107+
108+
const promise = firestoreClientSetPersistentCacheIndexAutoCreationEnabled(
109+
indexManager._client,
110+
isEnabled
111+
);
112+
113+
promise
114+
.then(_ =>
115+
logDebug(
116+
`setting persistent cache index auto creation ` +
117+
`isEnabled=${isEnabled} succeeded`
118+
)
119+
)
120+
.catch(error =>
121+
logWarn(
122+
`setting persistent cache index auto creation ` +
123+
`isEnabled=${isEnabled} failed`,
124+
error
125+
)
126+
);
127+
}
128+
129+
/**
130+
* Maps `Firestore` instances to their corresponding
131+
* `PersistentCacheIndexManager` instances.
132+
*
133+
* Use a `WeakMap` so that the mapping will be automatically dropped when the
134+
* `Firestore` instance is garbage collected. This emulates a private member
135+
* as described in https://goo.gle/454yvug.
136+
*/
137+
const persistentCacheIndexManagerByFirestore = new WeakMap<
138+
Firestore,
139+
PersistentCacheIndexManager
140+
>();

packages/firestore/src/core/firestore_client.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
localStoreExecuteQuery,
3030
localStoreGetNamedQuery,
3131
localStoreHandleUserChange,
32-
localStoreReadDocument
32+
localStoreReadDocument,
33+
localStoreSetIndexAutoCreationEnabled
3334
} from '../local/local_store_impl';
3435
import { Persistence } from '../local/persistence';
3536
import { Document } from '../model/document';
@@ -828,3 +829,15 @@ export function firestoreClientSetIndexConfiguration(
828829
);
829830
});
830831
}
832+
833+
export function firestoreClientSetPersistentCacheIndexAutoCreationEnabled(
834+
client: FirestoreClient,
835+
isEnabled: boolean
836+
): Promise<void> {
837+
return client.asyncQueue.enqueue(async () => {
838+
return localStoreSetIndexAutoCreationEnabled(
839+
await getLocalStore(client),
840+
isEnabled
841+
);
842+
});
843+
}

packages/firestore/src/local/index_manager.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ export const enum IndexType {
4141
FULL
4242
}
4343

44+
export function displayNameForIndexType(indexType: IndexType): string {
45+
switch (indexType) {
46+
case IndexType.NONE:
47+
return 'NONE';
48+
case IndexType.PARTIAL:
49+
return 'PARTIAL';
50+
case IndexType.FULL:
51+
return 'FULL';
52+
default:
53+
return `[unknown IndexType: ${indexType}]`;
54+
}
55+
}
56+
4457
/**
4558
* Represents a set of indexes that are used to execute queries efficiently.
4659
*
@@ -92,6 +105,12 @@ export interface IndexManager {
92105
index: FieldIndex
93106
): PersistencePromise<void>;
94107

108+
/** Creates a full matched field index which serves the given target. */
109+
createTargetIndexes(
110+
transaction: PersistenceTransaction,
111+
target: Target
112+
): PersistencePromise<void>;
113+
95114
/**
96115
* Returns a list of field indexes that correspond to the specified collection
97116
* group.

packages/firestore/src/local/indexeddb_index_manager.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,26 @@ export class IndexedDbIndexManager implements IndexManager {
252252
);
253253
}
254254

255+
createTargetIndexes(
256+
transaction: PersistenceTransaction,
257+
target: Target
258+
): PersistencePromise<void> {
259+
return PersistencePromise.forEach(
260+
this.getSubTargets(target),
261+
(subTarget: Target) => {
262+
return this.getIndexType(transaction, subTarget).next(type => {
263+
if (type === IndexType.NONE || type === IndexType.PARTIAL) {
264+
const targetIndexMatcher = new TargetIndexMatcher(subTarget);
265+
return this.addFieldIndex(
266+
transaction,
267+
targetIndexMatcher.buildTargetIndex()
268+
);
269+
}
270+
});
271+
}
272+
);
273+
}
274+
255275
getDocumentsMatchingTarget(
256276
transaction: PersistenceTransaction,
257277
target: Target

packages/firestore/src/local/indexeddb_remote_document_cache.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
} from './local_serializer';
5656
import { PersistencePromise } from './persistence_promise';
5757
import { PersistenceTransaction } from './persistence_transaction';
58+
import { QueryContext } from './query_context';
5859
import { RemoteDocumentCache } from './remote_document_cache';
5960
import { RemoteDocumentChangeBuffer } from './remote_document_change_buffer';
6061
import { SimpleDbStore } from './simple_db';
@@ -279,7 +280,8 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
279280
transaction: PersistenceTransaction,
280281
query: Query,
281282
offset: IndexOffset,
282-
mutatedDocs: OverlayMap
283+
mutatedDocs: OverlayMap,
284+
context?: QueryContext
283285
): PersistencePromise<MutableDocumentMap> {
284286
const collection = query.path;
285287
const startKey = [
@@ -300,6 +302,7 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
300302
return remoteDocumentsStore(transaction)
301303
.loadAll(IDBKeyRange.bound(startKey, endKey, true))
302304
.next(dbRemoteDocs => {
305+
context?.incrementDocumentReadCount(dbRemoteDocs.length);
303306
let results = mutableDocumentMap();
304307
for (const dbRemoteDoc of dbRemoteDocs) {
305308
const document = this.maybeDecodeDocument(

packages/firestore/src/local/local_documents_view.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { MutationQueue } from './mutation_queue';
6060
import { OverlayedDocument } from './overlayed_document';
6161
import { PersistencePromise } from './persistence_promise';
6262
import { PersistenceTransaction } from './persistence_transaction';
63+
import { QueryContext } from './query_context';
6364
import { RemoteDocumentCache } from './remote_document_cache';
6465

6566
/**
@@ -355,25 +356,30 @@ export class LocalDocumentsView {
355356
* @param transaction - The persistence transaction.
356357
* @param query - The query to match documents against.
357358
* @param offset - Read time and key to start scanning by (exclusive).
359+
* @param context - A optional tracker to keep a record of important details
360+
* during database local query execution.
358361
*/
359362
getDocumentsMatchingQuery(
360363
transaction: PersistenceTransaction,
361364
query: Query,
362-
offset: IndexOffset
365+
offset: IndexOffset,
366+
context?: QueryContext
363367
): PersistencePromise<DocumentMap> {
364368
if (isDocumentQuery(query)) {
365369
return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
366370
} else if (isCollectionGroupQuery(query)) {
367371
return this.getDocumentsMatchingCollectionGroupQuery(
368372
transaction,
369373
query,
370-
offset
374+
offset,
375+
context
371376
);
372377
} else {
373378
return this.getDocumentsMatchingCollectionQuery(
374379
transaction,
375380
query,
376-
offset
381+
offset,
382+
context
377383
);
378384
}
379385
}
@@ -472,7 +478,8 @@ export class LocalDocumentsView {
472478
private getDocumentsMatchingCollectionGroupQuery(
473479
transaction: PersistenceTransaction,
474480
query: Query,
475-
offset: IndexOffset
481+
offset: IndexOffset,
482+
context?: QueryContext
476483
): PersistencePromise<DocumentMap> {
477484
debugAssert(
478485
query.path.isEmpty(),
@@ -493,7 +500,8 @@ export class LocalDocumentsView {
493500
return this.getDocumentsMatchingCollectionQuery(
494501
transaction,
495502
collectionQuery,
496-
offset
503+
offset,
504+
context
497505
).next(r => {
498506
r.forEach((key, doc) => {
499507
results = results.insert(key, doc);
@@ -506,7 +514,8 @@ export class LocalDocumentsView {
506514
private getDocumentsMatchingCollectionQuery(
507515
transaction: PersistenceTransaction,
508516
query: Query,
509-
offset: IndexOffset
517+
offset: IndexOffset,
518+
context?: QueryContext
510519
): PersistencePromise<DocumentMap> {
511520
// Query the remote documents and overlay mutations.
512521
let overlays: OverlayMap;
@@ -518,7 +527,8 @@ export class LocalDocumentsView {
518527
transaction,
519528
query,
520529
offset,
521-
overlays
530+
overlays,
531+
context
522532
);
523533
})
524534
.next(remoteDocuments => {

0 commit comments

Comments
 (0)