Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markduckworth/ppl mep #8801

Draft
wants to merge 60 commits into
base: feat/pipelines
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e23ecfb
Multiple entry points for main package.
MarkDuckworth Dec 18, 2024
8006776
MEP for firestore lite building
MarkDuckworth Dec 19, 2024
db00e40
Fix issue with external exports
MarkDuckworth Dec 19, 2024
de97518
Refactor to support multiple entry points for the lite SDK. Fixes imp…
MarkDuckworth Dec 20, 2024
9c373ec
Type and comment cleanup
MarkDuckworth Jan 9, 2025
8b03745
Make PipelineSource generic and remove useFluentPipelines
MarkDuckworth Jan 9, 2025
2e1c8f6
Remove converter and placeholders for converter.
MarkDuckworth Jan 9, 2025
d67807e
cleanup: remove documentReferenceFactory
MarkDuckworth Jan 9, 2025
ea5c8e1
Linting and formatting
MarkDuckworth Jan 9, 2025
f251a04
Update the typings export for pipelines and lite pipelines so that it…
MarkDuckworth Jan 9, 2025
718ba98
Add missing Expr export
MarkDuckworth Jan 10, 2025
99ca193
Lint fixes
MarkDuckworth Jan 10, 2025
c911f97
TODO cleanup
MarkDuckworth Jan 10, 2025
229b0c7
update api review
MarkDuckworth Jan 10, 2025
0522ea7
Query to Proto with integration tests
MarkDuckworth Jan 22, 2025
50a65e4
API tweaks from review. Selectable, FilterCondition, and Accumulator …
MarkDuckworth Jan 23, 2025
d2291d0
Remove console.log statements from prune-dts.ts
MarkDuckworth Jan 27, 2025
d437b8e
Merge branch 'feat/pipelines' of github.com:firebase/firebase-js-sdk …
MarkDuckworth Jan 27, 2025
f44ea4d
code cleanup in prune-dts
MarkDuckworth Jan 27, 2025
16cfd24
Add missing imports to pipelines.ts
MarkDuckworth Jan 27, 2025
7d6aec1
If argument to FunctionExpr is a plain object or array, convert these…
MarkDuckworth Jan 29, 2025
e923dfd
Add tests for evaluating expressions in arrays and maps
MarkDuckworth Jan 29, 2025
29149c0
implement new FunctionExpressions as standalone functions
MarkDuckworth Feb 13, 2025
dbe6821
Fix typo and formatting in pipeline.test.ts
MarkDuckworth Feb 13, 2025
35a361d
Implement support for genericStage and genericFunction
MarkDuckworth Feb 13, 2025
da71f9a
Implement new function expressions as methods on the Expr class.
MarkDuckworth Feb 19, 2025
a06589a
Merge branch 'feat/pipelines' of github.com:firebase/firebase-js-sdk …
MarkDuckworth Feb 19, 2025
e820ee5
Pipeline test reorganization
MarkDuckworth Feb 19, 2025
33f5811
Implementing new stages Sample, Union, Unnest, Replace
MarkDuckworth Feb 21, 2025
ac42e1f
Rename FilterCondition to BooleanExpr
MarkDuckworth Feb 21, 2025
06d72b4
Update functions with variadic params to match signatures of the backend
MarkDuckworth Feb 22, 2025
b3d1f0c
Fix and implementation.
MarkDuckworth Feb 24, 2025
6c79ddd
Refactor AggregateFunction out of Expr
MarkDuckworth Feb 24, 2025
c4593b5
Add ScalarExpr class and make Expr a base class of AggregateFunction
MarkDuckworth Feb 25, 2025
db9ee0a
fixing missing imports and renames
MarkDuckworth Feb 25, 2025
ef1e42e
Remove classes for individual function types
MarkDuckworth Feb 25, 2025
c40ea12
Separate scalar expressions and aggregate functions into different cl…
MarkDuckworth Feb 25, 2025
3b38688
Remove deleted Expr classes from the pipelines exports
MarkDuckworth Feb 26, 2025
83e2cfe
Replace not(fn()) with notFn() in the query to pipeline conversion
MarkDuckworth Feb 26, 2025
4526ad1
Fix circular dependency between user_data_reader and expressions
MarkDuckworth Feb 26, 2025
2e981af
removing modular pipeline creation and removing fluent pipline execut…
MarkDuckworth Feb 26, 2025
a42b0d1
Change return type of execute from PipelineResult[] to PipelineSnapshot
MarkDuckworth Feb 26, 2025
8a06e25
Renames for API review: FunctionExpr, BooleanExpr, params
MarkDuckworth Feb 28, 2025
4e339ee
missing readonly
MarkDuckworth Feb 28, 2025
b303ec5
Build and test fixes
MarkDuckworth Feb 28, 2025
67f15fb
Replace Field.of() with field()
MarkDuckworth Feb 28, 2025
d31cf63
Add missing export PipelineSnaphsot
MarkDuckworth Feb 28, 2025
58d15c2
Replace Constant.of() with constant()
MarkDuckworth Feb 28, 2025
3268c63
Removed constant(...) overloads to create a map or array. Use the arr…
MarkDuckworth Feb 28, 2025
b909af7
Implemented and documented ascending|descending(fieldName) as a conve…
MarkDuckworth Feb 28, 2025
36def6e
fix tests creating a constant map and array
MarkDuckworth Feb 28, 2025
f1c68a5
Remove genericFunction, genericAggregateFunction, and genericBooleanE…
MarkDuckworth Feb 28, 2025
406929f
Pipeline result timestamp testing
MarkDuckworth Mar 3, 2025
9f1abbb
Testing timestamp edge cases
MarkDuckworth Mar 3, 2025
57c2a56
Query to pipeline conversion is performed from the PipelineSource cla…
MarkDuckworth Mar 3, 2025
0eda1ac
Timestamp tests and fix pipeline tests for change in pipeline creatio…
MarkDuckworth Mar 5, 2025
38d5052
test fix
MarkDuckworth Mar 6, 2025
c7beb1d
Refactor and rename all functions to match API proposal changes after…
MarkDuckworth Mar 6, 2025
5cc68d2
Fix build issues
MarkDuckworth Mar 6, 2025
2ccdac6
New integration util pipeline_export file to support the pipelines su…
MarkDuckworth Mar 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 11 additions & 69 deletions packages/firestore/lite/pipelines/pipelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ export type {

export { PipelineSource } from '../../src/lite-api/pipeline-source';

export { PipelineResult } from '../../src/lite-api/pipeline-result';
export {
PipelineResult,
PipelineSnapshot
} from '../../src/lite-api/pipeline-result';

export { Pipeline } from '../../src/lite-api/pipeline';

export { pipeline, execute } from '../../src/lite-api/pipeline_impl';
export { execute } from '../../src/lite-api/pipeline_impl';

export {
Stage,
Expand All @@ -76,6 +79,8 @@ export {
} from '../../src/lite-api/stage';

export {
field,
constant,
add,
subtract,
multiply,
Expand Down Expand Up @@ -132,79 +137,16 @@ export {
timestampToUnixSeconds,
timestampAdd,
timestampSub,
genericFunction,
ascending,
descending,
ExprWithAlias,
Field,
Fields,
Constant,
FirestoreFunction,
Add,
Subtract,
Multiply,
Divide,
Mod,
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
ArrayConcat,
ArrayReverse,
ArrayContains,
ArrayContainsAll,
ArrayContainsAny,
ArrayLength,
ArrayElement,
EqAny,
IsNan,
Exists,
Not,
And,
Or,
Xor,
Cond,
LogicalMaximum,
LogicalMinimum,
Reverse,
ReplaceFirst,
ReplaceAll,
CharLength,
ByteLength,
Like,
RegexContains,
RegexMatch,
StrContains,
StartsWith,
EndsWith,
ToLower,
ToUpper,
Trim,
StrConcat,
MapGet,
Count,
Sum,
Avg,
Minimum,
Maximum,
CosineDistance,
DotProduct,
EuclideanDistance,
VectorLength,
UnixMicrosToTimestamp,
TimestampToUnixMicros,
UnixMillisToTimestamp,
TimestampToUnixMillis,
UnixSecondsToTimestamp,
TimestampToUnixSeconds,
TimestampAdd,
TimestampSub,
FunctionExpr,
Ordering,
ExprType,
AccumulatorTarget,
AggregateWithAlias,
Selectable,
FilterCondition,
Accumulator
BooleanExpr,
AggregateFunction
} from '../../src/lite-api/expressions';
66 changes: 2 additions & 64 deletions packages/firestore/src/api/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
* limitations under the License.
*/

import { firestoreClientExecutePipeline } from '../core/firestore_client';
import { Pipeline as LitePipeline } from '../lite-api/pipeline';
import { PipelineResult } from '../lite-api/pipeline-result';
import { DocumentReference } from '../lite-api/reference';
import { Stage } from '../lite-api/stage';
import { UserDataReader } from '../lite-api/user_data_reader';
import { AbstractUserDataWriter } from '../lite-api/user_data_writer';
import { cast } from '../util/input_validation';

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

export class Pipeline extends LitePipeline {
/**
Expand All @@ -41,66 +37,8 @@ export class Pipeline extends LitePipeline {
db: Firestore,
userDataReader: UserDataReader,
userDataWriter: AbstractUserDataWriter,
stages: Stage[],
converter: unknown = {}
stages: Stage[]
): Pipeline {
return new Pipeline(db, userDataReader, userDataWriter, stages);
}

/**
* Executes this pipeline and returns a Promise to represent the asynchronous operation.
*
* <p>The returned Promise can be used to track the progress of the pipeline execution
* and retrieve the results (or handle any errors) asynchronously.
*
* <p>The pipeline results are returned as a list of {@link PipelineResult} objects. Each {@link
* PipelineResult} typically represents a single key/value map that has passed through all the
* stages of the pipeline, however this might differ depending on the stages involved in the
* pipeline. For example:
*
* <ul>
* <li>If there are no stages or only transformation stages, each {@link PipelineResult}
* represents a single document.</li>
* <li>If there is an aggregation, only a single {@link PipelineResult} is returned,
* representing the aggregated results over the entire dataset .</li>
* <li>If there is an aggregation stage with grouping, each {@link PipelineResult} represents a
* distinct group and its associated aggregated values.</li>
* </ul>
*
* <p>Example:
*
* ```typescript
* const futureResults = await firestore.pipeline().collection("books")
* .where(gt(Field.of("rating"), 4.5))
* .select("title", "author", "rating")
* .execute();
* ```
*
* @return A Promise representing the asynchronous pipeline execution.
*/
execute(): Promise<PipelineResult[]> {
const firestore = cast(this._db, Firestore);
const client = ensureFirestoreConfigured(firestore);
return firestoreClientExecutePipeline(client, this).then(result => {
const docs = result
// Currently ignore any response from ExecutePipeline that does
// not contain any document data in the `fields` property.
.filter(element => !!element.fields)
.map(
element =>
new PipelineResult(
this._userDataWriter,
element.key?.path
? new DocumentReference(firestore, null, element.key)
: undefined,
element.fields,
element.executionTime?.toTimestamp(),
element.createTime?.toTimestamp(),
element.updateTime?.toTimestamp()
)
);

return docs;
});
}
}
108 changes: 67 additions & 41 deletions packages/firestore/src/api/pipeline_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
*/

import { Pipeline } from '../api/pipeline';
import { toPipeline } from '../core/pipeline-util';
import { firestoreClientExecutePipeline } from '../core/firestore_client';
import { Pipeline as LitePipeline } from '../lite-api/pipeline';
import { PipelineResult } from '../lite-api/pipeline-result';
import { PipelineResult, PipelineSnapshot } from '../lite-api/pipeline-result';
import { PipelineSource } from '../lite-api/pipeline-source';
import { Stage } from '../lite-api/stage';
import { newUserDataReader } from '../lite-api/user_data_reader';
import { cast } from '../util/input_validation';

import { Firestore } from './database';
import { Query } from './reference';
import { ensureFirestoreConfigured, Firestore } from './database';
import { DocumentReference } from './reference';
import { ExpUserDataWriter } from './user_data_writer';

declare module './database' {
Expand All @@ -35,49 +35,75 @@ declare module './database' {
}

/**
* Experimental Modular API for console testing.
* @param firestore
*/
export function pipeline(firestore: Firestore): PipelineSource<Pipeline>;

/**
* Experimental Modular API for console testing.
* @param query
* Executes this pipeline and returns a Promise to represent the asynchronous operation.
*
* <p>The returned Promise can be used to track the progress of the pipeline execution
* and retrieve the results (or handle any errors) asynchronously.
*
* <p>The pipeline results are returned as a list of {@link PipelineResult} objects. Each {@link
* PipelineResult} typically represents a single key/value map that has passed through all the
* stages of the pipeline, however this might differ depending on the stages involved in the
* pipeline. For example:
*
* <ul>
* <li>If there are no stages or only transformation stages, each {@link PipelineResult}
* represents a single document.</li>
* <li>If there is an aggregation, only a single {@link PipelineResult} is returned,
* representing the aggregated results over the entire dataset .</li>
* <li>If there is an aggregation stage with grouping, each {@link PipelineResult} represents a
* distinct group and its associated aggregated values.</li>
* </ul>
*
* <p>Example:
*
* ```typescript
* const futureResults = await execute(firestore.pipeline().collection("books")
* .where(gt(field("rating"), 4.5))
* .select("title", "author", "rating"));
* ```
*
* @param pipeline The pipeline to execute.
* @return A Promise representing the asynchronous pipeline execution.
*/
export function pipeline(query: Query): Pipeline;
export function execute(pipeline: LitePipeline): Promise<PipelineSnapshot> {
const firestore = cast(pipeline._db, Firestore);
const client = ensureFirestoreConfigured(firestore);
return firestoreClientExecutePipeline(client, pipeline).then(result => {
// Get the execution time from the first result.
// firestoreClientExecutePipeline returns at least one PipelineStreamElement
// even if the returned document set is empty.
const executionTime =
result.length > 0 ? result[0].executionTime?.toTimestamp() : undefined;

export function pipeline(
firestoreOrQuery: Firestore | Query
): PipelineSource<Pipeline> | Pipeline {
if (firestoreOrQuery instanceof Firestore) {
const firestore = firestoreOrQuery;
return new PipelineSource<Pipeline>((stages: Stage[]) => {
return new Pipeline(
firestore,
newUserDataReader(firestore),
new ExpUserDataWriter(firestore),
stages
const docs = result
// Currently ignore any response from ExecutePipeline that does
// not contain any document data in the `fields` property.
.filter(element => !!element.fields)
.map(
element =>
new PipelineResult(
pipeline._userDataWriter,
element.key?.path
? new DocumentReference(firestore, null, element.key)
: undefined,
element.fields,
element.createTime?.toTimestamp(),
element.updateTime?.toTimestamp()
)
);
});
} else {
const query = firestoreOrQuery;
const db = cast<Firestore>(query.firestore, Firestore);

const litePipeline: LitePipeline = toPipeline(query._query, db);
return cast<Pipeline>(litePipeline, Pipeline);
}
}

export function execute(pipeline: LitePipeline): Promise<PipelineResult[]> {
return pipeline.execute();
return new PipelineSnapshot(pipeline, docs, executionTime);
});
}

// Augment the Firestore class with the pipeline() factory method
Firestore.prototype.pipeline = function (): PipelineSource<Pipeline> {
return pipeline(this);
};

// Augment the Query class with the pipeline() factory method
Query.prototype.pipeline = function (): Pipeline {
return pipeline(this);
return new PipelineSource<Pipeline>(this._databaseId, (stages: Stage[]) => {
return new Pipeline(
this,
newUserDataReader(this),
new ExpUserDataWriter(this),
stages
);
});
};
Loading
Loading