Skip to content

Commit e111ec1

Browse files
committed
Refactor and test pipeline options implementation
1 parent 281c441 commit e111ec1

File tree

7 files changed

+129
-257
lines changed

7 files changed

+129
-257
lines changed

packages/firestore/src/api/pipeline_impl.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ import { PipelineOptions } from '../lite-api/pipeline_settings';
2828
import { Stage } from '../lite-api/stage';
2929
import {
3030
newUserDataReader,
31-
parseData,
3231
UserDataReader,
3332
UserDataSource
3433
} from '../lite-api/user_data_reader';
35-
import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api';
3634
import { cast } from '../util/input_validation';
3735

3836
import { ensureFirestoreConfigured, Firestore } from './database';
@@ -84,17 +82,16 @@ export function execute(options: PipelineOptions): Promise<PipelineSnapshot>;
8482
export function execute(
8583
pipelineOrOptions: LitePipeline | PipelineOptions
8684
): Promise<PipelineSnapshot> {
87-
const pipeline: LitePipeline =
88-
pipelineOrOptions instanceof LitePipeline
89-
? pipelineOrOptions
90-
: pipelineOrOptions.pipeline;
91-
const options: StructuredPipelineOptions = !(
85+
const options: PipelineOptions = !(
9286
pipelineOrOptions instanceof LitePipeline
9387
)
9488
? pipelineOrOptions
95-
: {};
96-
const genericOptions: { [name: string]: unknown } =
97-
(pipelineOrOptions as PipelineOptions).genericOptions ?? {};
89+
: {
90+
pipeline: pipelineOrOptions
91+
};
92+
93+
const { pipeline, customOptions, ...rest } = options;
94+
const genericOptions: { [name:string]: unknown } = customOptions ?? {};
9895

9996
const firestore = cast(pipeline._db, Firestore);
10097
const client = ensureFirestoreConfigured(firestore);
@@ -104,13 +101,13 @@ export function execute(
104101
/* ignoreUndefinedProperties */ true
105102
);
106103
const context = udr.createContext(UserDataSource.Argument, 'execute');
107-
const optionsOverride: ApiClientObjectMap<Value> =
108-
parseData(genericOptions, context)?.mapValue?.fields ?? {};
104+
105+
const structuredPipelineOptions = new StructuredPipelineOptions(rest, genericOptions);
106+
structuredPipelineOptions._readUserData(udr, context);
109107

110108
const structuredPipeline: StructuredPipeline = new StructuredPipeline(
111109
pipeline,
112-
options,
113-
optionsOverride
110+
structuredPipelineOptions
114111
);
115112

116113
return firestoreClientExecutePipeline(client, structuredPipeline).then(

packages/firestore/src/core/structured_pipeline.ts

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,40 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ObjectValue } from '../model/object_value';
19-
import { FieldPath } from '../model/path';
18+
import {ParseContext} from "../api/parse_context";
19+
import {UserDataReader, UserDataSource} from "../lite-api/user_data_reader";
2020
import {
21-
StructuredPipeline as StructuredPipelineProto,
21+
ApiClientObjectMap, firestoreV1ApiClientInterfaces,
2222
Pipeline as PipelineProto,
23-
ApiClientObjectMap,
24-
Value
23+
StructuredPipeline as StructuredPipelineProto
2524
} from '../protos/firestore_proto_api';
26-
import { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer';
27-
import { mapToArray } from '../util/obj';
25+
import {
26+
JsonProtoSerializer,
27+
ProtoSerializable,
28+
UserData
29+
} from '../remote/serializer';
30+
31+
import {OptionsUtil} from "./options_util";
32+
33+
export class StructuredPipelineOptions implements UserData{
34+
proto: ApiClientObjectMap<firestoreV1ApiClientInterfaces.Value> | undefined;
35+
36+
readonly optionsUtil = new OptionsUtil({
37+
indexMode: {
38+
serverName: 'index_mode',
39+
}
40+
});
2841

29-
export interface StructuredPipelineOptions {
30-
indexMode?: 'recommended';
42+
constructor(
43+
private _userOptions: Record<string, unknown> = {},
44+
private _optionsOverride: Record<string, unknown> = {}) {}
45+
46+
_readUserData(dataReader: UserDataReader, context?: ParseContext): void {
47+
if (!context) {
48+
context = dataReader.createContext(UserDataSource.Argument, "StructuredPipelineOptions._readUserData");
49+
}
50+
this.proto = this.optionsUtil.getOptionsProto(context, this._userOptions, this._optionsOverride);
51+
}
3152
}
3253

3354
export class StructuredPipeline
@@ -36,45 +57,12 @@ export class StructuredPipeline
3657
constructor(
3758
private pipeline: ProtoSerializable<PipelineProto>,
3859
private options: StructuredPipelineOptions,
39-
private optionsOverride: ApiClientObjectMap<Value>
4060
) {}
4161

42-
/**
43-
* @private
44-
* @internal for testing
45-
*/
46-
_getKnownOptions(): ObjectValue {
47-
const options: ObjectValue = ObjectValue.empty();
48-
49-
// SERIALIZE KNOWN OPTIONS
50-
if (typeof this.options.indexMode === 'string') {
51-
options.set(FieldPath.fromServerFormat('index_mode'), {
52-
stringValue: this.options.indexMode
53-
});
54-
}
55-
56-
return options;
57-
}
58-
59-
private getOptionsProto(): ApiClientObjectMap<Value> {
60-
const options: ObjectValue = this._getKnownOptions();
61-
62-
// APPLY OPTIONS OVERRIDES
63-
const optionsMap = new Map(
64-
mapToArray(this.optionsOverride, (value, key) => [
65-
FieldPath.fromServerFormat(key),
66-
value
67-
])
68-
);
69-
options.setAll(optionsMap);
70-
71-
return options.value.mapValue.fields ?? {};
72-
}
73-
7462
_toProto(serializer: JsonProtoSerializer): StructuredPipelineProto {
7563
return {
7664
pipeline: this.pipeline._toProto(serializer),
77-
options: this.getOptionsProto()
65+
options: this.options.proto
7866
};
7967
}
8068
}

packages/firestore/src/lite-api/pipeline_impl.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
* limitations under the License.
1616
*/
1717

18+
import {
19+
StructuredPipeline,
20+
StructuredPipelineOptions
21+
} from "../core/structured_pipeline";
1822
import { invokeExecutePipeline } from '../remote/datastore';
1923

2024
import { getDatastore } from './components';
@@ -25,7 +29,11 @@ import { PipelineSource } from './pipeline-source';
2529
import { DocumentReference } from './reference';
2630
import { LiteUserDataWriter } from './reference_impl';
2731
import { Stage } from './stage';
28-
import { newUserDataReader } from './user_data_reader';
32+
import {
33+
newUserDataReader,
34+
UserDataReader,
35+
UserDataSource
36+
} from './user_data_reader';
2937

3038
declare module './database' {
3139
interface Firestore {
@@ -69,7 +77,22 @@ declare module './database' {
6977
*/
7078
export function execute(pipeline: Pipeline): Promise<PipelineSnapshot> {
7179
const datastore = getDatastore(pipeline._db);
72-
return invokeExecutePipeline(datastore, pipeline).then(result => {
80+
81+
const udr = new UserDataReader(
82+
pipeline._db._databaseId,
83+
/* ignoreUndefinedProperties */ true
84+
);
85+
const context = udr.createContext(UserDataSource.Argument, 'execute');
86+
87+
const structuredPipelineOptions = new StructuredPipelineOptions({}, {});
88+
structuredPipelineOptions._readUserData(udr, context);
89+
90+
const structuredPipeline: StructuredPipeline = new StructuredPipeline(
91+
pipeline,
92+
structuredPipelineOptions
93+
);
94+
95+
return invokeExecutePipeline(datastore, structuredPipeline).then(result => {
7396
// Get the execution time from the first result.
7497
// firestoreClientExecutePipeline returns at least one PipelineStreamElement
7598
// even if the returned document set is empty.

packages/firestore/src/lite-api/pipeline_settings.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
114
import type { Pipeline } from './pipeline';
215

316
/**
4-
* Options defining how a Pipeline is evaluated.
17+
* Options defining Pipeline execution.
518
*/
619
export interface PipelineOptions {
720
/**
@@ -18,21 +31,21 @@ export interface PipelineOptions {
1831
* An escape hatch to set options not known at SDK build time. These values
1932
* will be passed directly to the Firestore backend and not used by the SDK.
2033
*
21-
* The generic option name will be used as provided. And must match the name
34+
* The option name will be used as provided. And must match the name
2235
* format used by the backend (hint: use a snake_case_name).
2336
*
24-
* Generic option values can be any type supported
37+
* Custom option values can be any type supported
2538
* by Firestore (for example: string, boolean, number, map, …). Value types
2639
* not known to the SDK will be rejected.
2740
*
28-
* Values specified in genericOptions will take precedence over any options
41+
* Values specified in customOptions will take precedence over any options
2942
* with the same name set by the SDK.
3043
*
3144
* Override the `example_option`:
3245
* ```
3346
* execute({
3447
* pipeline: myPipeline,
35-
* genericOptions: {
48+
* customOptions: {
3649
* // Override `example_option`. This will not
3750
* // merge with the existing `example_option` object.
3851
* "example_option": {
@@ -42,20 +55,20 @@ export interface PipelineOptions {
4255
* }
4356
* ```
4457
*
45-
* `genericOptions` supports dot notation, if you want to override
58+
* `customOptions` supports dot notation, if you want to override
4659
* a nested option.
4760
* ```
4861
* execute({
4962
* pipeline: myPipeline,
50-
* genericOptions: {
63+
* customOptions: {
5164
* // Override `example_option.foo` and do not override
5265
* // any other properties of `example_option`.
5366
* "example_option.foo": "bar"
5467
* }
5568
* }
5669
* ```
5770
*/
58-
genericOptions?: {
71+
customOptions?: {
5972
[name: string]: unknown;
6073
};
6174
}

packages/firestore/src/util/input_validation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import {DocumentData} from "../lite-api/reference";
1819
import { DocumentKey } from '../model/document_key';
1920
import { ResourcePath } from '../model/path';
2021

@@ -92,7 +93,7 @@ export function validateCollectionPath(path: ResourcePath): void {
9293
* Returns true if it's a non-null object without a custom prototype
9394
* (i.e. excludes Array, Date, etc.).
9495
*/
95-
export function isPlainObject(input: unknown): boolean {
96+
export function isPlainObject(input: unknown): input is DocumentData {
9697
return (
9798
typeof input === 'object' &&
9899
input !== null &&

packages/firestore/test/unit/api/pipeline_impl.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('execute(Pipeline|PipelineOptions)', () => {
165165

166166
await execute({
167167
pipeline: firestore.pipeline().collection('foo'),
168-
genericOptions: {
168+
customOptions: {
169169
'foo': 'bar'
170170
}
171171
});

0 commit comments

Comments
 (0)