Skip to content

Commit 2d04af9

Browse files
Add setIndexConfiguration API (#5843)
1 parent 827b080 commit 2d04af9

File tree

5 files changed

+406
-9
lines changed

5 files changed

+406
-9
lines changed

packages/firestore/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,4 @@ export type { ByteString as _ByteString } from './util/byte_string';
158158
export { logWarn as _logWarn } from './util/log';
159159
export { EmptyAuthCredentialsProvider as _EmptyAuthCredentialsProvider } from './api/credentials';
160160
export { EmptyAppCheckTokenProvider as _EmptyAppCheckTokenProvider } from './api/credentials';
161+
export { setIndexConfiguration as _setIndexConfiguration } from './api/index_configuration';

packages/firestore/src/api/database.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -503,13 +503,13 @@ export function terminate(firestore: Firestore): Promise<void> {
503503
/**
504504
* Loads a Firestore bundle into the local cache.
505505
*
506-
* @param firestore - The {@link Firestore} instance to load bundles for for.
507-
* @param bundleData - An object representing the bundle to be loaded. Valid objects are
508-
* `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
506+
* @param firestore - The {@link Firestore} instance to load bundles for.
507+
* @param bundleData - An object representing the bundle to be loaded. Valid
508+
* objects are `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
509509
*
510-
* @returns
511-
* A `LoadBundleTask` object, which notifies callers with progress updates, and completion
512-
* or error events. It can be used as a `Promise<LoadBundleTaskProgress>`.
510+
* @returns A `LoadBundleTask` object, which notifies callers with progress
511+
* updates, and completion or error events. It can be used as a
512+
* `Promise<LoadBundleTaskProgress>`.
513513
*/
514514
export function loadBundle(
515515
firestore: Firestore,
@@ -528,11 +528,16 @@ export function loadBundle(
528528
}
529529

530530
/**
531-
* Reads a Firestore {@link Query} from local cache, identified by the given name.
531+
* Reads a Firestore {@link Query} from local cache, identified by the given
532+
* name.
532533
*
533534
* The named queries are packaged into bundles on the server side (along
534-
* with resulting documents), and loaded to local cache using `loadBundle`. Once in local
535-
* cache, use this method to extract a {@link Query} by name.
535+
* with resulting documents), and loaded to local cache using `loadBundle`. Once
536+
* in local cache, use this method to extract a {@link Query} by name.
537+
*
538+
* @param firestore - The {@link Firestore} instance to read the query from.
539+
* @param name - The name of the query.
540+
* @returns A `Promise` that is resolved with the Query or `null`.
536541
*/
537542
export function namedQuery(
538543
firestore: Firestore,
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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 { fieldPathFromDotSeparatedString } from '../lite-api/user_data_reader';
19+
import { FieldIndex, Kind, Segment } from '../model/field_index';
20+
import { Code, FirestoreError } from '../util/error';
21+
import { cast } from '../util/input_validation';
22+
23+
import { ensureFirestoreConfigured, Firestore } from './database';
24+
25+
export {
26+
connectFirestoreEmulator,
27+
EmulatorMockTokenOptions
28+
} from '../lite-api/database';
29+
30+
// TODO(indexing): Remove "@internal" from the API.
31+
32+
/**
33+
* A single field element in an index configuration.
34+
*
35+
* @internal
36+
*/
37+
export interface IndexField {
38+
/** The field path to index. */
39+
readonly fieldPath: string;
40+
/**
41+
* What type of array index to create. Set to `CONTAINS` for `array-contains`
42+
* and `array-contains-any` indexes.
43+
*
44+
* Only one of `arrayConfig` or `order` should be set;
45+
*/
46+
readonly arrayConfig?: 'CONTAINS';
47+
/**
48+
* What type of array index to create. Set to `ASCENDING` or 'DESCENDING` for
49+
* `==`, `!=`, `<=`, `<=`, `in` and `not-in` filters.
50+
*
51+
* Only one of `arrayConfig` or `order` should be set.
52+
*/
53+
readonly order?: 'ASCENDING' | 'DESCENDING';
54+
55+
[key: string]: unknown;
56+
}
57+
58+
/**
59+
* The SDK definition of a Firestore index.
60+
*
61+
* @internal
62+
*/
63+
export interface Index {
64+
/** The ID of the collection to index. */
65+
readonly collectionGroup: string;
66+
/** A list of fields to index. */
67+
readonly fields?: IndexField[];
68+
69+
[key: string]: unknown;
70+
}
71+
72+
/**
73+
* A list of Firestore indexes to speed up local query execution.
74+
*
75+
* See {@link https://firebase.google.com/docs/reference/firestore/indexes/#json_format | JSON Format}
76+
* for a description of the format of the index definition.
77+
*
78+
* @internal
79+
*/
80+
export interface IndexConfiguration {
81+
/** A list of all Firestore indexes. */
82+
readonly indexes?: Index[];
83+
84+
[key: string]: unknown;
85+
}
86+
87+
/**
88+
* Configures indexing for local query execution. Any previous index
89+
* configuration is overridden. The `Promise` resolves once the index
90+
* configuration has been persisted.
91+
*
92+
* The index entries themselves are created asynchronously. You can continue to
93+
* use queries that require indexing even if the indices are not yet available.
94+
* Query execution will automatically start using the index once the index
95+
* entries have been written.
96+
*
97+
* Indexes are only supported with IndexedDb persistence. Invoke either
98+
* `enableIndexedDbPersistence()` or `enableMultiTabIndexedDbPersistence()`
99+
* before setting an index configuration. If IndexedDb is not enabled, any
100+
* index configuration is ignored.
101+
*
102+
* @internal
103+
* @param firestore - The {@link Firestore} instance to configure indexes for.
104+
* @param configuration -The index definition.
105+
* @throws FirestoreError if the JSON format is invalid.
106+
* @returns A `Promise` that resolves once all indices are successfully
107+
* configured.
108+
*/
109+
export function setIndexConfiguration(
110+
firestore: Firestore,
111+
configuration: IndexConfiguration
112+
): Promise<void>;
113+
/**
114+
* Configures indexing for local query execution. Any previous index
115+
* configuration is overridden. The `Promise` resolves once the index
116+
* configuration has been persisted.
117+
*
118+
* The index entries themselves are created asynchronously. You can continue to
119+
* use queries that require indexing even if the indices are not yet available.
120+
* Query execution will automatically start using the index once the index
121+
* entries have been written.
122+
*
123+
* Indexes are only supported with IndexedDb persistence. Invoke either
124+
* `enableIndexedDbPersistence()` or `enableMultiTabIndexedDbPersistence()`
125+
* before setting an index configuration. If IndexedDb is not enabled, any
126+
* index configuration is ignored.
127+
*
128+
* The method accepts the JSON format exported by the Firebase CLI (`firebase
129+
* firestore:indexes`). If the JSON format is invalid, this method throws an
130+
* error.
131+
*
132+
* @internal
133+
* @param firestore - The {@link Firestore} instance to configure indexes for.
134+
* @param json -The JSON format exported by the Firebase CLI.
135+
* @throws FirestoreError if the JSON format is invalid.
136+
* @returns A `Promise` that resolves once all indices are successfully
137+
* configured.
138+
*/
139+
export function setIndexConfiguration(
140+
firestore: Firestore,
141+
json: string
142+
): Promise<void>;
143+
export function setIndexConfiguration(
144+
firestore: Firestore,
145+
jsonOrConfiguration: string | IndexConfiguration
146+
): Promise<void> {
147+
firestore = cast(firestore, Firestore);
148+
ensureFirestoreConfigured(firestore);
149+
150+
const indexConfiguration =
151+
typeof jsonOrConfiguration === 'string'
152+
? (tryParseJson(jsonOrConfiguration) as IndexConfiguration)
153+
: jsonOrConfiguration;
154+
const parsedIndexes: FieldIndex[] = [];
155+
156+
// PORTING NOTE: We don't return an error if the user has not enabled
157+
// persistence since `enableIndexeddbPersistence()` can fail on the Web.
158+
159+
if (Array.isArray(indexConfiguration.indexes)) {
160+
for (const index of indexConfiguration.indexes) {
161+
const collectionGroup = tryGetString(index, 'collectionGroup');
162+
163+
const segments: Segment[] = [];
164+
if (Array.isArray(index.fields)) {
165+
for (const field of index.fields) {
166+
const fieldPathString = tryGetString(field, 'fieldPath');
167+
const fieldPath = fieldPathFromDotSeparatedString(
168+
'setIndexConfiguration',
169+
fieldPathString
170+
);
171+
172+
if (field.arrayConfig === 'CONTAINS') {
173+
segments.push(new Segment(fieldPath, Kind.CONTAINS));
174+
} else if (field.order === 'ASCENDING') {
175+
segments.push(new Segment(fieldPath, Kind.ASCENDING));
176+
} else if (field.order === 'DESCENDING') {
177+
segments.push(new Segment(fieldPath, Kind.DESCENDING));
178+
}
179+
}
180+
}
181+
182+
parsedIndexes.push(
183+
new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments)
184+
);
185+
}
186+
}
187+
188+
// TODO(indexing): Configure indexes
189+
return Promise.resolve();
190+
}
191+
192+
function tryParseJson(json: string): Record<string, unknown> {
193+
try {
194+
return JSON.parse(json);
195+
} catch (e) {
196+
throw new FirestoreError(
197+
Code.INVALID_ARGUMENT,
198+
'Failed to parse JSON:' + e.message
199+
);
200+
}
201+
}
202+
203+
function tryGetString(data: Record<string, unknown>, property: string): string {
204+
if (typeof data[property] !== 'string') {
205+
throw new FirestoreError(
206+
Code.INVALID_ARGUMENT,
207+
'Missing string value for: ' + property
208+
);
209+
}
210+
return data[property] as string;
211+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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 { FieldPath } from './path';
19+
20+
/**
21+
* An index definition for field indexes in Firestore.
22+
*
23+
* Every index is associated with a collection. The definition contains a list
24+
* of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
25+
* `CONTAINS` for ArrayContains/ArrayContainsAny queries).
26+
*
27+
* Unlike the backend, the SDK does not differentiate between collection or
28+
* collection group-scoped indices. Every index can be used for both single
29+
* collection and collection group queries.
30+
*/
31+
export class FieldIndex {
32+
/** An ID for an index that has not yet been added to persistence. */
33+
static UNKNOWN_ID: -1;
34+
35+
constructor(
36+
/**
37+
* The index ID. Returns -1 if the index ID is not available (e.g. the index
38+
* has not yet been persisted).
39+
*/
40+
readonly indexId: number,
41+
/** The collection ID this index applies to. */
42+
readonly collectionGroup: string,
43+
/** The field segments for this index. */
44+
readonly segments: Segment[]
45+
) {}
46+
}
47+
48+
/** The type of the index, e.g. for which type of query it can be used. */
49+
export const enum Kind {
50+
/**
51+
* Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.
52+
*/
53+
ASCENDING,
54+
/**
55+
* Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.
56+
*/
57+
DESCENDING,
58+
/** Contains index. Can be used for ArrayContains and ArrayContainsAny. */
59+
CONTAINS
60+
}
61+
62+
/** An index component consisting of field path and index type. */
63+
export class Segment {
64+
constructor(
65+
/** The field path of the component. */
66+
readonly fieldPath: FieldPath,
67+
/** The fields sorting order. */
68+
readonly kind: Kind
69+
) {}
70+
}

0 commit comments

Comments
 (0)