Skip to content

Commit 44fde63

Browse files
committed
Merge branch 'firestore-pipelines-dart-api-v2' into firestore-pipelines-web
2 parents 474e951 + 41f97df commit 44fde63

File tree

138 files changed

+7453
-17
lines changed

Some content is hidden

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

138 files changed

+7453
-17
lines changed

packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart

Lines changed: 228 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,36 @@
44

55
part of '../cloud_firestore.dart';
66

7-
/// A pipeline for querying and transforming Firestore data
7+
/// A pipeline for querying and transforming Firestore data.
8+
///
9+
/// A [Pipeline] is composed of a sequence of stages. Each stage processes the
10+
/// output from the previous one, and the final stage's output is the result of
11+
/// the pipeline's execution.
12+
///
13+
/// Start with [FirebaseFirestore.pipeline] and a source stage (e.g.
14+
/// [PipelineSource.collection]), then add transformation and filtering stages.
15+
/// Call [execute] to run the pipeline.
16+
///
17+
/// Example:
18+
/// ```dart
19+
/// final snapshot = await FirebaseFirestore.instance
20+
/// .pipeline()
21+
/// .collection('users')
22+
/// .where(Expression.field('active').equal(true))
23+
/// .limit(10)
24+
/// .execute();
25+
/// for (final result in snapshot.result) {
26+
/// print(result.data());
27+
/// }
28+
/// ```
29+
///
30+
/// **Note on execution:** The stages are conceptual. The Firestore backend may
31+
/// optimize execution (e.g., reordering or merging stages) as long as the final
32+
/// result remains the same.
33+
///
34+
/// **Important limitations:** Pipelines operate on a request/response basis only.
35+
/// They do not utilize or update the local SDK cache, and they do not support
36+
/// realtime snapshot listeners.
837
class Pipeline {
938
final FirebaseFirestore _firestore;
1039
final PipelinePlatform _delegate;
@@ -23,7 +52,17 @@ class Pipeline {
2352
return _delegate.stages;
2453
}
2554

26-
/// Executes the pipeline and returns a snapshot of the results
55+
/// Executes this pipeline and returns the results as a [PipelineSnapshot].
56+
///
57+
/// Example:
58+
/// ```dart
59+
/// final snapshot = await firestore
60+
/// .pipeline()
61+
/// .collection('products')
62+
/// .limit(5)
63+
/// .execute();
64+
/// print('Got ${snapshot.result.length} documents');
65+
/// ```
2766
Future<PipelineSnapshot> execute({ExecuteOptions? options}) async {
2867
final optionsMap = options != null
2968
? {
@@ -54,7 +93,22 @@ class Pipeline {
5493

5594
// Pipeline Actions
5695

57-
/// Adds fields to documents using expressions
96+
/// Adds new fields to outputs from previous stages.
97+
///
98+
/// This stage allows you to compute values on-the-fly based on existing data
99+
/// from previous stages or constants. You can create new fields or overwrite
100+
/// existing ones. The added fields are defined using [Selectable]s, which can
101+
/// be a [Field] (from [Expression.field]) or an expression with an alias via
102+
/// [Expression.as].
103+
///
104+
/// Example:
105+
/// ```dart
106+
/// firestore.pipeline().collection('books').addFields(
107+
/// Expression.field('rating').as('bookRating'),
108+
/// Expression.field('title').stringReplaceAllLiteral('Item', 'Doc').as('title_replaced'),
109+
/// Expression.field('score').abs().as('abs_score'),
110+
/// );
111+
/// ```
58112
Pipeline addFields(
59113
Selectable selectable1, [
60114
Selectable? selectable2,
@@ -124,7 +178,22 @@ class Pipeline {
124178
);
125179
}
126180

127-
/// Aggregates data using aggregate functions
181+
/// Performs aggregation operations on the documents from previous stages.
182+
///
183+
/// This stage allows you to calculate aggregate values over a set of documents.
184+
/// Define aggregations using [AliasedAggregateFunction]s, typically by calling
185+
/// [PipelineAggregateFunction.as] on [PipelineAggregateFunction] instances
186+
/// such as [Expression.field] with [Expression.sum], [Expression.average],
187+
/// or [CountAll].
188+
///
189+
/// Example:
190+
/// ```dart
191+
/// firestore.pipeline().collection('books').aggregate(
192+
/// Expression.field('score').sum().as('total_score'),
193+
/// Expression.field('score').average().as('avg_score'),
194+
/// CountAll().as('count'),
195+
/// );
196+
/// ```
128197
Pipeline aggregate(
129198
AliasedAggregateFunction aggregateFunction1, [
130199
AliasedAggregateFunction? aggregateFunction2,
@@ -247,7 +316,18 @@ class Pipeline {
247316
);
248317
}
249318

250-
/// Gets distinct values based on expressions
319+
/// Returns a set of distinct values from the inputs to this stage.
320+
///
321+
/// This stage runs through the results from previous stages to include only
322+
/// results with unique combinations of the given [Selectable] expressions
323+
/// (e.g. [Expression.field], or expressions with [Expression.as]).
324+
///
325+
/// Example:
326+
/// ```dart
327+
/// firestore.pipeline().collection('books').distinct(
328+
/// Expression.field('category').as('category'),
329+
/// );
330+
/// ```
251331
Pipeline distinct(
252332
Selectable expression1, [
253333
Selectable? expression2,
@@ -318,7 +398,23 @@ class Pipeline {
318398
);
319399
}
320400

321-
/// Finds nearest vectors using vector similarity search
401+
/// Performs a vector similarity search.
402+
///
403+
/// Orders the result set by most similar to least similar and returns
404+
/// documents in that order. Requires a vector index on [vectorField].
405+
/// [vectorValue] is the query embedding; [distanceMeasure] specifies how to
406+
/// compare vectors (e.g. [DistanceMeasure.cosine]). Use [limit] to return
407+
/// only the first N documents.
408+
///
409+
/// Example:
410+
/// ```dart
411+
/// firestore.pipeline().collection('books').findNearest(
412+
/// Field('embedding'),
413+
/// [0.1, 0.2, 0.3],
414+
/// DistanceMeasure.cosine,
415+
/// limit: 10,
416+
/// );
417+
/// ```
322418
Pipeline findNearest(
323419
Field vectorField,
324420
List<double> vectorValue,
@@ -337,7 +433,18 @@ class Pipeline {
337433
);
338434
}
339435

340-
/// Limits the number of results
436+
/// Limits the maximum number of documents returned by previous stages to
437+
/// [limit].
438+
///
439+
/// Useful for pagination (with [offset]) or to cap data retrieval and
440+
/// improve performance.
441+
///
442+
/// Example:
443+
/// ```dart
444+
/// firestore.pipeline().collection('books')
445+
/// .sort(Expression.field('rating').descending())
446+
/// .limit(10);
447+
/// ```
341448
Pipeline limit(int limit) {
342449
final stage = _LimitStage(limit);
343450
return Pipeline._(
@@ -346,7 +453,18 @@ class Pipeline {
346453
);
347454
}
348455

349-
/// Offsets the results
456+
/// Skips the first [offset] documents from the results of previous stages.
457+
///
458+
/// Useful for implementing pagination; typically used with [limit] to control
459+
/// the size of each page.
460+
///
461+
/// Example:
462+
/// ```dart
463+
/// firestore.pipeline().collection('books')
464+
/// .sort(Expression.field('published').descending())
465+
/// .offset(20)
466+
/// .limit(20);
467+
/// ```
350468
Pipeline offset(int offset) {
351469
final stage = _OffsetStage(offset);
352470
return Pipeline._(
@@ -355,7 +473,13 @@ class Pipeline {
355473
);
356474
}
357475

358-
/// Removes specified fields from documents
476+
/// Removes fields from outputs of previous stages.
477+
///
478+
/// Example:
479+
/// ```dart
480+
/// firestore.pipeline().collection('books')
481+
/// .removeFields('rating', 'cost');
482+
/// ```
359483
Pipeline removeFields(
360484
String fieldPath1, [
361485
String? fieldPath2,
@@ -426,7 +550,18 @@ class Pipeline {
426550
);
427551
}
428552

429-
/// Replaces documents with the result of an expression
553+
/// Fully overwrites each document with the value of the given expression.
554+
///
555+
/// This stage allows you to emit a nested map or array as the document: each
556+
/// document from the previous stage is replaced by the evaluated [expression]
557+
/// (e.g. [Expression.field] referencing a nested map or array).
558+
///
559+
/// Example:
560+
/// ```dart
561+
/// // Emit the 'parents' map as the document.
562+
/// firestore.pipeline().collection('people')
563+
/// .replaceWith(Expression.field('parents'));
564+
/// ```
430565
Pipeline replaceWith(Expression expression) {
431566
final stage = _ReplaceWithStage(expression);
432567
return Pipeline._(
@@ -435,7 +570,18 @@ class Pipeline {
435570
);
436571
}
437572

438-
/// Samples documents using a sampling strategy
573+
/// Performs a pseudo-random sampling of the input documents.
574+
///
575+
/// Use [PipelineSample.withSize] for a fixed number of documents, or
576+
/// [PipelineSample.withPercentage] for a fraction of the result set.
577+
///
578+
/// Example:
579+
/// ```dart
580+
/// firestore.pipeline().collection('books')
581+
/// .sample(PipelineSample.withSize(10));
582+
/// firestore.pipeline().collection('books')
583+
/// .sample(PipelineSample.withPercentage(0.5));
584+
/// ```
439585
Pipeline sample(PipelineSample sample) {
440586
final stage = _SampleStage(sample);
441587
return Pipeline._(
@@ -444,7 +590,19 @@ class Pipeline {
444590
);
445591
}
446592

447-
/// Selects specific fields using selectable expressions
593+
/// Selects or creates a set of fields from the outputs of previous stages.
594+
///
595+
/// Only the selected fields (with their aliases) appear in the output. Use
596+
/// [Expression.field] with [Expression.as] for expressions. Use [addFields]
597+
/// if you only want to add or overwrite fields while keeping the rest.
598+
///
599+
/// Example:
600+
/// ```dart
601+
/// firestore.pipeline().collection('books').select(
602+
/// Expression.field('name').as('name'),
603+
/// Expression.field('address').toUpperCase().as('upperAddress'),
604+
/// );
605+
/// ```
448606
Pipeline select(
449607
Selectable expression1, [
450608
Selectable? expression2,
@@ -515,8 +673,20 @@ class Pipeline {
515673
);
516674
}
517675

518-
/// Sorts results using one or more ordering specifications.
519-
/// Orderings are applied in sequence (e.g. primary sort by first, then by second, etc.).
676+
/// Sorts the documents from previous stages based on one or more orderings.
677+
///
678+
/// Orderings are applied in sequence (primary sort by the first, then by the
679+
/// second, etc.). If documents have the same value for a field used for
680+
/// sorting, the next ordering is used. Use [Expression.field] with
681+
/// [Expression.descending] and [Expression.ascending].
682+
///
683+
/// Example:
684+
/// ```dart
685+
/// firestore.pipeline().collection('books').sort(
686+
/// Expression.field('rating').descending(),
687+
/// Expression.field('title').ascending(),
688+
/// );
689+
/// ```
520690
Pipeline sort(
521691
Ordering order, [
522692
Ordering? order2,
@@ -587,7 +757,21 @@ class Pipeline {
587757
);
588758
}
589759

590-
/// Unnests arrays into separate documents
760+
/// Takes a specified array from the input documents and outputs a document
761+
/// for each element, with the element stored in a field named by the alias.
762+
///
763+
/// For each document from the previous stage, this stage emits zero or more
764+
/// documents (one per array element). Use [Expression.field].as() to specify
765+
/// the array and the output field name. Optionally use [indexField] to add
766+
/// the array index to each emitted document.
767+
///
768+
/// Example:
769+
/// ```dart
770+
/// // Input: { "title": "Guide", "tags": ["comedy", "space"] }
771+
/// // Output: one doc per tag with field "tag".
772+
/// firestore.pipeline().collection('books')
773+
/// .unnest(Expression.field('tags').as('tag'));
774+
/// ```
591775
Pipeline unnest(Selectable expression, [String? indexField]) {
592776
final stage = _UnnestStage(expression, indexField);
593777
return Pipeline._(
@@ -596,7 +780,18 @@ class Pipeline {
596780
);
597781
}
598782

599-
/// Unions results with another pipeline
783+
/// Performs a union of all documents from this pipeline and [pipeline],
784+
/// including duplicates.
785+
///
786+
/// Documents from the previous stage and from [pipeline] are combined. The
787+
/// order of documents emitted from this stage is undefined.
788+
///
789+
/// Example:
790+
/// ```dart
791+
/// firestore.pipeline().collection('books').union(
792+
/// firestore.pipeline().collection('magazines'),
793+
/// );
794+
/// ```
600795
Pipeline union(Pipeline pipeline) {
601796
final stage = _UnionStage(pipeline);
602797
return Pipeline._(
@@ -605,7 +800,23 @@ class Pipeline {
605800
);
606801
}
607802

608-
/// Filters documents using a boolean expression
803+
/// Filters the documents from previous stages to only include those matching
804+
/// the specified [BooleanExpression].
805+
///
806+
/// This stage applies conditions to the data, similar to a WHERE clause. You
807+
/// can use [Expression.field] with [Expression.equal], [Expression.greaterThan],
808+
/// [Expression.lessThan], etc., and combine conditions with [Expression.and]
809+
/// and [Expression.or].
810+
///
811+
/// Example:
812+
/// ```dart
813+
/// firestore.pipeline().collection('books').where(
814+
/// Expression.and(
815+
/// Expression.field('rating').greaterThan(4.0),
816+
/// Expression.field('genre').equal('Science Fiction'),
817+
/// ),
818+
/// );
819+
/// ```
609820
Pipeline where(BooleanExpression expression) {
610821
final stage = _WhereStage(expression);
611822
return Pipeline._(

0 commit comments

Comments
 (0)