Skip to content

Commit 27e3e24

Browse files
committed
[2/4] Offline pipeline evaluation and tests
1 parent 085e6f2 commit 27e3e24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+18348
-121
lines changed

common/api-review/firestore-pipelines.api.md

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ export function charLength(stringExpression: Expression): FunctionExpression;
199199
// @beta (undocumented)
200200
export class CollectionGroupSource extends Stage {
201201
constructor(collectionId: string, options: StageOptions);
202-
}
202+
// (undocumented)
203+
readonly collectionId: string;
204+
}
203205

204206
// @public
205207
export type CollectionGroupStageOptions = StageOptions & {
@@ -247,7 +249,9 @@ export function conditional(condition: BooleanExpression, thenExpr: Expression,
247249
// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Expression" which is marked as @beta
248250
//
249251
// @public
250-
export function constant(value: number): Expression;
252+
export function constant(value: number, options?: {
253+
preferIntegers: boolean;
254+
}): Expression;
251255

252256
// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Expression" which is marked as @beta
253257
//
@@ -1154,17 +1158,17 @@ export class Pipeline {
11541158
sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline;
11551159
// (undocumented)
11561160
sort(options: SortStageOptions): Pipeline;
1161+
// Warning: (ae-incompatible-release-tags) The symbol "stages" is marked as @public, but its signature references "Stage" which is marked as @beta
1162+
//
11571163
// (undocumented)
1158-
stages: any;
1164+
stages: Stage[];
11591165
union(other: Pipeline): Pipeline;
11601166
// (undocumented)
11611167
union(options: UnionStageOptions): Pipeline;
11621168
// Warning: (ae-incompatible-release-tags) The symbol "unnest" is marked as @public, but its signature references "Selectable" which is marked as @beta
11631169
unnest(selectable: Selectable, indexField?: string): Pipeline;
11641170
// (undocumented)
11651171
unnest(options: UnnestStageOptions): Pipeline;
1166-
// (undocumented)
1167-
userDataReader: any;
11681172
// Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta
11691173
where(condition: BooleanExpression): Pipeline;
11701174
// (undocumented)
@@ -1180,18 +1184,42 @@ export interface PipelineExecuteOptions {
11801184
};
11811185
}
11821186

1183-
// Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts
1184-
//
11851187
// @beta
1186-
export class PipelineResult<AppModelType = DocumentData> {
1188+
export class PipelineResult {
1189+
get createTime(): Timestamp | undefined;
11871190
/* Excluded from this release type: _ref */
11881191
/* Excluded from this release type: _fields */
11891192
/* Excluded from this release type: __constructor */
1190-
get createTime(): Timestamp | undefined;
1191-
data(): AppModelType;
1193+
/* Excluded from this release type: fromDocument */
1194+
// Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts
1195+
data(): DocumentData | undefined;
1196+
/* Excluded from this release type: _ref */
1197+
/* Excluded from this release type: _fields */
1198+
/* Excluded from this release type: __constructor */
1199+
/* Excluded from this release type: fromDocument */
11921200
get(fieldPath: string | FieldPath | Field): any;
1201+
/* Excluded from this release type: _ref */
1202+
/* Excluded from this release type: _fields */
1203+
/* Excluded from this release type: __constructor */
1204+
/* Excluded from this release type: fromDocument */
11931205
get id(): string | undefined;
1206+
/* Excluded from this release type: _ref */
1207+
/* Excluded from this release type: _fields */
1208+
/* Excluded from this release type: __constructor */
1209+
/* Excluded from this release type: fromDocument */
1210+
// Warning: (ae-forgotten-export) The symbol "SnapshotMetadata" needs to be exported by the entry point pipelines.d.ts
1211+
//
1212+
// (undocumented)
1213+
readonly metadata?: SnapshotMetadata | undefined;
1214+
/* Excluded from this release type: _ref */
1215+
/* Excluded from this release type: _fields */
1216+
/* Excluded from this release type: __constructor */
1217+
/* Excluded from this release type: fromDocument */
11941218
get ref(): DocumentReference | undefined;
1219+
/* Excluded from this release type: _ref */
1220+
/* Excluded from this release type: _fields */
1221+
/* Excluded from this release type: __constructor */
1222+
/* Excluded from this release type: fromDocument */
11951223
get updateTime(): Timestamp | undefined;
11961224
}
11971225

@@ -1215,7 +1243,7 @@ export class PipelineSource<PipelineType> {
12151243
collection(options: CollectionStageOptions): PipelineType;
12161244
collectionGroup(collectionId: string): PipelineType;
12171245
collectionGroup(options: CollectionGroupStageOptions): PipelineType;
1218-
createFrom(query: Query): Pipeline;
1246+
createFrom(query: Query): PipelineType;
12191247
database(): PipelineType;
12201248
database(options: DatabaseStageOptions): PipelineType;
12211249
documents(docs: Array<string | DocumentReference>): PipelineType;
@@ -1337,7 +1365,9 @@ export type SelectStageOptions = StageOptions & {
13371365
// @beta (undocumented)
13381366
export class Sort extends Stage {
13391367
constructor(orderings: Ordering[], options: StageOptions);
1340-
}
1368+
// (undocumented)
1369+
readonly orderings: Ordering[];
1370+
}
13411371

13421372
// @public
13431373
export type SortStageOptions = StageOptions & {
@@ -1535,7 +1565,9 @@ export function vectorLength(fieldName: string): FunctionExpression;
15351565
// @beta (undocumented)
15361566
export class Where extends Stage {
15371567
constructor(condition: BooleanExpression, options: StageOptions);
1538-
}
1568+
// (undocumented)
1569+
readonly condition: BooleanExpression;
1570+
}
15391571

15401572
// @public
15411573
export type WhereStageOptions = StageOptions & {
@@ -1548,19 +1580,19 @@ export function xor(first: BooleanExpression, second: BooleanExpression, ...addi
15481580

15491581
// Warnings were encountered during analysis:
15501582
//
1551-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:73:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta
1552-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:118:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta
1553-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:123:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta
1554-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:785:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts
1555-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:1100:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta
1556-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3160:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta
1557-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5041:59 - (ae-incompatible-release-tags) The symbol "__index" is marked as @public, but its signature references "Expression" which is marked as @beta
1558-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5447:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta
1559-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5457:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta
1560-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5584:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta
1561-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5601:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta
1562-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6337:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta
1563-
// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6386:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta
1583+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:73:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta
1584+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:118:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta
1585+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:123:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta
1586+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:785:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts
1587+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:1102:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta
1588+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3162:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta
1589+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5042:59 - (ae-incompatible-release-tags) The symbol "__index" is marked as @public, but its signature references "Expression" which is marked as @beta
1590+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5450:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta
1591+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5460:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta
1592+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5587:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta
1593+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5604:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta
1594+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6340:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta
1595+
// /Users/wuandy/projects/firebase-clients/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6389:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta
15641596

15651597
// (No @packageDocumentation comment for this package)
15661598

packages/firestore/src/api/pipeline_impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ export function execute(
130130
element =>
131131
new PipelineResult(
132132
pipeline._userDataWriter,
133-
element.fields!,
134133
element.key?.path
135134
? new DocumentReference(firestore, null, element.key)
136135
: undefined,
136+
element.fields,
137137
element.createTime?.toTimestamp(),
138138
element.updateTime?.toTimestamp()
139139
)
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Firestore } from '../lite-api/database';
2+
import { BooleanExpression, Ordering } from '../lite-api/expressions';
3+
import { isReadableUserData, ReadableUserData } from '../lite-api/pipeline';
4+
import { Limit, Sort, Stage, Where } from '../lite-api/stage';
5+
import { UserDataReader, UserDataSource } from '../lite-api/user_data_reader';
6+
import { AbstractUserDataWriter } from '../lite-api/user_data_writer';
7+
import {
8+
Stage as ProtoStage,
9+
StructuredPipeline
10+
} from '../protos/firestore_proto_api';
11+
import { JsonProtoSerializer } from '../remote/serializer';
12+
13+
/**
14+
* @beta
15+
*
16+
* The RealtimePipeline class provides a flexible and expressive framework for building complex data
17+
* transformation and query pipelines that can be used with Firestore's real-time and offline capabilities.
18+
*
19+
* A RealtimePipeline takes data sources, such as Firestore collections or collection groups, and applies
20+
* a series of stages that are chained together. Each stage takes the output from the previous stage
21+
* (or the data source) and produces an output for the next stage (or as the final output of the
22+
* pipeline).
23+
*
24+
* Expressions can be used within each stage to filter and transform data through the stage.
25+
*
26+
* NOTE: Both the initial and subsequent snapshots for RealtimePipeline take the consideration of the SDK's cache.
27+
* They might include results that have not been synchronized with the server yet, and wait for subsequent snapshots
28+
* to reflect the latest server state, this is the same as classic Firestore {@link Query}.
29+
* This behavior is different from the {@link Pipeline} class, which does not take the consideration of the SDK's cache.
30+
*
31+
* Usage Examples:
32+
*
33+
* ```typescript
34+
* const db: Firestore; // Assumes a valid firestore instance.
35+
*
36+
* // Example 1: Listen to books published after 1980
37+
* const unsubscribe = onRealtimePipelineSnapshot(db.realtimePipeline()
38+
* .collection("books")
39+
* .where(field("published").gt(1980)),
40+
* (snapshot) => {
41+
* // Handle the snapshot
42+
* }
43+
* );
44+
* ```
45+
*/
46+
// TODO(pipeline): Add more examples to showcase functions
47+
export class RealtimePipeline {
48+
/**
49+
* @internal
50+
* @private
51+
* @param _db
52+
* @param userDataReader
53+
* @param _userDataWriter
54+
* @param _documentReferenceFactory
55+
* @param stages
56+
*/
57+
constructor(
58+
/**
59+
* @internal
60+
* @private
61+
*/
62+
public _db: Firestore,
63+
/**
64+
* @internal
65+
* @private
66+
*/
67+
readonly userDataReader: UserDataReader,
68+
/**
69+
* @internal
70+
* @private
71+
*/
72+
public _userDataWriter: AbstractUserDataWriter,
73+
readonly stages: Stage[]
74+
) {}
75+
76+
/**
77+
* Reads user data for each expression in the expressionMap.
78+
* @param name Name of the calling function. Used for error messages when invalid user data is encountered.
79+
* @param expressionMap
80+
* @return the expressionMap argument.
81+
* @private
82+
* @internal
83+
*/
84+
protected readUserData<
85+
T extends
86+
| Map<string, ReadableUserData>
87+
| ReadableUserData[]
88+
| ReadableUserData
89+
>(name: string, expressionMap: T): T {
90+
const context = this.userDataReader.createContext(
91+
UserDataSource.Argument,
92+
name
93+
);
94+
if (isReadableUserData(expressionMap)) {
95+
expressionMap._readUserData(context);
96+
} else if (Array.isArray(expressionMap)) {
97+
expressionMap.forEach(readableData =>
98+
readableData._readUserData(context)
99+
);
100+
} else {
101+
expressionMap.forEach(expr => expr._readUserData(context));
102+
}
103+
return expressionMap;
104+
}
105+
106+
where(condition: BooleanExpression): RealtimePipeline {
107+
const copy = this.stages.map(s => s);
108+
this.readUserData('where', condition);
109+
copy.push(new Where(condition, {}));
110+
return new RealtimePipeline(
111+
this._db,
112+
this.userDataReader,
113+
this._userDataWriter,
114+
copy
115+
);
116+
}
117+
118+
limit(limit: number): RealtimePipeline {
119+
const copy = this.stages.map(s => s);
120+
copy.push(new Limit(limit, {}));
121+
return new RealtimePipeline(
122+
this._db,
123+
this.userDataReader,
124+
this._userDataWriter,
125+
copy
126+
);
127+
}
128+
129+
sort(...orderings: Ordering[]): RealtimePipeline;
130+
sort(options: { orderings: Ordering[] }): RealtimePipeline;
131+
sort(
132+
optionsOrOrderings:
133+
| Ordering
134+
| {
135+
orderings: Ordering[];
136+
},
137+
...rest: Ordering[]
138+
): RealtimePipeline {
139+
const copy = this.stages.map(s => s);
140+
// Option object
141+
if ('orderings' in optionsOrOrderings) {
142+
copy.push(
143+
new Sort(this.readUserData('sort', optionsOrOrderings.orderings), {})
144+
);
145+
} else {
146+
// Ordering object
147+
copy.push(
148+
new Sort(this.readUserData('sort', [optionsOrOrderings, ...rest]), {})
149+
);
150+
}
151+
152+
return new RealtimePipeline(
153+
this._db,
154+
this.userDataReader,
155+
this._userDataWriter,
156+
copy
157+
);
158+
}
159+
160+
/**
161+
* @internal
162+
* @private
163+
*/
164+
_toStructuredPipeline(
165+
jsonProtoSerializer: JsonProtoSerializer
166+
): StructuredPipeline {
167+
const stages: ProtoStage[] = this.stages.map(stage =>
168+
stage._toProto(jsonProtoSerializer)
169+
);
170+
return { pipeline: { stages } };
171+
}
172+
}

0 commit comments

Comments
 (0)