From 90bd9b5ddba24a290bd3d1c2ba9377776777051b Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 6 Feb 2026 11:19:09 +0000 Subject: [PATCH 01/20] feat(firestore): add support for Pipeline APIs --- .../cloud_firestore/lib/cloud_firestore.dart | 9 + .../lib/pipeline_snapshot.dart | 26 + .../cloud_firestore/lib/src/firestore.dart | 19 + .../cloud_firestore/lib/src/pipeline.dart | 477 ++++++++++++++++++ .../lib/src/pipeline_aggregate.dart | 58 +++ .../lib/src/pipeline_distance.dart | 17 + .../lib/src/pipeline_expression.dart | 394 +++++++++++++++ .../lib/src/pipeline_ordering.dart | 30 ++ .../lib/src/pipeline_sample.dart | 43 ++ .../lib/src/pipeline_source.dart | 66 +++ .../lib/src/pipeline_stage.dart | 387 ++++++++++++++ .../cloud_firestore_platform_interface.dart | 1 + .../platform_interface_firestore.dart | 8 + .../platform_interface_pipeline_snapshot.dart | 40 ++ 14 files changed, 1575 insertions(+) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 6efbb19345ce..c84108ac3686 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -53,7 +53,16 @@ part 'src/filters.dart'; part 'src/firestore.dart'; part 'src/load_bundle_task.dart'; part 'src/load_bundle_task_snapshot.dart'; +part 'pipeline_snapshot.dart'; part 'src/persistent_cache_index_manager.dart'; +part 'src/pipeline.dart'; +part 'src/pipeline_aggregate.dart'; +part 'src/pipeline_distance.dart'; +part 'src/pipeline_expression.dart'; +part 'src/pipeline_ordering.dart'; +part 'src/pipeline_sample.dart'; +part 'src/pipeline_source.dart'; +part 'src/pipeline_stage.dart'; part 'src/query.dart'; part 'src/query_document_snapshot.dart'; part 'src/query_snapshot.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart new file mode 100644 index 000000000000..f08cb6735987 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart @@ -0,0 +1,26 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of 'cloud_firestore.dart'; + +/// Result of executing a pipeline +class PipelineResult { + final DocumentReference> document; + final DateTime createTime; + final DateTime updateTime; + + PipelineResult({ + required this.document, + required this.createTime, + required this.updateTime, + }); +} + +/// Snapshot containing pipeline execution results +class PipelineSnapshot { + final List result; + final DateTime executionTime; + + PipelineSnapshot._(this.result, this.executionTime); +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 4f735629aee1..3e583c3f3331 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -339,6 +339,25 @@ class FirebaseFirestore extends FirebasePluginPlatform { return null; } + /// Returns a [PipelineSource] for creating and executing pipelines. + /// + /// Pipelines allow you to perform complex queries and transformations on + /// Firestore data using a fluent API. + /// + /// Example: + /// ```dart + /// final snapshot = await FirebaseFirestore.instance + /// .pipeline() + /// .collection('users') + /// .where(PipelineFilter(Field('age'), isGreaterThan: 18)) + /// .sort(Field('name').ascending()) + /// .limit(10) + /// .execute(); + /// ``` + PipelineSource pipeline() { + return PipelineSource._(this); + } + /// Configures indexing for local query execution. Any previous index configuration is overridden. /// /// The index entries themselves are created asynchronously. You can continue to use queries that diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart new file mode 100644 index 000000000000..e7849f43afd7 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -0,0 +1,477 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// A pipeline for querying and transforming Firestore data +class Pipeline { + final List _stages; + final FirebaseFirestore _firestore; + + Pipeline._(this._firestore, this._stages); + + /// Executes the pipeline and returns a snapshot of the results + Future execute() async { + final platformSnapshot = + await _firestore._delegate.executePipeline(_toSerializableStages()); + return _convertPlatformSnapshot(platformSnapshot); + } + + /// Converts platform snapshot to public snapshot + PipelineSnapshot _convertPlatformSnapshot( + PipelineSnapshotPlatform platformSnapshot) { + final results = platformSnapshot.results.map((platformResult) { + return PipelineResult( + document: _JsonDocumentReference( + _firestore, + platformResult.document, + ), + createTime: platformResult.createTime, + updateTime: platformResult.updateTime, + ); + }).toList(); + + return PipelineSnapshot._(results, platformSnapshot.executionTime); + } + + /// Converts stages to serializable format for platform communication + List> _toSerializableStages() { + return _stages.map((stage) => stage.toMap()).toList(); + } + + /// Public method to get serializable stages (used by union stage) + List> getSerializableStages() { + return _toSerializableStages(); + } + + // Pipeline Actions + + /// Adds fields to documents using expressions + Pipeline addFields( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _AddFieldsStage(expressions), + ]); + } + + /// Aggregates data using aggregate functions + Pipeline aggregate( + PipelineAggregateFunction aggregateFunction1, [ + PipelineAggregateFunction? aggregateFunction2, + PipelineAggregateFunction? aggregateFunction3, + PipelineAggregateFunction? aggregateFunction4, + PipelineAggregateFunction? aggregateFunction5, + PipelineAggregateFunction? aggregateFunction6, + PipelineAggregateFunction? aggregateFunction7, + PipelineAggregateFunction? aggregateFunction8, + PipelineAggregateFunction? aggregateFunction9, + PipelineAggregateFunction? aggregateFunction10, + PipelineAggregateFunction? aggregateFunction11, + PipelineAggregateFunction? aggregateFunction12, + PipelineAggregateFunction? aggregateFunction13, + PipelineAggregateFunction? aggregateFunction14, + PipelineAggregateFunction? aggregateFunction15, + PipelineAggregateFunction? aggregateFunction16, + PipelineAggregateFunction? aggregateFunction17, + PipelineAggregateFunction? aggregateFunction18, + PipelineAggregateFunction? aggregateFunction19, + PipelineAggregateFunction? aggregateFunction20, + PipelineAggregateFunction? aggregateFunction21, + PipelineAggregateFunction? aggregateFunction22, + PipelineAggregateFunction? aggregateFunction23, + PipelineAggregateFunction? aggregateFunction24, + PipelineAggregateFunction? aggregateFunction25, + PipelineAggregateFunction? aggregateFunction26, + PipelineAggregateFunction? aggregateFunction27, + PipelineAggregateFunction? aggregateFunction28, + PipelineAggregateFunction? aggregateFunction29, + PipelineAggregateFunction? aggregateFunction30, + ]) { + final functions = [aggregateFunction1]; + if (aggregateFunction2 != null) functions.add(aggregateFunction2); + if (aggregateFunction3 != null) functions.add(aggregateFunction3); + if (aggregateFunction4 != null) functions.add(aggregateFunction4); + if (aggregateFunction5 != null) functions.add(aggregateFunction5); + if (aggregateFunction6 != null) functions.add(aggregateFunction6); + if (aggregateFunction7 != null) functions.add(aggregateFunction7); + if (aggregateFunction8 != null) functions.add(aggregateFunction8); + if (aggregateFunction9 != null) functions.add(aggregateFunction9); + if (aggregateFunction10 != null) functions.add(aggregateFunction10); + if (aggregateFunction11 != null) functions.add(aggregateFunction11); + if (aggregateFunction12 != null) functions.add(aggregateFunction12); + if (aggregateFunction13 != null) functions.add(aggregateFunction13); + if (aggregateFunction14 != null) functions.add(aggregateFunction14); + if (aggregateFunction15 != null) functions.add(aggregateFunction15); + if (aggregateFunction16 != null) functions.add(aggregateFunction16); + if (aggregateFunction17 != null) functions.add(aggregateFunction17); + if (aggregateFunction18 != null) functions.add(aggregateFunction18); + if (aggregateFunction19 != null) functions.add(aggregateFunction19); + if (aggregateFunction20 != null) functions.add(aggregateFunction20); + if (aggregateFunction21 != null) functions.add(aggregateFunction21); + if (aggregateFunction22 != null) functions.add(aggregateFunction22); + if (aggregateFunction23 != null) functions.add(aggregateFunction23); + if (aggregateFunction24 != null) functions.add(aggregateFunction24); + if (aggregateFunction25 != null) functions.add(aggregateFunction25); + if (aggregateFunction26 != null) functions.add(aggregateFunction26); + if (aggregateFunction27 != null) functions.add(aggregateFunction27); + if (aggregateFunction28 != null) functions.add(aggregateFunction28); + if (aggregateFunction29 != null) functions.add(aggregateFunction29); + if (aggregateFunction30 != null) functions.add(aggregateFunction30); + + return Pipeline._(_firestore, [ + ..._stages, + _AggregateStage(functions), + ]); + } + + /// Gets distinct values based on expressions + Pipeline distinct( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _DistinctStage(expressions), + ]); + } + + /// Finds nearest vectors using vector similarity search + Pipeline findNearest( + Field vectorField, + List vectorValue, + DistanceMeasure distanceMeasure, { + int? limit, + }) { + return Pipeline._(_firestore, [ + ..._stages, + _FindNearestStage(vectorField, vectorValue, distanceMeasure, + limit: limit), + ]); + } + + /// Limits the number of results + Pipeline limit(int limit) { + return Pipeline._(_firestore, [ + ..._stages, + _LimitStage(limit), + ]); + } + + /// Offsets the results + Pipeline offset(int offset) { + return Pipeline._(_firestore, [ + ..._stages, + _OffsetStage(offset), + ]); + } + + /// Removes specified fields from documents + Pipeline removeFields( + String fieldPath1, [ + String? fieldPath2, + String? fieldPath3, + String? fieldPath4, + String? fieldPath5, + String? fieldPath6, + String? fieldPath7, + String? fieldPath8, + String? fieldPath9, + String? fieldPath10, + String? fieldPath11, + String? fieldPath12, + String? fieldPath13, + String? fieldPath14, + String? fieldPath15, + String? fieldPath16, + String? fieldPath17, + String? fieldPath18, + String? fieldPath19, + String? fieldPath20, + String? fieldPath21, + String? fieldPath22, + String? fieldPath23, + String? fieldPath24, + String? fieldPath25, + String? fieldPath26, + String? fieldPath27, + String? fieldPath28, + String? fieldPath29, + String? fieldPath30, + ]) { + final fieldPaths = [fieldPath1]; + if (fieldPath2 != null) fieldPaths.add(fieldPath2); + if (fieldPath3 != null) fieldPaths.add(fieldPath3); + if (fieldPath4 != null) fieldPaths.add(fieldPath4); + if (fieldPath5 != null) fieldPaths.add(fieldPath5); + if (fieldPath6 != null) fieldPaths.add(fieldPath6); + if (fieldPath7 != null) fieldPaths.add(fieldPath7); + if (fieldPath8 != null) fieldPaths.add(fieldPath8); + if (fieldPath9 != null) fieldPaths.add(fieldPath9); + if (fieldPath10 != null) fieldPaths.add(fieldPath10); + if (fieldPath11 != null) fieldPaths.add(fieldPath11); + if (fieldPath12 != null) fieldPaths.add(fieldPath12); + if (fieldPath13 != null) fieldPaths.add(fieldPath13); + if (fieldPath14 != null) fieldPaths.add(fieldPath14); + if (fieldPath15 != null) fieldPaths.add(fieldPath15); + if (fieldPath16 != null) fieldPaths.add(fieldPath16); + if (fieldPath17 != null) fieldPaths.add(fieldPath17); + if (fieldPath18 != null) fieldPaths.add(fieldPath18); + if (fieldPath19 != null) fieldPaths.add(fieldPath19); + if (fieldPath20 != null) fieldPaths.add(fieldPath20); + if (fieldPath21 != null) fieldPaths.add(fieldPath21); + if (fieldPath22 != null) fieldPaths.add(fieldPath22); + if (fieldPath23 != null) fieldPaths.add(fieldPath23); + if (fieldPath24 != null) fieldPaths.add(fieldPath24); + if (fieldPath25 != null) fieldPaths.add(fieldPath25); + if (fieldPath26 != null) fieldPaths.add(fieldPath26); + if (fieldPath27 != null) fieldPaths.add(fieldPath27); + if (fieldPath28 != null) fieldPaths.add(fieldPath28); + if (fieldPath29 != null) fieldPaths.add(fieldPath29); + if (fieldPath30 != null) fieldPaths.add(fieldPath30); + + return Pipeline._(_firestore, [ + ..._stages, + _RemoveFieldsStage(fieldPaths), + ]); + } + + /// Replaces documents with the result of an expression + Pipeline replaceWith(PipelineExpression expression) { + return Pipeline._(_firestore, [ + ..._stages, + _ReplaceWithStage(expression), + ]); + } + + /// Samples documents using a sampling strategy + Pipeline sample(PipelineSample sample) { + return Pipeline._(_firestore, [ + ..._stages, + _SampleStage(sample), + ]); + } + + /// Selects specific fields using expressions + Pipeline select( + PipelineExpression expression1, [ + PipelineExpression? expression2, + PipelineExpression? expression3, + PipelineExpression? expression4, + PipelineExpression? expression5, + PipelineExpression? expression6, + PipelineExpression? expression7, + PipelineExpression? expression8, + PipelineExpression? expression9, + PipelineExpression? expression10, + PipelineExpression? expression11, + PipelineExpression? expression12, + PipelineExpression? expression13, + PipelineExpression? expression14, + PipelineExpression? expression15, + PipelineExpression? expression16, + PipelineExpression? expression17, + PipelineExpression? expression18, + PipelineExpression? expression19, + PipelineExpression? expression20, + PipelineExpression? expression21, + PipelineExpression? expression22, + PipelineExpression? expression23, + PipelineExpression? expression24, + PipelineExpression? expression25, + PipelineExpression? expression26, + PipelineExpression? expression27, + PipelineExpression? expression28, + PipelineExpression? expression29, + PipelineExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return Pipeline._(_firestore, [ + ..._stages, + _SelectStage(expressions), + ]); + } + + /// Sorts results using an ordering specification + Pipeline sort(Ordering ordering) { + return Pipeline._(_firestore, [ + ..._stages, + _SortStage(ordering), + ]); + } + + /// Unnests arrays into separate documents + Pipeline unnest(PipelineExpression expression, [String? indexField]) { + return Pipeline._(_firestore, [ + ..._stages, + _UnnestStage(expression, indexField), + ]); + } + + /// Unions results with another pipeline + Pipeline union(Pipeline pipeline) { + return Pipeline._(_firestore, [ + ..._stages, + _UnionStage(pipeline), + ]); + } + + /// Filters documents using a boolean expression + Pipeline where(BooleanExpression expression) { + return Pipeline._(_firestore, [ + ..._stages, + _WhereStage(expression), + ]); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart new file mode 100644 index 000000000000..1dd2fff39d52 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -0,0 +1,58 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base class for aggregate functions used in pipelines +abstract class PipelineAggregateFunction implements PipelineSerializable { + String? _alias; + + /// Assigns an alias to this aggregate function + PipelineAggregateFunction as(String alias) { + _alias = alias; + return this; + } + + String? get alias => _alias; + + String get name; + + @override + Map toMap() { + final map = { + 'name': name, + }; + if (_alias != null) { + map['alias'] = _alias; + } + return map; + } +} + +/// Counts all documents in the pipeline result +class CountAll extends PipelineAggregateFunction { + CountAll(); + + @override + String get name => 'count_all'; +} + +/// Counts non-null values of the specified expression +class Count extends PipelineAggregateFunction { + final PipelineExpression expression; + + Count(this.expression); + + @override + String get name => 'count'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart new file mode 100644 index 000000000000..8f01b16de52e --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_distance.dart @@ -0,0 +1,17 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Distance measure algorithms for vector similarity search +enum DistanceMeasure { + /// Cosine similarity + cosine, + + /// Euclidean distance + euclidean, + + /// Dot product + dotProduct, +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart new file mode 100644 index 000000000000..415764350786 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -0,0 +1,394 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base interface for pipeline serialization +abstract class PipelineSerializable { + Map toMap(); +} + +/// Base class for all pipeline expressions +abstract class PipelineExpression implements PipelineSerializable { + String? _alias; + + /// Assigns an alias to this expression + PipelineExpression as(String alias) { + _alias = alias; + return this; + } + + /// Creates a descending ordering for this expression + Ordering descending() { + return Ordering(this, OrderDirection.desc); + } + + /// Creates an ascending ordering for this expression + Ordering ascending() { + return Ordering(this, OrderDirection.asc); + } + + String? get alias => _alias; + + String get name; + + @override + Map toMap() { + final map = { + 'name': name, + }; + if (_alias != null) { + map['alias'] = _alias; + } + return map; + } +} + +/// Base class for function expressions +abstract class FunctionExpression extends PipelineExpression {} + +/// Represents a field reference in a pipeline expression +class Field extends PipelineExpression { + final String fieldName; + + Field(this.fieldName); + + @override + String get name => 'field'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'field': fieldName, + }; + return map; + } +} + +/// Represents a constant literal value in a pipeline expression +class Constant extends PipelineExpression { + final dynamic literal; + + Constant(this.literal); + + @override + String get name => 'constant'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'literal': literal, + }; + return map; + } +} + +/// Represents a concatenation function expression +class Concat extends FunctionExpression { + final List expressions; + + Concat(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }; + return map; + } +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends PipelineExpression { + final PipelineExpression expression; + + AliasedExpression(this.expression); + + @override + String get name => 'alias'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Base class for boolean expressions used in filtering +abstract class BooleanExpression extends PipelineExpression {} + +/// Represents a filter expression for pipeline where clauses +class PipelineFilter extends BooleanExpression { + final Object field; + final Object? isEqualTo; + final Object? isNotEqualTo; + final Object? isLessThan; + final Object? isLessThanOrEqualTo; + final Object? isGreaterThan; + final Object? isGreaterThanOrEqualTo; + final Object? arrayContains; + final List? arrayContainsAny; + final List? whereIn; + final List? whereNotIn; + final bool? isNull; + final bool? isNotNull; + final BooleanExpression? _andExpression; + final BooleanExpression? _orExpression; + + PipelineFilter( + this.field, { + this.isEqualTo, + this.isNotEqualTo, + this.isLessThan, + this.isLessThanOrEqualTo, + this.isGreaterThan, + this.isGreaterThanOrEqualTo, + this.arrayContains, + this.arrayContainsAny, + this.whereIn, + this.whereNotIn, + this.isNull, + this.isNotNull, + }) : _andExpression = null, + _orExpression = null; + + PipelineFilter._internal({ + required BooleanExpression? andExpression, + required BooleanExpression? orExpression, + }) : field = '', + isEqualTo = null, + isNotEqualTo = null, + isLessThan = null, + isLessThanOrEqualTo = null, + isGreaterThan = null, + isGreaterThanOrEqualTo = null, + arrayContains = null, + arrayContainsAny = null, + whereIn = null, + whereNotIn = null, + isNull = null, + isNotNull = null, + _andExpression = andExpression, + _orExpression = orExpression; + + /// Creates an OR filter combining multiple boolean expressions + static PipelineFilter or( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + orExpression: _combineExpressions(expressions, 'or'), + andExpression: null, + ); + } + + /// Creates an AND filter combining multiple boolean expressions + static PipelineFilter and( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + andExpression: _combineExpressions(expressions, 'and'), + orExpression: null, + ); + } + + static BooleanExpression _combineExpressions( + List expressions, + String operator, + ) { + if (expressions.length == 1) return expressions.first; + + // Create a nested structure for multiple expressions + BooleanExpression result = expressions.first; + for (int i = 1; i < expressions.length; i++) { + if (operator == 'and') { + result = PipelineFilter.and(result, expressions[i]); + } else { + result = PipelineFilter.or(result, expressions[i]); + } + } + return result; + } + + @override + String get name => 'filter'; + + @override + Map toMap() { + final map = super.toMap(); + + if (_andExpression != null) { + map['args'] = { + 'operator': 'and', + 'expressions': [_andExpression.toMap()], + }; + return map; + } + + if (_orExpression != null) { + map['args'] = { + 'operator': 'or', + 'expressions': [_orExpression.toMap()], + }; + return map; + } + + final args = {}; + if (field is String) { + args['field'] = field; + } else if (field is Field) { + args['field'] = (field as Field).fieldName; + } + + if (isEqualTo != null) args['isEqualTo'] = isEqualTo; + if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; + if (isLessThan != null) args['isLessThan'] = isLessThan; + if (isLessThanOrEqualTo != null) { + args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; + } + if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; + if (isGreaterThanOrEqualTo != null) { + args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; + } + if (arrayContains != null) args['arrayContains'] = arrayContains; + if (arrayContainsAny != null) { + args['arrayContainsAny'] = arrayContainsAny; + } + if (whereIn != null) args['whereIn'] = whereIn; + if (whereNotIn != null) args['whereNotIn'] = whereNotIn; + if (isNull != null) args['isNull'] = isNull; + if (isNotNull != null) args['isNotNull'] = isNotNull; + + map['args'] = args; + return map; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart new file mode 100644 index 000000000000..b9832c32a105 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart @@ -0,0 +1,30 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Direction for ordering results +enum OrderDirection { + /// Ascending order + asc, + + /// Descending order + desc, +} + +/// Represents an ordering specification for pipeline sorting +class Ordering implements PipelineSerializable { + final PipelineExpression expression; + final OrderDirection direction; + + Ordering(this.expression, this.direction); + + @override + Map toMap() { + return { + 'expression': expression.toMap(), + 'order_direction': direction == OrderDirection.asc ? 'asc' : 'desc', + }; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart new file mode 100644 index 000000000000..4136c9922872 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_sample.dart @@ -0,0 +1,43 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base class for pipeline sampling strategies +abstract class PipelineSample implements PipelineSerializable { + const PipelineSample(); + + /// Creates a sample with a fixed size + factory PipelineSample.withSize(int size) = _PipelineSampleSize; + + /// Creates a sample with a percentage + factory PipelineSample.withPercentage(double percentage) = + _PipelineSamplePercentage; +} + +/// Sample stage with a fixed size +class _PipelineSampleSize extends PipelineSample { + final int size; + + const _PipelineSampleSize(this.size); + + @override + Map toMap() => { + 'type': 'size', + 'value': size, + }; +} + +/// Sample stage with a percentage +class _PipelineSamplePercentage extends PipelineSample { + final double percentage; + + const _PipelineSamplePercentage(this.percentage); + + @override + Map toMap() => { + 'type': 'percentage', + 'value': percentage, + }; +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart new file mode 100644 index 000000000000..12e4f1ba6cb7 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart @@ -0,0 +1,66 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Provides methods to create pipelines from different sources +class PipelineSource { + final FirebaseFirestore _firestore; + + PipelineSource._(this._firestore); + + /// Creates a pipeline from a collection path + Pipeline collection(String collectionPath) { + if (collectionPath.isEmpty) { + throw ArgumentError('A collection path must be a non-empty string.'); + } else if (collectionPath.contains('//')) { + throw ArgumentError('A collection path must not contain "//".'); + } + + return Pipeline._(_firestore, [ + _CollectionPipelineStage(collectionPath), + ]); + } + + /// Creates a pipeline from a collection reference + Pipeline collectionReference( + CollectionReference> collectionReference) { + return Pipeline._(_firestore, [ + _CollectionPipelineStage(collectionReference.path), + ]); + } + + /// Creates a pipeline from a collection group + Pipeline collectionGroup(String collectionId) { + if (collectionId.isEmpty) { + throw ArgumentError('A collection ID must be a non-empty string.'); + } else if (collectionId.contains('/')) { + throw ArgumentError( + 'A collection ID passed to collectionGroup() cannot contain "/".', + ); + } + + return Pipeline._(_firestore, [ + _CollectionGroupPipelineStage(collectionId), + ]); + } + + /// Creates a pipeline from a list of document references + Pipeline documents(List>> documents) { + if (documents.isEmpty) { + throw ArgumentError('Documents list must not be empty.'); + } + + return Pipeline._(_firestore, [ + _DocumentsPipelineStage(documents), + ]); + } + + /// Creates a pipeline from the entire database + Pipeline database() { + return Pipeline._(_firestore, [ + _DatabasePipelineStage(), + ]); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart new file mode 100644 index 000000000000..9207533abbe8 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -0,0 +1,387 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Base sealed class for all pipeline stages +sealed class PipelineStage implements PipelineSerializable { + String get name; +} + +/// Stage representing a collection source +final class _CollectionPipelineStage extends PipelineStage { + final String collectionPath; + + _CollectionPipelineStage(this.collectionPath); + + @override + String get name => 'collection'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'path': collectionPath, + }, + }; + } +} + +/// Stage representing a documents source +final class _DocumentsPipelineStage extends PipelineStage { + final List>> documents; + + _DocumentsPipelineStage(this.documents); + + @override + String get name => 'documents'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': documents + .map((doc) => { + 'path': doc.path, + }) + .toList(), + }; + } +} + +/// Stage representing a database source +final class _DatabasePipelineStage extends PipelineStage { + _DatabasePipelineStage(); + + @override + String get name => 'database'; + + @override + Map toMap() { + return { + 'stage': name, + }; + } +} + +/// Stage representing a collection group source +final class _CollectionGroupPipelineStage extends PipelineStage { + final String collectionPath; + + _CollectionGroupPipelineStage(this.collectionPath); + + @override + String get name => 'collection_group'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'path': collectionPath, + }, + }; + } +} + +/// Stage for adding fields to documents +final class _AddFieldsStage extends PipelineStage { + final List expressions; + + _AddFieldsStage(this.expressions); + + @override + String get name => 'add_fields'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for aggregating data +final class _AggregateStage extends PipelineStage { + final List aggregateFunctions; + + _AggregateStage(this.aggregateFunctions); + + @override + String get name => 'aggregate'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'aggregate_functions': + aggregateFunctions.map((func) => func.toMap()).toList(), + }, + }; + } +} + +/// Stage for getting distinct values +final class _DistinctStage extends PipelineStage { + final List expressions; + + _DistinctStage(this.expressions); + + @override + String get name => 'distinct'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for finding nearest vectors +final class _FindNearestStage extends PipelineStage { + final Field vectorField; + final List vectorValue; + final DistanceMeasure distanceMeasure; + final int? limit; + + _FindNearestStage( + this.vectorField, + this.vectorValue, + this.distanceMeasure, { + this.limit, + }); + + @override + String get name => 'find_nearest'; + + @override + Map toMap() { + final map = { + 'stage': name, + 'args': { + 'vector_field': vectorField.fieldName, + 'vector_value': vectorValue, + 'distance_measure': distanceMeasure.name, + }, + }; + if (limit != null) { + map['args']['limit'] = limit; + } + return map; + } +} + +/// Stage for limiting results +final class _LimitStage extends PipelineStage { + final int limit; + + _LimitStage(this.limit); + + @override + String get name => 'limit'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'limit': limit, + }, + }; + } +} + +/// Stage for offsetting results +final class _OffsetStage extends PipelineStage { + final int offset; + + _OffsetStage(this.offset); + + @override + String get name => 'offset'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'offset': offset, + }, + }; + } +} + +/// Stage for removing fields +final class _RemoveFieldsStage extends PipelineStage { + final List fieldPaths; + + _RemoveFieldsStage(this.fieldPaths); + + @override + String get name => 'remove_fields'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'field_paths': fieldPaths, + }, + }; + } +} + +/// Stage for replacing documents +final class _ReplaceWithStage extends PipelineStage { + final PipelineExpression expression; + + _ReplaceWithStage(this.expression); + + @override + String get name => 'replace_with'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Stage for sampling documents +final class _SampleStage extends PipelineStage { + final PipelineSample sample; + + _SampleStage(this.sample); + + @override + String get name => 'sample'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': sample.toMap(), + }; + } +} + +/// Stage for selecting specific fields +final class _SelectStage extends PipelineStage { + final List expressions; + + _SelectStage(this.expressions); + + @override + String get name => 'select'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Stage for sorting results +final class _SortStage extends PipelineStage { + final Ordering ordering; + + _SortStage(this.ordering); + + @override + String get name => 'sort'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': ordering.expression.toMap(), + 'order_direction': + ordering.direction == OrderDirection.asc ? 'asc' : 'desc', + }, + }; + } +} + +/// Stage for unnesting arrays +final class _UnnestStage extends PipelineStage { + final PipelineExpression expression; + final String? indexField; + + _UnnestStage(this.expression, this.indexField); + + @override + String get name => 'unnest'; + + @override + Map toMap() { + final map = { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + if (indexField != null) { + map['args']['index_field'] = indexField; + } + return map; + } +} + +/// Stage for union with another pipeline +final class _UnionStage extends PipelineStage { + final Pipeline pipeline; + + _UnionStage(this.pipeline); + + @override + String get name => 'union'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'pipeline': pipeline.getSerializableStages(), + }, + }; + } +} + +/// Stage for filtering documents +final class _WhereStage extends PipelineStage { + final BooleanExpression expression; + + _WhereStage(this.expression); + + @override + String get name => 'where'; + + @override + Map toMap() { + return { + 'stage': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 9f9b8e7866e0..58a894053a76 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -29,6 +29,7 @@ export 'src/platform_interface/platform_interface_index_definitions.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; +export 'src/platform_interface/platform_interface_pipeline_snapshot.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; export 'src/platform_interface/platform_interface_transaction.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart index 3faffc0a3778..1e76c5825670 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart @@ -243,6 +243,14 @@ abstract class FirebaseFirestorePlatform extends PlatformInterface { throw UnimplementedError('setLoggingEnabled() is not implemented'); } + /// Executes a pipeline and returns the results. + /// + /// The [stages] parameter contains the serialized pipeline stages. + Future executePipeline( + List> stages) { + throw UnimplementedError('executePipeline() is not implemented'); + } + @override //ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) => diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart new file mode 100644 index 000000000000..8b8d3b6fda30 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart @@ -0,0 +1,40 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../../cloud_firestore_platform_interface.dart'; +import 'platform_interface_document_reference.dart'; + +/// Platform interface for [PipelineSnapshot]. +abstract class PipelineSnapshotPlatform extends PlatformInterface { + /// Create an instance of [PipelineSnapshotPlatform]. + PipelineSnapshotPlatform() : super(token: _token); + + static final Object _token = Object(); + + /// The results of the pipeline execution + List get results; + + /// The execution time of the pipeline + DateTime get executionTime; +} + +/// Platform interface for [PipelineResult]. +abstract class PipelineResultPlatform extends PlatformInterface { + /// Create an instance of [PipelineResultPlatform]. + PipelineResultPlatform() : super(token: _token); + + static final Object _token = Object(); + + /// The document reference + DocumentReferencePlatform get document; + + /// The creation time of the document + DateTime get createTime; + + /// The update time of the document + DateTime get updateTime; +} From 1c69d01467363a9db930420130ad891beab58fcd Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 11 Feb 2026 15:06:06 +0000 Subject: [PATCH 02/20] feat(firestore): implement pipeline execution and stages with options --- .../cloud_firestore/lib/cloud_firestore.dart | 1 + .../cloud_firestore/lib/src/pipeline.dart | 356 +++++++++--------- .../lib/src/pipeline_aggregate.dart | 2 +- .../lib/src/pipeline_execute_options.dart | 20 + .../lib/src/pipeline_expression.dart | 121 +++--- .../lib/src/pipeline_ordering.dart | 4 +- .../lib/src/pipeline_source.dart | 30 +- .../lib/src/pipeline_stage.dart | 12 +- .../cloud_firestore_platform_interface.dart | 1 + .../method_channel_firestore.dart | 31 ++ .../method_channel_pipeline.dart | 51 +++ .../method_channel_pipeline_snapshot.dart | 69 ++++ .../platform_interface_firestore.dart | 9 +- .../platform_interface_pipeline.dart | 54 +++ 14 files changed, 516 insertions(+), 245 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index c84108ac3686..b0febde386b2 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -58,6 +58,7 @@ part 'src/persistent_cache_index_manager.dart'; part 'src/pipeline.dart'; part 'src/pipeline_aggregate.dart'; part 'src/pipeline_distance.dart'; +part 'src/pipeline_execute_options.dart'; part 'src/pipeline_expression.dart'; part 'src/pipeline_ordering.dart'; part 'src/pipeline_sample.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index e7849f43afd7..956685e3e8a7 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -6,15 +6,31 @@ part of '../cloud_firestore.dart'; /// A pipeline for querying and transforming Firestore data class Pipeline { - final List _stages; final FirebaseFirestore _firestore; + final PipelinePlatform _delegate; - Pipeline._(this._firestore, this._stages); + Pipeline._(this._firestore, this._delegate) { + PipelinePlatform.verify(_delegate); + } + + /// Exposes the [stages] on the pipeline delegate. + /// + /// This should only be used for testing to ensure that all + /// pipeline stages are correctly set on the underlying delegate + /// when being tested from a different package. + @visibleForTesting + List> get stages { + return _delegate.stages; + } /// Executes the pipeline and returns a snapshot of the results - Future execute() async { - final platformSnapshot = - await _firestore._delegate.executePipeline(_toSerializableStages()); + Future execute({ExecuteOptions? options}) async { + final optionsMap = options != null + ? { + 'indexMode': options.indexMode.name, + } + : null; + final platformSnapshot = await _delegate.execute(options: optionsMap); return _convertPlatformSnapshot(platformSnapshot); } @@ -35,52 +51,42 @@ class Pipeline { return PipelineSnapshot._(results, platformSnapshot.executionTime); } - /// Converts stages to serializable format for platform communication - List> _toSerializableStages() { - return _stages.map((stage) => stage.toMap()).toList(); - } - - /// Public method to get serializable stages (used by union stage) - List> getSerializableStages() { - return _toSerializableStages(); - } - // Pipeline Actions /// Adds fields to documents using expressions Pipeline addFields( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Expression expression1, [ + Expression? expression2, + Expression? expression3, + Expression? expression4, + Expression? expression5, + Expression? expression6, + Expression? expression7, + Expression? expression8, + Expression? expression9, + Expression? expression10, + Expression? expression11, + Expression? expression12, + Expression? expression13, + Expression? expression14, + Expression? expression15, + Expression? expression16, + Expression? expression17, + Expression? expression18, + Expression? expression19, + Expression? expression20, + Expression? expression21, + Expression? expression22, + Expression? expression23, + Expression? expression24, + Expression? expression25, + Expression? expression26, + Expression? expression27, + Expression? expression28, + Expression? expression29, + Expression? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -111,10 +117,11 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _AddFieldsStage(expressions), - ]); + final stage = _AddFieldsStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Aggregates data using aggregate functions @@ -181,46 +188,47 @@ class Pipeline { if (aggregateFunction29 != null) functions.add(aggregateFunction29); if (aggregateFunction30 != null) functions.add(aggregateFunction30); - return Pipeline._(_firestore, [ - ..._stages, - _AggregateStage(functions), - ]); + final stage = _AggregateStage(functions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Gets distinct values based on expressions Pipeline distinct( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Expression expression1, [ + Expression? expression2, + Expression? expression3, + Expression? expression4, + Expression? expression5, + Expression? expression6, + Expression? expression7, + Expression? expression8, + Expression? expression9, + Expression? expression10, + Expression? expression11, + Expression? expression12, + Expression? expression13, + Expression? expression14, + Expression? expression15, + Expression? expression16, + Expression? expression17, + Expression? expression18, + Expression? expression19, + Expression? expression20, + Expression? expression21, + Expression? expression22, + Expression? expression23, + Expression? expression24, + Expression? expression25, + Expression? expression26, + Expression? expression27, + Expression? expression28, + Expression? expression29, + Expression? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -251,10 +259,11 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _DistinctStage(expressions), - ]); + final stage = _DistinctStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Finds nearest vectors using vector similarity search @@ -264,27 +273,30 @@ class Pipeline { DistanceMeasure distanceMeasure, { int? limit, }) { - return Pipeline._(_firestore, [ - ..._stages, - _FindNearestStage(vectorField, vectorValue, distanceMeasure, - limit: limit), - ]); + final stage = _FindNearestStage(vectorField, vectorValue, distanceMeasure, + limit: limit); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Limits the number of results Pipeline limit(int limit) { - return Pipeline._(_firestore, [ - ..._stages, - _LimitStage(limit), - ]); + final stage = _LimitStage(limit); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Offsets the results Pipeline offset(int offset) { - return Pipeline._(_firestore, [ - ..._stages, - _OffsetStage(offset), - ]); + final stage = _OffsetStage(offset); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Removes specified fields from documents @@ -351,62 +363,65 @@ class Pipeline { if (fieldPath29 != null) fieldPaths.add(fieldPath29); if (fieldPath30 != null) fieldPaths.add(fieldPath30); - return Pipeline._(_firestore, [ - ..._stages, - _RemoveFieldsStage(fieldPaths), - ]); + final stage = _RemoveFieldsStage(fieldPaths); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Replaces documents with the result of an expression - Pipeline replaceWith(PipelineExpression expression) { - return Pipeline._(_firestore, [ - ..._stages, - _ReplaceWithStage(expression), - ]); + Pipeline replaceWith(Expression expression) { + final stage = _ReplaceWithStage(expression); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Samples documents using a sampling strategy Pipeline sample(PipelineSample sample) { - return Pipeline._(_firestore, [ - ..._stages, - _SampleStage(sample), - ]); + final stage = _SampleStage(sample); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } - /// Selects specific fields using expressions + /// Selects specific fields using selectable expressions Pipeline select( - PipelineExpression expression1, [ - PipelineExpression? expression2, - PipelineExpression? expression3, - PipelineExpression? expression4, - PipelineExpression? expression5, - PipelineExpression? expression6, - PipelineExpression? expression7, - PipelineExpression? expression8, - PipelineExpression? expression9, - PipelineExpression? expression10, - PipelineExpression? expression11, - PipelineExpression? expression12, - PipelineExpression? expression13, - PipelineExpression? expression14, - PipelineExpression? expression15, - PipelineExpression? expression16, - PipelineExpression? expression17, - PipelineExpression? expression18, - PipelineExpression? expression19, - PipelineExpression? expression20, - PipelineExpression? expression21, - PipelineExpression? expression22, - PipelineExpression? expression23, - PipelineExpression? expression24, - PipelineExpression? expression25, - PipelineExpression? expression26, - PipelineExpression? expression27, - PipelineExpression? expression28, - PipelineExpression? expression29, - PipelineExpression? expression30, + Selectable expression1, [ + Selectable? expression2, + Selectable? expression3, + Selectable? expression4, + Selectable? expression5, + Selectable? expression6, + Selectable? expression7, + Selectable? expression8, + Selectable? expression9, + Selectable? expression10, + Selectable? expression11, + Selectable? expression12, + Selectable? expression13, + Selectable? expression14, + Selectable? expression15, + Selectable? expression16, + Selectable? expression17, + Selectable? expression18, + Selectable? expression19, + Selectable? expression20, + Selectable? expression21, + Selectable? expression22, + Selectable? expression23, + Selectable? expression24, + Selectable? expression25, + Selectable? expression26, + Selectable? expression27, + Selectable? expression28, + Selectable? expression29, + Selectable? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -437,41 +452,46 @@ class Pipeline { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return Pipeline._(_firestore, [ - ..._stages, - _SelectStage(expressions), - ]); + final stage = _SelectStage(expressions); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Sorts results using an ordering specification Pipeline sort(Ordering ordering) { - return Pipeline._(_firestore, [ - ..._stages, - _SortStage(ordering), - ]); + final stage = _SortStage(ordering); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Unnests arrays into separate documents - Pipeline unnest(PipelineExpression expression, [String? indexField]) { - return Pipeline._(_firestore, [ - ..._stages, - _UnnestStage(expression, indexField), - ]); + Pipeline unnest(Expression expression, [String? indexField]) { + final stage = _UnnestStage(expression, indexField); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Unions results with another pipeline Pipeline union(Pipeline pipeline) { - return Pipeline._(_firestore, [ - ..._stages, - _UnionStage(pipeline), - ]); + final stage = _UnionStage(pipeline); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } /// Filters documents using a boolean expression Pipeline where(BooleanExpression expression) { - return Pipeline._(_firestore, [ - ..._stages, - _WhereStage(expression), - ]); + final stage = _WhereStage(expression); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index 1dd2fff39d52..f0946ac18995 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -40,7 +40,7 @@ class CountAll extends PipelineAggregateFunction { /// Counts non-null values of the specified expression class Count extends PipelineAggregateFunction { - final PipelineExpression expression; + final Expression expression; Count(this.expression); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart new file mode 100644 index 000000000000..ee8b8e2eb42d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_execute_options.dart @@ -0,0 +1,20 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of '../cloud_firestore.dart'; + +/// Index mode for pipeline execution +enum IndexMode { + /// Use recommended index mode + recommended, +} + +/// Options for executing a pipeline +class ExecuteOptions { + final IndexMode indexMode; + + const ExecuteOptions({ + this.indexMode = IndexMode.recommended, + }); +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 415764350786..6323f23941fe 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -1,4 +1,4 @@ -// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -10,13 +10,13 @@ abstract class PipelineSerializable { } /// Base class for all pipeline expressions -abstract class PipelineExpression implements PipelineSerializable { - String? _alias; - - /// Assigns an alias to this expression - PipelineExpression as(String alias) { - _alias = alias; - return this; +abstract class Expression implements PipelineSerializable { + /// Creates an aliased expression + AliasedExpression as(String alias) { + return AliasedExpression( + alias: alias, + expression: this, + ); } /// Creates a descending ordering for this expression @@ -29,27 +29,54 @@ abstract class PipelineExpression implements PipelineSerializable { return Ordering(this, OrderDirection.asc); } - String? get alias => _alias; - String get name; @override Map toMap() { - final map = { + return { 'name': name, }; - if (_alias != null) { - map['alias'] = _alias; - } - return map; } } /// Base class for function expressions -abstract class FunctionExpression extends PipelineExpression {} +abstract class FunctionExpression extends Expression {} + +/// Base class for selectable expressions (can be used in select stage) +abstract class Selectable extends Expression { + String get alias; + Expression get expression; +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends Selectable { + @override + final String alias; + + @override + final Expression expression; + + AliasedExpression({ + required this.alias, + required this.expression, + }); + + @override + String get name => 'alias'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} /// Represents a field reference in a pipeline expression -class Field extends PipelineExpression { +class Field extends Selectable { final String fieldName; Field(this.fieldName); @@ -57,18 +84,25 @@ class Field extends PipelineExpression { @override String get name => 'field'; + @override + String get alias => fieldName; + + @override + Expression get expression => this; + @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'field': fieldName, + return { + 'name': name, + 'args': { + 'field': fieldName, + }, }; - return map; } } /// Represents a constant literal value in a pipeline expression -class Constant extends PipelineExpression { +class Constant extends Expression { final dynamic literal; Constant(this.literal); @@ -78,17 +112,18 @@ class Constant extends PipelineExpression { @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'literal': literal, + return { + 'name': name, + 'args': { + 'literal': literal, + }, }; - return map; } } /// Represents a concatenation function expression class Concat extends FunctionExpression { - final List expressions; + final List expressions; Concat(this.expressions); @@ -97,35 +132,17 @@ class Concat extends FunctionExpression { @override Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expressions': expressions.map((expr) => expr.toMap()).toList(), - }; - return map; - } -} - -/// Represents an aliased expression wrapper -class AliasedExpression extends PipelineExpression { - final PipelineExpression expression; - - AliasedExpression(this.expression); - - @override - String get name => 'alias'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, }; - return map; } } /// Base class for boolean expressions used in filtering -abstract class BooleanExpression extends PipelineExpression {} +abstract class BooleanExpression extends Expression {} /// Represents a filter expression for pipeline where clauses class PipelineFilter extends BooleanExpression { @@ -246,8 +263,8 @@ class PipelineFilter extends BooleanExpression { if (expression30 != null) expressions.add(expression30); return PipelineFilter._internal( - orExpression: _combineExpressions(expressions, 'or'), andExpression: null, + orExpression: _combineExpressions(expressions, 'or'), ); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart index b9832c32a105..b55687fa5d8b 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_ordering.dart @@ -1,4 +1,4 @@ -// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -15,7 +15,7 @@ enum OrderDirection { /// Represents an ordering specification for pipeline sorting class Ordering implements PipelineSerializable { - final PipelineExpression expression; + final Expression expression; final OrderDirection direction; Ordering(this.expression, this.direction); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart index 12e4f1ba6cb7..c981d53cae0d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_source.dart @@ -18,17 +18,17 @@ class PipelineSource { throw ArgumentError('A collection path must not contain "//".'); } - return Pipeline._(_firestore, [ - _CollectionPipelineStage(collectionPath), - ]); + final stage = _CollectionPipelineStage(collectionPath); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a collection reference Pipeline collectionReference( CollectionReference> collectionReference) { - return Pipeline._(_firestore, [ - _CollectionPipelineStage(collectionReference.path), - ]); + final stage = _CollectionPipelineStage(collectionReference.path); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a collection group @@ -41,9 +41,9 @@ class PipelineSource { ); } - return Pipeline._(_firestore, [ - _CollectionGroupPipelineStage(collectionId), - ]); + final stage = _CollectionGroupPipelineStage(collectionId); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from a list of document references @@ -52,15 +52,15 @@ class PipelineSource { throw ArgumentError('Documents list must not be empty.'); } - return Pipeline._(_firestore, [ - _DocumentsPipelineStage(documents), - ]); + final stage = _DocumentsPipelineStage(documents); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } /// Creates a pipeline from the entire database Pipeline database() { - return Pipeline._(_firestore, [ - _DatabasePipelineStage(), - ]); + final stage = _DatabasePipelineStage(); + final delegate = _firestore._delegate.pipeline([stage.toMap()]); + return Pipeline._(_firestore, delegate); } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 9207533abbe8..7532ddb8e544 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -88,7 +88,7 @@ final class _CollectionGroupPipelineStage extends PipelineStage { /// Stage for adding fields to documents final class _AddFieldsStage extends PipelineStage { - final List expressions; + final List expressions; _AddFieldsStage(this.expressions); @@ -129,7 +129,7 @@ final class _AggregateStage extends PipelineStage { /// Stage for getting distinct values final class _DistinctStage extends PipelineStage { - final List expressions; + final List expressions; _DistinctStage(this.expressions); @@ -243,7 +243,7 @@ final class _RemoveFieldsStage extends PipelineStage { /// Stage for replacing documents final class _ReplaceWithStage extends PipelineStage { - final PipelineExpression expression; + final Expression expression; _ReplaceWithStage(this.expression); @@ -281,7 +281,7 @@ final class _SampleStage extends PipelineStage { /// Stage for selecting specific fields final class _SelectStage extends PipelineStage { - final List expressions; + final List expressions; _SelectStage(this.expressions); @@ -323,7 +323,7 @@ final class _SortStage extends PipelineStage { /// Stage for unnesting arrays final class _UnnestStage extends PipelineStage { - final PipelineExpression expression; + final Expression expression; final String? indexField; _UnnestStage(this.expression, this.indexField); @@ -360,7 +360,7 @@ final class _UnionStage extends PipelineStage { return { 'stage': name, 'args': { - 'pipeline': pipeline.getSerializableStages(), + 'pipeline': pipeline.stages, }, }; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 58a894053a76..2e7e97bec179 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -29,6 +29,7 @@ export 'src/platform_interface/platform_interface_index_definitions.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; +export 'src/platform_interface/platform_interface_pipeline.dart'; export 'src/platform_interface/platform_interface_pipeline_snapshot.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart index 573e5a3c91b9..73aacc45f2bb 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart @@ -15,6 +15,8 @@ import 'package:flutter/services.dart'; import 'method_channel_collection_reference.dart'; import 'method_channel_document_reference.dart'; +import 'method_channel_pipeline.dart'; +import 'method_channel_pipeline_snapshot.dart'; import 'method_channel_query.dart'; import 'method_channel_transaction.dart'; import 'method_channel_write_batch.dart'; @@ -350,4 +352,33 @@ class MethodChannelFirebaseFirestore extends FirebaseFirestorePlatform { convertPlatformException(e, stack); } } + + @override + PipelinePlatform pipeline(List> initialStages) { + return MethodChannelPipeline(this, pigeonApp, stages: initialStages); + } + + @override + Future executePipeline( + List> stages, { + Map? options, + }) async { + try { + final MethodChannel channel = const MethodChannel( + 'plugins.flutter.io/firebase_firestore', + ); + final result = await channel.invokeMethod>( + 'Pipeline#execute', + { + 'app': pigeonApp, + 'stages': stages, + if (options != null) 'options': options, + }, + ); + + return MethodChannelPipelineSnapshot(this, pigeonApp, result!); + } on PlatformException catch (e, stack) { + throw convertPlatformException(e, stack); + } + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart new file mode 100644 index 000000000000..7e0b65e43c7d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline.dart @@ -0,0 +1,51 @@ +// ignore_for_file: require_trailing_commas, unnecessary_lambdas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_pipeline.dart' + as pipeline; + +/// An implementation of [PipelinePlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipeline extends pipeline.PipelinePlatform { + /// Create a [MethodChannelPipeline] from [stages] + MethodChannelPipeline( + FirebaseFirestorePlatform _firestore, + this.pigeonApp, { + List>? stages, + }) : super(_firestore, stages); + + final FirestorePigeonFirebaseApp pigeonApp; + + /// Creates a new instance of [MethodChannelPipeline], however overrides + /// any existing [stages]. + /// + /// This is in place to ensure that changes to a pipeline don't mutate + /// other pipelines. + MethodChannelPipeline _copyWithStages(List> newStages) { + return MethodChannelPipeline( + firestore, + pigeonApp, + stages: List.unmodifiable([ + ...stages, + ...newStages, + ]), + ); + } + + @override + pipeline.PipelinePlatform addStage(Map serializedStage) { + return _copyWithStages([serializedStage]); + } + + @override + Future execute({ + Map? options, + }) async { + return firestore.executePipeline(stages, options: options); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart new file mode 100644 index 000000000000..16cde78be465 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -0,0 +1,69 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_document_reference.dart'; + +/// An implementation of [PipelineSnapshotPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { + final List _results; + final DateTime _executionTime; + + /// Creates a [MethodChannelPipelineSnapshot] from the given [data] + MethodChannelPipelineSnapshot( + FirebaseFirestorePlatform firestore, + FirestorePigeonFirebaseApp pigeonApp, + Map data, + ) : _results = (data['results'] as List) + .map((result) => MethodChannelPipelineResult( + firestore, + pigeonApp, + result['document'] as String, + DateTime.fromMillisecondsSinceEpoch(result['createTime']), + DateTime.fromMillisecondsSinceEpoch(result['updateTime']), + )) + .toList(), + _executionTime = DateTime.fromMillisecondsSinceEpoch( + data['executionTime'] as int, + ), + super(); + + @override + List get results => _results; + + @override + DateTime get executionTime => _executionTime; +} + +/// An implementation of [PipelineResultPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelPipelineResult extends PipelineResultPlatform { + final DocumentReferencePlatform _document; + final DateTime _createTime; + final DateTime _updateTime; + + MethodChannelPipelineResult( + FirebaseFirestorePlatform firestore, + FirestorePigeonFirebaseApp pigeonApp, + String documentPath, + this._createTime, + this._updateTime, + ) : _document = MethodChannelDocumentReference( + firestore, + documentPath, + pigeonApp, + ), + super(); + + @override + DocumentReferencePlatform get document => _document; + + @override + DateTime get createTime => _createTime; + + @override + DateTime get updateTime => _updateTime; +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart index 1e76c5825670..ee9cd3a32478 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart @@ -243,11 +243,18 @@ abstract class FirebaseFirestorePlatform extends PlatformInterface { throw UnimplementedError('setLoggingEnabled() is not implemented'); } + /// Creates a pipeline platform instance with initial stages. + PipelinePlatform pipeline(List> initialStages) { + throw UnimplementedError('pipeline() is not implemented'); + } + /// Executes a pipeline and returns the results. /// /// The [stages] parameter contains the serialized pipeline stages. Future executePipeline( - List> stages) { + List> stages, { + Map? options, + }) { throw UnimplementedError('executePipeline() is not implemented'); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart new file mode 100644 index 000000000000..2d0449ee4aef --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline.dart @@ -0,0 +1,54 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:meta/meta.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// Represents a pipeline for querying and transforming Firestore data. +@immutable +abstract class PipelinePlatform extends PlatformInterface { + /// Create a [PipelinePlatform] instance + PipelinePlatform(this.firestore, List>? stages) + : _stages = stages ?? [], + super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [PipelinePlatform]. + /// + /// This is used by the app-facing [Pipeline] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verify(PipelinePlatform instance) { + PlatformInterface.verify(instance, _token); + } + + /// The [FirebaseFirestorePlatform] interface for this current pipeline. + final FirebaseFirestorePlatform firestore; + + /// Stores the pipeline stages. + final List> _stages; + + /// Exposes the [stages] on the pipeline delegate. + /// + /// This should only be used for testing to ensure that all + /// pipeline stages are correctly set on the underlying delegate + /// when being tested from a different package. + List> get stages { + return List.unmodifiable(_stages); + } + + /// Adds a serialized stage to the pipeline + PipelinePlatform addStage(Map serializedStage); + + /// Executes the pipeline and returns a snapshot of the results + Future execute({ + Map? options, + }); +} From 1094fd0e90c54be73bca226a76ca90296ee41e09 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Thu, 12 Feb 2026 10:09:53 +0000 Subject: [PATCH 03/20] refactor: change PipelineSerializable to mixin and update Constant class properties --- .../cloud_firestore/lib/src/pipeline_expression.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 6323f23941fe..07d575168af5 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -5,7 +5,7 @@ part of '../cloud_firestore.dart'; /// Base interface for pipeline serialization -abstract class PipelineSerializable { +mixin PipelineSerializable { Map toMap(); } @@ -101,11 +101,11 @@ class Field extends Selectable { } } -/// Represents a constant literal value in a pipeline expression +/// Represents a constant value in a pipeline expression class Constant extends Expression { - final dynamic literal; + final Object value; - Constant(this.literal); + Constant(this.value); @override String get name => 'constant'; @@ -115,7 +115,7 @@ class Constant extends Expression { return { 'name': name, 'args': { - 'literal': literal, + 'value': value, }, }; } From 89eb1984ac1a6dad13e8e6adfcf6d863806a14aa Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Thu, 12 Feb 2026 13:08:16 +0000 Subject: [PATCH 04/20] chore: add `Expression` functions --- .../lib/src/pipeline_expression.dart | 3118 +++++++++++++++-- 1 file changed, 2820 insertions(+), 298 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 07d575168af5..2c295e0f2e6f 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -9,6 +9,12 @@ mixin PipelineSerializable { Map toMap(); } +/// Helper function to convert values to Expression (wraps in Constant if needed) +Expression _toExpression(Object? value) { + if (value is Expression) return value; + return Constant(value!); +} + /// Base class for all pipeline expressions abstract class Expression implements PipelineSerializable { /// Creates an aliased expression @@ -29,280 +35,1485 @@ abstract class Expression implements PipelineSerializable { return Ordering(this, OrderDirection.asc); } - String get name; + // ============================================================================ + // CONDITIONAL / LOGIC OPERATIONS + // ============================================================================ - @override - Map toMap() { - return { - 'name': name, - }; + /// Returns an alternative expression if this expression is absent + Expression ifAbsent(Expression elseExpr) { + return _IfAbsentExpression(this, elseExpr); } -} -/// Base class for function expressions -abstract class FunctionExpression extends Expression {} + /// Returns an alternative value if this expression is absent + Expression ifAbsentValue(Object? elseValue) { + return _IfAbsentExpression(this, _toExpression(elseValue)); + } -/// Base class for selectable expressions (can be used in select stage) -abstract class Selectable extends Expression { - String get alias; - Expression get expression; -} + /// Returns an alternative expression if this expression errors + Expression ifError(Expression catchExpr) { + return _IfErrorExpression(this, catchExpr); + } -/// Represents an aliased expression wrapper -class AliasedExpression extends Selectable { - @override - final String alias; + /// Returns an alternative value if this expression errors + Expression ifErrorValue(Object? catchValue) { + return _IfErrorExpression(this, _toExpression(catchValue)); + } - @override - final Expression expression; + /// Checks if this expression is absent (null/undefined) + // ignore: use_to_and_as_if_applicable + BooleanExpression isAbsent() { + return _IsAbsentExpression(this); + } - AliasedExpression({ - required this.alias, - required this.expression, - }); + /// Checks if this expression produces an error + // ignore: use_to_and_as_if_applicable + BooleanExpression isError() { + return _IsErrorExpression(this); + } - @override - String get name => 'alias'; + /// Checks if this field expression exists in the document + // ignore: use_to_and_as_if_applicable + BooleanExpression exists() { + return _ExistsExpression(this); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'expression': expression.toMap(), - }, - }; + // ============================================================================ + // TYPE CONVERSION + // ============================================================================ + + /// Casts this expression to a boolean expression + BooleanExpression asBoolean() { + return _AsBooleanExpression(this); } -} -/// Represents a field reference in a pipeline expression -class Field extends Selectable { - final String fieldName; + /// Converts this expression to a string with a format + Expression toStringWithFormat(Expression format) { + return _ToStringWithFormatExpression(this, format); + } - Field(this.fieldName); + /// Converts this expression to a string with a literal format + Expression toStringWithFormatLiteral(String format) { + return _ToStringWithFormatExpression(this, Constant(format)); + } - @override - String get name => 'field'; + // ============================================================================ + // BITWISE OPERATIONS + // ============================================================================ - @override - String get alias => fieldName; + /// Performs bitwise AND with another expression + Expression bitAnd(Expression bitsOther) { + return _BitAndExpression(this, bitsOther); + } - @override - Expression get expression => this; + /// Performs bitwise AND with byte array + Expression bitAndBytes(List bitsOther) { + return _BitAndExpression(this, Constant(bitsOther)); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'field': fieldName, - }, - }; + /// Performs bitwise OR with another expression + Expression bitOr(Expression bitsOther) { + return _BitOrExpression(this, bitsOther); } -} -/// Represents a constant value in a pipeline expression -class Constant extends Expression { - final Object value; + /// Performs bitwise OR with byte array + Expression bitOrBytes(List bitsOther) { + return _BitOrExpression(this, Constant(bitsOther)); + } - Constant(this.value); + /// Performs bitwise XOR with another expression + Expression bitXor(Expression bitsOther) { + return _BitXorExpression(this, bitsOther); + } - @override - String get name => 'constant'; + /// Performs bitwise XOR with byte array + Expression bitXorBytes(List bitsOther) { + return _BitXorExpression(this, Constant(bitsOther)); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'value': value, - }, - }; + /// Performs bitwise NOT on this expression + // ignore: use_to_and_as_if_applicable + Expression bitNot() { + return _BitNotExpression(this); } -} -/// Represents a concatenation function expression -class Concat extends FunctionExpression { - final List expressions; + /// Shifts bits left by an expression amount + Expression bitLeftShift(Expression numberExpr) { + return _BitLeftShiftExpression(this, numberExpr); + } - Concat(this.expressions); + /// Shifts bits left by a literal amount + Expression bitLeftShiftLiteral(int number) { + return _BitLeftShiftExpression(this, Constant(number)); + } - @override - String get name => 'concat'; + /// Shifts bits right by an expression amount + Expression bitRightShift(Expression numberExpr) { + return _BitRightShiftExpression(this, numberExpr); + } - @override - Map toMap() { - return { - 'name': name, - 'args': { - 'expressions': expressions.map((expr) => expr.toMap()).toList(), - }, - }; + /// Shifts bits right by a literal amount + Expression bitRightShiftLiteral(int number) { + return _BitRightShiftExpression(this, Constant(number)); } -} -/// Base class for boolean expressions used in filtering -abstract class BooleanExpression extends Expression {} + // ============================================================================ + // DOCUMENT / PATH OPERATIONS + // ============================================================================ -/// Represents a filter expression for pipeline where clauses -class PipelineFilter extends BooleanExpression { - final Object field; - final Object? isEqualTo; - final Object? isNotEqualTo; - final Object? isLessThan; - final Object? isLessThanOrEqualTo; - final Object? isGreaterThan; - final Object? isGreaterThanOrEqualTo; - final Object? arrayContains; - final List? arrayContainsAny; - final List? whereIn; - final List? whereNotIn; - final bool? isNull; - final bool? isNotNull; - final BooleanExpression? _andExpression; - final BooleanExpression? _orExpression; + /// Returns the document ID from this path expression + // ignore: use_to_and_as_if_applicable + Expression documentId() { + return _DocumentIdExpression(this); + } - PipelineFilter( - this.field, { - this.isEqualTo, - this.isNotEqualTo, - this.isLessThan, - this.isLessThanOrEqualTo, - this.isGreaterThan, - this.isGreaterThanOrEqualTo, - this.arrayContains, - this.arrayContainsAny, - this.whereIn, - this.whereNotIn, - this.isNull, - this.isNotNull, - }) : _andExpression = null, - _orExpression = null; + /// Returns the collection ID from this path expression + // ignore: use_to_and_as_if_applicable + Expression collectionId() { + return _CollectionIdExpression(this); + } - PipelineFilter._internal({ - required BooleanExpression? andExpression, - required BooleanExpression? orExpression, - }) : field = '', - isEqualTo = null, - isNotEqualTo = null, - isLessThan = null, - isLessThanOrEqualTo = null, - isGreaterThan = null, - isGreaterThanOrEqualTo = null, - arrayContains = null, - arrayContainsAny = null, - whereIn = null, - whereNotIn = null, - isNull = null, - isNotNull = null, - _andExpression = andExpression, - _orExpression = orExpression; + // ============================================================================ + // MAP OPERATIONS + // ============================================================================ - /// Creates an OR filter combining multiple boolean expressions - static PipelineFilter or( - BooleanExpression expression1, [ - BooleanExpression? expression2, - BooleanExpression? expression3, - BooleanExpression? expression4, - BooleanExpression? expression5, - BooleanExpression? expression6, - BooleanExpression? expression7, - BooleanExpression? expression8, - BooleanExpression? expression9, - BooleanExpression? expression10, - BooleanExpression? expression11, - BooleanExpression? expression12, - BooleanExpression? expression13, - BooleanExpression? expression14, - BooleanExpression? expression15, - BooleanExpression? expression16, - BooleanExpression? expression17, - BooleanExpression? expression18, - BooleanExpression? expression19, - BooleanExpression? expression20, - BooleanExpression? expression21, - BooleanExpression? expression22, - BooleanExpression? expression23, - BooleanExpression? expression24, - BooleanExpression? expression25, - BooleanExpression? expression26, - BooleanExpression? expression27, - BooleanExpression? expression28, - BooleanExpression? expression29, - BooleanExpression? expression30, - ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); - if (expression3 != null) expressions.add(expression3); - if (expression4 != null) expressions.add(expression4); - if (expression5 != null) expressions.add(expression5); - if (expression6 != null) expressions.add(expression6); - if (expression7 != null) expressions.add(expression7); - if (expression8 != null) expressions.add(expression8); - if (expression9 != null) expressions.add(expression9); - if (expression10 != null) expressions.add(expression10); - if (expression11 != null) expressions.add(expression11); - if (expression12 != null) expressions.add(expression12); - if (expression13 != null) expressions.add(expression13); - if (expression14 != null) expressions.add(expression14); - if (expression15 != null) expressions.add(expression15); - if (expression16 != null) expressions.add(expression16); - if (expression17 != null) expressions.add(expression17); - if (expression18 != null) expressions.add(expression18); - if (expression19 != null) expressions.add(expression19); - if (expression20 != null) expressions.add(expression20); - if (expression21 != null) expressions.add(expression21); - if (expression22 != null) expressions.add(expression22); - if (expression23 != null) expressions.add(expression23); - if (expression24 != null) expressions.add(expression24); - if (expression25 != null) expressions.add(expression25); - if (expression26 != null) expressions.add(expression26); - if (expression27 != null) expressions.add(expression27); - if (expression28 != null) expressions.add(expression28); - if (expression29 != null) expressions.add(expression29); - if (expression30 != null) expressions.add(expression30); + /// Gets a value from this map expression by key expression + Expression mapGet(Expression key) { + return _MapGetExpression(this, key); + } - return PipelineFilter._internal( - andExpression: null, - orExpression: _combineExpressions(expressions, 'or'), - ); + /// Gets a value from this map expression by literal key + Expression mapGetLiteral(String key) { + return _MapGetExpression(this, Constant(key)); } - /// Creates an AND filter combining multiple boolean expressions - static PipelineFilter and( - BooleanExpression expression1, [ - BooleanExpression? expression2, - BooleanExpression? expression3, - BooleanExpression? expression4, - BooleanExpression? expression5, - BooleanExpression? expression6, - BooleanExpression? expression7, - BooleanExpression? expression8, - BooleanExpression? expression9, - BooleanExpression? expression10, - BooleanExpression? expression11, - BooleanExpression? expression12, - BooleanExpression? expression13, - BooleanExpression? expression14, - BooleanExpression? expression15, - BooleanExpression? expression16, - BooleanExpression? expression17, - BooleanExpression? expression18, - BooleanExpression? expression19, - BooleanExpression? expression20, - BooleanExpression? expression21, - BooleanExpression? expression22, - BooleanExpression? expression23, - BooleanExpression? expression24, - BooleanExpression? expression25, - BooleanExpression? expression26, - BooleanExpression? expression27, - BooleanExpression? expression28, - BooleanExpression? expression29, - BooleanExpression? expression30, - ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); + /// Returns the keys from this map expression + // ignore: use_to_and_as_if_applicable + Expression mapKeys() { + return _MapKeysExpression(this); + } + + /// Returns the values from this map expression + // ignore: use_to_and_as_if_applicable + Expression mapValues() { + return _MapValuesExpression(this); + } + + // ============================================================================ + // ALIASING + // ============================================================================ + + /// Assigns an alias to this expression for use in output + Selectable alias(String alias) { + return AliasedExpression(alias: alias, expression: this); + } + + // ============================================================================ + // ARITHMETIC OPERATIONS + // ============================================================================ + + /// Adds this expression to another expression + Expression add(Expression other) { + return _AddExpression(this, other); + } + + /// Adds a number to this expression + Expression addNumber(num other) { + return _AddExpression(this, Constant(other)); + } + + /// Subtracts another expression from this expression + Expression subtract(Expression other) { + return _SubtractExpression(this, other); + } + + /// Subtracts a number from this expression + Expression subtractNumber(num other) { + return _SubtractExpression(this, Constant(other)); + } + + /// Multiplies this expression by another expression + Expression multiply(Expression other) { + return _MultiplyExpression(this, other); + } + + /// Multiplies this expression by a number + Expression multiplyNumber(num other) { + return _MultiplyExpression(this, Constant(other)); + } + + /// Divides this expression by another expression + Expression divide(Expression other) { + return _DivideExpression(this, other); + } + + /// Divides this expression by a number + Expression divideNumber(num other) { + return _DivideExpression(this, Constant(other)); + } + + /// Returns the remainder of dividing this expression by another + Expression modulo(Expression other) { + return _ModuloExpression(this, other); + } + + /// Returns the remainder of dividing this expression by a number + Expression moduloNumber(num other) { + return _ModuloExpression(this, Constant(other)); + } + + /// Returns the absolute value of this expression + // ignore: use_to_and_as_if_applicable + Expression abs() { + return _AbsExpression(this); + } + + /// Returns the negation of this expression + // ignore: use_to_and_as_if_applicable + Expression negate() { + return _NegateExpression(this); + } + + // ============================================================================ + // COMPARISON OPERATIONS (return BooleanExpression) + // ============================================================================ + + /// Checks if this expression equals another expression + BooleanExpression equal(Expression other) { + return _EqualExpression(this, other); + } + + /// Checks if this expression equals a value + BooleanExpression equalValue(Object? value) { + return _EqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression does not equal another expression + BooleanExpression notEqual(Expression other) { + return _NotEqualExpression(this, other); + } + + /// Checks if this expression does not equal a value + BooleanExpression notEqualValue(Object? value) { + return _NotEqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression is greater than another expression + BooleanExpression greaterThan(Expression other) { + return _GreaterThanExpression(this, other); + } + + /// Checks if this expression is greater than a value + BooleanExpression greaterThanValue(Object? value) { + return _GreaterThanExpression(this, _toExpression(value)); + } + + /// Checks if this expression is greater than or equal to another expression + BooleanExpression greaterThanOrEqual(Expression other) { + return _GreaterThanOrEqualExpression(this, other); + } + + /// Checks if this expression is greater than or equal to a value + BooleanExpression greaterThanOrEqualValue(Object? value) { + return _GreaterThanOrEqualExpression(this, _toExpression(value)); + } + + /// Checks if this expression is less than another expression + BooleanExpression lessThan(Expression other) { + return _LessThanExpression(this, other); + } + + /// Checks if this expression is less than a value + BooleanExpression lessThanValue(Object? value) { + return _LessThanExpression(this, _toExpression(value)); + } + + /// Checks if this expression is less than or equal to another expression + BooleanExpression lessThanOrEqual(Expression other) { + return _LessThanOrEqualExpression(this, other); + } + + /// Checks if this expression is less than or equal to a value + BooleanExpression lessThanOrEqualValue(Object? value) { + return _LessThanOrEqualExpression(this, _toExpression(value)); + } + + // ============================================================================ + // STRING OPERATIONS + // ============================================================================ + + /// Returns the length of this string expression + // ignore: use_to_and_as_if_applicable + Expression length() { + return _LengthExpression(this); + } + + /// Concatenates this expression with other expressions/values + Expression concat(List others) { + final expressions = [this]; + for (final other in others) { + expressions.add(_toExpression(other)); + } + return _ConcatExpression(expressions); + } + + /// Converts this string expression to lowercase + Expression toLowerCase() { + return _ToLowerCaseExpression(this); + } + + /// Converts this string expression to uppercase + Expression toUpperCase() { + return _ToUpperCaseExpression(this); + } + + /// Extracts a substring from this string expression + Expression substring(Expression start, Expression end) { + return _SubstringExpression(this, start, end); + } + + /// Extracts a substring using literal indices + Expression substringLiteral(int start, int end) { + return _SubstringExpression(this, Constant(start), Constant(end)); + } + + /// Replaces occurrences of a pattern in this string + Expression replace(Expression find, Expression replacement) { + return _ReplaceExpression(this, find, replacement); + } + + /// Replaces occurrences of a string literal + Expression replaceLiteral(String find, String replacement) { + return _ReplaceExpression(this, Constant(find), Constant(replacement)); + } + + /// Splits this string expression by a delimiter + Expression split(Expression delimiter) { + return _SplitExpression(this, delimiter); + } + + /// Splits this string by a literal delimiter + Expression splitLiteral(String delimiter) { + return _SplitExpression(this, Constant(delimiter)); + } + + /// Joins array elements with a delimiter + Expression join(Expression delimiter) { + return _JoinExpression(this, delimiter); + } + + /// Joins array elements with a literal delimiter + Expression joinLiteral(String delimiter) { + return _JoinExpression(this, Constant(delimiter)); + } + + /// Trims whitespace from this string expression + // ignore: use_to_and_as_if_applicable + Expression trim() { + return _TrimExpression(this); + } + + // ============================================================================ + // ARRAY OPERATIONS + // ============================================================================ + + /// Concatenates this array with another array expression + Expression arrayConcat(Expression secondArray) { + return _ArrayConcatExpression(this, secondArray); + } + + /// Concatenates this array with multiple arrays/values + Expression arrayConcatMultiple(List otherArrays) { + final expressions = [this]; + for (final other in otherArrays) { + expressions.add(_toExpression(other)); + } + return _ArrayConcatMultipleExpression(expressions); + } + + /// Checks if this array contains an element expression + BooleanExpression arrayContainsElement(Expression element) { + return _ArrayContainsExpression(this, element); + } + + /// Checks if this array contains a value + BooleanExpression arrayContainsValue(Object? element) { + return _ArrayContainsExpression(this, _toExpression(element)); + } + + /// Returns the length of this array expression + // ignore: use_to_and_as_if_applicable + Expression arrayLength() { + return _ArrayLengthExpression(this); + } + + /// Reverses this array expression + // ignore: use_to_and_as_if_applicable + Expression arrayReverse() { + return _ArrayReverseExpression(this); + } + + /// Returns the sum of numeric elements in this array + // ignore: use_to_and_as_if_applicable + Expression arraySum() { + return _ArraySumExpression(this); + } + + /// Extracts a slice from this array + Expression arraySlice(Expression start, Expression end) { + return _ArraySliceExpression(this, start, end); + } + + /// Extracts a slice using literal indices + Expression arraySliceLiteral(int start, int end) { + return _ArraySliceExpression(this, Constant(start), Constant(end)); + } + + // ============================================================================ + // AGGREGATE FUNCTIONS + // ============================================================================ + + /// Creates a sum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction sum() { + return Sum(this); + } + + /// Creates an average aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction average() { + return Average(this); + } + + /// Creates a count aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction count() { + return Count(this); + } + + /// Creates a count distinct aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction countDistinct() { + return CountDistinct(this); + } + + /// Creates a minimum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction minimum() { + return Minimum(this); + } + + /// Creates a maximum aggregation function from this expression + // ignore: use_to_and_as_if_applicable + PipelineAggregateFunction maximum() { + return Maximum(this); + } + + String get name; + + @override + Map toMap() { + return { + 'name': name, + }; + } + + // ============================================================================ + // STATIC FACTORY METHODS + // ============================================================================ + + /// Creates a constant expression from a string value + static Expression constantString(String value) => Constant(value); + + /// Creates a constant expression from a number value + static Expression constantNumber(num value) => Constant(value); + + /// Creates a constant expression from a boolean value + static Expression constantBoolean(bool value) => Constant(value); + + /// Creates a constant expression from a DateTime value + static Expression constantDateTime(DateTime value) => Constant(value); + + /// Creates a constant expression from a Timestamp value + static Expression constantTimestamp(Timestamp value) => Constant(value); + + /// Creates a constant expression from a GeoPoint value + static Expression constantGeoPoint(GeoPoint value) => Constant(value); + + /// Creates a constant expression from a Blob value + static Expression constantBlob(Blob value) => Constant(value); + + /// Creates a constant expression from a DocumentReference value + static Expression constantDocumentReference(DocumentReference value) => + Constant(value); + + /// Creates a constant expression from a byte array value + static Expression constantBytes(List value) => Constant(value); + + /// Creates a constant expression from a VectorValue value + static Expression constantVector(VectorValue value) => Constant(value); + + /// Creates a constant expression from any value (convenience) + static Expression constant(Object? value) => Constant(value!); + + /// Creates a field reference expression from a field path string + static Field field(String fieldPath) => Field(fieldPath); + + /// Creates a field reference expression from a FieldPath object + static Field fieldPath(FieldPath fieldPath) => Field(fieldPath.toString()); + + /// Creates a null value expression + static Expression nullValue() => _NullExpression(); + + /// Creates a conditional (ternary) expression + static Expression conditional( + BooleanExpression condition, + Expression thenExpr, + Expression elseExpr, + ) { + return _ConditionalExpression(condition, thenExpr, elseExpr); + } + + /// Creates a conditional expression with literal values + static Expression conditionalValues( + BooleanExpression condition, + Object? thenValue, + Object? elseValue, + ) { + return _ConditionalExpression( + condition, + _toExpression(thenValue), + _toExpression(elseValue), + ); + } + + /// Creates an array expression from elements + static Expression array(List elements) { + return _ArrayExpression( + elements.map(_toExpression).toList(), + ); + } + + /// Creates a map expression from key-value pairs + static Expression map(Map data) { + return _MapExpression(data.map((k, v) => MapEntry(k, _toExpression(v)))); + } + + /// Creates a map expression from alternating key-value expressions + static Expression mapFromPairs(List keyValuePairs) { + return _MapFromPairsExpression(keyValuePairs); + } + + /// Returns the current timestamp + static Expression currentTimestamp() { + return _CurrentTimestampExpression(); + } + + /// Adds time to a timestamp expression + static Expression timestampAdd( + Expression timestamp, + String unit, + Expression amount, + ) { + return _TimestampAddExpression(timestamp, unit, amount); + } + + /// Adds time to a timestamp with a literal amount + static Expression timestampAddLiteral( + Expression timestamp, + String unit, + int amount, + ) { + return _TimestampAddExpression(timestamp, unit, Constant(amount)); + } + + /// Subtracts time from a timestamp expression + static Expression timestampSubtract( + Expression timestamp, + String unit, + Expression amount, + ) { + return _TimestampSubtractExpression(timestamp, unit, amount); + } + + /// Subtracts time from a timestamp with a literal amount + static Expression timestampSubtractLiteral( + Expression timestamp, + String unit, + int amount, + ) { + return _TimestampSubtractExpression(timestamp, unit, Constant(amount)); + } + + /// Calculates the difference between two timestamps + static Expression timestampDiff( + Expression timestamp1, + Expression timestamp2, + String unit, + ) { + return _TimestampDiffExpression(timestamp1, timestamp2, unit); + } + + /// Truncates a timestamp to a specific unit + static Expression timestampTruncate( + Expression timestamp, + String unit, + ) { + return _TimestampTruncateExpression(timestamp, unit); + } + + /// Calculates the distance between two GeoPoint expressions + static Expression distance( + Expression geoPoint1, + Expression geoPoint2, + ) { + return _DistanceExpression(geoPoint1, geoPoint2); + } + + /// Creates a document ID expression from a DocumentReference + static Expression documentIdFromRef(DocumentReference docRef) { + return _DocumentIdFromRefExpression(docRef); + } + + /// Checks if a value is in a list (IN operator) + static BooleanExpression equalAny( + Expression value, + List values, + ) { + return _EqualAnyExpression(value, values.map(_toExpression).toList()); + } + + /// Checks if a value is not in a list (NOT IN operator) + static BooleanExpression notEqualAny( + Expression value, + List values, + ) { + return _NotEqualAnyExpression(value, values.map(_toExpression).toList()); + } + + /// Checks if a field exists in the document + static BooleanExpression existsField(String fieldName) { + return _ExistsExpression(Field(fieldName)); + } + + /// Returns an expression if another is absent + static Expression ifAbsentStatic( + Expression ifExpr, + Expression elseExpr, + ) { + return _IfAbsentExpression(ifExpr, elseExpr); + } + + /// Returns a value if an expression is absent + static Expression ifAbsentValueStatic( + Expression ifExpr, + Object? elseValue, + ) { + return _IfAbsentExpression(ifExpr, _toExpression(elseValue)); + } + + /// Checks if an expression is absent + static BooleanExpression isAbsentStatic(Expression value) { + return _IsAbsentExpression(value); + } + + /// Checks if a field is absent + static BooleanExpression isAbsentField(String fieldName) { + return _IsAbsentExpression(Field(fieldName)); + } + + /// Returns an expression if another errors + static BooleanExpression ifErrorStatic( + BooleanExpression tryExpr, + BooleanExpression catchExpr, + ) { + return _IfErrorExpression(tryExpr, catchExpr) as BooleanExpression; + } + + /// Checks if an expression produces an error + static BooleanExpression isErrorStatic(Expression expr) { + return _IsErrorExpression(expr); + } + + /// Joins array elements with a delimiter + static Expression joinStatic( + Expression arrayExpression, + Expression delimiterExpression, + ) { + return _JoinExpression(arrayExpression, delimiterExpression); + } + + /// Joins array elements with a literal delimiter + static Expression joinStaticLiteral( + Expression arrayExpression, + String delimiter, + ) { + return _JoinExpression(arrayExpression, Constant(delimiter)); + } + + /// Joins a field's array with a delimiter + static Expression joinField( + String arrayFieldName, + String delimiter, + ) { + return _JoinExpression(Field(arrayFieldName), Constant(delimiter)); + } + + /// Concatenates arrays + static Expression arrayConcatStatic( + Expression firstArray, + Expression secondArray, + List? otherArrays, + ) { + final expressions = [firstArray, secondArray]; + if (otherArrays != null) { + for (final other in otherArrays) { + expressions.add(_toExpression(other)); + } + } + return _ArrayConcatMultipleExpression(expressions); + } + + /// Returns the length of an expression + static Expression lengthStatic(Expression expr) { + return _LengthExpression(expr); + } + + /// Returns the length of a field + static Expression lengthField(String fieldName) { + return _LengthExpression(Field(fieldName)); + } + + /// Returns the absolute value of an expression + static Expression absStatic(Expression numericExpr) { + return _AbsExpression(numericExpr); + } + + /// Returns the absolute value of a field + static Expression absField(String numericField) { + return _AbsExpression(Field(numericField)); + } + + /// Negates an expression + static Expression negateStatic(Expression numericExpr) { + return _NegateExpression(numericExpr); + } + + /// Negates a field + static Expression negateField(String numericField) { + return _NegateExpression(Field(numericField)); + } + + /// Adds two expressions + static Expression addStatic( + Expression first, + Expression second, + ) { + return _AddExpression(first, second); + } + + /// Adds an expression and a number + static Expression addStaticNumber( + Expression first, + num second, + ) { + return _AddExpression(first, Constant(second)); + } + + /// Adds a field and an expression + static Expression addField( + String numericFieldName, + Expression second, + ) { + return _AddExpression(Field(numericFieldName), second); + } + + /// Adds a field and a number + static Expression addFieldNumber( + String numericFieldName, + num second, + ) { + return _AddExpression(Field(numericFieldName), Constant(second)); + } + + /// Subtracts two expressions + static Expression subtractStatic( + Expression minuend, + Expression subtrahend, + ) { + return _SubtractExpression(minuend, subtrahend); + } + + /// Multiplies two expressions + static Expression multiplyStatic( + Expression multiplicand, + Expression multiplier, + ) { + return _MultiplyExpression(multiplicand, multiplier); + } + + /// Divides two expressions + static Expression divideStatic( + Expression dividend, + Expression divisor, + ) { + return _DivideExpression(dividend, divisor); + } + + /// Returns modulo of two expressions + static Expression moduloStatic( + Expression dividend, + Expression divisor, + ) { + return _ModuloExpression(dividend, divisor); + } + + /// Compares two expressions for equality + static BooleanExpression equalStatic( + Expression left, + Expression right, + ) { + return _EqualExpression(left, right); + } + + /// Compares expression with value for equality + static BooleanExpression equalStaticValue( + Expression left, + Object? right, + ) { + return _EqualExpression(left, _toExpression(right)); + } + + /// Compares field with value for equality + static BooleanExpression equalField( + String fieldName, + Object? value, + ) { + return _EqualExpression(Field(fieldName), _toExpression(value)); + } + + /// Compares two expressions for inequality + static BooleanExpression notEqualStatic( + Expression left, + Expression right, + ) { + return _NotEqualExpression(left, right); + } + + /// Compares expression with value for inequality + static BooleanExpression notEqualStaticValue( + Expression left, + Object? right, + ) { + return _NotEqualExpression(left, _toExpression(right)); + } + + /// Greater than comparison + static BooleanExpression greaterThanStatic( + Expression left, + Expression right, + ) { + return _GreaterThanExpression(left, right); + } + + /// Greater than comparison with value + static BooleanExpression greaterThanStaticValue( + Expression left, + Object? right, + ) { + return _GreaterThanExpression(left, _toExpression(right)); + } + + /// Greater than comparison for field + static BooleanExpression greaterThanField( + String fieldName, + Object? value, + ) { + return _GreaterThanExpression(Field(fieldName), _toExpression(value)); + } + + /// Greater than or equal comparison + static BooleanExpression greaterThanOrEqualStatic( + Expression left, + Expression right, + ) { + return _GreaterThanOrEqualExpression(left, right); + } + + /// Less than comparison + static BooleanExpression lessThanStatic( + Expression left, + Expression right, + ) { + return _LessThanExpression(left, right); + } + + /// Less than comparison with value + static BooleanExpression lessThanStaticValue( + Expression left, + Object? right, + ) { + return _LessThanExpression(left, _toExpression(right)); + } + + /// Less than comparison for field + static BooleanExpression lessThanField( + String fieldName, + Object? value, + ) { + return _LessThanExpression(Field(fieldName), _toExpression(value)); + } + + /// Less than or equal comparison + static BooleanExpression lessThanOrEqualStatic( + Expression left, + Expression right, + ) { + return _LessThanOrEqualExpression(left, right); + } + + /// Concatenates expressions + static Expression concatStatic( + Expression first, + Expression second, + List? others, + ) { + final expressions = [first, second]; + if (others != null) { + for (final other in others) { + expressions.add(_toExpression(other)); + } + } + return _ConcatExpression(expressions); + } + + /// Converts to lowercase + static Expression toLowerCaseStatic(Expression stringExpr) { + return _ToLowerCaseExpression(stringExpr); + } + + /// Converts field to lowercase + static Expression toLowerCaseField(String stringField) { + return _ToLowerCaseExpression(Field(stringField)); + } + + /// Converts to uppercase + static Expression toUpperCaseStatic(Expression stringExpr) { + return _ToUpperCaseExpression(stringExpr); + } + + /// Converts field to uppercase + static Expression toUpperCaseField(String stringField) { + return _ToUpperCaseExpression(Field(stringField)); + } + + /// Trims whitespace + static Expression trimStatic(Expression stringExpr) { + return _TrimExpression(stringExpr); + } + + /// Trims field whitespace + static Expression trimField(String stringField) { + return _TrimExpression(Field(stringField)); + } + + /// Extracts substring + static Expression substringStatic( + Expression stringExpr, + Expression start, + Expression end, + ) { + return _SubstringExpression(stringExpr, start, end); + } + + /// Replaces in string + static Expression replaceStatic( + Expression stringExpr, + Expression find, + Expression replacement, + ) { + return _ReplaceExpression(stringExpr, find, replacement); + } + + /// Splits string + static Expression splitStatic( + Expression stringExpr, + Expression delimiter, + ) { + return _SplitExpression(stringExpr, delimiter); + } + + /// Reverses array + static Expression arrayReverseStatic(Expression array) { + return _ArrayReverseExpression(array); + } + + /// Reverses field array + static Expression arrayReverseField(String arrayFieldName) { + return _ArrayReverseExpression(Field(arrayFieldName)); + } + + /// Sums array + static Expression arraySumStatic(Expression array) { + return _ArraySumExpression(array); + } + + /// Sums field array + static Expression arraySumField(String arrayFieldName) { + return _ArraySumExpression(Field(arrayFieldName)); + } + + /// Gets array length + static Expression arrayLengthStatic(Expression array) { + return _ArrayLengthExpression(array); + } + + /// Gets field array length + static Expression arrayLengthField(String arrayFieldName) { + return _ArrayLengthExpression(Field(arrayFieldName)); + } + + /// Slices array + static Expression arraySliceStatic( + Expression array, + Expression start, + Expression end, + ) { + return _ArraySliceExpression(array, start, end); + } + + /// Checks array contains + static BooleanExpression arrayContainsElementStatic( + Expression array, + Expression element, + ) { + return _ArrayContainsExpression(array, element); + } + + /// Checks field array contains + static BooleanExpression arrayContainsField( + String arrayFieldName, + Object? element, + ) { + return _ArrayContainsExpression( + Field(arrayFieldName), _toExpression(element)); + } + + /// Creates a raw/custom function expression + static Expression rawFunction( + String name, + List args, + ) { + return _RawFunctionExpression(name, args); + } +} + +/// Base class for function expressions +abstract class FunctionExpression extends Expression {} + +/// Base class for selectable expressions (can be used in select stage) +abstract class Selectable extends Expression { + String get aliasName; + Expression get expression; +} + +/// Represents an aliased expression wrapper +class AliasedExpression extends Selectable { + final String _alias; + + @override + String get aliasName => _alias; + + @override + final Expression expression; + + AliasedExpression({ + required String alias, + required this.expression, + }) : _alias = alias; + + @override + String get name => 'alias'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'alias': _alias, + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a field reference in a pipeline expression +class Field extends Selectable { + final String fieldName; + + Field(this.fieldName); + + @override + String get name => 'field'; + + @override + String get aliasName => fieldName; + + @override + Expression get expression => this; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'field': fieldName, + }, + }; + } +} + +/// Represents a null value expression +class _NullExpression extends Expression { + _NullExpression(); + + @override + String get name => 'null'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': null, + }, + }; + } +} + +/// Represents a constant value in a pipeline expression +class Constant extends Expression { + final Object value; + + Constant(this.value); + + @override + String get name => 'constant'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value, + }, + }; + } +} + +/// Represents a concatenation function expression +class Concat extends FunctionExpression { + final List expressions; + + Concat(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a concat function expression (internal) +class _ConcatExpression extends FunctionExpression { + final List expressions; + + _ConcatExpression(this.expressions); + + @override + String get name => 'concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expressions': expressions.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a length function expression +class _LengthExpression extends FunctionExpression { + final Expression expression; + + _LengthExpression(this.expression); + + @override + String get name => 'length'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toLowerCase function expression +class _ToLowerCaseExpression extends FunctionExpression { + final Expression expression; + + _ToLowerCaseExpression(this.expression); + + @override + String get name => 'to_lower_case'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toUpperCase function expression +class _ToUpperCaseExpression extends FunctionExpression { + final Expression expression; + + _ToUpperCaseExpression(this.expression); + + @override + String get name => 'to_upper_case'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a substring function expression +class _SubstringExpression extends FunctionExpression { + final Expression expression; + final Expression start; + final Expression end; + + _SubstringExpression(this.expression, this.start, this.end); + + @override + String get name => 'substring'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'start': start.toMap(), + 'end': end.toMap(), + }, + }; + } +} + +/// Represents a replace function expression +class _ReplaceExpression extends FunctionExpression { + final Expression expression; + final Expression find; + final Expression replacement; + + _ReplaceExpression(this.expression, this.find, this.replacement); + + @override + String get name => 'replace'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'find': find.toMap(), + 'replacement': replacement.toMap(), + }, + }; + } +} + +/// Represents a split function expression +class _SplitExpression extends FunctionExpression { + final Expression expression; + final Expression delimiter; + + _SplitExpression(this.expression, this.delimiter); + + @override + String get name => 'split'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'delimiter': delimiter.toMap(), + }, + }; + } +} + +/// Represents a join function expression +class _JoinExpression extends FunctionExpression { + final Expression expression; + final Expression delimiter; + + _JoinExpression(this.expression, this.delimiter); + + @override + String get name => 'join'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'delimiter': delimiter.toMap(), + }, + }; + } +} + +/// Represents a trim function expression +class _TrimExpression extends FunctionExpression { + final Expression expression; + + _TrimExpression(this.expression); + + @override + String get name => 'trim'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Base class for boolean expressions used in filtering +abstract class BooleanExpression extends Expression {} + +/// Represents a filter expression for pipeline where clauses +class PipelineFilter extends BooleanExpression { + final Object field; + final Object? isEqualTo; + final Object? isNotEqualTo; + final Object? isLessThan; + final Object? isLessThanOrEqualTo; + final Object? isGreaterThan; + final Object? isGreaterThanOrEqualTo; + final Object? arrayContains; + final List? arrayContainsAny; + final List? whereIn; + final List? whereNotIn; + final bool? isNull; + final bool? isNotNull; + final BooleanExpression? _andExpression; + final BooleanExpression? _orExpression; + + PipelineFilter( + this.field, { + this.isEqualTo, + this.isNotEqualTo, + this.isLessThan, + this.isLessThanOrEqualTo, + this.isGreaterThan, + this.isGreaterThanOrEqualTo, + this.arrayContains, + this.arrayContainsAny, + this.whereIn, + this.whereNotIn, + this.isNull, + this.isNotNull, + }) : _andExpression = null, + _orExpression = null; + + PipelineFilter._internal({ + required BooleanExpression? andExpression, + required BooleanExpression? orExpression, + }) : field = '', + isEqualTo = null, + isNotEqualTo = null, + isLessThan = null, + isLessThanOrEqualTo = null, + isGreaterThan = null, + isGreaterThanOrEqualTo = null, + arrayContains = null, + arrayContainsAny = null, + whereIn = null, + whereNotIn = null, + isNull = null, + isNotNull = null, + _andExpression = andExpression, + _orExpression = orExpression; + + /// Creates an OR filter combining multiple boolean expressions + static PipelineFilter or( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); if (expression5 != null) expressions.add(expression5); @@ -332,80 +1543,1391 @@ class PipelineFilter extends BooleanExpression { if (expression29 != null) expressions.add(expression29); if (expression30 != null) expressions.add(expression30); - return PipelineFilter._internal( - andExpression: _combineExpressions(expressions, 'and'), - orExpression: null, - ); + return PipelineFilter._internal( + andExpression: null, + orExpression: _combineExpressions(expressions, 'or'), + ); + } + + /// Creates an AND filter combining multiple boolean expressions + static PipelineFilter and( + BooleanExpression expression1, [ + BooleanExpression? expression2, + BooleanExpression? expression3, + BooleanExpression? expression4, + BooleanExpression? expression5, + BooleanExpression? expression6, + BooleanExpression? expression7, + BooleanExpression? expression8, + BooleanExpression? expression9, + BooleanExpression? expression10, + BooleanExpression? expression11, + BooleanExpression? expression12, + BooleanExpression? expression13, + BooleanExpression? expression14, + BooleanExpression? expression15, + BooleanExpression? expression16, + BooleanExpression? expression17, + BooleanExpression? expression18, + BooleanExpression? expression19, + BooleanExpression? expression20, + BooleanExpression? expression21, + BooleanExpression? expression22, + BooleanExpression? expression23, + BooleanExpression? expression24, + BooleanExpression? expression25, + BooleanExpression? expression26, + BooleanExpression? expression27, + BooleanExpression? expression28, + BooleanExpression? expression29, + BooleanExpression? expression30, + ]) { + final expressions = [expression1]; + if (expression2 != null) expressions.add(expression2); + if (expression3 != null) expressions.add(expression3); + if (expression4 != null) expressions.add(expression4); + if (expression5 != null) expressions.add(expression5); + if (expression6 != null) expressions.add(expression6); + if (expression7 != null) expressions.add(expression7); + if (expression8 != null) expressions.add(expression8); + if (expression9 != null) expressions.add(expression9); + if (expression10 != null) expressions.add(expression10); + if (expression11 != null) expressions.add(expression11); + if (expression12 != null) expressions.add(expression12); + if (expression13 != null) expressions.add(expression13); + if (expression14 != null) expressions.add(expression14); + if (expression15 != null) expressions.add(expression15); + if (expression16 != null) expressions.add(expression16); + if (expression17 != null) expressions.add(expression17); + if (expression18 != null) expressions.add(expression18); + if (expression19 != null) expressions.add(expression19); + if (expression20 != null) expressions.add(expression20); + if (expression21 != null) expressions.add(expression21); + if (expression22 != null) expressions.add(expression22); + if (expression23 != null) expressions.add(expression23); + if (expression24 != null) expressions.add(expression24); + if (expression25 != null) expressions.add(expression25); + if (expression26 != null) expressions.add(expression26); + if (expression27 != null) expressions.add(expression27); + if (expression28 != null) expressions.add(expression28); + if (expression29 != null) expressions.add(expression29); + if (expression30 != null) expressions.add(expression30); + + return PipelineFilter._internal( + andExpression: _combineExpressions(expressions, 'and'), + orExpression: null, + ); + } + + static BooleanExpression _combineExpressions( + List expressions, + String operator, + ) { + if (expressions.length == 1) return expressions.first; + + // Create a nested structure for multiple expressions + BooleanExpression result = expressions.first; + for (int i = 1; i < expressions.length; i++) { + if (operator == 'and') { + result = PipelineFilter.and(result, expressions[i]); + } else { + result = PipelineFilter.or(result, expressions[i]); + } + } + return result; + } + + @override + String get name => 'filter'; + + @override + Map toMap() { + final map = super.toMap(); + + if (_andExpression != null) { + map['args'] = { + 'operator': 'and', + 'expressions': [_andExpression.toMap()], + }; + return map; + } + + if (_orExpression != null) { + map['args'] = { + 'operator': 'or', + 'expressions': [_orExpression.toMap()], + }; + return map; + } + + final args = {}; + if (field is String) { + args['field'] = field; + } else if (field is Field) { + args['field'] = (field as Field).fieldName; + } + + if (isEqualTo != null) args['isEqualTo'] = isEqualTo; + if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; + if (isLessThan != null) args['isLessThan'] = isLessThan; + if (isLessThanOrEqualTo != null) { + args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; + } + if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; + if (isGreaterThanOrEqualTo != null) { + args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; + } + if (arrayContains != null) args['arrayContains'] = arrayContains; + if (arrayContainsAny != null) { + args['arrayContainsAny'] = arrayContainsAny; + } + if (whereIn != null) args['whereIn'] = whereIn; + if (whereNotIn != null) args['whereNotIn'] = whereNotIn; + if (isNull != null) args['isNull'] = isNull; + if (isNotNull != null) args['isNotNull'] = isNotNull; + + map['args'] = args; + return map; + } +} + +// ============================================================================ +// PATTERN DEMONSTRATION - Concrete Function Expression Classes +// ============================================================================ + +/// Represents an addition function expression +class _AddExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _AddExpression(this.left, this.right); + + @override + String get name => 'add'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a subtraction function expression +class _SubtractExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _SubtractExpression(this.left, this.right); + + @override + String get name => 'subtract'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents an equality comparison function expression +class _EqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _EqualExpression(this.left, this.right); + + @override + String get name => 'equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a greater-than comparison function expression +class _GreaterThanExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _GreaterThanExpression(this.left, this.right); + + @override + String get name => 'greater_than'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a multiply function expression +class _MultiplyExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _MultiplyExpression(this.left, this.right); + + @override + String get name => 'multiply'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a divide function expression +class _DivideExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _DivideExpression(this.left, this.right); + + @override + String get name => 'divide'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a modulo function expression +class _ModuloExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _ModuloExpression(this.left, this.right); + + @override + String get name => 'modulo'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents an absolute value function expression +class _AbsExpression extends FunctionExpression { + final Expression expression; + + _AbsExpression(this.expression); + + @override + String get name => 'abs'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a negation function expression +class _NegateExpression extends FunctionExpression { + final Expression expression; + + _NegateExpression(this.expression); + + @override + String get name => 'negate'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a not-equal comparison function expression +class _NotEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _NotEqualExpression(this.left, this.right); + + @override + String get name => 'not_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a greater-than-or-equal comparison function expression +class _GreaterThanOrEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _GreaterThanOrEqualExpression(this.left, this.right); + + @override + String get name => 'greater_than_or_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a less-than comparison function expression +class _LessThanExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _LessThanExpression(this.left, this.right); + + @override + String get name => 'less_than'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a less-than-or-equal comparison function expression +class _LessThanOrEqualExpression extends BooleanExpression { + final Expression left; + final Expression right; + + _LessThanOrEqualExpression(this.left, this.right); + + @override + String get name => 'less_than_or_equal'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +// ============================================================================ +// ARRAY OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an array concat function expression +class _ArrayConcatExpression extends FunctionExpression { + final Expression firstArray; + final Expression secondArray; + + _ArrayConcatExpression(this.firstArray, this.secondArray); + + @override + String get name => 'array_concat'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'first': firstArray.toMap(), + 'second': secondArray.toMap(), + }, + }; + } +} + +/// Represents an array concat multiple function expression +class _ArrayConcatMultipleExpression extends FunctionExpression { + final List arrays; + + _ArrayConcatMultipleExpression(this.arrays); + + @override + String get name => 'array_concat_multiple'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'arrays': arrays.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents an array contains function expression +class _ArrayContainsExpression extends BooleanExpression { + final Expression array; + final Expression element; + + _ArrayContainsExpression(this.array, this.element); + + @override + String get name => 'array_contains'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'array': array.toMap(), + 'element': element.toMap(), + }, + }; + } +} + +/// Represents an array length function expression +class _ArrayLengthExpression extends FunctionExpression { + final Expression expression; + + _ArrayLengthExpression(this.expression); + + @override + String get name => 'array_length'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array reverse function expression +class _ArrayReverseExpression extends FunctionExpression { + final Expression expression; + + _ArrayReverseExpression(this.expression); + + @override + String get name => 'array_reverse'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array sum function expression +class _ArraySumExpression extends FunctionExpression { + final Expression expression; + + _ArraySumExpression(this.expression); + + @override + String get name => 'array_sum'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an array slice function expression +class _ArraySliceExpression extends FunctionExpression { + final Expression array; + final Expression start; + final Expression end; + + _ArraySliceExpression(this.array, this.start, this.end); + + @override + String get name => 'array_slice'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'array': array.toMap(), + 'start': start.toMap(), + 'end': end.toMap(), + }, + }; + } +} + +// ============================================================================ +// CONDITIONAL / LOGIC OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an ifAbsent function expression +class _IfAbsentExpression extends FunctionExpression { + final Expression expression; + final Expression elseExpr; + + _IfAbsentExpression(this.expression, this.elseExpr); + + @override + String get name => 'if_absent'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'else': elseExpr.toMap(), + }, + }; + } +} + +/// Represents an ifError function expression +class _IfErrorExpression extends FunctionExpression { + final Expression expression; + final Expression catchExpr; + + _IfErrorExpression(this.expression, this.catchExpr); + + @override + String get name => 'if_error'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'catch': catchExpr.toMap(), + }, + }; + } +} + +/// Represents an isAbsent function expression +class _IsAbsentExpression extends BooleanExpression { + final Expression expression; + + _IsAbsentExpression(this.expression); + + @override + String get name => 'is_absent'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an isError function expression +class _IsErrorExpression extends BooleanExpression { + final Expression expression; + + _IsErrorExpression(this.expression); + + @override + String get name => 'is_error'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents an exists function expression +class _ExistsExpression extends BooleanExpression { + final Expression expression; + + _ExistsExpression(this.expression); + + @override + String get name => 'exists'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a conditional (ternary) function expression +class _ConditionalExpression extends FunctionExpression { + final BooleanExpression condition; + final Expression thenExpr; + final Expression elseExpr; + + _ConditionalExpression(this.condition, this.thenExpr, this.elseExpr); + + @override + String get name => 'conditional'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'condition': condition.toMap(), + 'then': thenExpr.toMap(), + 'else': elseExpr.toMap(), + }, + }; + } +} + +// ============================================================================ +// TYPE CONVERSION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an asBoolean function expression +class _AsBooleanExpression extends BooleanExpression { + final Expression expression; + + _AsBooleanExpression(this.expression); + + @override + String get name => 'as_boolean'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a toStringWithFormat function expression +class _ToStringWithFormatExpression extends FunctionExpression { + final Expression expression; + final Expression format; + + _ToStringWithFormatExpression(this.expression, this.format); + + @override + String get name => 'to_string_with_format'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'format': format.toMap(), + }, + }; + } +} + +// ============================================================================ +// BITWISE OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a bitAnd function expression +class _BitAndExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitAndExpression(this.left, this.right); + + @override + String get name => 'bit_and'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitOr function expression +class _BitOrExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitOrExpression(this.left, this.right); + + @override + String get name => 'bit_or'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitXor function expression +class _BitXorExpression extends FunctionExpression { + final Expression left; + final Expression right; + + _BitXorExpression(this.left, this.right); + + @override + String get name => 'bit_xor'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'left': left.toMap(), + 'right': right.toMap(), + }, + }; + } +} + +/// Represents a bitNot function expression +class _BitNotExpression extends FunctionExpression { + final Expression expression; + + _BitNotExpression(this.expression); + + @override + String get name => 'bit_not'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a bitLeftShift function expression +class _BitLeftShiftExpression extends FunctionExpression { + final Expression expression; + final Expression amount; + + _BitLeftShiftExpression(this.expression, this.amount); + + @override + String get name => 'bit_left_shift'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'amount': amount.toMap(), + }, + }; + } +} + +/// Represents a bitRightShift function expression +class _BitRightShiftExpression extends FunctionExpression { + final Expression expression; + final Expression amount; + + _BitRightShiftExpression(this.expression, this.amount); + + @override + String get name => 'bit_right_shift'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + 'amount': amount.toMap(), + }, + }; + } +} + +// ============================================================================ +// DOCUMENT / PATH OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a documentId function expression +class _DocumentIdExpression extends FunctionExpression { + final Expression expression; + + _DocumentIdExpression(this.expression); + + @override + String get name => 'document_id'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a collectionId function expression +class _CollectionIdExpression extends FunctionExpression { + final Expression expression; + + _CollectionIdExpression(this.expression); + + @override + String get name => 'collection_id'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a documentIdFromRef function expression +class _DocumentIdFromRefExpression extends FunctionExpression { + final DocumentReference docRef; + + _DocumentIdFromRefExpression(this.docRef); + + @override + String get name => 'document_id_from_ref'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'doc_ref': docRef.path, + }, + }; + } +} + +// ============================================================================ +// MAP OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a mapGet function expression +class _MapGetExpression extends FunctionExpression { + final Expression map; + final Expression key; + + _MapGetExpression(this.map, this.key); + + @override + String get name => 'map_get'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'map': map.toMap(), + 'key': key.toMap(), + }, + }; + } +} + +/// Represents a mapKeys function expression +class _MapKeysExpression extends FunctionExpression { + final Expression expression; + + _MapKeysExpression(this.expression); + + @override + String get name => 'map_keys'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +/// Represents a mapValues function expression +class _MapValuesExpression extends FunctionExpression { + final Expression expression; + + _MapValuesExpression(this.expression); + + @override + String get name => 'map_values'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + +// ============================================================================ +// TIMESTAMP OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents a currentTimestamp function expression +class _CurrentTimestampExpression extends FunctionExpression { + _CurrentTimestampExpression(); + + @override + String get name => 'current_timestamp'; + + @override + Map toMap() { + return { + 'name': name, + }; + } +} + +/// Represents a timestampAdd function expression +class _TimestampAddExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + final Expression amount; + + _TimestampAddExpression(this.timestamp, this.unit, this.amount); + + @override + String get name => 'timestamp_add'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + 'amount': amount.toMap(), + }, + }; } +} - static BooleanExpression _combineExpressions( - List expressions, - String operator, - ) { - if (expressions.length == 1) return expressions.first; +/// Represents a timestampSubtract function expression +class _TimestampSubtractExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + final Expression amount; - // Create a nested structure for multiple expressions - BooleanExpression result = expressions.first; - for (int i = 1; i < expressions.length; i++) { - if (operator == 'and') { - result = PipelineFilter.and(result, expressions[i]); - } else { - result = PipelineFilter.or(result, expressions[i]); - } - } - return result; + _TimestampSubtractExpression(this.timestamp, this.unit, this.amount); + + @override + String get name => 'timestamp_subtract'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + 'amount': amount.toMap(), + }, + }; } +} + +/// Represents a timestampDiff function expression +class _TimestampDiffExpression extends FunctionExpression { + final Expression timestamp1; + final Expression timestamp2; + final String unit; + + _TimestampDiffExpression(this.timestamp1, this.timestamp2, this.unit); @override - String get name => 'filter'; + String get name => 'timestamp_diff'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp1': timestamp1.toMap(), + 'timestamp2': timestamp2.toMap(), + 'unit': unit, + }, + }; + } +} + +/// Represents a timestampTruncate function expression +class _TimestampTruncateExpression extends FunctionExpression { + final Expression timestamp; + final String unit; + + _TimestampTruncateExpression(this.timestamp, this.unit); + + @override + String get name => 'timestamp_truncate'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'timestamp': timestamp.toMap(), + 'unit': unit, + }, + }; + } +} + +/// Represents a distance function expression +class _DistanceExpression extends FunctionExpression { + final Expression geoPoint1; + final Expression geoPoint2; + + _DistanceExpression(this.geoPoint1, this.geoPoint2); + + @override + String get name => 'distance'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'geo_point1': geoPoint1.toMap(), + 'geo_point2': geoPoint2.toMap(), + }, + }; + } +} + +// ============================================================================ +// SPECIAL OPERATION EXPRESSION CLASSES +// ============================================================================ + +/// Represents an equalAny (IN) function expression +class _EqualAnyExpression extends BooleanExpression { + final Expression value; + final List values; + + _EqualAnyExpression(this.value, this.values); + + @override + String get name => 'equal_any'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value.toMap(), + 'values': values.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a notEqualAny (NOT IN) function expression +class _NotEqualAnyExpression extends BooleanExpression { + final Expression value; + final List values; + + _NotEqualAnyExpression(this.value, this.values); + + @override + String get name => 'not_equal_any'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'value': value.toMap(), + 'values': values.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents an array expression +class _ArrayExpression extends FunctionExpression { + final List elements; + + _ArrayExpression(this.elements); + + @override + String get name => 'array'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'elements': elements.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a map expression +class _MapExpression extends FunctionExpression { + final Map data; + + _MapExpression(this.data); + + @override + String get name => 'map'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'data': data.map((k, v) => MapEntry(k, v.toMap())), + }, + }; + } +} + +/// Represents a mapFromPairs expression +class _MapFromPairsExpression extends FunctionExpression { + final List keyValuePairs; + + _MapFromPairsExpression(this.keyValuePairs); + + @override + String get name => 'map_from_pairs'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'pairs': keyValuePairs.map((expr) => expr.toMap()).toList(), + }, + }; + } +} + +/// Represents a raw function expression +class _RawFunctionExpression extends FunctionExpression { + final String functionName; + final List args; + + _RawFunctionExpression(this.functionName, this.args); + + @override + String get name => functionName; + + @override + Map toMap() { + return { + 'name': name, + 'args': args.map((expr) => expr.toMap()).toList(), + }; + } +} + +// ============================================================================ +// AGGREGATE FUNCTION CLASSES +// ============================================================================ + +/// Sums numeric values of the specified expression +class Sum extends PipelineAggregateFunction { + final Expression expression; + + Sum(this.expression); + + @override + String get name => 'sum'; @override Map toMap() { final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} - if (_andExpression != null) { - map['args'] = { - 'operator': 'and', - 'expressions': [_andExpression.toMap()], - }; - return map; - } +/// Calculates average of numeric values of the specified expression +class Average extends PipelineAggregateFunction { + final Expression expression; - if (_orExpression != null) { - map['args'] = { - 'operator': 'or', - 'expressions': [_orExpression.toMap()], - }; - return map; - } + Average(this.expression); - final args = {}; - if (field is String) { - args['field'] = field; - } else if (field is Field) { - args['field'] = (field as Field).fieldName; - } + @override + String get name => 'average'; - if (isEqualTo != null) args['isEqualTo'] = isEqualTo; - if (isNotEqualTo != null) args['isNotEqualTo'] = isNotEqualTo; - if (isLessThan != null) args['isLessThan'] = isLessThan; - if (isLessThanOrEqualTo != null) { - args['isLessThanOrEqualTo'] = isLessThanOrEqualTo; - } - if (isGreaterThan != null) args['isGreaterThan'] = isGreaterThan; - if (isGreaterThanOrEqualTo != null) { - args['isGreaterThanOrEqualTo'] = isGreaterThanOrEqualTo; - } - if (arrayContains != null) args['arrayContains'] = arrayContains; - if (arrayContainsAny != null) { - args['arrayContainsAny'] = arrayContainsAny; - } - if (whereIn != null) args['whereIn'] = whereIn; - if (whereNotIn != null) args['whereNotIn'] = whereNotIn; - if (isNull != null) args['isNull'] = isNull; - if (isNotNull != null) args['isNotNull'] = isNotNull; + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} - map['args'] = args; +/// Counts distinct values of the specified expression +class CountDistinct extends PipelineAggregateFunction { + final Expression expression; + + CountDistinct(this.expression); + + @override + String get name => 'count_distinct'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds minimum value of the specified expression +class Minimum extends PipelineAggregateFunction { + final Expression expression; + + Minimum(this.expression); + + @override + String get name => 'minimum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds maximum value of the specified expression +class Maximum extends PipelineAggregateFunction { + final Expression expression; + + Maximum(this.expression); + + @override + String get name => 'maximum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; return map; } } From d92a2b0d3480ff5ac0bc8d1768571d06b6f12ce0 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 13 Feb 2026 11:17:58 +0000 Subject: [PATCH 05/20] chore: add support for Pigeon --- .../GeneratedAndroidFirebaseFirestore.java | 273 +++++- .../cloud_firestore/FirestoreMessages.g.m | 137 ++- .../Public/FirestoreMessages.g.h | 29 +- .../cloud_firestore/lib/src/firestore.dart | 1 + .../cloud_firestore/windows/messages.g.cpp | 177 +++- .../cloud_firestore/windows/messages.g.h | 72 +- .../method_channel_firestore.dart | 28 +- .../method_channel_pipeline_snapshot.dart | 15 +- .../lib/src/pigeon/messages.pigeon.dart | 409 ++++---- .../pigeons/messages.dart | 29 + .../test/pigeon/test_api.dart | 899 +++++++----------- 11 files changed, 1275 insertions(+), 794 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index f7d24bc7c7ec..3638853507b6 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -12,6 +12,7 @@ import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -325,7 +326,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(persistenceEnabled); toListResult.add(host); @@ -433,7 +434,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(appName); toListResult.add((settings == null) ? null : settings.toList()); @@ -512,7 +513,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(hasPendingWrites); toListResult.add(isFromCache); @@ -603,7 +604,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(path); toListResult.add(data); @@ -724,7 +725,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add((document == null) ? null : document.toList()); @@ -833,7 +834,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(documents); toListResult.add(documentChanges); @@ -856,6 +857,187 @@ public ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PigeonPipelineResult { + private @NonNull String documentPath; + + public @NonNull String getDocumentPath() { + return documentPath; + } + + public void setDocumentPath(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"documentPath\" is null."); + } + this.documentPath = setterArg; + } + + private @NonNull Long createTime; + + public @NonNull Long getCreateTime() { + return createTime; + } + + public void setCreateTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"createTime\" is null."); + } + this.createTime = setterArg; + } + + private @NonNull Long updateTime; + + public @NonNull Long getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"updateTime\" is null."); + } + this.updateTime = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PigeonPipelineResult() {} + + public static final class Builder { + + private @Nullable String documentPath; + + public @NonNull Builder setDocumentPath(@NonNull String setterArg) { + this.documentPath = setterArg; + return this; + } + + private @Nullable Long createTime; + + public @NonNull Builder setCreateTime(@NonNull Long setterArg) { + this.createTime = setterArg; + return this; + } + + private @Nullable Long updateTime; + + public @NonNull Builder setUpdateTime(@NonNull Long setterArg) { + this.updateTime = setterArg; + return this; + } + + public @NonNull PigeonPipelineResult build() { + PigeonPipelineResult pigeonReturn = new PigeonPipelineResult(); + pigeonReturn.setDocumentPath(documentPath); + pigeonReturn.setCreateTime(createTime); + pigeonReturn.setUpdateTime(updateTime); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(documentPath); + toListResult.add(createTime); + toListResult.add(updateTime); + return toListResult; + } + + static @NonNull PigeonPipelineResult fromList(@NonNull ArrayList list) { + PigeonPipelineResult pigeonResult = new PigeonPipelineResult(); + Object documentPath = list.get(0); + pigeonResult.setDocumentPath((String) documentPath); + Object createTime = list.get(1); + pigeonResult.setCreateTime( + (createTime == null) + ? null + : ((createTime instanceof Integer) ? (Integer) createTime : (Long) createTime)); + Object updateTime = list.get(2); + pigeonResult.setUpdateTime( + (updateTime == null) + ? null + : ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime)); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PigeonPipelineSnapshot { + private @NonNull List results; + + public @NonNull List getResults() { + return results; + } + + public void setResults(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"results\" is null."); + } + this.results = setterArg; + } + + private @NonNull Long executionTime; + + public @NonNull Long getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"executionTime\" is null."); + } + this.executionTime = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PigeonPipelineSnapshot() {} + + public static final class Builder { + + private @Nullable List results; + + public @NonNull Builder setResults(@NonNull List setterArg) { + this.results = setterArg; + return this; + } + + private @Nullable Long executionTime; + + public @NonNull Builder setExecutionTime(@NonNull Long setterArg) { + this.executionTime = setterArg; + return this; + } + + public @NonNull PigeonPipelineSnapshot build() { + PigeonPipelineSnapshot pigeonReturn = new PigeonPipelineSnapshot(); + pigeonReturn.setResults(results); + pigeonReturn.setExecutionTime(executionTime); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(results); + toListResult.add(executionTime); + return toListResult; + } + + static @NonNull PigeonPipelineSnapshot fromList(@NonNull ArrayList list) { + PigeonPipelineSnapshot pigeonResult = new PigeonPipelineSnapshot(); + Object results = list.get(0); + pigeonResult.setResults((List) results); + Object executionTime = list.get(1); + pigeonResult.setExecutionTime( + (executionTime == null) + ? null + : ((executionTime instanceof Integer) + ? (Integer) executionTime + : (Long) executionTime)); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonGetOptions { private @NonNull Source source; @@ -913,7 +1095,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(source == null ? null : source.index); toListResult.add(serverTimestampBehavior == null ? null : serverTimestampBehavior.index); @@ -978,7 +1160,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(merge); toListResult.add(mergeFields); @@ -1087,7 +1269,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add(path); @@ -1219,7 +1401,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(path); toListResult.add(data); @@ -1422,7 +1604,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(9); toListResult.add(where); toListResult.add(orderBy); @@ -1517,7 +1699,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1605,7 +1787,7 @@ public static final class Builder { } @NonNull - public ArrayList toList() { + ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1632,7 +1814,7 @@ public interface Result { void error(@NonNull Throwable error); } - private static class FirebaseFirestoreHostApiCodec extends FlutterFirebaseFirestoreMessageCodec { + private static class FirebaseFirestoreHostApiCodec extends StandardMessageCodec { public static final FirebaseFirestoreHostApiCodec INSTANCE = new FirebaseFirestoreHostApiCodec(); @@ -1660,12 +1842,16 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 136: return PigeonGetOptions.fromList((ArrayList) readValue(buffer)); case (byte) 137: - return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); + return PigeonPipelineResult.fromList((ArrayList) readValue(buffer)); case (byte) 138: - return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + return PigeonPipelineSnapshot.fromList((ArrayList) readValue(buffer)); case (byte) 139: - return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); case (byte) 140: + return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + case (byte) 141: + return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + case (byte) 142: return PigeonTransactionCommand.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -1701,17 +1887,23 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof PigeonGetOptions) { stream.write(136); writeValue(stream, ((PigeonGetOptions) value).toList()); - } else if (value instanceof PigeonQueryParameters) { + } else if (value instanceof PigeonPipelineResult) { stream.write(137); + writeValue(stream, ((PigeonPipelineResult) value).toList()); + } else if (value instanceof PigeonPipelineSnapshot) { + stream.write(138); + writeValue(stream, ((PigeonPipelineSnapshot) value).toList()); + } else if (value instanceof PigeonQueryParameters) { + stream.write(139); writeValue(stream, ((PigeonQueryParameters) value).toList()); } else if (value instanceof PigeonQuerySnapshot) { - stream.write(138); + stream.write(140); writeValue(stream, ((PigeonQuerySnapshot) value).toList()); } else if (value instanceof PigeonSnapshotMetadata) { - stream.write(139); + stream.write(141); writeValue(stream, ((PigeonSnapshotMetadata) value).toList()); } else if (value instanceof PigeonTransactionCommand) { - stream.write(140); + stream.write(142); writeValue(stream, ((PigeonTransactionCommand) value).toList()); } else { super.writeValue(stream, value); @@ -1836,6 +2028,12 @@ void persistenceCacheIndexManagerRequest( @NonNull PersistenceCacheIndexManagerRequest request, @NonNull Result result); + void executePipeline( + @NonNull FirestorePigeonFirebaseApp app, + @NonNull List> stages, + @Nullable Map options, + @NonNull Result result); + /** The codec used by FirebaseFirestoreHostApi. */ static @NonNull MessageCodec getCodec() { return FirebaseFirestoreHostApiCodec.INSTANCE; @@ -2624,6 +2822,39 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0); + List> stagesArg = (List>) args.get(1); + Map optionsArg = (Map) args.get(2); + Result resultCallback = + new Result() { + public void success(PigeonPipelineSnapshot result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.executePipeline(appArg, stagesArg, optionsArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 34e717b694a7..2818f5742f4b 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -5,8 +5,6 @@ // See also: https://pub.dev/packages/pigeon #import "FirestoreMessages.g.h" -#import "FLTFirebaseFirestoreReader.h" -#import "FLTFirebaseFirestoreWriter.h" #if TARGET_OS_OSX #import @@ -168,6 +166,18 @@ + (nullable PigeonQuerySnapshot *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface PigeonPipelineResult () ++ (PigeonPipelineResult *)fromList:(NSArray *)list; ++ (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface PigeonPipelineSnapshot () ++ (PigeonPipelineSnapshot *)fromList:(NSArray *)list; ++ (nullable PigeonPipelineSnapshot *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface PigeonGetOptions () + (PigeonGetOptions *)fromList:(NSArray *)list; + (nullable PigeonGetOptions *)nullableFromList:(NSArray *)list; @@ -349,7 +359,7 @@ + (instancetype)makeWithType:(DocumentChangeType)type pigeonResult.type = type; pigeonResult.document = document; pigeonResult.oldIndex = oldIndex; - pigeonResult.index = newIndex; + pigeonResult.newIndex = newIndex; return pigeonResult; } + (PigeonDocumentChange *)fromList:(NSArray *)list { @@ -360,8 +370,8 @@ + (PigeonDocumentChange *)fromList:(NSArray *)list { NSAssert(pigeonResult.document != nil, @""); pigeonResult.oldIndex = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.oldIndex != nil, @""); - pigeonResult.index = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.index != nil, @""); + pigeonResult.newIndex = GetNullableObjectAtIndex(list, 3); + NSAssert(pigeonResult.newIndex != nil, @""); return pigeonResult; } + (nullable PigeonDocumentChange *)nullableFromList:(NSArray *)list { @@ -372,7 +382,7 @@ - (NSArray *)toList { @(self.type), (self.document ? [self.document toList] : [NSNull null]), (self.oldIndex ?: [NSNull null]), - (self.index ?: [NSNull null]), + (self.newIndex ?: [NSNull null]), ]; } @end @@ -410,6 +420,65 @@ - (NSArray *)toList { } @end +@implementation PigeonPipelineResult ++ (instancetype)makeWithDocumentPath:(NSString *)documentPath + createTime:(NSNumber *)createTime + updateTime:(NSNumber *)updateTime { + PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; + pigeonResult.documentPath = documentPath; + pigeonResult.createTime = createTime; + pigeonResult.updateTime = updateTime; + return pigeonResult; +} ++ (PigeonPipelineResult *)fromList:(NSArray *)list { + PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; + pigeonResult.documentPath = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.documentPath != nil, @""); + pigeonResult.createTime = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.createTime != nil, @""); + pigeonResult.updateTime = GetNullableObjectAtIndex(list, 2); + NSAssert(pigeonResult.updateTime != nil, @""); + return pigeonResult; +} ++ (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list { + return (list) ? [PigeonPipelineResult fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.documentPath ?: [NSNull null]), + (self.createTime ?: [NSNull null]), + (self.updateTime ?: [NSNull null]), + ]; +} +@end + +@implementation PigeonPipelineSnapshot ++ (instancetype)makeWithResults:(NSArray *)results + executionTime:(NSNumber *)executionTime { + PigeonPipelineSnapshot *pigeonResult = [[PigeonPipelineSnapshot alloc] init]; + pigeonResult.results = results; + pigeonResult.executionTime = executionTime; + return pigeonResult; +} ++ (PigeonPipelineSnapshot *)fromList:(NSArray *)list { + PigeonPipelineSnapshot *pigeonResult = [[PigeonPipelineSnapshot alloc] init]; + pigeonResult.results = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.results != nil, @""); + pigeonResult.executionTime = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.executionTime != nil, @""); + return pigeonResult; +} ++ (nullable PigeonPipelineSnapshot *)nullableFromList:(NSArray *)list { + return (list) ? [PigeonPipelineSnapshot fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.results ?: [NSNull null]), + (self.executionTime ?: [NSNull null]), + ]; +} +@end + @implementation PigeonGetOptions + (instancetype)makeWithSource:(Source)source serverTimestampBehavior:(ServerTimestampBehavior)serverTimestampBehavior { @@ -649,7 +718,7 @@ - (NSArray *)toList { } @end -@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader +@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader @end @implementation FirebaseFirestoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { @@ -673,12 +742,16 @@ - (nullable id)readValueOfType:(UInt8)type { case 136: return [PigeonGetOptions fromList:[self readValue]]; case 137: - return [PigeonQueryParameters fromList:[self readValue]]; + return [PigeonPipelineResult fromList:[self readValue]]; case 138: - return [PigeonQuerySnapshot fromList:[self readValue]]; + return [PigeonPipelineSnapshot fromList:[self readValue]]; case 139: - return [PigeonSnapshotMetadata fromList:[self readValue]]; + return [PigeonQueryParameters fromList:[self readValue]]; case 140: + return [PigeonQuerySnapshot fromList:[self readValue]]; + case 141: + return [PigeonSnapshotMetadata fromList:[self readValue]]; + case 142: return [PigeonTransactionCommand fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -686,7 +759,7 @@ - (nullable id)readValueOfType:(UInt8)type { } @end -@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter +@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter @end @implementation FirebaseFirestoreHostApiCodecWriter - (void)writeValue:(id)value { @@ -717,18 +790,24 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[PigeonGetOptions class]]) { [self writeByte:136]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { + } else if ([value isKindOfClass:[PigeonPipelineResult class]]) { [self writeByte:137]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { + } else if ([value isKindOfClass:[PigeonPipelineSnapshot class]]) { [self writeByte:138]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { [self writeByte:139]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { [self writeByte:140]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + [self writeByte:141]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + [self writeByte:142]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1379,4 +1458,32 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.cloud_firestore_platform_interface." + @"FirebaseFirestoreHostApi.executePipeline" + binaryMessenger:binaryMessenger + codec:FirebaseFirestoreHostApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(executePipelineApp:stages:options:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(executePipelineApp:stages:options:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); + NSArray *> *arg_stages = GetNullableObjectAtIndex(args, 1); + NSDictionary *arg_options = GetNullableObjectAtIndex(args, 2); + [api executePipelineApp:arg_app + stages:arg_stages + options:arg_options + completion:^(PigeonPipelineSnapshot *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 2eabaeaef25f..1f43fa87bf89 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -165,6 +165,8 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @class PigeonDocumentSnapshot; @class PigeonDocumentChange; @class PigeonQuerySnapshot; +@class PigeonPipelineResult; +@class PigeonPipelineSnapshot; @class PigeonGetOptions; @class PigeonDocumentOption; @class PigeonTransactionCommand; @@ -229,7 +231,7 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, assign) DocumentChangeType type; @property(nonatomic, strong) PigeonDocumentSnapshot *document; @property(nonatomic, strong) NSNumber *oldIndex; -@property(nonatomic, strong) NSNumber *index; +@property(nonatomic, strong) NSNumber *newIndex; @end @interface PigeonQuerySnapshot : NSObject @@ -243,6 +245,26 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, strong) PigeonSnapshotMetadata *metadata; @end +@interface PigeonPipelineResult : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithDocumentPath:(NSString *)documentPath + createTime:(NSNumber *)createTime + updateTime:(NSNumber *)updateTime; +@property(nonatomic, copy) NSString *documentPath; +@property(nonatomic, strong) NSNumber *createTime; +@property(nonatomic, strong) NSNumber *updateTime; +@end + +@interface PigeonPipelineSnapshot : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithResults:(NSArray *)results + executionTime:(NSNumber *)executionTime; +@property(nonatomic, strong) NSArray *results; +@property(nonatomic, strong) NSNumber *executionTime; +@end + @interface PigeonGetOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; @@ -416,6 +438,11 @@ NSObject *FirebaseFirestoreHostApiGetCodec(void); - (void)persistenceCacheIndexManagerRequestApp:(FirestorePigeonFirebaseApp *)app request:(PersistenceCacheIndexManagerRequest)request completion:(void (^)(FlutterError *_Nullable))completion; +- (void)executePipelineApp:(FirestorePigeonFirebaseApp *)app + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(PigeonPipelineSnapshot *_Nullable, + FlutterError *_Nullable))completion; @end extern void FirebaseFirestoreHostApiSetup(id binaryMessenger, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 3e583c3f3331..32fe4f3e16db 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -354,6 +354,7 @@ class FirebaseFirestore extends FirebasePluginPlatform { /// .limit(10) /// .execute(); /// ``` + // ignore: use_to_and_as_if_applicable PipelineSource pipeline() { return PipelineSource._(this); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index bb9f58d5775d..a70d4a7b95d5 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -397,6 +397,88 @@ PigeonQuerySnapshot PigeonQuerySnapshot::FromEncodableList( return decoded; } +// PigeonPipelineResult + +PigeonPipelineResult::PigeonPipelineResult(const std::string& document_path, + int64_t create_time, + int64_t update_time) + : document_path_(document_path), + create_time_(create_time), + update_time_(update_time) {} + +const std::string& PigeonPipelineResult::document_path() const { + return document_path_; +} + +void PigeonPipelineResult::set_document_path(std::string_view value_arg) { + document_path_ = value_arg; +} + +int64_t PigeonPipelineResult::create_time() const { return create_time_; } + +void PigeonPipelineResult::set_create_time(int64_t value_arg) { + create_time_ = value_arg; +} + +int64_t PigeonPipelineResult::update_time() const { return update_time_; } + +void PigeonPipelineResult::set_update_time(int64_t value_arg) { + update_time_ = value_arg; +} + +EncodableList PigeonPipelineResult::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(document_path_)); + list.push_back(EncodableValue(create_time_)); + list.push_back(EncodableValue(update_time_)); + return list; +} + +PigeonPipelineResult PigeonPipelineResult::FromEncodableList( + const EncodableList& list) { + PigeonPipelineResult decoded(std::get(list[0]), + list[1].LongValue(), list[2].LongValue()); + return decoded; +} + +// PigeonPipelineSnapshot + +PigeonPipelineSnapshot::PigeonPipelineSnapshot(const EncodableList& results, + int64_t execution_time) + : results_(results), execution_time_(execution_time) {} + +const EncodableList& PigeonPipelineSnapshot::results() const { + return results_; +} + +void PigeonPipelineSnapshot::set_results(const EncodableList& value_arg) { + results_ = value_arg; +} + +int64_t PigeonPipelineSnapshot::execution_time() const { + return execution_time_; +} + +void PigeonPipelineSnapshot::set_execution_time(int64_t value_arg) { + execution_time_ = value_arg; +} + +EncodableList PigeonPipelineSnapshot::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(results_)); + list.push_back(EncodableValue(execution_time_)); + return list; +} + +PigeonPipelineSnapshot PigeonPipelineSnapshot::FromEncodableList( + const EncodableList& list) { + PigeonPipelineSnapshot decoded(std::get(list[0]), + list[1].LongValue()); + return decoded; +} + // PigeonGetOptions PigeonGetOptions::PigeonGetOptions( @@ -1034,20 +1116,25 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(PigeonGetOptions::FromEncodableList( std::get(ReadValue(stream)))); case 137: - return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( + return CustomEncodableValue(PigeonPipelineResult::FromEncodableList( std::get(ReadValue(stream)))); case 138: - return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + return CustomEncodableValue(PigeonPipelineSnapshot::FromEncodableList( std::get(ReadValue(stream)))); case 139: - return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( std::get(ReadValue(stream)))); case 140: + return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + std::get(ReadValue(stream)))); + case 141: + return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + std::get(ReadValue(stream)))); + case 142: return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); default: - return cloud_firestore_windows::FirestoreCodec::ReadValueOfType(type, - stream); + return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } @@ -1127,8 +1214,24 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( stream); return; } - if (custom_value->type() == typeid(PigeonQueryParameters)) { + if (custom_value->type() == typeid(PigeonPipelineResult)) { stream->WriteByte(137); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(PigeonPipelineSnapshot)) { + stream->WriteByte(138); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(PigeonQueryParameters)) { + stream->WriteByte(139); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1136,7 +1239,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonQuerySnapshot)) { - stream->WriteByte(138); + stream->WriteByte(140); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1144,7 +1247,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonSnapshotMetadata)) { - stream->WriteByte(139); + stream->WriteByte(141); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1152,7 +1255,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonTransactionCommand)) { - stream->WriteByte(140); + stream->WriteByte(142); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1160,7 +1263,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } } - cloud_firestore_windows::FirestoreCodec::WriteValue(value, stream); + flutter::StandardCodecSerializer::WriteValue(value, stream); } /// The codec used by FirebaseFirestoreHostApi. @@ -2290,8 +2393,8 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("request_arg unexpectedly null.")); return; } - const PersistenceCacheIndexManagerRequestEnum& request_arg = - (PersistenceCacheIndexManagerRequestEnum) + const PersistenceCacheIndexManagerRequest& request_arg = + (PersistenceCacheIndexManagerRequest) encodable_request_arg.LongValue(); api->PersistenceCacheIndexManagerRequest( app_arg, request_arg, @@ -2312,6 +2415,56 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel->SetMessageHandler(nullptr); } } + { + auto channel = std::make_unique>( + binary_messenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface." + "FirebaseFirestoreHostApi.executePipeline", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_stages_arg = args.at(1); + if (encodable_stages_arg.IsNull()) { + reply(WrapError("stages_arg unexpectedly null.")); + return; + } + const auto& stages_arg = + std::get(encodable_stages_arg); + const auto& encodable_options_arg = args.at(2); + const auto* options_arg = + std::get_if(&encodable_options_arg); + api->ExecutePipeline( + app_arg, stages_arg, options_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } } EncodableValue FirebaseFirestoreHostApi::WrapError( diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 8aecb887facc..40e4369d79d8 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -15,8 +15,6 @@ #include #include -#include "firestore_codec.h" - namespace cloud_firestore_windows { // Generated class from Pigeon. @@ -136,7 +134,7 @@ enum class AggregateSource { // [PersistenceCacheIndexManagerRequest] represents the request types for the // persistence cache index manager. -enum class PersistenceCacheIndexManagerRequestEnum { +enum class PersistenceCacheIndexManagerRequest { enableIndexAutoCreation = 0, disableIndexAutoCreation = 1, deleteAllIndexes = 2 @@ -239,11 +237,10 @@ class PigeonSnapshotMetadata { bool is_from_cache() const; void set_is_from_cache(bool value_arg); + private: static PigeonSnapshotMetadata FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class PigeonDocumentSnapshot; friend class PigeonQuerySnapshot; friend class FirebaseFirestoreHostApi; @@ -274,11 +271,10 @@ class PigeonDocumentSnapshot { const PigeonSnapshotMetadata& metadata() const; void set_metadata(const PigeonSnapshotMetadata& value_arg); + private: static PigeonDocumentSnapshot FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class PigeonDocumentChange; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; @@ -307,11 +303,10 @@ class PigeonDocumentChange { int64_t new_index() const; void set_new_index(int64_t value_arg); + private: static PigeonDocumentChange FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; - - private: friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; DocumentChangeType type_; @@ -348,6 +343,56 @@ class PigeonQuerySnapshot { PigeonSnapshotMetadata metadata_; }; +// Generated class from Pigeon that represents data sent in messages. +class PigeonPipelineResult { + public: + // Constructs an object setting all fields. + explicit PigeonPipelineResult(const std::string& document_path, + int64_t create_time, int64_t update_time); + + const std::string& document_path() const; + void set_document_path(std::string_view value_arg); + + int64_t create_time() const; + void set_create_time(int64_t value_arg); + + int64_t update_time() const; + void set_update_time(int64_t value_arg); + + private: + static PigeonPipelineResult FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + std::string document_path_; + int64_t create_time_; + int64_t update_time_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class PigeonPipelineSnapshot { + public: + // Constructs an object setting all fields. + explicit PigeonPipelineSnapshot(const flutter::EncodableList& results, + int64_t execution_time); + + const flutter::EncodableList& results() const; + void set_results(const flutter::EncodableList& value_arg); + + int64_t execution_time() const; + void set_execution_time(int64_t value_arg); + + private: + static PigeonPipelineSnapshot FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + flutter::EncodableList results_; + int64_t execution_time_; +}; + // Generated class from Pigeon that represents data sent in messages. class PigeonGetOptions { public: @@ -613,7 +658,7 @@ class AggregateQueryResponse { }; class FirebaseFirestoreHostApiCodecSerializer - : public cloud_firestore_windows::FirestoreCodec { + : public flutter::StandardCodecSerializer { public: FirebaseFirestoreHostApiCodecSerializer(); inline static FirebaseFirestoreHostApiCodecSerializer& GetInstance() { @@ -724,8 +769,13 @@ class FirebaseFirestoreHostApi { std::function reply)> result) = 0; virtual void PersistenceCacheIndexManagerRequest( const FirestorePigeonFirebaseApp& app, - const PersistenceCacheIndexManagerRequestEnum& request, + const PersistenceCacheIndexManagerRequest& request, std::function reply)> result) = 0; + virtual void ExecutePipeline( + const FirestorePigeonFirebaseApp& app, + const flutter::EncodableList& stages, + const flutter::EncodableMap* options, + std::function reply)> result) = 0; // The codec used by FirebaseFirestoreHostApi. static const flutter::StandardMessageCodec& GetCodec(); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart index 73aacc45f2bb..889ea4c5351b 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart @@ -364,21 +364,25 @@ class MethodChannelFirebaseFirestore extends FirebaseFirestorePlatform { Map? options, }) async { try { - final MethodChannel channel = const MethodChannel( - 'plugins.flutter.io/firebase_firestore', + // Convert stages to Pigeon format (List?>) + final List?> pigeonStages = stages.map((stage) { + return stage.map(MapEntry.new); + }).toList(); + + // Convert options to Pigeon format (Map?) + final Map? pigeonOptions = options?.map( + MapEntry.new, ); - final result = await channel.invokeMethod>( - 'Pipeline#execute', - { - 'app': pigeonApp, - 'stages': stages, - if (options != null) 'options': options, - }, + + final PigeonPipelineSnapshot result = await pigeonChannel.executePipeline( + pigeonApp, + pigeonStages, + pigeonOptions, ); - return MethodChannelPipelineSnapshot(this, pigeonApp, result!); - } on PlatformException catch (e, stack) { - throw convertPlatformException(e, stack); + return MethodChannelPipelineSnapshot(this, pigeonApp, result); + } catch (e, stack) { + convertPlatformException(e, stack); } } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart index 16cde78be465..51ff99ffcff9 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -12,22 +12,23 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { final List _results; final DateTime _executionTime; - /// Creates a [MethodChannelPipelineSnapshot] from the given [data] + /// Creates a [MethodChannelPipelineSnapshot] from the given [pigeonSnapshot] MethodChannelPipelineSnapshot( FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, - Map data, - ) : _results = (data['results'] as List) + PigeonPipelineSnapshot pigeonSnapshot, + ) : _results = (pigeonSnapshot.results ?? []) + .whereType() .map((result) => MethodChannelPipelineResult( firestore, pigeonApp, - result['document'] as String, - DateTime.fromMillisecondsSinceEpoch(result['createTime']), - DateTime.fromMillisecondsSinceEpoch(result['updateTime']), + result.documentPath, + DateTime.fromMillisecondsSinceEpoch(result.createTime), + DateTime.fromMillisecondsSinceEpoch(result.updateTime), )) .toList(), _executionTime = DateTime.fromMillisecondsSinceEpoch( - data['executionTime'] as int, + pigeonSnapshot.executionTime, ), super(); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index 85af6edc5260..0ea2ee91ccd5 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart'; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; @@ -301,6 +300,63 @@ class PigeonQuerySnapshot { } } +class PigeonPipelineResult { + PigeonPipelineResult({ + required this.documentPath, + required this.createTime, + required this.updateTime, + }); + + String documentPath; + + int createTime; + + int updateTime; + + Object encode() { + return [ + documentPath, + createTime, + updateTime, + ]; + } + + static PigeonPipelineResult decode(Object result) { + result as List; + return PigeonPipelineResult( + documentPath: result[0]! as String, + createTime: result[1]! as int, + updateTime: result[2]! as int, + ); + } +} + +class PigeonPipelineSnapshot { + PigeonPipelineSnapshot({ + required this.results, + required this.executionTime, + }); + + List results; + + int executionTime; + + Object encode() { + return [ + results, + executionTime, + ]; + } + + static PigeonPipelineSnapshot decode(Object result) { + result as List; + return PigeonPipelineSnapshot( + results: (result[0] as List?)!.cast(), + executionTime: result[1]! as int, + ); + } +} + class PigeonGetOptions { PigeonGetOptions({ required this.source, @@ -555,7 +611,7 @@ class AggregateQueryResponse { } } -class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { +class _FirebaseFirestoreHostApiCodec extends StandardMessageCodec { const _FirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { @@ -586,18 +642,24 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonPipelineResult) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonPipelineSnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -625,12 +687,16 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { case 136: return PigeonGetOptions.decode(readValue(buffer)!); case 137: - return PigeonQueryParameters.decode(readValue(buffer)!); + return PigeonPipelineResult.decode(readValue(buffer)!); case 138: - return PigeonQuerySnapshot.decode(readValue(buffer)!); + return PigeonPipelineSnapshot.decode(readValue(buffer)!); case 139: - return PigeonSnapshotMetadata.decode(readValue(buffer)!); + return PigeonQueryParameters.decode(readValue(buffer)!); case 140: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 141: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 142: return PigeonTransactionCommand.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -649,14 +715,11 @@ class FirebaseFirestoreHostApi { static const MessageCodec codec = _FirebaseFirestoreHostApiCodec(); Future loadBundle( - FirestorePigeonFirebaseApp arg_app, - Uint8List arg_bundle, - ) async { + FirestorePigeonFirebaseApp arg_app, Uint8List arg_bundle) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_bundle]) as List?; if (replyList == null) { @@ -680,16 +743,12 @@ class FirebaseFirestoreHostApi { } } - Future namedQueryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_name, - PigeonGetOptions arg_options, - ) async { + Future namedQueryGet(FirestorePigeonFirebaseApp arg_app, + String arg_name, PigeonGetOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_name, arg_options]) as List?; if (replyList == null) { @@ -715,10 +774,9 @@ class FirebaseFirestoreHostApi { Future clearPersistence(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -739,10 +797,9 @@ class FirebaseFirestoreHostApi { Future disableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -763,10 +820,9 @@ class FirebaseFirestoreHostApi { Future enableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -787,10 +843,9 @@ class FirebaseFirestoreHostApi { Future terminate(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -811,10 +866,9 @@ class FirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -834,14 +888,11 @@ class FirebaseFirestoreHostApi { } Future setIndexConfiguration( - FirestorePigeonFirebaseApp arg_app, - String arg_indexConfiguration, - ) async { + FirestorePigeonFirebaseApp arg_app, String arg_indexConfiguration) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_indexConfiguration]) as List?; if (replyList == null) { @@ -862,10 +913,9 @@ class FirebaseFirestoreHostApi { Future setLoggingEnabled(bool arg_loggingEnabled) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_loggingEnabled]) as List?; if (replyList == null) { @@ -885,13 +935,11 @@ class FirebaseFirestoreHostApi { } Future snapshotsInSyncSetup( - FirestorePigeonFirebaseApp arg_app, - ) async { + FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -915,16 +963,12 @@ class FirebaseFirestoreHostApi { } } - Future transactionCreate( - FirestorePigeonFirebaseApp arg_app, - int arg_timeout, - int arg_maxAttempts, - ) async { + Future transactionCreate(FirestorePigeonFirebaseApp arg_app, + int arg_timeout, int arg_maxAttempts) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_timeout, arg_maxAttempts]) as List?; @@ -950,18 +994,16 @@ class FirebaseFirestoreHostApi { } Future transactionStoreResult( - String arg_transactionId, - PigeonTransactionResult arg_resultType, - List? arg_commands, - ) async { + String arg_transactionId, + PigeonTransactionResult arg_resultType, + List? arg_commands) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( - [arg_transactionId, arg_resultType.index, arg_commands], - ) as List?; + [arg_transactionId, arg_resultType.index, arg_commands]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -979,15 +1021,13 @@ class FirebaseFirestoreHostApi { } Future transactionGet( - FirestorePigeonFirebaseApp arg_app, - String arg_transactionId, - String arg_path, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_transactionId, + String arg_path) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_transactionId, arg_path]) as List?; @@ -1012,15 +1052,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceSet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceSet(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1039,15 +1076,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceUpdate( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceUpdate(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1067,14 +1101,12 @@ class FirebaseFirestoreHostApi { } Future documentReferenceGet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1098,15 +1130,12 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceDelete( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request, - ) async { + Future documentReferenceDelete(FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1126,23 +1155,21 @@ class FirebaseFirestoreHostApi { } Future queryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, arg_isCollectionGroup, arg_parameters, - arg_options, + arg_options ]) as List?; if (replyList == null) { throw PlatformException( @@ -1166,25 +1193,23 @@ class FirebaseFirestoreHostApi { } Future> aggregateQuery( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - PigeonQueryParameters arg_parameters, - AggregateSource arg_source, - List arg_queries, - bool arg_isCollectionGroup, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + PigeonQueryParameters arg_parameters, + AggregateSource arg_source, + List arg_queries, + bool arg_isCollectionGroup) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, arg_parameters, arg_source.index, arg_queries, - arg_isCollectionGroup, + arg_isCollectionGroup ]) as List?; if (replyList == null) { throw PlatformException( @@ -1207,15 +1232,12 @@ class FirebaseFirestoreHostApi { } } - Future writeBatchCommit( - FirestorePigeonFirebaseApp arg_app, - List arg_writes, - ) async { + Future writeBatchCommit(FirestorePigeonFirebaseApp arg_app, + List arg_writes) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_app, arg_writes]) as List?; if (replyList == null) { @@ -1235,19 +1257,17 @@ class FirebaseFirestoreHostApi { } Future querySnapshot( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - bool arg_includeMetadataChanges, - ListenSource arg_source, - ) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + bool arg_includeMetadataChanges, + ListenSource arg_source) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_path, @@ -1255,7 +1275,7 @@ class FirebaseFirestoreHostApi { arg_parameters, arg_options, arg_includeMetadataChanges, - arg_source.index, + arg_source.index ]) as List?; if (replyList == null) { throw PlatformException( @@ -1279,21 +1299,19 @@ class FirebaseFirestoreHostApi { } Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_parameters, - bool arg_includeMetadataChanges, - ListenSource arg_source, - ) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_parameters, + bool arg_includeMetadataChanges, + ListenSource arg_source) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_app, arg_parameters, arg_includeMetadataChanges, - arg_source.index, + arg_source.index ]) as List?; if (replyList == null) { throw PlatformException( @@ -1317,14 +1335,12 @@ class FirebaseFirestoreHostApi { } Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp arg_app, - PersistenceCacheIndexManagerRequest arg_request, - ) async { + FirestorePigeonFirebaseApp arg_app, + PersistenceCacheIndexManagerRequest arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_app, arg_request.index]) as List?; if (replyList == null) { @@ -1342,4 +1358,35 @@ class FirebaseFirestoreHostApi { return; } } + + Future executePipeline( + FirestorePigeonFirebaseApp arg_app, + List?> arg_stages, + Map? arg_options) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_app, arg_stages, arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as PigeonPipelineSnapshot?)!; + } + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart index 19a5ddfdc4ee..33b146653a45 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -118,6 +118,28 @@ class PigeonQuerySnapshot { final PigeonSnapshotMetadata metadata; } +class PigeonPipelineResult { + const PigeonPipelineResult({ + required this.documentPath, + required this.createTime, + required this.updateTime, + }); + + final String documentPath; + final int createTime; // Timestamp in milliseconds since epoch + final int updateTime; // Timestamp in milliseconds since epoch +} + +class PigeonPipelineSnapshot { + const PigeonPipelineSnapshot({ + required this.results, + required this.executionTime, + }); + + final List results; + final int executionTime; // Timestamp in milliseconds since epoch +} + /// An enumeration of firestore source types. enum Source { /// Causes Firestore to try to retrieve an up-to-date (server-retrieved) snapshot, but fall back to @@ -442,4 +464,11 @@ abstract class FirebaseFirestoreHostApi { FirestorePigeonFirebaseApp app, PersistenceCacheIndexManagerRequest request, ); + + @async + PigeonPipelineSnapshot executePipeline( + FirestorePigeonFirebaseApp app, + List?> stages, + Map? options, + ); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart index 32d57ebdcb48..67978a0a8f2c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart @@ -6,13 +6,13 @@ // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; -import 'dart:typed_data' show Uint8List; - -import 'package:cloud_firestore_platform_interface/src/pigeon/messages.pigeon.dart'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:cloud_firestore_platform_interface/src/pigeon/messages.pigeon.dart'; + class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { const _TestFirebaseFirestoreHostApiCodec(); @override @@ -44,18 +44,24 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonPipelineResult) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonPipelineSnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -63,22 +69,40 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { - return switch (type) { - 128 => AggregateQuery.decode(readValue(buffer)!), - 129 => AggregateQueryResponse.decode(readValue(buffer)!), - 130 => DocumentReferenceRequest.decode(readValue(buffer)!), - 131 => FirestorePigeonFirebaseApp.decode(readValue(buffer)!), - 132 => PigeonDocumentChange.decode(readValue(buffer)!), - 133 => PigeonDocumentOption.decode(readValue(buffer)!), - 134 => PigeonDocumentSnapshot.decode(readValue(buffer)!), - 135 => PigeonFirebaseSettings.decode(readValue(buffer)!), - 136 => PigeonGetOptions.decode(readValue(buffer)!), - 137 => PigeonQueryParameters.decode(readValue(buffer)!), - 138 => PigeonQuerySnapshot.decode(readValue(buffer)!), - 139 => PigeonSnapshotMetadata.decode(readValue(buffer)!), - 140 => PigeonTransactionCommand.decode(readValue(buffer)!), - _ => super.readValueOfType(type, buffer) - }; + switch (type) { + case 128: + return AggregateQuery.decode(readValue(buffer)!); + case 129: + return AggregateQueryResponse.decode(readValue(buffer)!); + case 130: + return DocumentReferenceRequest.decode(readValue(buffer)!); + case 131: + return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); + case 132: + return PigeonDocumentChange.decode(readValue(buffer)!); + case 133: + return PigeonDocumentOption.decode(readValue(buffer)!); + case 134: + return PigeonDocumentSnapshot.decode(readValue(buffer)!); + case 135: + return PigeonFirebaseSettings.decode(readValue(buffer)!); + case 136: + return PigeonGetOptions.decode(readValue(buffer)!); + case 137: + return PigeonPipelineResult.decode(readValue(buffer)!); + case 138: + return PigeonPipelineSnapshot.decode(readValue(buffer)!); + case 139: + return PigeonQueryParameters.decode(readValue(buffer)!); + case 140: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 141: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 142: + return PigeonTransactionCommand.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } } } @@ -91,10 +115,7 @@ abstract class TestFirebaseFirestoreHostApi { Future loadBundle(FirestorePigeonFirebaseApp app, Uint8List bundle); Future namedQueryGet( - FirestorePigeonFirebaseApp app, - String name, - PigeonGetOptions options, - ); + FirestorePigeonFirebaseApp app, String name, PigeonGetOptions options); Future clearPersistence(FirestorePigeonFirebaseApp app); @@ -107,106 +128,82 @@ abstract class TestFirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp app); Future setIndexConfiguration( - FirestorePigeonFirebaseApp app, - String indexConfiguration, - ); + FirestorePigeonFirebaseApp app, String indexConfiguration); Future setLoggingEnabled(bool loggingEnabled); Future snapshotsInSyncSetup(FirestorePigeonFirebaseApp app); Future transactionCreate( - FirestorePigeonFirebaseApp app, - int timeout, - int maxAttempts, - ); + FirestorePigeonFirebaseApp app, int timeout, int maxAttempts); Future transactionStoreResult( - String transactionId, - PigeonTransactionResult resultType, - List? commands, - ); + String transactionId, + PigeonTransactionResult resultType, + List? commands); Future transactionGet( - FirestorePigeonFirebaseApp app, - String transactionId, - String path, - ); + FirestorePigeonFirebaseApp app, String transactionId, String path); Future documentReferenceSet( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceUpdate( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceGet( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future documentReferenceDelete( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest request, - ); + FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); Future queryGet( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - ); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options); Future> aggregateQuery( - FirestorePigeonFirebaseApp app, - String path, - PigeonQueryParameters parameters, - AggregateSource source, - List queries, - bool isCollectionGroup, - ); + FirestorePigeonFirebaseApp app, + String path, + PigeonQueryParameters parameters, + AggregateSource source, + List queries, + bool isCollectionGroup); Future writeBatchCommit( - FirestorePigeonFirebaseApp app, - List writes, - ); + FirestorePigeonFirebaseApp app, List writes); Future querySnapshot( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - bool includeMetadataChanges, - ListenSource source, - ); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + bool includeMetadataChanges, + ListenSource source); Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest parameters, - bool includeMetadataChanges, - ListenSource source, - ); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest parameters, + bool includeMetadataChanges, + ListenSource source); Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp app, - PersistenceCacheIndexManagerRequest request, - ); + FirestorePigeonFirebaseApp app, + PersistenceCacheIndexManagerRequest request); + + Future executePipeline(FirestorePigeonFirebaseApp app, + List?> stages, Map? options); - static void setup( - TestFirebaseFirestoreHostApi? api, { - BinaryMessenger? binaryMessenger, - }) { + static void setup(TestFirebaseFirestoreHostApi? api, + {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -214,22 +211,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.'); final Uint8List? arg_bundle = (args[1] as Uint8List?); - assert( - arg_bundle != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.', - ); + assert(arg_bundle != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.'); final String output = await api.loadBundle(arg_app!, arg_bundle!); return [output]; }); @@ -237,10 +228,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -248,27 +238,19 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_name = (args[1] as String?); - assert( - arg_name != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.', - ); + assert(arg_name != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.'); final PigeonGetOptions? arg_options = (args[2] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.', - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.'); final PigeonQuerySnapshot output = await api.namedQueryGet(arg_app!, arg_name!, arg_options!); return [output]; @@ -277,10 +259,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -288,17 +269,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.'); await api.clearPersistence(arg_app!); return []; }); @@ -306,10 +283,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -317,17 +293,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); await api.disableNetwork(arg_app!); return []; }); @@ -335,10 +307,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -346,17 +317,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); await api.enableNetwork(arg_app!); return []; }); @@ -364,10 +331,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -375,17 +341,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.'); await api.terminate(arg_app!); return []; }); @@ -393,10 +355,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -404,17 +365,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.'); await api.waitForPendingWrites(arg_app!); return []; }); @@ -422,10 +379,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -433,22 +389,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_indexConfiguration = (args[1] as String?); - assert( - arg_indexConfiguration != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.', - ); + assert(arg_indexConfiguration != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.'); await api.setIndexConfiguration(arg_app!, arg_indexConfiguration!); return []; }); @@ -456,10 +406,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -467,16 +416,12 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.'); final List args = (message as List?)!; final bool? arg_loggingEnabled = (args[0] as bool?); - assert( - arg_loggingEnabled != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.', - ); + assert(arg_loggingEnabled != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.'); await api.setLoggingEnabled(arg_loggingEnabled!); return []; }); @@ -484,10 +429,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -495,17 +439,13 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.'); final String output = await api.snapshotsInSyncSetup(arg_app!); return [output]; }); @@ -513,10 +453,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -524,42 +463,30 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.'); final int? arg_timeout = (args[1] as int?); - assert( - arg_timeout != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', - ); + assert(arg_timeout != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); final int? arg_maxAttempts = (args[2] as int?); - assert( - arg_maxAttempts != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', - ); + assert(arg_maxAttempts != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); final String output = await api.transactionCreate( - arg_app!, - arg_timeout!, - arg_maxAttempts!, - ); + arg_app!, arg_timeout!, arg_maxAttempts!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -567,40 +494,30 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.'); final List args = (message as List?)!; final String? arg_transactionId = (args[0] as String?); - assert( - arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.', - ); + assert(arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.'); final PigeonTransactionResult? arg_resultType = args[1] == null ? null : PigeonTransactionResult.values[args[1]! as int]; - assert( - arg_resultType != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.', - ); + assert(arg_resultType != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.'); final List? arg_commands = (args[2] as List?)?.cast(); await api.transactionStoreResult( - arg_transactionId!, - arg_resultType!, - arg_commands, - ); + arg_transactionId!, arg_resultType!, arg_commands); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -608,27 +525,19 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_transactionId = (args[1] as String?); - assert( - arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', - ); + assert(arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); final String? arg_path = (args[2] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); final PigeonDocumentSnapshot output = await api.transactionGet(arg_app!, arg_transactionId!, arg_path!); return [output]; @@ -637,10 +546,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -648,23 +556,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceSet(arg_app!, arg_request!); return []; }); @@ -672,10 +574,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -683,23 +584,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceUpdate(arg_app!, arg_request!); return []; }); @@ -707,10 +602,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -718,23 +612,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.'); final PigeonDocumentSnapshot output = await api.documentReferenceGet(arg_app!, arg_request!); return [output]; @@ -743,10 +631,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -754,23 +641,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.'); await api.documentReferenceDelete(arg_app!, arg_request!); return []; }); @@ -778,10 +659,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -789,55 +669,37 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.'); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.'); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.'); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.', - ); - final PigeonQuerySnapshot output = await api.queryGet( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.'); + final PigeonQuerySnapshot output = await api.queryGet(arg_app!, + arg_path!, arg_isCollectionGroup!, arg_parameters!, arg_options!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -845,63 +707,47 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.'); final PigeonQueryParameters? arg_parameters = (args[2] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.'); final AggregateSource? arg_source = args[3] == null ? null : AggregateSource.values[args[3]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.', - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.'); final List? arg_queries = (args[4] as List?)?.cast(); - assert( - arg_queries != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.', - ); + assert(arg_queries != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.'); final bool? arg_isCollectionGroup = (args[5] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.'); final List output = await api.aggregateQuery( - arg_app!, - arg_path!, - arg_parameters!, - arg_source!, - arg_queries!, - arg_isCollectionGroup!, - ); + arg_app!, + arg_path!, + arg_parameters!, + arg_source!, + arg_queries!, + arg_isCollectionGroup!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -909,23 +755,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.'); final List? arg_writes = (args[1] as List?)?.cast(); - assert( - arg_writes != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.', - ); + assert(arg_writes != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.'); await api.writeBatchCommit(arg_app!, arg_writes!); return []; }); @@ -933,10 +773,9 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -944,68 +783,50 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); final String? arg_path = (args[1] as String?); - assert( - arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.', - ); + assert(arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.'); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert( - arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', - ); + assert(arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.'); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.', - ); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.'); final bool? arg_includeMetadataChanges = (args[5] as bool?); - assert( - arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', - ); + assert(arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); final ListenSource? arg_source = args[6] == null ? null : ListenSource.values[args[6]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.', - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.'); final String output = await api.querySnapshot( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - arg_includeMetadataChanges!, - arg_source!, - ); + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + arg_includeMetadataChanges!, + arg_source!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -1013,50 +834,35 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); final DocumentReferenceRequest? arg_parameters = (args[1] as DocumentReferenceRequest?); - assert( - arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.', - ); + assert(arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.'); final bool? arg_includeMetadataChanges = (args[2] as bool?); - assert( - arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.', - ); + assert(arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.'); final ListenSource? arg_source = args[3] == null ? null : ListenSource.values[args[3]! as int]; - assert( - arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.', - ); - final String output = await api.documentReferenceSnapshot( - arg_app!, - arg_parameters!, - arg_includeMetadataChanges!, - arg_source!, - ); + assert(arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.'); + final String output = await api.documentReferenceSnapshot(arg_app!, + arg_parameters!, arg_includeMetadataChanges!, arg_source!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -1064,29 +870,54 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.'); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert( - arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.', - ); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.'); final PersistenceCacheIndexManagerRequest? arg_request = args[1] == null ? null : PersistenceCacheIndexManagerRequest.values[args[1]! as int]; - assert( - arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.', - ); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.'); await api.persistenceCacheIndexManagerRequest(arg_app!, arg_request!); return []; }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.'); + final List args = (message as List?)!; + final FirestorePigeonFirebaseApp? arg_app = + (args[0] as FirestorePigeonFirebaseApp?); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.'); + final List?>? arg_stages = + (args[1] as List?)?.cast?>(); + assert(arg_stages != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.'); + final Map? arg_options = + (args[2] as Map?)?.cast(); + final PigeonPipelineSnapshot output = + await api.executePipeline(arg_app!, arg_stages!, arg_options); + return [output]; + }); + } + } } } From b4089d8cacbdd879838e932c6b0e3922eb6ae00a Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 17 Feb 2026 15:58:53 +0000 Subject: [PATCH 06/20] chore: implement pipeline execution and expression parsing utilities for Android --- .../FlutterFirebaseFirestorePlugin.java | 65 +++ .../GeneratedAndroidFirebaseFirestore.java | 6 +- .../firestore/utils/ExpressionHelpers.java | 152 +++++ .../firestore/utils/ExpressionParsers.java | 531 ++++++++++++++++++ .../firestore/utils/PipelineParser.java | 114 ++++ .../utils/PipelineStageHandlers.java | 311 ++++++++++ .../cloud_firestore/lib/src/pipeline.dart | 291 +++++----- .../lib/src/pipeline_aggregate.dart | 77 ++- .../lib/src/pipeline_expression.dart | 81 ++- .../lib/src/pipeline_stage.dart | 30 +- 10 files changed, 1511 insertions(+), 147 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java create mode 100644 packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 4b65130f62a4..297f70fb24d1 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -28,6 +28,8 @@ import com.google.firebase.firestore.MemoryCacheSettings; import com.google.firebase.firestore.PersistentCacheIndexManager; import com.google.firebase.firestore.PersistentCacheSettings; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.PipelineResult; import com.google.firebase.firestore.Query; import com.google.firebase.firestore.QuerySnapshot; import com.google.firebase.firestore.SetOptions; @@ -52,6 +54,7 @@ import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler; import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter; import io.flutter.plugins.firebase.firestore.utils.PigeonParser; +import io.flutter.plugins.firebase.firestore.utils.PipelineParser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -983,4 +986,66 @@ public void documentReferenceSnapshot( parameters.getServerTimestampBehavior()), PigeonParser.parseListenSource(source)))); } + + @Override + public void executePipeline( + @NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app, + @NonNull List> stages, + @Nullable Map options, + @NonNull + GeneratedAndroidFirebaseFirestore.Result< + GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot> + result) { + cachedThreadPool.execute( + () -> { + try { + FirebaseFirestore firestore = getFirestoreFromPigeon(app); + + // Execute pipeline using Android Firestore SDK + Pipeline.Snapshot snapshot = PipelineParser.executePipeline(firestore, stages, options); + + // Convert Pipeline.Snapshot to PigeonPipelineSnapshot + List pipelineResults = + new ArrayList<>(); + + // Iterate through snapshot results + for (PipelineResult pipelineResult : snapshot.getResults()) { + GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder = + new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder(); + resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + + // Convert timestamps (assuming they're in milliseconds) + if (pipelineResult.getCreateTime() != null) { + resultBuilder.setCreateTime(pipelineResult.getCreateTime().toDate().getTime()); + } else { + resultBuilder.setCreateTime(0L); + } + + if (pipelineResult.getUpdateTime() != null) { + resultBuilder.setUpdateTime(pipelineResult.getUpdateTime().toDate().getTime()); + } else { + resultBuilder.setUpdateTime(0L); + } + + pipelineResults.add(resultBuilder.build()); + } + + // Build the snapshot + GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder snapshotBuilder = + new GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder(); + snapshotBuilder.setResults(pipelineResults); + + // Set execution time (use current time if not available from snapshot) + if (snapshot.getExecutionTime() != null) { + snapshotBuilder.setExecutionTime(snapshot.getExecutionTime().toDate().getTime()); + } else { + snapshotBuilder.setExecutionTime(System.currentTimeMillis()); + } + + result.success(snapshotBuilder.build()); + } catch (Exception e) { + ExceptionConverter.sendErrorToFlutter(result, e); + } + }); + } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 3638853507b6..1711e9159d80 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -513,7 +513,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(hasPendingWrites); toListResult.add(isFromCache); @@ -604,7 +604,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(path); toListResult.add(data); @@ -725,7 +725,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add((document == null) ? null : document.toList()); diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java new file mode 100644 index 000000000000..cb162d253310 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -0,0 +1,152 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import androidx.annotation.NonNull; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.Blob; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.VectorValue; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import java.util.List; +import java.util.Map; + +/** Helper utilities for parsing expressions and handling common patterns. */ +class ExpressionHelpers { + + /** + * Parses an "and" expression from a list of expression maps. Uses Expression.and() with varargs + * signature. + * + * @param exprMaps List of expression maps to combine with AND + * @param parser Reference to ExpressionParsers for recursive parsing + */ + @SuppressWarnings("unchecked") + static BooleanExpression parseAndExpression( + @NonNull List> exprMaps, @NonNull ExpressionParsers parser) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'and' requires at least one expression"); + } + + BooleanExpression first = parser.parseBooleanExpression(exprMaps.get(0)); + if (exprMaps.size() == 1) { + return first; + } + + BooleanExpression[] rest = new BooleanExpression[exprMaps.size() - 1]; + for (int i = 1; i < exprMaps.size(); i++) { + rest[i - 1] = parser.parseBooleanExpression(exprMaps.get(i)); + } + return Expression.and(first, rest); + } + + /** + * Parses an "or" expression from a list of expression maps. Uses Expression.or() with varargs + * signature. + * + * @param exprMaps List of expression maps to combine with OR + * @param parser Reference to ExpressionParsers for recursive parsing + */ + @SuppressWarnings("unchecked") + static BooleanExpression parseOrExpression( + @NonNull List> exprMaps, @NonNull ExpressionParsers parser) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + + BooleanExpression first = parser.parseBooleanExpression(exprMaps.get(0)); + if (exprMaps.size() == 1) { + return first; + } + + BooleanExpression[] rest = new BooleanExpression[exprMaps.size() - 1]; + for (int i = 1; i < exprMaps.size(); i++) { + rest[i - 1] = parser.parseBooleanExpression(exprMaps.get(i)); + } + return Expression.or(first, rest); + } + + /** + * Parses a constant value based on its type to match Android SDK constant() overloads. Valid + * types: String, Number, Boolean, Date, Timestamp, GeoPoint, byte[], Blob, DocumentReference, + * VectorValue + */ + static Expression parseConstantValue(@NonNull Object value) { + + if (value instanceof String) { + return Expression.constant((String) value); + } else if (value instanceof Number) { + return Expression.constant((Number) value); + } else if (value instanceof Boolean) { + return Expression.constant((Boolean) value); + } else if (value instanceof java.util.Date) { + return Expression.constant((java.util.Date) value); + } else if (value instanceof Timestamp) { + return Expression.constant((Timestamp) value); + } else if (value instanceof GeoPoint) { + return Expression.constant((GeoPoint) value); + } else if (value instanceof byte[]) { + return Expression.constant((byte[]) value); + } else if (value instanceof List) { + // Handle List from Dart which comes as List or List + // This represents byte[] (byte array) for constant expressions + @SuppressWarnings("unchecked") + List list = (List) value; + // Check if all elements are numbers (for byte array) + boolean isByteArray = true; + for (Object item : list) { + if (!(item instanceof Number)) { + isByteArray = false; + break; + } + } + if (isByteArray && !list.isEmpty()) { + byte[] byteArray = new byte[list.size()]; + for (int i = 0; i < list.size(); i++) { + byteArray[i] = ((Number) list.get(i)).byteValue(); + } + return Expression.constant(byteArray); + } + // If not a byte array, fall through to error + } else if (value instanceof Blob) { + return Expression.constant((Blob) value); + } else if (value instanceof DocumentReference) { + return Expression.constant((DocumentReference) value); + } else if (value instanceof VectorValue) { + return Expression.constant((VectorValue) value); + } + + throw new IllegalArgumentException( + "Constant value must be one of: String, Number, Boolean, Date, Timestamp, " + + "GeoPoint, byte[], Blob, DocumentReference, or VectorValue. Got: " + + value.getClass().getName()); + } + + /** Safely extracts a single expression from args map. */ + @SuppressWarnings("unchecked") + static Map extractExpression( + @NonNull Map args, @NonNull String key) { + Map exprMap = (Map) args.get(key); + if (exprMap == null) { + throw new IllegalArgumentException("Missing required expression argument: " + key); + } + return exprMap; + } + + /** Safely extracts a list of expressions from args map. */ + @SuppressWarnings("unchecked") + static List> extractExpressionList( + @NonNull Map args, @NonNull String key) { + List> exprMaps = (List>) args.get(key); + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("Missing or empty expression list: " + key); + } + return exprMaps; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java new file mode 100644 index 000000000000..150d932ae6d3 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -0,0 +1,531 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.firebase.firestore.pipeline.AggregateFunction; +import com.google.firebase.firestore.pipeline.AggregateOptions; +import com.google.firebase.firestore.pipeline.AggregateStage; +import com.google.firebase.firestore.pipeline.AliasedAggregate; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import com.google.firebase.firestore.pipeline.FindNearestStage; +import com.google.firebase.firestore.pipeline.Selectable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Handles parsing of all expression types from Dart map representations to Android SDK objects. */ +class ExpressionParsers { + private static final String TAG = "ExpressionParsers"; + + /** Binary operation on two expressions. Used instead of BiFunction for API 23 compatibility. */ + private interface BinaryExpressionOp { + R apply(Expression left, Expression right); + } + + /** Parses an expression from a map representation. */ + @SuppressWarnings("unchecked") + Expression parseExpression(@NonNull Map expressionMap) { + String name = (String) expressionMap.get("name"); + if (name == null) { + // Might be a field reference directly (legacy format) + if (expressionMap.containsKey("field_name")) { + String fieldName = (String) expressionMap.get("field_name"); + return Expression.field(fieldName); + } + // Check for field in args (current format) + Map argsCheck = (Map) expressionMap.get("args"); + if (argsCheck != null && argsCheck.containsKey("field")) { + String fieldName = (String) argsCheck.get("field"); + return Expression.field(fieldName); + } + throw new IllegalArgumentException("Expression must have a 'name' field"); + } + + Map args = (Map) expressionMap.get("args"); + if (args == null) { + args = new HashMap<>(); + } + + switch (name) { + case "field": + { + String fieldName = (String) args.get("field"); + if (fieldName == null) { + throw new IllegalArgumentException("Field expression must have a 'field' argument"); + } + return Expression.field(fieldName); + } + case "constant": + { + Object value = args.get("value"); + return ExpressionHelpers.parseConstantValue(value); + } + case "alias": + { + Map exprMap = (Map) args.get("expression"); + String alias = (String) args.get("alias"); + Expression expr = parseExpression(exprMap); + return expr.alias(alias); + } + // Comparison operations + case "equal": + return parseBinaryComparison(args, (left, right) -> left.equal(right)); + case "not_equal": + return parseBinaryComparison(args, (left, right) -> left.notEqual(right)); + case "greater_than": + return parseBinaryComparison(args, (left, right) -> left.greaterThan(right)); + case "greater_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.greaterThanOrEqual(right)); + case "less_than": + return parseBinaryComparison(args, (left, right) -> left.lessThan(right)); + case "less_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.lessThanOrEqual(right)); + // Arithmetic operations + case "add": + return parseBinaryOperation(args, (left, right) -> left.add(right)); + case "subtract": + return parseBinaryOperation(args, (left, right) -> left.subtract(right)); + case "multiply": + return parseBinaryOperation(args, (left, right) -> left.multiply(right)); + case "divide": + return parseBinaryOperation(args, (left, right) -> left.divide(right)); + case "modulo": + return parseBinaryOperation(args, (left, right) -> left.mod(right)); + // Logic operations + case "and": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } + case "or": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseOrExpression(exprMaps, this); + } + case "not": + { + Map exprMap = (Map) args.get("expression"); + BooleanExpression expr = parseBooleanExpression(exprMap); + return Expression.not(expr); + } + default: + Log.w(TAG, "Unsupported expression type: " + name); + throw new UnsupportedOperationException("Expression type not yet implemented: " + name); + } + } + + /** Helper to parse binary comparison operations (equal, not_equal, greater_than, etc.). */ + @SuppressWarnings("unchecked") + private BooleanExpression parseBinaryComparison( + @NonNull Map args, @NonNull BinaryExpressionOp operation) { + Map leftMap = (Map) args.get("left"); + Map rightMap = (Map) args.get("right"); + Expression left = parseExpression(leftMap); + Expression right = parseExpression(rightMap); + return operation.apply(left, right); + } + + /** Helper to parse binary arithmetic operations (add, subtract, multiply, etc.). */ + @SuppressWarnings("unchecked") + private Expression parseBinaryOperation( + @NonNull Map args, @NonNull BinaryExpressionOp operation) { + Map leftMap = (Map) args.get("left"); + Map rightMap = (Map) args.get("right"); + Expression left = parseExpression(leftMap); + Expression right = parseExpression(rightMap); + return operation.apply(left, right); + } + + /** + * Parses a boolean expression from a map representation. Boolean expressions are used in where + * clauses and return BooleanExpression. + */ + @SuppressWarnings("unchecked") + BooleanExpression parseBooleanExpression(@NonNull Map expressionMap) { + String name = (String) expressionMap.get("name"); + if (name == null) { + throw new IllegalArgumentException("BooleanExpression must have a 'name' field"); + } + + Map args = (Map) expressionMap.get("args"); + if (args == null) { + args = new HashMap<>(); + } + + switch (name) { + // Comparison operations - these return BooleanExpression + case "equal": + return parseBinaryComparison(args, (left, right) -> left.equal(right)); + case "not_equal": + return parseBinaryComparison(args, (left, right) -> left.notEqual(right)); + case "greater_than": + return parseBinaryComparison(args, (left, right) -> left.greaterThan(right)); + case "greater_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.greaterThanOrEqual(right)); + case "less_than": + return parseBinaryComparison(args, (left, right) -> left.lessThan(right)); + case "less_than_or_equal": + return parseBinaryComparison(args, (left, right) -> left.lessThanOrEqual(right)); + // Logical operations - these return BooleanExpression + case "and": + { + List> exprMaps = (List>) args.get("expressions"); + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } + case "or": + { + List> exprMaps = (List>) args.get("expressions"); + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + if (exprMaps.size() == 1) { + return parseBooleanExpression(exprMaps.get(0)); + } + // BooleanExpression.or() takes exactly 2 parameters, so we chain them + BooleanExpression result = parseBooleanExpression(exprMaps.get(0)); + for (int i = 1; i < exprMaps.size(); i++) { + BooleanExpression next = parseBooleanExpression(exprMaps.get(i)); + result = BooleanExpression.or(result, next); + } + return result; + } + case "not": + { + Map exprMap = (Map) args.get("expression"); + BooleanExpression expr = parseBooleanExpression(exprMap); + return expr.not(); + } + // Boolean-specific expressions + case "is_absent": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.isAbsent(); + } + case "is_error": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.isError(); + } + case "exists": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.exists(); + } + case "array_contains": + { + Map arrayMap = (Map) args.get("array"); + Map elementMap = (Map) args.get("element"); + Expression array = parseExpression(arrayMap); + Expression element = parseExpression(elementMap); + return array.arrayContains(element); + } + case "array_contains_all": + { + Map arrayMap = (Map) args.get("array"); + Map arrayExprMap = (Map) args.get("array_expression"); + Expression array = parseExpression(arrayMap); + Expression arrayExpr = parseExpression(arrayExprMap); + return array.arrayContainsAll(arrayExpr); + } + case "array_contains_any": + { + Map arrayMap = (Map) args.get("array"); + Map arrayExprMap = (Map) args.get("array_expression"); + Expression array = parseExpression(arrayMap); + Expression arrayExpr = parseExpression(arrayExprMap); + return array.arrayContainsAny(arrayExpr); + } + case "equal_any": + { + Map valueMap = (Map) args.get("value"); + List> valuesMaps = (List>) args.get("values"); + Expression value = parseExpression(valueMap); + Expression[] values = new Expression[valuesMaps.size()]; + for (int i = 0; i < valuesMaps.size(); i++) { + values[i] = parseExpression(valuesMaps.get(i)); + } + return value.equalAny(List.of(values)); + } + case "not_equal_any": + { + Map valueMap = (Map) args.get("value"); + List> valuesMaps = (List>) args.get("values"); + Expression value = parseExpression(valueMap); + Expression[] values = new Expression[valuesMaps.size()]; + for (int i = 0; i < valuesMaps.size(); i++) { + values[i] = parseExpression(valuesMaps.get(i)); + } + return value.notEqualAny(List.of(values)); + } + case "as_boolean": + { + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + return expr.asBoolean(); + } + // Handle filter expressions (PipelineFilter) + case "filter": + return parseFilterExpression(args); + default: + // Try parsing as a regular expression first, then cast to BooleanExpression if possible + Expression expr = parseExpression(expressionMap); + if (expr instanceof BooleanExpression) { + return (BooleanExpression) expr; + } + Log.w(TAG, "Expression type '" + name + "' is not a BooleanExpression, attempting cast"); + throw new IllegalArgumentException( + "Expression type '" + name + "' cannot be used as a BooleanExpression"); + } + } + + /** + * Parses a filter expression (PipelineFilter) which can have operator-based or field-based forms. + */ + @SuppressWarnings("unchecked") + private BooleanExpression parseFilterExpression(@NonNull Map args) { + // PipelineFilter can have various forms - check for operator-based or field-based + if (args.containsKey("operator")) { + String operator = (String) args.get("operator"); + List> exprMaps = (List>) args.get("expressions"); + if ("and".equals(operator)) { + return ExpressionHelpers.parseAndExpression(exprMaps, this); + } else if ("or".equals(operator)) { + if (exprMaps == null || exprMaps.isEmpty()) { + throw new IllegalArgumentException("'or' requires at least one expression"); + } + if (exprMaps.size() == 1) { + return parseBooleanExpression(exprMaps.get(0)); + } + // BooleanExpression.or() takes exactly 2 parameters, so we chain them + BooleanExpression result = parseBooleanExpression(exprMaps.get(0)); + for (int i = 1; i < exprMaps.size(); i++) { + BooleanExpression next = parseBooleanExpression(exprMaps.get(i)); + result = BooleanExpression.or(result, next); + } + return result; + } + } + // Field-based filter - parse field and create appropriate comparison + String fieldName = (String) args.get("field"); + Expression fieldExpr = Expression.field(fieldName); + + return parseFieldBasedFilter(fieldExpr, args); + } + + /** Parses field-based filter comparisons (isEqualTo, isGreaterThan, etc.). */ + @SuppressWarnings("unchecked") + private BooleanExpression parseFieldBasedFilter( + @NonNull Expression fieldExpr, @NonNull Map args) { + if (args.containsKey("isEqualTo")) { + Object value = args.get("isEqualTo"); + return value instanceof Map + ? fieldExpr.equal(parseExpression((Map) value)) + : fieldExpr.equal(value); + } + if (args.containsKey("isNotEqualTo")) { + Object value = args.get("isNotEqualTo"); + return value instanceof Map + ? fieldExpr.notEqual(parseExpression((Map) value)) + : fieldExpr.notEqual(value); + } + if (args.containsKey("isGreaterThan")) { + Object value = args.get("isGreaterThan"); + return value instanceof Map + ? fieldExpr.greaterThan(parseExpression((Map) value)) + : fieldExpr.greaterThan(value); + } + if (args.containsKey("isGreaterThanOrEqualTo")) { + Object value = args.get("isGreaterThanOrEqualTo"); + return value instanceof Map + ? fieldExpr.greaterThanOrEqual(parseExpression((Map) value)) + : fieldExpr.greaterThanOrEqual(value); + } + if (args.containsKey("isLessThan")) { + Object value = args.get("isLessThan"); + return value instanceof Map + ? fieldExpr.lessThan(parseExpression((Map) value)) + : fieldExpr.lessThan(value); + } + if (args.containsKey("isLessThanOrEqualTo")) { + Object value = args.get("isLessThanOrEqualTo"); + return value instanceof Map + ? fieldExpr.lessThanOrEqual(parseExpression((Map) value)) + : fieldExpr.lessThanOrEqual(value); + } + if (args.containsKey("arrayContains")) { + Object value = args.get("arrayContains"); + return value instanceof Map + ? fieldExpr.arrayContains(parseExpression((Map) value)) + : fieldExpr.arrayContains(value); + } + throw new IllegalArgumentException("Unsupported filter expression format"); + } + + /** + * Parses a Selectable from a map representation. Selectables are Field or AliasedExpression + * types. + */ + @SuppressWarnings("unchecked") + Selectable parseSelectable(@NonNull Map expressionMap) { + Expression expr = parseExpression(expressionMap); + if (!(expr instanceof Selectable)) { + throw new IllegalArgumentException( + "Expression must be a Selectable (Field or AliasedExpression). Got: " + + expressionMap.get("name")); + } + return (Selectable) expr; + } + + /** Parses an aggregate function from a map representation. */ + @SuppressWarnings("unchecked") + AggregateFunction parseAggregateFunction(@NonNull Map aggregateMap) { + String functionName = (String) aggregateMap.get("function"); + if (functionName == null) { + // Try "name" as fallback + functionName = (String) aggregateMap.get("name"); + } + Map args = (Map) aggregateMap.get("args"); + + Map exprMap = (Map) args.get("expression"); + Expression expr = parseExpression(exprMap); + + switch (functionName) { + case "sum": + return AggregateFunction.sum(expr); + case "average": + return AggregateFunction.average(expr); + case "count": + return AggregateFunction.count(expr); + case "count_distinct": + return AggregateFunction.countDistinct(expr); + case "minimum": + return AggregateFunction.minimum(expr); + case "maximum": + return AggregateFunction.maximum(expr); + default: + throw new IllegalArgumentException("Unknown aggregate function: " + functionName); + } + } + + /** + * Parses an AliasedAggregate from a Dart AliasedAggregateFunction map representation. Since Dart + * API only accepts AliasedAggregateFunction, we can directly construct AliasedAggregate. + */ + @SuppressWarnings("unchecked") + AliasedAggregate parseAliasedAggregate(@NonNull Map aggregateMap) { + // Check if this is an aliased aggregate function (Dart AliasedAggregateFunction format) + String name = (String) aggregateMap.get("name"); + if ("alias".equals(name)) { + Map args = (Map) aggregateMap.get("args"); + String alias = (String) args.get("alias"); + Map aggregateFunctionMap = + (Map) args.get("aggregate_function"); + + // Parse the underlying aggregate function + AggregateFunction function = parseAggregateFunction(aggregateFunctionMap); + + // Apply the alias to get AliasedAggregate + return function.alias(alias); + } + + // If not in alias format, it might be a direct aggregate function with alias field + // This shouldn't happen with the new Dart API, but handle for backward compatibility + String alias = (String) aggregateMap.get("alias"); + if (alias != null) { + AggregateFunction function = parseAggregateFunction(aggregateMap); + return function.alias(alias); + } + + throw new IllegalArgumentException( + "Aggregate function must have an alias. Expected AliasedAggregateFunction format."); + } + + /** Parses an AggregateStage from a map representation. */ + @SuppressWarnings("unchecked") + AggregateStage parseAggregateStage(@NonNull Map stageMap) { + // Parse accumulators (required) + List> accumulatorMaps = + (List>) stageMap.get("accumulators"); + if (accumulatorMaps == null || accumulatorMaps.isEmpty()) { + throw new IllegalArgumentException("AggregateStage must have at least one accumulator"); + } + + // Parse accumulators as AliasedAggregate + AliasedAggregate[] accumulators = new AliasedAggregate[accumulatorMaps.size()]; + for (int i = 0; i < accumulatorMaps.size(); i++) { + accumulators[i] = parseAliasedAggregate(accumulatorMaps.get(i)); + } + + // Build AggregateStage with accumulators + AggregateStage aggregateStage; + if (accumulators.length == 1) { + aggregateStage = AggregateStage.withAccumulators(accumulators[0]); + } else { + AliasedAggregate[] rest = new AliasedAggregate[accumulators.length - 1]; + System.arraycopy(accumulators, 1, rest, 0, rest.length); + aggregateStage = AggregateStage.withAccumulators(accumulators[0], rest); + } + + // Parse optional groups and add them using withGroups() + // withGroups(group: Selectable, vararg additionalGroups: Any) + List> groupMaps = (List>) stageMap.get("groups"); + if (groupMaps != null && !groupMaps.isEmpty()) { + // Parse first group as Selectable (required) + Selectable firstGroup = parseSelectable(groupMaps.get(0)); + + if (groupMaps.size() == 1) { + // Only one group + aggregateStage = aggregateStage.withGroups(firstGroup); + } else { + // Multiple groups - parse remaining as Any[] (varargs) + Object[] additionalGroups = new Object[groupMaps.size() - 1]; + for (int i = 1; i < groupMaps.size(); i++) { + // Parse as Expression first, then convert to Object (can be Selectable or Any) + Expression expr = parseExpression(groupMaps.get(i)); + additionalGroups[i - 1] = expr; + } + aggregateStage = aggregateStage.withGroups(firstGroup, additionalGroups); + } + } + + return aggregateStage; + } + + /** Parses AggregateOptions from a map representation. */ + @SuppressWarnings("unchecked") + AggregateOptions parseAggregateOptions(@NonNull Map optionsMap) { + // For now, AggregateOptions is empty, but this method is ready for future options + return new AggregateOptions(); + } + + /** + * Converts a Dart DistanceMeasure enum name to Android FindNearestStage.DistanceMeasure enum. + * Dart enum values: cosine, euclidean, dotProduct Android enum values: COSINE, EUCLIDEAN, + * DOT_PRODUCT + */ + FindNearestStage.DistanceMeasure parseDistanceMeasure(@NonNull String dartEnumName) { + switch (dartEnumName) { + case "cosine": + return FindNearestStage.DistanceMeasure.COSINE; + case "euclidean": + return FindNearestStage.DistanceMeasure.EUCLIDEAN; + case "dotProduct": + return FindNearestStage.DistanceMeasure.DOT_PRODUCT; + default: + throw new IllegalArgumentException( + "Unknown distance measure: " + + dartEnumName + + ". Expected: cosine, euclidean, or dotProduct"); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java new file mode 100644 index 000000000000..609f34be03ca --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java @@ -0,0 +1,114 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.Pipeline.Snapshot; +import com.google.firebase.firestore.PipelineSource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PipelineParser { + private static final String TAG = "PipelineParser"; + private static final ExpressionParsers expressionParsers = new ExpressionParsers(); + private static final PipelineStageHandlers stageHandlers = + new PipelineStageHandlers(expressionParsers); + + /** + * Executes a pipeline from a list of stage maps. + * + * @param firestore The Firestore instance + * @param stages List of stage maps, each with 'stage' and 'args' fields + * @param options Optional execution options + * @return The pipeline snapshot result + */ + public static Snapshot executePipeline( + @NonNull FirebaseFirestore firestore, + @NonNull List> stages, + @Nullable Map options) + throws Exception { + if (stages == null || stages.isEmpty()) { + throw new IllegalArgumentException("Pipeline must have at least one stage"); + } + + PipelineSource pipelineSource = firestore.pipeline(); + Pipeline pipeline = null; + + // Process each stage in order + for (int i = 0; i < stages.size(); i++) { + Map stageMap = stages.get(i); + String stageName = (String) stageMap.get("stage"); + if (stageName == null) { + throw new IllegalArgumentException("Stage must have a 'stage' field"); + } + + @SuppressWarnings("unchecked") + Map args = (Map) stageMap.get("args"); + + if (i == 0) { + // First stage must be a source stage (collection, collection_group, documents, database) + pipeline = applySourceStage(pipelineSource, stageName, args, firestore); + } else { + // Subsequent stages are methods on the Pipeline instance + pipeline = stageHandlers.applyStage(pipeline, stageName, args, firestore); + } + } + + // Execute the pipeline + Task task = pipeline.execute(); + return Tasks.await(task); + } + + /** + * Applies a source stage (collection, collection_group, documents, database) to PipelineSource. + * These are the only stages that can be the first stage and return a Pipeline instance. + */ + @SuppressWarnings("unchecked") + private static Pipeline applySourceStage( + @NonNull PipelineSource pipelineSource, + @NonNull String stageName, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { + switch (stageName) { + case "collection": + { + String path = (String) args.get("path"); + return pipelineSource.collection(path); + } + case "collection_group": + { + String path = (String) args.get("path"); + return pipelineSource.collectionGroup(path); + } + case "database": + { + return pipelineSource.database(); + } + case "documents": + { + List> docMaps = (List>) args; + List docRefs = new ArrayList<>(); + for (Map docMap : docMaps) { + String docPath = (String) docMap.get("path"); + docRefs.add(firestore.document(docPath)); + } + return pipelineSource.documents(docRefs.toArray(new DocumentReference[0])); + } + default: + throw new IllegalArgumentException( + "First stage must be one of: collection, collection_group, documents, database. Got: " + + stageName); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java new file mode 100644 index 000000000000..7aa36fba7be4 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -0,0 +1,311 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.utils; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Pipeline; +import com.google.firebase.firestore.pipeline.AggregateOptions; +import com.google.firebase.firestore.pipeline.AggregateStage; +import com.google.firebase.firestore.pipeline.AliasedAggregate; +import com.google.firebase.firestore.pipeline.BooleanExpression; +import com.google.firebase.firestore.pipeline.Expression; +import com.google.firebase.firestore.pipeline.Field; +import com.google.firebase.firestore.pipeline.FindNearestOptions; +import com.google.firebase.firestore.pipeline.FindNearestStage; +import com.google.firebase.firestore.pipeline.Ordering; +import com.google.firebase.firestore.pipeline.Selectable; +import com.google.firebase.firestore.pipeline.UnnestOptions; +import java.util.List; +import java.util.Map; + +/** Handles parsing and applying pipeline stages to Pipeline instances. */ +class PipelineStageHandlers { + private static final String TAG = "PipelineStageHandlers"; + private final ExpressionParsers parsers; + + PipelineStageHandlers(@NonNull ExpressionParsers parsers) { + this.parsers = parsers; + } + + /** Applies a pipeline stage to a Pipeline instance. */ + @SuppressWarnings("unchecked") + Pipeline applyStage( + @NonNull Pipeline pipeline, + @NonNull String stageName, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { + switch (stageName) { + case "where": + return handleWhere(pipeline, args); + case "limit": + return handleLimit(pipeline, args); + case "offset": + return handleOffset(pipeline, args); + case "sort": + return handleSort(pipeline, args); + case "select": + return handleSelect(pipeline, args); + case "add_fields": + return handleAddFields(pipeline, args); + case "remove_fields": + return handleRemoveFields(pipeline, args); + case "distinct": + return handleDistinct(pipeline, args); + case "aggregate": + return handleAggregate(pipeline, args); + case "unnest": + return handleUnnest(pipeline, args); + case "replace_with": + return handleReplaceWith(pipeline, args); + case "union": + return handleUnion(pipeline, args); + case "sample": + return handleSample(pipeline, args); + case "find_nearest": + return handleFindNearest(pipeline, args); + default: + throw new IllegalArgumentException("Unknown pipeline stage: " + stageName); + } + } + + private Pipeline handleWhere(@NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + BooleanExpression booleanExpression = parsers.parseBooleanExpression(expressionMap); + return pipeline.where(booleanExpression); + } + + private Pipeline handleLimit(@NonNull Pipeline pipeline, @Nullable Map args) { + Number limit = (Number) args.get("limit"); + return pipeline.limit(limit.intValue()); + } + + private Pipeline handleOffset(@NonNull Pipeline pipeline, @Nullable Map args) { + Number offset = (Number) args.get("offset"); + return pipeline.offset(offset.intValue()); + } + + private Pipeline handleSort(@NonNull Pipeline pipeline, @Nullable Map args) { + Map orderingMap = (Map) args.get("expression"); + Expression expression = parsers.parseExpression(orderingMap); + String direction = (String) args.get("order_direction"); + Ordering ordering = "asc".equals(direction) ? expression.ascending() : expression.descending(); + return pipeline.sort(ordering); + } + + private Pipeline handleSelect(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'select' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstSelection = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as varargs + if (expressionMaps.size() == 1) { + return pipeline.select(firstSelection); + } + + Object[] additionalSelections = new Object[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + Expression expr = parsers.parseExpression(expressionMaps.get(i)); + // Additional selections can be Selectable or any Object + additionalSelections[i - 1] = expr; + } + + return pipeline.select(firstSelection, additionalSelections); + } + + private Pipeline handleAddFields(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'add_fields' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstField = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as Selectable varargs + if (expressionMaps.size() == 1) { + return pipeline.addFields(firstField); + } + + Selectable[] additionalFields = new Selectable[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + additionalFields[i - 1] = parsers.parseSelectable(expressionMaps.get(i)); + } + + return pipeline.addFields(firstField, additionalFields); + } + + private Pipeline handleRemoveFields( + @NonNull Pipeline pipeline, @Nullable Map args) { + List fieldPaths = (List) args.get("field_paths"); + + if (fieldPaths == null || fieldPaths.isEmpty()) { + throw new IllegalArgumentException("'remove_fields' requires at least one field path"); + } + + // Convert first field path string to Field + Field firstField = Expression.field(fieldPaths.get(0)); + + // Convert remaining field paths to Field varargs + if (fieldPaths.size() == 1) { + return pipeline.removeFields(firstField); + } + + Field[] additionalFields = new Field[fieldPaths.size() - 1]; + for (int i = 1; i < fieldPaths.size(); i++) { + additionalFields[i - 1] = Expression.field(fieldPaths.get(i)); + } + + return pipeline.removeFields(firstField, additionalFields); + } + + private Pipeline handleDistinct(@NonNull Pipeline pipeline, @Nullable Map args) { + List> expressionMaps = (List>) args.get("expressions"); + + if (expressionMaps == null || expressionMaps.isEmpty()) { + throw new IllegalArgumentException("'distinct' requires at least one expression"); + } + + // Parse first expression as Selectable + Selectable firstGroup = parsers.parseSelectable(expressionMaps.get(0)); + + // Parse remaining expressions as varargs (can be Selectable or Any) + if (expressionMaps.size() == 1) { + return pipeline.distinct(firstGroup); + } + + Object[] additionalGroups = new Object[expressionMaps.size() - 1]; + for (int i = 1; i < expressionMaps.size(); i++) { + Expression expr = parsers.parseExpression(expressionMaps.get(i)); + // Additional groups can be Selectable or any Object + additionalGroups[i - 1] = expr; + } + + return pipeline.distinct(firstGroup, additionalGroups); + } + + @SuppressWarnings("unchecked") + private Pipeline handleAggregate(@NonNull Pipeline pipeline, @Nullable Map args) { + // Check if this is using aggregate_stage (new API) or aggregate_functions (legacy API) + if (args.containsKey("aggregate_stage")) { + // New API: aggregateStage with optional options + Map aggregateStageMap = (Map) args.get("aggregate_stage"); + AggregateStage aggregateStage = parsers.parseAggregateStage(aggregateStageMap); + + // Parse optional options + Map optionsMap = (Map) args.get("options"); + if (optionsMap != null && !optionsMap.isEmpty()) { + AggregateOptions options = parsers.parseAggregateOptions(optionsMap); + return pipeline.aggregate(aggregateStage, options); + } else { + return pipeline.aggregate(aggregateStage); + } + } else { + // Legacy API: aggregate_functions (varargs) + List> aggregateMaps = + (List>) args.get("aggregate_functions"); + + if (aggregateMaps == null || aggregateMaps.isEmpty()) { + throw new IllegalArgumentException( + "'aggregate' requires at least one aggregate function or an aggregate_stage"); + } + + // Parse first aggregate function as AliasedAggregate + AliasedAggregate firstAccumulator = parsers.parseAliasedAggregate(aggregateMaps.get(0)); + + // Parse remaining aggregate functions as AliasedAggregate varargs + if (aggregateMaps.size() == 1) { + return pipeline.aggregate(firstAccumulator); + } + + AliasedAggregate[] additionalAccumulators = new AliasedAggregate[aggregateMaps.size() - 1]; + for (int i = 1; i < aggregateMaps.size(); i++) { + additionalAccumulators[i - 1] = parsers.parseAliasedAggregate(aggregateMaps.get(i)); + } + + return pipeline.aggregate(firstAccumulator, additionalAccumulators); + } + } + + private Pipeline handleUnnest(@NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + Selectable expression = parsers.parseSelectable(expressionMap); + String indexField = (String) args.get("index_field"); + if (indexField != null) { + return pipeline.unnest(expression, new UnnestOptions().withIndexField(indexField)); + } else { + return pipeline.unnest(expression); + } + } + + private Pipeline handleReplaceWith( + @NonNull Pipeline pipeline, @Nullable Map args) { + Map expressionMap = (Map) args.get("expression"); + Expression expression = parsers.parseExpression(expressionMap); + return pipeline.replaceWith(expression); + } + + private Pipeline handleUnion(@NonNull Pipeline pipeline, @Nullable Map args) { + // Union requires a nested pipeline + List> nestedStages = (List>) args.get("pipeline"); + // Note: This would require creating a nested pipeline, which may not be directly + // supported. This is a placeholder for now. + Log.w(TAG, "Union stage not yet fully implemented"); + throw new UnsupportedOperationException("Union stage not yet implemented"); + } + + private Pipeline handleSample(@NonNull Pipeline pipeline, @Nullable Map args) { + // Sample stage parsing + Map sampleMap = (Map) args; + // Parse sample configuration + Log.w(TAG, "Sample stage not yet fully implemented"); + throw new UnsupportedOperationException("Sample stage not yet implemented"); + } + + @SuppressWarnings("unchecked") + private Pipeline handleFindNearest( + @NonNull Pipeline pipeline, @Nullable Map args) { + String vectorField = (String) args.get("vector_field"); + List vectorValue = (List) args.get("vector_value"); + String distanceMeasureStr = (String) args.get("distance_measure"); + Number limitObj = (Number) args.get("limit"); + + if (distanceMeasureStr == null) { + throw new IllegalArgumentException("'find_nearest' requires a 'distance_measure' argument"); + } + + // Convert Dart enum name to Android enum value + FindNearestStage.DistanceMeasure distanceMeasure = + parsers.parseDistanceMeasure(distanceMeasureStr); + + // Convert vector value to double array + double[] vectorArray = new double[vectorValue.size()]; + for (int i = 0; i < vectorValue.size(); i++) { + vectorArray[i] = vectorValue.get(i).doubleValue(); + } + + Field fieldExpr = Expression.field(vectorField); + + if (limitObj != null) { + return pipeline.findNearest( + vectorField, + Expression.vector(vectorArray), + distanceMeasure, + new FindNearestOptions().withLimit(limitObj.intValue())); + } else { + return pipeline.findNearest(fieldExpr, vectorArray, distanceMeasure); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index 956685e3e8a7..eea1c5fa23ae 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -55,69 +55,58 @@ class Pipeline { /// Adds fields to documents using expressions Pipeline addFields( - Expression expression1, [ - Expression? expression2, - Expression? expression3, - Expression? expression4, - Expression? expression5, - Expression? expression6, - Expression? expression7, - Expression? expression8, - Expression? expression9, - Expression? expression10, - Expression? expression11, - Expression? expression12, - Expression? expression13, - Expression? expression14, - Expression? expression15, - Expression? expression16, - Expression? expression17, - Expression? expression18, - Expression? expression19, - Expression? expression20, - Expression? expression21, - Expression? expression22, - Expression? expression23, - Expression? expression24, - Expression? expression25, - Expression? expression26, - Expression? expression27, - Expression? expression28, - Expression? expression29, - Expression? expression30, + Selectable selectable1, [ + Selectable? selectable2, + Selectable? selectable3, + Selectable? selectable4, + Selectable? selectable5, + Selectable? selectable6, + Selectable? selectable7, + Selectable? selectable8, + Selectable? selectable9, + Selectable? selectable10, + Selectable? selectable11, + Selectable? selectable12, + Selectable? selectable13, + Selectable? selectable14, + Selectable? selectable15, + Selectable? selectable16, + Selectable? selectable17, + Selectable? selectable18, + Selectable? selectable19, + Selectable? selectable20, + Selectable? selectable21, + Selectable? selectable22, + Selectable? selectable23, + Selectable? selectable24, + Selectable? selectable25, + Selectable? selectable26, + Selectable? selectable27, + Selectable? selectable28, + Selectable? selectable29, + Selectable? selectable30, ]) { - final expressions = [expression1]; - if (expression2 != null) expressions.add(expression2); - if (expression3 != null) expressions.add(expression3); - if (expression4 != null) expressions.add(expression4); - if (expression5 != null) expressions.add(expression5); - if (expression6 != null) expressions.add(expression6); - if (expression7 != null) expressions.add(expression7); - if (expression8 != null) expressions.add(expression8); - if (expression9 != null) expressions.add(expression9); - if (expression10 != null) expressions.add(expression10); - if (expression11 != null) expressions.add(expression11); - if (expression12 != null) expressions.add(expression12); - if (expression13 != null) expressions.add(expression13); - if (expression14 != null) expressions.add(expression14); - if (expression15 != null) expressions.add(expression15); - if (expression16 != null) expressions.add(expression16); - if (expression17 != null) expressions.add(expression17); - if (expression18 != null) expressions.add(expression18); - if (expression19 != null) expressions.add(expression19); - if (expression20 != null) expressions.add(expression20); - if (expression21 != null) expressions.add(expression21); - if (expression22 != null) expressions.add(expression22); - if (expression23 != null) expressions.add(expression23); - if (expression24 != null) expressions.add(expression24); - if (expression25 != null) expressions.add(expression25); - if (expression26 != null) expressions.add(expression26); - if (expression27 != null) expressions.add(expression27); - if (expression28 != null) expressions.add(expression28); - if (expression29 != null) expressions.add(expression29); - if (expression30 != null) expressions.add(expression30); - - final stage = _AddFieldsStage(expressions); + final selectables = [selectable1]; + if (selectable2 != null) selectables.add(selectable2); + if (selectable3 != null) selectables.add(selectable3); + if (selectable4 != null) selectables.add(selectable4); + if (selectable5 != null) selectables.add(selectable5); + if (selectable6 != null) selectables.add(selectable6); + if (selectable7 != null) selectables.add(selectable7); + if (selectable8 != null) selectables.add(selectable8); + if (selectable9 != null) selectables.add(selectable9); + if (selectable10 != null) selectables.add(selectable10); + if (selectable21 != null) selectables.add(selectable21); + if (selectable22 != null) selectables.add(selectable22); + if (selectable23 != null) selectables.add(selectable23); + if (selectable24 != null) selectables.add(selectable24); + if (selectable25 != null) selectables.add(selectable25); + if (selectable26 != null) selectables.add(selectable26); + if (selectable27 != null) selectables.add(selectable27); + if (selectable28 != null) selectables.add(selectable28); + if (selectable29 != null) selectables.add(selectable29); + if (selectable30 != null) selectables.add(selectable30); + final stage = _AddFieldsStage(selectables); return Pipeline._( _firestore, _delegate.addStage(stage.toMap()), @@ -126,38 +115,38 @@ class Pipeline { /// Aggregates data using aggregate functions Pipeline aggregate( - PipelineAggregateFunction aggregateFunction1, [ - PipelineAggregateFunction? aggregateFunction2, - PipelineAggregateFunction? aggregateFunction3, - PipelineAggregateFunction? aggregateFunction4, - PipelineAggregateFunction? aggregateFunction5, - PipelineAggregateFunction? aggregateFunction6, - PipelineAggregateFunction? aggregateFunction7, - PipelineAggregateFunction? aggregateFunction8, - PipelineAggregateFunction? aggregateFunction9, - PipelineAggregateFunction? aggregateFunction10, - PipelineAggregateFunction? aggregateFunction11, - PipelineAggregateFunction? aggregateFunction12, - PipelineAggregateFunction? aggregateFunction13, - PipelineAggregateFunction? aggregateFunction14, - PipelineAggregateFunction? aggregateFunction15, - PipelineAggregateFunction? aggregateFunction16, - PipelineAggregateFunction? aggregateFunction17, - PipelineAggregateFunction? aggregateFunction18, - PipelineAggregateFunction? aggregateFunction19, - PipelineAggregateFunction? aggregateFunction20, - PipelineAggregateFunction? aggregateFunction21, - PipelineAggregateFunction? aggregateFunction22, - PipelineAggregateFunction? aggregateFunction23, - PipelineAggregateFunction? aggregateFunction24, - PipelineAggregateFunction? aggregateFunction25, - PipelineAggregateFunction? aggregateFunction26, - PipelineAggregateFunction? aggregateFunction27, - PipelineAggregateFunction? aggregateFunction28, - PipelineAggregateFunction? aggregateFunction29, - PipelineAggregateFunction? aggregateFunction30, + AliasedAggregateFunction aggregateFunction1, [ + AliasedAggregateFunction? aggregateFunction2, + AliasedAggregateFunction? aggregateFunction3, + AliasedAggregateFunction? aggregateFunction4, + AliasedAggregateFunction? aggregateFunction5, + AliasedAggregateFunction? aggregateFunction6, + AliasedAggregateFunction? aggregateFunction7, + AliasedAggregateFunction? aggregateFunction8, + AliasedAggregateFunction? aggregateFunction9, + AliasedAggregateFunction? aggregateFunction10, + AliasedAggregateFunction? aggregateFunction11, + AliasedAggregateFunction? aggregateFunction12, + AliasedAggregateFunction? aggregateFunction13, + AliasedAggregateFunction? aggregateFunction14, + AliasedAggregateFunction? aggregateFunction15, + AliasedAggregateFunction? aggregateFunction16, + AliasedAggregateFunction? aggregateFunction17, + AliasedAggregateFunction? aggregateFunction18, + AliasedAggregateFunction? aggregateFunction19, + AliasedAggregateFunction? aggregateFunction20, + AliasedAggregateFunction? aggregateFunction21, + AliasedAggregateFunction? aggregateFunction22, + AliasedAggregateFunction? aggregateFunction23, + AliasedAggregateFunction? aggregateFunction24, + AliasedAggregateFunction? aggregateFunction25, + AliasedAggregateFunction? aggregateFunction26, + AliasedAggregateFunction? aggregateFunction27, + AliasedAggregateFunction? aggregateFunction28, + AliasedAggregateFunction? aggregateFunction29, + AliasedAggregateFunction? aggregateFunction30, ]) { - final functions = [aggregateFunction1]; + final functions = [aggregateFunction1]; if (aggregateFunction2 != null) functions.add(aggregateFunction2); if (aggregateFunction3 != null) functions.add(aggregateFunction3); if (aggregateFunction4 != null) functions.add(aggregateFunction4); @@ -195,40 +184,92 @@ class Pipeline { ); } + /// Performs optionally grouped aggregation operations on the documents from previous stages. + /// + /// This method allows you to calculate aggregate values over a set of documents, optionally + /// grouped by one or more fields or expressions. You can specify: + /// + /// - **Grouping Fields or Expressions**: One or more fields or functions to group the documents by. + /// For each distinct combination of values in these fields, a separate group is created. + /// If no grouping fields are provided, a single group containing all documents is used. + /// + /// - **Aggregate Functions**: One or more accumulation operations to perform within each group. + /// These are defined using [AliasedAggregateFunction] expressions, which are typically created + /// by calling `.as('alias')` on [PipelineAggregateFunction] instances. Each aggregation calculates + /// a value (e.g., sum, average, count) based on the documents within its group. + /// + /// Example: + /// ```dart + /// pipeline.aggregateStage( + /// AggregateStage( + /// accumulators: [ + /// Expression.field('likes').sum().as('total_likes'), + /// Expression.field('likes').average().as('avg_likes'), + /// ], + /// groups: [Expression.field('category')], + /// ), + /// ); + /// ``` + /// + /// With options: + /// ```dart + /// pipeline.aggregateStage( + /// AggregateStage( + /// accumulators: [ + /// Expression.field('likes').sum().as('total_likes'), + /// ], + /// ), + /// options: AggregateOptions(), + /// ); + /// ``` + Pipeline aggregateStage( + AggregateStage aggregateStage, { + AggregateOptions? options, + }) { + final stage = _AggregateStageWithOptions( + aggregateStage, + options ?? AggregateOptions(), + ); + return Pipeline._( + _firestore, + _delegate.addStage(stage.toMap()), + ); + } + /// Gets distinct values based on expressions Pipeline distinct( - Expression expression1, [ - Expression? expression2, - Expression? expression3, - Expression? expression4, - Expression? expression5, - Expression? expression6, - Expression? expression7, - Expression? expression8, - Expression? expression9, - Expression? expression10, - Expression? expression11, - Expression? expression12, - Expression? expression13, - Expression? expression14, - Expression? expression15, - Expression? expression16, - Expression? expression17, - Expression? expression18, - Expression? expression19, - Expression? expression20, - Expression? expression21, - Expression? expression22, - Expression? expression23, - Expression? expression24, - Expression? expression25, - Expression? expression26, - Expression? expression27, - Expression? expression28, - Expression? expression29, - Expression? expression30, + Selectable expression1, [ + Selectable? expression2, + Selectable? expression3, + Selectable? expression4, + Selectable? expression5, + Selectable? expression6, + Selectable? expression7, + Selectable? expression8, + Selectable? expression9, + Selectable? expression10, + Selectable? expression11, + Selectable? expression12, + Selectable? expression13, + Selectable? expression14, + Selectable? expression15, + Selectable? expression16, + Selectable? expression17, + Selectable? expression18, + Selectable? expression19, + Selectable? expression20, + Selectable? expression21, + Selectable? expression22, + Selectable? expression23, + Selectable? expression24, + Selectable? expression25, + Selectable? expression26, + Selectable? expression27, + Selectable? expression28, + Selectable? expression29, + Selectable? expression30, ]) { - final expressions = [expression1]; + final expressions = [expression1]; if (expression2 != null) expressions.add(expression2); if (expression3 != null) expressions.add(expression3); if (expression4 != null) expressions.add(expression4); @@ -469,7 +510,7 @@ class Pipeline { } /// Unnests arrays into separate documents - Pipeline unnest(Expression expression, [String? indexField]) { + Pipeline unnest(Selectable expression, [String? indexField]) { final stage = _UnnestStage(expression, indexField); return Pipeline._( _firestore, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index f0946ac18995..403d26b7f3a4 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -6,27 +6,45 @@ part of '../cloud_firestore.dart'; /// Base class for aggregate functions used in pipelines abstract class PipelineAggregateFunction implements PipelineSerializable { - String? _alias; - /// Assigns an alias to this aggregate function - PipelineAggregateFunction as(String alias) { - _alias = alias; - return this; + AliasedAggregateFunction as(String alias) { + return AliasedAggregateFunction( + alias: alias, + aggregateFunction: this, + ); } - String? get alias => _alias; - String get name; @override Map toMap() { - final map = { + return { 'name': name, }; - if (_alias != null) { - map['alias'] = _alias; - } - return map; + } +} + +/// Represents an aggregate function with an alias +class AliasedAggregateFunction implements PipelineSerializable { + final String _alias; + final PipelineAggregateFunction aggregateFunction; + + AliasedAggregateFunction({ + required String alias, + required this.aggregateFunction, + }) : _alias = alias; + + String get alias => _alias; + + @override + Map toMap() { + return { + 'name': 'alias', + 'args': { + 'alias': _alias, + 'aggregate_function': aggregateFunction.toMap(), + }, + }; } } @@ -56,3 +74,38 @@ class Count extends PipelineAggregateFunction { return map; } } + +/// Represents an aggregate stage with functions and optional grouping +class AggregateStage implements PipelineSerializable { + final List accumulators; + final List? groups; + + AggregateStage({ + required this.accumulators, + this.groups, + }); + + @override + Map toMap() { + final map = { + 'accumulators': accumulators.map((acc) => acc.toMap()).toList(), + }; + if (groups != null && groups!.isNotEmpty) { + map['groups'] = groups!.map((group) => group.toMap()).toList(); + } + return map; + } +} + +/// Options for aggregate operations +class AggregateOptions implements PipelineSerializable { + // Add any aggregate-specific options here as needed + // For now, this is a placeholder for future options + + AggregateOptions(); + + @override + Map toMap() { + return {}; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index 2c295e0f2e6f..b8a9fc5d9200 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -553,7 +553,32 @@ abstract class Expression implements PipelineSerializable { static Expression constantVector(VectorValue value) => Constant(value); /// Creates a constant expression from any value (convenience) - static Expression constant(Object? value) => Constant(value!); + /// + /// Valid types: String, num, bool, DateTime, Timestamp, GeoPoint, List (byte[]), + /// Blob, DocumentReference, VectorValue + static Expression constant(Object? value) { + if (value == null) { + return Constant(null); + } + // Validate that the value is one of the accepted types + if (value is! String && + value is! num && + value is! bool && + value is! DateTime && + value is! Timestamp && + value is! GeoPoint && + value is! List && + value is! Blob && + value is! DocumentReference && + value is! VectorValue) { + throw ArgumentError( + 'Constant value must be one of: String, num, bool, DateTime, Timestamp, ' + 'GeoPoint, List (byte[]), Blob, DocumentReference, or VectorValue. ' + 'Got: ${value.runtimeType}', + ); + } + return Constant(value); + } /// Creates a field reference expression from a field path string static Field field(String fieldPath) => Field(fieldPath); @@ -734,6 +759,11 @@ abstract class Expression implements PipelineSerializable { return _IsErrorExpression(expr); } + /// Negates a boolean expression + static BooleanExpression not(BooleanExpression expression) { + return _NotExpression(expression); + } + /// Joins array elements with a delimiter static Expression joinStatic( Expression arrayExpression, @@ -1191,10 +1221,33 @@ class _NullExpression extends Expression { } /// Represents a constant value in a pipeline expression +/// +/// Valid types: String, num, bool, DateTime, Timestamp, GeoPoint, List (byte[]), +/// Blob, DocumentReference, VectorValue, or null class Constant extends Expression { - final Object value; - - Constant(this.value); + final Object? value; + + Constant(this.value) { + if (value != null) { + // Validate that the value is one of the accepted types + if (value is! String && + value is! num && + value is! bool && + value is! DateTime && + value is! Timestamp && + value is! GeoPoint && + value is! List && + value is! Blob && + value is! DocumentReference && + value is! VectorValue) { + throw ArgumentError( + 'Constant value must be one of: String, num, bool, DateTime, Timestamp, ' + 'GeoPoint, List (byte[]), Blob, DocumentReference, or VectorValue. ' + 'Got: ${value.runtimeType}', + ); + } + } + } @override String get name => 'constant'; @@ -2237,6 +2290,26 @@ class _ExistsExpression extends BooleanExpression { } } +/// Represents a not (negation) function expression +class _NotExpression extends BooleanExpression { + final BooleanExpression expression; + + _NotExpression(this.expression); + + @override + String get name => 'not'; + + @override + Map toMap() { + return { + 'name': name, + 'args': { + 'expression': expression.toMap(), + }, + }; + } +} + /// Represents a conditional (ternary) function expression class _ConditionalExpression extends FunctionExpression { final BooleanExpression condition; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 7532ddb8e544..14bbdf735e81 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -108,7 +108,7 @@ final class _AddFieldsStage extends PipelineStage { /// Stage for aggregating data final class _AggregateStage extends PipelineStage { - final List aggregateFunctions; + final List aggregateFunctions; _AggregateStage(this.aggregateFunctions); @@ -127,9 +127,33 @@ final class _AggregateStage extends PipelineStage { } } +/// Stage for aggregating data with options and grouping +final class _AggregateStageWithOptions extends PipelineStage { + final AggregateStage aggregateStage; + final AggregateOptions? options; + + _AggregateStageWithOptions(this.aggregateStage, this.options); + + @override + String get name => 'aggregate'; + + @override + Map toMap() { + final map = aggregateStage.toMap(); + final optionsMap = options?.toMap(); + return { + 'stage': name, + 'args': { + 'aggregate_stage': map, + 'options': optionsMap, + }, + }; + } +} + /// Stage for getting distinct values final class _DistinctStage extends PipelineStage { - final List expressions; + final List expressions; _DistinctStage(this.expressions); @@ -323,7 +347,7 @@ final class _SortStage extends PipelineStage { /// Stage for unnesting arrays final class _UnnestStage extends PipelineStage { - final Expression expression; + final Selectable expression; final String? indexField; _UnnestStage(this.expression, this.indexField); From 7d9035350bc50e8d4e75e7695acb1b05c0d36301 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 18 Feb 2026 15:37:30 +0000 Subject: [PATCH 07/20] chore: enhance PigeonPipelineResult to support nullable fields and additional data mapping --- .../FlutterFirebaseFirestorePlugin.java | 9 +- .../GeneratedAndroidFirebaseFirestore.java | 58 +++++++----- .../firestore/utils/ExpressionParsers.java | 11 ++- .../cloud_firestore/FirestoreMessages.g.m | 13 +-- .../Public/FirestoreMessages.g.h | 17 ++-- .../lib/pipeline_snapshot.dart | 16 ++-- .../cloud_firestore/lib/src/pipeline.dart | 4 +- .../cloud_firestore/windows/messages.g.cpp | 88 +++++++++++++++---- .../cloud_firestore/windows/messages.g.h | 30 +++++-- .../method_channel_pipeline_snapshot.dart | 29 ++++-- .../lib/src/pigeon/messages.pigeon.dart | 24 +++-- .../platform_interface_pipeline_snapshot.dart | 8 +- .../pigeons/messages.dart | 15 ++-- 13 files changed, 224 insertions(+), 98 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 297f70fb24d1..4a17da9d532f 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -1012,7 +1012,9 @@ public void executePipeline( for (PipelineResult pipelineResult : snapshot.getResults()) { GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder = new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder(); - resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + if (pipelineResult.getRef() != null) { + resultBuilder.setDocumentPath(pipelineResult.getRef().getPath()); + } // Convert timestamps (assuming they're in milliseconds) if (pipelineResult.getCreateTime() != null) { @@ -1027,6 +1029,11 @@ public void executePipeline( resultBuilder.setUpdateTime(0L); } + Map data = pipelineResult.getData(); + if (data != null) { + resultBuilder.setData(data); + } + pipelineResults.add(resultBuilder.build()); } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 1711e9159d80..892793f339d8 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -859,86 +859,94 @@ ArrayList toList() { /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonPipelineResult { - private @NonNull String documentPath; + private @Nullable String documentPath; - public @NonNull String getDocumentPath() { + public @Nullable String getDocumentPath() { return documentPath; } - public void setDocumentPath(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"documentPath\" is null."); - } + public void setDocumentPath(@Nullable String setterArg) { this.documentPath = setterArg; } - private @NonNull Long createTime; + private @Nullable Long createTime; - public @NonNull Long getCreateTime() { + public @Nullable Long getCreateTime() { return createTime; } - public void setCreateTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"createTime\" is null."); - } + public void setCreateTime(@Nullable Long setterArg) { this.createTime = setterArg; } - private @NonNull Long updateTime; + private @Nullable Long updateTime; - public @NonNull Long getUpdateTime() { + public @Nullable Long getUpdateTime() { return updateTime; } - public void setUpdateTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"updateTime\" is null."); - } + public void setUpdateTime(@Nullable Long setterArg) { this.updateTime = setterArg; } - /** Constructor is non-public to enforce null safety; use Builder. */ - PigeonPipelineResult() {} + /** All fields in the result (from PipelineResult.data() on Android). */ + private @Nullable Map data; + + public @Nullable Map getData() { + return data; + } + + public void setData(@Nullable Map setterArg) { + this.data = setterArg; + } public static final class Builder { private @Nullable String documentPath; - public @NonNull Builder setDocumentPath(@NonNull String setterArg) { + public @NonNull Builder setDocumentPath(@Nullable String setterArg) { this.documentPath = setterArg; return this; } private @Nullable Long createTime; - public @NonNull Builder setCreateTime(@NonNull Long setterArg) { + public @NonNull Builder setCreateTime(@Nullable Long setterArg) { this.createTime = setterArg; return this; } private @Nullable Long updateTime; - public @NonNull Builder setUpdateTime(@NonNull Long setterArg) { + public @NonNull Builder setUpdateTime(@Nullable Long setterArg) { this.updateTime = setterArg; return this; } + private @Nullable Map data; + + public @NonNull Builder setData(@Nullable Map setterArg) { + this.data = setterArg; + return this; + } + public @NonNull PigeonPipelineResult build() { PigeonPipelineResult pigeonReturn = new PigeonPipelineResult(); pigeonReturn.setDocumentPath(documentPath); pigeonReturn.setCreateTime(createTime); pigeonReturn.setUpdateTime(updateTime); + pigeonReturn.setData(data); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(3); + ArrayList toListResult = new ArrayList(4); toListResult.add(documentPath); toListResult.add(createTime); toListResult.add(updateTime); + toListResult.add(data); return toListResult; } @@ -956,6 +964,8 @@ ArrayList toList() { (updateTime == null) ? null : ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime)); + Object data = list.get(3); + pigeonResult.setData((Map) data); return pigeonResult; } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java index 150d932ae6d3..1f80f671a97b 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionParsers.java @@ -395,9 +395,12 @@ AggregateFunction parseAggregateFunction(@NonNull Map aggregateM functionName = (String) aggregateMap.get("name"); } Map args = (Map) aggregateMap.get("args"); - - Map exprMap = (Map) args.get("expression"); - Expression expr = parseExpression(exprMap); + Map exprMap; + Expression expr = null; + if (args != null) { + exprMap = (Map) args.get("expression"); + expr = parseExpression(exprMap); + } switch (functionName) { case "sum": @@ -412,6 +415,8 @@ AggregateFunction parseAggregateFunction(@NonNull Map aggregateM return AggregateFunction.minimum(expr); case "maximum": return AggregateFunction.maximum(expr); + case "count_all": + return AggregateFunction.countAll(); default: throw new IllegalArgumentException("Unknown aggregate function: " + functionName); } diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 2818f5742f4b..0672709a2921 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -421,23 +421,23 @@ - (NSArray *)toList { @end @implementation PigeonPipelineResult -+ (instancetype)makeWithDocumentPath:(NSString *)documentPath - createTime:(NSNumber *)createTime - updateTime:(NSNumber *)updateTime { ++ (instancetype)makeWithDocumentPath:(nullable NSString *)documentPath + createTime:(nullable NSNumber *)createTime + updateTime:(nullable NSNumber *)updateTime + data:(nullable NSDictionary *)data { PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; pigeonResult.documentPath = documentPath; pigeonResult.createTime = createTime; pigeonResult.updateTime = updateTime; + pigeonResult.data = data; return pigeonResult; } + (PigeonPipelineResult *)fromList:(NSArray *)list { PigeonPipelineResult *pigeonResult = [[PigeonPipelineResult alloc] init]; pigeonResult.documentPath = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.documentPath != nil, @""); pigeonResult.createTime = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.createTime != nil, @""); pigeonResult.updateTime = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.updateTime != nil, @""); + pigeonResult.data = GetNullableObjectAtIndex(list, 3); return pigeonResult; } + (nullable PigeonPipelineResult *)nullableFromList:(NSArray *)list { @@ -448,6 +448,7 @@ - (NSArray *)toList { (self.documentPath ?: [NSNull null]), (self.createTime ?: [NSNull null]), (self.updateTime ?: [NSNull null]), + (self.data ?: [NSNull null]), ]; } @end diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 1f43fa87bf89..28885bdc9132 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -246,14 +246,15 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @end @interface PigeonPipelineResult : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithDocumentPath:(NSString *)documentPath - createTime:(NSNumber *)createTime - updateTime:(NSNumber *)updateTime; -@property(nonatomic, copy) NSString *documentPath; -@property(nonatomic, strong) NSNumber *createTime; -@property(nonatomic, strong) NSNumber *updateTime; ++ (instancetype)makeWithDocumentPath:(nullable NSString *)documentPath + createTime:(nullable NSNumber *)createTime + updateTime:(nullable NSNumber *)updateTime + data:(nullable NSDictionary *)data; +@property(nonatomic, copy, nullable) NSString *documentPath; +@property(nonatomic, strong, nullable) NSNumber *createTime; +@property(nonatomic, strong, nullable) NSNumber *updateTime; +/// All fields in the result (from PipelineResult.data() on Android). +@property(nonatomic, strong, nullable) NSDictionary *data; @end @interface PigeonPipelineSnapshot : NSObject diff --git a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart index f08cb6735987..ac1f287acb18 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/pipeline_snapshot.dart @@ -7,14 +7,20 @@ part of 'cloud_firestore.dart'; /// Result of executing a pipeline class PipelineResult { final DocumentReference> document; - final DateTime createTime; - final DateTime updateTime; + final DateTime? createTime; + final DateTime? updateTime; + final Map? _data; PipelineResult({ required this.document, - required this.createTime, - required this.updateTime, - }); + this.createTime, + this.updateTime, + Map? data, + }) : _data = data; + + /// Retrieves all fields in the result as a map. + /// Returns null if the result has no data. + Map? data() => _data; } /// Snapshot containing pipeline execution results diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index eea1c5fa23ae..dce563ab21b7 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -36,7 +36,8 @@ class Pipeline { /// Converts platform snapshot to public snapshot PipelineSnapshot _convertPlatformSnapshot( - PipelineSnapshotPlatform platformSnapshot) { + PipelineSnapshotPlatform platformSnapshot, + ) { final results = platformSnapshot.results.map((platformResult) { return PipelineResult( document: _JsonDocumentReference( @@ -45,6 +46,7 @@ class Pipeline { ), createTime: platformResult.createTime, updateTime: platformResult.updateTime, + data: platformResult.data, ); }).toList(); diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index a70d4a7b95d5..b52f92e5c3a2 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -399,46 +399,102 @@ PigeonQuerySnapshot PigeonQuerySnapshot::FromEncodableList( // PigeonPipelineResult -PigeonPipelineResult::PigeonPipelineResult(const std::string& document_path, - int64_t create_time, - int64_t update_time) - : document_path_(document_path), - create_time_(create_time), - update_time_(update_time) {} +PigeonPipelineResult::PigeonPipelineResult() {} -const std::string& PigeonPipelineResult::document_path() const { - return document_path_; +PigeonPipelineResult::PigeonPipelineResult(const std::string* document_path, + const int64_t* create_time, + const int64_t* update_time, + const EncodableMap* data) + : document_path_(document_path ? std::optional(*document_path) + : std::nullopt), + create_time_(create_time ? std::optional(*create_time) + : std::nullopt), + update_time_(update_time ? std::optional(*update_time) + : std::nullopt), + data_(data ? std::optional(*data) : std::nullopt) {} + +const std::string* PigeonPipelineResult::document_path() const { + return document_path_ ? &(*document_path_) : nullptr; +} + +void PigeonPipelineResult::set_document_path( + const std::string_view* value_arg) { + document_path_ = + value_arg ? std::optional(*value_arg) : std::nullopt; } void PigeonPipelineResult::set_document_path(std::string_view value_arg) { document_path_ = value_arg; } -int64_t PigeonPipelineResult::create_time() const { return create_time_; } +const int64_t* PigeonPipelineResult::create_time() const { + return create_time_ ? &(*create_time_) : nullptr; +} + +void PigeonPipelineResult::set_create_time(const int64_t* value_arg) { + create_time_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} void PigeonPipelineResult::set_create_time(int64_t value_arg) { create_time_ = value_arg; } -int64_t PigeonPipelineResult::update_time() const { return update_time_; } +const int64_t* PigeonPipelineResult::update_time() const { + return update_time_ ? &(*update_time_) : nullptr; +} + +void PigeonPipelineResult::set_update_time(const int64_t* value_arg) { + update_time_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} void PigeonPipelineResult::set_update_time(int64_t value_arg) { update_time_ = value_arg; } +const EncodableMap* PigeonPipelineResult::data() const { + return data_ ? &(*data_) : nullptr; +} + +void PigeonPipelineResult::set_data(const EncodableMap* value_arg) { + data_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PigeonPipelineResult::set_data(const EncodableMap& value_arg) { + data_ = value_arg; +} + EncodableList PigeonPipelineResult::ToEncodableList() const { EncodableList list; - list.reserve(3); - list.push_back(EncodableValue(document_path_)); - list.push_back(EncodableValue(create_time_)); - list.push_back(EncodableValue(update_time_)); + list.reserve(4); + list.push_back(document_path_ ? EncodableValue(*document_path_) + : EncodableValue()); + list.push_back(create_time_ ? EncodableValue(*create_time_) + : EncodableValue()); + list.push_back(update_time_ ? EncodableValue(*update_time_) + : EncodableValue()); + list.push_back(data_ ? EncodableValue(*data_) : EncodableValue()); return list; } PigeonPipelineResult PigeonPipelineResult::FromEncodableList( const EncodableList& list) { - PigeonPipelineResult decoded(std::get(list[0]), - list[1].LongValue(), list[2].LongValue()); + PigeonPipelineResult decoded; + auto& encodable_document_path = list[0]; + if (!encodable_document_path.IsNull()) { + decoded.set_document_path(std::get(encodable_document_path)); + } + auto& encodable_create_time = list[1]; + if (!encodable_create_time.IsNull()) { + decoded.set_create_time(encodable_create_time.LongValue()); + } + auto& encodable_update_time = list[2]; + if (!encodable_update_time.IsNull()) { + decoded.set_update_time(encodable_update_time.LongValue()); + } + auto& encodable_data = list[3]; + if (!encodable_data.IsNull()) { + decoded.set_data(std::get(encodable_data)); + } return decoded; } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 40e4369d79d8..3589afb9db14 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -346,28 +346,42 @@ class PigeonQuerySnapshot { // Generated class from Pigeon that represents data sent in messages. class PigeonPipelineResult { public: + // Constructs an object setting all non-nullable fields. + PigeonPipelineResult(); + // Constructs an object setting all fields. - explicit PigeonPipelineResult(const std::string& document_path, - int64_t create_time, int64_t update_time); + explicit PigeonPipelineResult(const std::string* document_path, + const int64_t* create_time, + const int64_t* update_time, + const flutter::EncodableMap* data); - const std::string& document_path() const; + const std::string* document_path() const; + void set_document_path(const std::string_view* value_arg); void set_document_path(std::string_view value_arg); - int64_t create_time() const; + const int64_t* create_time() const; + void set_create_time(const int64_t* value_arg); void set_create_time(int64_t value_arg); - int64_t update_time() const; + const int64_t* update_time() const; + void set_update_time(const int64_t* value_arg); void set_update_time(int64_t value_arg); + // All fields in the result (from PipelineResult.data() on Android). + const flutter::EncodableMap* data() const; + void set_data(const flutter::EncodableMap* value_arg); + void set_data(const flutter::EncodableMap& value_arg); + private: static PigeonPipelineResult FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; - std::string document_path_; - int64_t create_time_; - int64_t update_time_; + std::optional document_path_; + std::optional create_time_; + std::optional update_time_; + std::optional data_; }; // Generated class from Pigeon that represents data sent in messages. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart index 51ff99ffcff9..2566fd79cf10 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_pipeline_snapshot.dart @@ -17,14 +17,19 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, PigeonPipelineSnapshot pigeonSnapshot, - ) : _results = (pigeonSnapshot.results ?? []) + ) : _results = pigeonSnapshot.results .whereType() .map((result) => MethodChannelPipelineResult( firestore, pigeonApp, result.documentPath, - DateTime.fromMillisecondsSinceEpoch(result.createTime), - DateTime.fromMillisecondsSinceEpoch(result.updateTime), + result.createTime != null + ? DateTime.fromMillisecondsSinceEpoch(result.createTime!) + : null, + result.updateTime != null + ? DateTime.fromMillisecondsSinceEpoch(result.updateTime!) + : null, + result.data?.cast(), )) .toList(), _executionTime = DateTime.fromMillisecondsSinceEpoch( @@ -43,28 +48,34 @@ class MethodChannelPipelineSnapshot extends PipelineSnapshotPlatform { /// communicate with Firebase plugins. class MethodChannelPipelineResult extends PipelineResultPlatform { final DocumentReferencePlatform _document; - final DateTime _createTime; - final DateTime _updateTime; + final DateTime? _createTime; + final DateTime? _updateTime; + final Map? _data; MethodChannelPipelineResult( FirebaseFirestorePlatform firestore, FirestorePigeonFirebaseApp pigeonApp, - String documentPath, + String? documentPath, this._createTime, this._updateTime, + Map? data, ) : _document = MethodChannelDocumentReference( firestore, - documentPath, + documentPath ?? '', pigeonApp, ), + _data = data, super(); @override DocumentReferencePlatform get document => _document; @override - DateTime get createTime => _createTime; + DateTime? get createTime => _createTime; @override - DateTime get updateTime => _updateTime; + DateTime? get updateTime => _updateTime; + + @override + Map? get data => _data; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index 0ea2ee91ccd5..ef835c2b1c83 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -302,31 +302,37 @@ class PigeonQuerySnapshot { class PigeonPipelineResult { PigeonPipelineResult({ - required this.documentPath, - required this.createTime, - required this.updateTime, + this.documentPath, + this.createTime, + this.updateTime, + this.data, }); - String documentPath; + String? documentPath; + + int? createTime; - int createTime; + int? updateTime; - int updateTime; + /// All fields in the result (from PipelineResult.data() on Android). + Map? data; Object encode() { return [ documentPath, createTime, updateTime, + data, ]; } static PigeonPipelineResult decode(Object result) { result as List; return PigeonPipelineResult( - documentPath: result[0]! as String, - createTime: result[1]! as int, - updateTime: result[2]! as int, + documentPath: result[0] as String?, + createTime: result[1] as int?, + updateTime: result[2] as int?, + data: (result[3] as Map?)?.cast(), ); } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart index 8b8d3b6fda30..d4760207a80d 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_pipeline_snapshot.dart @@ -33,8 +33,12 @@ abstract class PipelineResultPlatform extends PlatformInterface { DocumentReferencePlatform get document; /// The creation time of the document - DateTime get createTime; + DateTime? get createTime; /// The update time of the document - DateTime get updateTime; + DateTime? get updateTime; + + /// All fields in the result (from PipelineResult.data() on the native SDK). + /// Returns null if the result has no data. + Map? get data; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart index 33b146653a45..a71ecdec270c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -120,14 +120,17 @@ class PigeonQuerySnapshot { class PigeonPipelineResult { const PigeonPipelineResult({ - required this.documentPath, - required this.createTime, - required this.updateTime, + this.documentPath, + this.createTime, + this.updateTime, + this.data, }); - final String documentPath; - final int createTime; // Timestamp in milliseconds since epoch - final int updateTime; // Timestamp in milliseconds since epoch + final String? documentPath; + final int? createTime; // Timestamp in milliseconds since epoch + final int? updateTime; // Timestamp in milliseconds since epoch + /// All fields in the result (from PipelineResult.data() on Android). + final Map? data; } class PigeonPipelineSnapshot { From 6fbd3e4f4b2e1dd65ef8298246cdf5014ff563ae Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 20 Feb 2026 15:10:50 +0000 Subject: [PATCH 08/20] chore: enhance pipeline stage handling --- .../GeneratedAndroidFirebaseFirestore.java | 41 ++++++++++++------- .../firestore/utils/ExpressionHelpers.java | 22 ---------- .../firestore/utils/PipelineParser.java | 22 +++++----- .../utils/PipelineStageHandlers.java | 31 +++++++++----- 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index 892793f339d8..a3fd90023413 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -12,7 +12,6 @@ import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -219,6 +218,20 @@ private AggregateType(final int index) { } } + /** Sample mode for the pipeline sample stage. */ + public enum PipelineSampleMode { + /** Sample a fixed number of documents. */ + SIZE(0), + /** Sample a percentage of documents (0.0 to 1.0). */ + PERCENTAGE(1); + + final int index; + + private PipelineSampleMode(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonFirebaseSettings { private @Nullable Boolean persistenceEnabled; @@ -326,7 +339,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(persistenceEnabled); toListResult.add(host); @@ -434,7 +447,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(appName); toListResult.add((settings == null) ? null : settings.toList()); @@ -834,7 +847,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(documents); toListResult.add(documentChanges); @@ -941,7 +954,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(documentPath); toListResult.add(createTime); @@ -1026,7 +1039,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(results); toListResult.add(executionTime); @@ -1105,7 +1118,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(source == null ? null : source.index); toListResult.add(serverTimestampBehavior == null ? null : serverTimestampBehavior.index); @@ -1170,7 +1183,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(merge); toListResult.add(mergeFields); @@ -1279,7 +1292,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(type == null ? null : type.index); toListResult.add(path); @@ -1411,7 +1424,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(5); toListResult.add(path); toListResult.add(data); @@ -1614,7 +1627,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(9); toListResult.add(where); toListResult.add(orderBy); @@ -1709,7 +1722,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1797,7 +1810,7 @@ public static final class Builder { } @NonNull - ArrayList toList() { + public ArrayList toList() { ArrayList toListResult = new ArrayList(3); toListResult.add(type == null ? null : type.index); toListResult.add(field); @@ -1824,7 +1837,7 @@ public interface Result { void error(@NonNull Throwable error); } - private static class FirebaseFirestoreHostApiCodec extends StandardMessageCodec { + private static class FirebaseFirestoreHostApiCodec extends FlutterFirebaseFirestoreMessageCodec { public static final FirebaseFirestoreHostApiCodec INSTANCE = new FirebaseFirestoreHostApiCodec(); diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java index cb162d253310..b3ef31dbb038 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -127,26 +127,4 @@ static Expression parseConstantValue(@NonNull Object value) { + "GeoPoint, byte[], Blob, DocumentReference, or VectorValue. Got: " + value.getClass().getName()); } - - /** Safely extracts a single expression from args map. */ - @SuppressWarnings("unchecked") - static Map extractExpression( - @NonNull Map args, @NonNull String key) { - Map exprMap = (Map) args.get(key); - if (exprMap == null) { - throw new IllegalArgumentException("Missing required expression argument: " + key); - } - return exprMap; - } - - /** Safely extracts a list of expressions from args map. */ - @SuppressWarnings("unchecked") - static List> extractExpressionList( - @NonNull Map args, @NonNull String key) { - List> exprMaps = (List>) args.get(key); - if (exprMaps == null || exprMaps.isEmpty()) { - throw new IllegalArgumentException("Missing or empty expression list: " + key); - } - return exprMaps; - } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java index 609f34be03ca..2e0296d92f74 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineParser.java @@ -38,14 +38,21 @@ public static Snapshot executePipeline( @NonNull List> stages, @Nullable Map options) throws Exception { - if (stages == null || stages.isEmpty()) { - throw new IllegalArgumentException("Pipeline must have at least one stage"); - } + Pipeline pipeline = buildPipeline(firestore, stages); + Task task = pipeline.execute(); + return Tasks.await(task); + } + /** + * Builds a Pipeline from a list of stage maps without executing it. Used when a stage (e.g. + * union) requires another pipeline as an argument. + */ + @SuppressWarnings("unchecked") + public static Pipeline buildPipeline( + @NonNull FirebaseFirestore firestore, @NonNull List> stages) { PipelineSource pipelineSource = firestore.pipeline(); Pipeline pipeline = null; - // Process each stage in order for (int i = 0; i < stages.size(); i++) { Map stageMap = stages.get(i); String stageName = (String) stageMap.get("stage"); @@ -53,21 +60,16 @@ public static Snapshot executePipeline( throw new IllegalArgumentException("Stage must have a 'stage' field"); } - @SuppressWarnings("unchecked") Map args = (Map) stageMap.get("args"); if (i == 0) { - // First stage must be a source stage (collection, collection_group, documents, database) pipeline = applySourceStage(pipelineSource, stageName, args, firestore); } else { - // Subsequent stages are methods on the Pipeline instance pipeline = stageHandlers.applyStage(pipeline, stageName, args, firestore); } } - // Execute the pipeline - Task task = pipeline.execute(); - return Tasks.await(task); + return pipeline; } /** diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java index 7aa36fba7be4..3883e36225b2 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -6,7 +6,6 @@ package io.flutter.plugins.firebase.firestore.utils; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.firebase.firestore.FirebaseFirestore; @@ -20,6 +19,7 @@ import com.google.firebase.firestore.pipeline.FindNearestOptions; import com.google.firebase.firestore.pipeline.FindNearestStage; import com.google.firebase.firestore.pipeline.Ordering; +import com.google.firebase.firestore.pipeline.SampleStage; import com.google.firebase.firestore.pipeline.Selectable; import com.google.firebase.firestore.pipeline.UnnestOptions; import java.util.List; @@ -27,7 +27,6 @@ /** Handles parsing and applying pipeline stages to Pipeline instances. */ class PipelineStageHandlers { - private static final String TAG = "PipelineStageHandlers"; private final ExpressionParsers parsers; PipelineStageHandlers(@NonNull ExpressionParsers parsers) { @@ -65,7 +64,7 @@ Pipeline applyStage( case "replace_with": return handleReplaceWith(pipeline, args); case "union": - return handleUnion(pipeline, args); + return handleUnion(pipeline, args, firestore); case "sample": return handleSample(pipeline, args); case "find_nearest": @@ -257,21 +256,31 @@ private Pipeline handleReplaceWith( return pipeline.replaceWith(expression); } - private Pipeline handleUnion(@NonNull Pipeline pipeline, @Nullable Map args) { - // Union requires a nested pipeline + @SuppressWarnings("unchecked") + private Pipeline handleUnion( + @NonNull Pipeline pipeline, + @Nullable Map args, + @NonNull FirebaseFirestore firestore) { List> nestedStages = (List>) args.get("pipeline"); - // Note: This would require creating a nested pipeline, which may not be directly - // supported. This is a placeholder for now. - Log.w(TAG, "Union stage not yet fully implemented"); - throw new UnsupportedOperationException("Union stage not yet implemented"); + if (nestedStages == null || nestedStages.isEmpty()) { + throw new IllegalArgumentException("'union' requires a non-empty 'pipeline' argument"); + } + Pipeline otherPipeline = PipelineParser.buildPipeline(firestore, nestedStages); + return pipeline.union(otherPipeline); } private Pipeline handleSample(@NonNull Pipeline pipeline, @Nullable Map args) { // Sample stage parsing Map sampleMap = (Map) args; // Parse sample configuration - Log.w(TAG, "Sample stage not yet fully implemented"); - throw new UnsupportedOperationException("Sample stage not yet implemented"); + String type = (String) sampleMap.get("type"); + if (type == "percentage") { + double value = (double) sampleMap.get("value"); + return pipeline.sample(SampleStage.withPercentage(value)); + } else { + int value = (int) sampleMap.get("value"); + return pipeline.sample(SampleStage.withDocLimit(value)); + } } @SuppressWarnings("unchecked") From 3e557c62b6eea5df3b7880f0cf25fde6547b9a85 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 24 Feb 2026 15:16:01 +0000 Subject: [PATCH 09/20] chore: refactor sorting functionality in pipeline stages to support multiple orderings --- .../GeneratedAndroidFirebaseFirestore.java | 14 - .../utils/PipelineStageHandlers.java | 31 +- .../cloud_firestore/lib/src/firestore.dart | 2 +- .../cloud_firestore/lib/src/pipeline.dart | 69 +++- .../lib/src/pipeline_stage.dart | 14 +- .../lib/src/pigeon/messages.pigeon.dart | 311 ++++++++++-------- 6 files changed, 284 insertions(+), 157 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index a3fd90023413..4e6e315d71fe 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java @@ -218,20 +218,6 @@ private AggregateType(final int index) { } } - /** Sample mode for the pipeline sample stage. */ - public enum PipelineSampleMode { - /** Sample a fixed number of documents. */ - SIZE(0), - /** Sample a percentage of documents (0.0 to 1.0). */ - PERCENTAGE(1); - - final int index; - - private PipelineSampleMode(final int index) { - this.index = index; - } - } - /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonFirebaseSettings { private @Nullable Boolean persistenceEnabled; diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java index 3883e36225b2..78ef835e3006 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/PipelineStageHandlers.java @@ -90,12 +90,33 @@ private Pipeline handleOffset(@NonNull Pipeline pipeline, @Nullable Map args) { - Map orderingMap = (Map) args.get("expression"); - Expression expression = parsers.parseExpression(orderingMap); - String direction = (String) args.get("order_direction"); - Ordering ordering = "asc".equals(direction) ? expression.ascending() : expression.descending(); - return pipeline.sort(ordering); + List> orderingMaps = (List>) args.get("orderings"); + if (orderingMaps == null || orderingMaps.isEmpty()) { + throw new IllegalArgumentException("'sort' requires at least one ordering"); + } + + Map firstMap = orderingMaps.get(0); + Expression expression = + parsers.parseExpression((Map) firstMap.get("expression")); + String direction = (String) firstMap.get("order_direction"); + Ordering firstOrdering = + "asc".equals(direction) ? expression.ascending() : expression.descending(); + + if (orderingMaps.size() == 1) { + return pipeline.sort(firstOrdering); + } + + Ordering[] additionalOrderings = new Ordering[orderingMaps.size() - 1]; + for (int i = 1; i < orderingMaps.size(); i++) { + Map map = orderingMaps.get(i); + expression = parsers.parseExpression((Map) map.get("expression")); + direction = (String) map.get("order_direction"); + additionalOrderings[i - 1] = + "asc".equals(direction) ? expression.ascending() : expression.descending(); + } + return pipeline.sort(firstOrdering, additionalOrderings); } private Pipeline handleSelect(@NonNull Pipeline pipeline, @Nullable Map args) { diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 32fe4f3e16db..e5fcc3264cac 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -350,7 +350,7 @@ class FirebaseFirestore extends FirebasePluginPlatform { /// .pipeline() /// .collection('users') /// .where(PipelineFilter(Field('age'), isGreaterThan: 18)) - /// .sort(Field('name').ascending()) + /// .sort(Field('name').ascending(), Field('age').descending()) /// .limit(10) /// .execute(); /// ``` diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart index dce563ab21b7..3565b835bbc9 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline.dart @@ -502,9 +502,72 @@ class Pipeline { ); } - /// Sorts results using an ordering specification - Pipeline sort(Ordering ordering) { - final stage = _SortStage(ordering); + /// Sorts results using one or more ordering specifications. + /// Orderings are applied in sequence (e.g. primary sort by first, then by second, etc.). + Pipeline sort( + Ordering order, [ + Ordering? order2, + Ordering? order3, + Ordering? order4, + Ordering? order5, + Ordering? order6, + Ordering? order7, + Ordering? order8, + Ordering? order9, + Ordering? order10, + Ordering? order11, + Ordering? order12, + Ordering? order13, + Ordering? order14, + Ordering? order15, + Ordering? order16, + Ordering? order17, + Ordering? order18, + Ordering? order19, + Ordering? order20, + Ordering? order21, + Ordering? order22, + Ordering? order23, + Ordering? order24, + Ordering? order25, + Ordering? order26, + Ordering? order27, + Ordering? order28, + Ordering? order29, + Ordering? order30, + ]) { + final orderings = [order]; + if (order2 != null) orderings.add(order2); + if (order3 != null) orderings.add(order3); + if (order4 != null) orderings.add(order4); + if (order5 != null) orderings.add(order5); + if (order6 != null) orderings.add(order6); + if (order7 != null) orderings.add(order7); + if (order8 != null) orderings.add(order8); + if (order9 != null) orderings.add(order9); + if (order10 != null) orderings.add(order10); + if (order11 != null) orderings.add(order11); + if (order12 != null) orderings.add(order12); + if (order13 != null) orderings.add(order13); + if (order14 != null) orderings.add(order14); + if (order15 != null) orderings.add(order15); + if (order16 != null) orderings.add(order16); + if (order17 != null) orderings.add(order17); + if (order18 != null) orderings.add(order18); + if (order19 != null) orderings.add(order19); + if (order20 != null) orderings.add(order20); + if (order21 != null) orderings.add(order21); + if (order22 != null) orderings.add(order22); + if (order23 != null) orderings.add(order23); + if (order24 != null) orderings.add(order24); + if (order25 != null) orderings.add(order25); + if (order26 != null) orderings.add(order26); + if (order27 != null) orderings.add(order27); + if (order28 != null) orderings.add(order28); + if (order29 != null) orderings.add(order29); + if (order30 != null) orderings.add(order30); + + final stage = _SortStage(orderings); return Pipeline._( _firestore, _delegate.addStage(stage.toMap()), diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 14bbdf735e81..11c4ec624da1 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -325,9 +325,9 @@ final class _SelectStage extends PipelineStage { /// Stage for sorting results final class _SortStage extends PipelineStage { - final Ordering ordering; + final List orderings; - _SortStage(this.ordering); + _SortStage(this.orderings); @override String get name => 'sort'; @@ -337,9 +337,13 @@ final class _SortStage extends PipelineStage { return { 'stage': name, 'args': { - 'expression': ordering.expression.toMap(), - 'order_direction': - ordering.direction == OrderDirection.asc ? 'asc' : 'desc', + 'orderings': orderings + .map((o) => { + 'expression': o.expression.toMap(), + 'order_direction': + o.direction == OrderDirection.asc ? 'asc' : 'desc', + }) + .toList(), }, }; } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index ef835c2b1c83..224ded3d6bf6 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart'; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; @@ -617,7 +618,7 @@ class AggregateQueryResponse { } } -class _FirebaseFirestoreHostApiCodec extends StandardMessageCodec { +class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { const _FirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { @@ -721,11 +722,14 @@ class FirebaseFirestoreHostApi { static const MessageCodec codec = _FirebaseFirestoreHostApiCodec(); Future loadBundle( - FirestorePigeonFirebaseApp arg_app, Uint8List arg_bundle) async { + FirestorePigeonFirebaseApp arg_app, + Uint8List arg_bundle, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_bundle]) as List?; if (replyList == null) { @@ -749,12 +753,16 @@ class FirebaseFirestoreHostApi { } } - Future namedQueryGet(FirestorePigeonFirebaseApp arg_app, - String arg_name, PigeonGetOptions arg_options) async { + Future namedQueryGet( + FirestorePigeonFirebaseApp arg_app, + String arg_name, + PigeonGetOptions arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_name, arg_options]) as List?; if (replyList == null) { @@ -780,9 +788,10 @@ class FirebaseFirestoreHostApi { Future clearPersistence(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -803,9 +812,10 @@ class FirebaseFirestoreHostApi { Future disableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -826,9 +836,10 @@ class FirebaseFirestoreHostApi { Future enableNetwork(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -849,9 +860,10 @@ class FirebaseFirestoreHostApi { Future terminate(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -872,9 +884,10 @@ class FirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp arg_app) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -894,11 +907,14 @@ class FirebaseFirestoreHostApi { } Future setIndexConfiguration( - FirestorePigeonFirebaseApp arg_app, String arg_indexConfiguration) async { + FirestorePigeonFirebaseApp arg_app, + String arg_indexConfiguration, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_indexConfiguration]) as List?; if (replyList == null) { @@ -919,9 +935,10 @@ class FirebaseFirestoreHostApi { Future setLoggingEnabled(bool arg_loggingEnabled) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_loggingEnabled]) as List?; if (replyList == null) { @@ -941,11 +958,13 @@ class FirebaseFirestoreHostApi { } Future snapshotsInSyncSetup( - FirestorePigeonFirebaseApp arg_app) async { + FirestorePigeonFirebaseApp arg_app, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app]) as List?; if (replyList == null) { @@ -969,12 +988,16 @@ class FirebaseFirestoreHostApi { } } - Future transactionCreate(FirestorePigeonFirebaseApp arg_app, - int arg_timeout, int arg_maxAttempts) async { + Future transactionCreate( + FirestorePigeonFirebaseApp arg_app, + int arg_timeout, + int arg_maxAttempts, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_timeout, arg_maxAttempts]) as List?; @@ -1000,16 +1023,18 @@ class FirebaseFirestoreHostApi { } Future transactionStoreResult( - String arg_transactionId, - PigeonTransactionResult arg_resultType, - List? arg_commands) async { + String arg_transactionId, + PigeonTransactionResult arg_resultType, + List? arg_commands, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send( - [arg_transactionId, arg_resultType.index, arg_commands]) - as List?; + [arg_transactionId, arg_resultType.index, arg_commands], + ) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -1027,13 +1052,15 @@ class FirebaseFirestoreHostApi { } Future transactionGet( - FirestorePigeonFirebaseApp arg_app, - String arg_transactionId, - String arg_path) async { + FirestorePigeonFirebaseApp arg_app, + String arg_transactionId, + String arg_path, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_transactionId, arg_path]) as List?; @@ -1058,12 +1085,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceSet(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceSet( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1082,12 +1112,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceUpdate(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceUpdate( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1107,12 +1140,14 @@ class FirebaseFirestoreHostApi { } Future documentReferenceGet( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1136,12 +1171,15 @@ class FirebaseFirestoreHostApi { } } - Future documentReferenceDelete(FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_request) async { + Future documentReferenceDelete( + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_request]) as List?; if (replyList == null) { @@ -1161,21 +1199,23 @@ class FirebaseFirestoreHostApi { } Future queryGet( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, arg_isCollectionGroup, arg_parameters, - arg_options + arg_options, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1199,23 +1239,25 @@ class FirebaseFirestoreHostApi { } Future> aggregateQuery( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - PigeonQueryParameters arg_parameters, - AggregateSource arg_source, - List arg_queries, - bool arg_isCollectionGroup) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + PigeonQueryParameters arg_parameters, + AggregateSource arg_source, + List arg_queries, + bool arg_isCollectionGroup, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, arg_parameters, arg_source.index, arg_queries, - arg_isCollectionGroup + arg_isCollectionGroup, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1238,12 +1280,15 @@ class FirebaseFirestoreHostApi { } } - Future writeBatchCommit(FirestorePigeonFirebaseApp arg_app, - List arg_writes) async { + Future writeBatchCommit( + FirestorePigeonFirebaseApp arg_app, + List arg_writes, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([arg_app, arg_writes]) as List?; if (replyList == null) { @@ -1263,17 +1308,19 @@ class FirebaseFirestoreHostApi { } Future querySnapshot( - FirestorePigeonFirebaseApp arg_app, - String arg_path, - bool arg_isCollectionGroup, - PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, - bool arg_includeMetadataChanges, - ListenSource arg_source) async { + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + bool arg_includeMetadataChanges, + ListenSource arg_source, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_path, @@ -1281,7 +1328,7 @@ class FirebaseFirestoreHostApi { arg_parameters, arg_options, arg_includeMetadataChanges, - arg_source.index + arg_source.index, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1305,19 +1352,21 @@ class FirebaseFirestoreHostApi { } Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp arg_app, - DocumentReferenceRequest arg_parameters, - bool arg_includeMetadataChanges, - ListenSource arg_source) async { + FirestorePigeonFirebaseApp arg_app, + DocumentReferenceRequest arg_parameters, + bool arg_includeMetadataChanges, + ListenSource arg_source, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel.send([ arg_app, arg_parameters, arg_includeMetadataChanges, - arg_source.index + arg_source.index, ]) as List?; if (replyList == null) { throw PlatformException( @@ -1341,12 +1390,14 @@ class FirebaseFirestoreHostApi { } Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp arg_app, - PersistenceCacheIndexManagerRequest arg_request) async { + FirestorePigeonFirebaseApp arg_app, + PersistenceCacheIndexManagerRequest arg_request, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_request.index]) as List?; if (replyList == null) { @@ -1366,13 +1417,15 @@ class FirebaseFirestoreHostApi { } Future executePipeline( - FirestorePigeonFirebaseApp arg_app, - List?> arg_stages, - Map? arg_options) async { + FirestorePigeonFirebaseApp arg_app, + List?> arg_stages, + Map? arg_options, + ) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', - codec, - binaryMessenger: _binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: _binaryMessenger, + ); final List? replyList = await channel .send([arg_app, arg_stages, arg_options]) as List?; if (replyList == null) { From d3c2eeb110e6ddefcc584cd9a97480f466c2bbf4 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Tue, 24 Feb 2026 15:18:00 +0000 Subject: [PATCH 10/20] chore: implement iOS support for Firestore pipeline execution --- .../FLTFirebaseFirestorePlugin.m | 77 ++ .../cloud_firestore/FLTPipelineParser.m | 512 +++++++++++ .../cloud_firestore/FirestoreMessages.g.m | 14 +- .../Private/FLTPipelineParser.h | 23 + .../Public/FirestoreMessages.g.h | 2 +- .../cloud_firestore/windows/messages.g.cpp | 5 +- .../cloud_firestore/windows/messages.g.h | 13 +- .../pigeons/generate_pigeon.sh | 4 +- .../test/pigeon/test_api.dart | 829 +++++++++++------- 9 files changed, 1168 insertions(+), 311 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m create mode 100644 packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m index b7fa740fe68c..ab826056264f 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTFirebaseFirestorePlugin.m @@ -15,6 +15,7 @@ #import "include/cloud_firestore/Private/FLTFirebaseFirestoreReader.h" #import "include/cloud_firestore/Private/FLTFirebaseFirestoreUtils.h" #import "include/cloud_firestore/Private/FLTLoadBundleStreamHandler.h" +#import "include/cloud_firestore/Private/FLTPipelineParser.h" #import "include/cloud_firestore/Private/FLTQuerySnapshotStreamHandler.h" #import "include/cloud_firestore/Private/FLTSnapshotsInSyncStreamHandler.h" #import "include/cloud_firestore/Private/FLTTransactionStreamHandler.h" @@ -73,6 +74,20 @@ - (NSString *)registerEventChannelWithPrefix:(NSString *)prefix static NSCache *_serverTimestampMap; +static id _Nullable FLTPipelineNullSafe(id value) { + return (value == nil || [value isKindOfClass:[NSNull class]]) ? nil : value; +} + +static NSNumber *_Nullable FLTPipelineTimestampToMs(id value) { + if (!value) return nil; + if ([value isKindOfClass:[NSNumber class]]) return value; + if ([value isKindOfClass:[FIRTimestamp class]]) { + FIRTimestamp *ts = value; + return @((int64_t)ts.seconds * 1000 + (int64_t)ts.nanoseconds / 1000000); + } + return nil; +} + @implementation FLTFirebaseFirestorePlugin { NSMutableDictionary *_eventChannels; NSMutableDictionary *> *_streamHandlers; @@ -883,4 +898,66 @@ - (void)aggregateQueryApp:(nonnull FirestorePigeonFirebaseApp *)app }]; } +- (void)executePipelineApp:(nonnull FirestorePigeonFirebaseApp *)app + stages:(nonnull NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(nonnull void (^)(PigeonPipelineSnapshot *_Nullable, + FlutterError *_Nullable))completion { + FIRFirestore *firestore = [self getFIRFirestoreFromAppNameFromPigeon:app]; + + [FLTPipelineParser + executePipelineWithFirestore:firestore + stages:stages + options:options + completion:^(id _Nullable snapshot, NSError *_Nullable error) { + if (error) { + completion(nil, [self convertToFlutterError:error]); + return; + } + if (snapshot == nil) { + completion( + nil, + [FlutterError errorWithCode:@"error" + message:@"Pipeline execution returned no result" + details:nil]); + return; + } + + NSMutableArray *pigeonResults = + [NSMutableArray array]; + NSArray *results = [snapshot results]; + if ([results isKindOfClass:[NSArray class]]) { + for (id result in results) { + id ref = [result reference]; + NSString *path = (ref && [ref respondsToSelector:@selector(path)]) + ? [ref path] + : FLTPipelineNullSafe([result documentID]); + NSNumber *createTime = + FLTPipelineTimestampToMs([result valueForKey:@"create_time"]); + NSNumber *updateTime = + FLTPipelineTimestampToMs([result valueForKey:@"update_time"]); + NSDictionary *data = FLTPipelineNullSafe([result data]); + PigeonPipelineResult *pigeonResult = + [PigeonPipelineResult makeWithDocumentPath:path + createTime:createTime + updateTime:updateTime + data:data]; + [pigeonResults addObject:pigeonResult]; + } + } + + NSNumber *executionTime = + FLTPipelineTimestampToMs([snapshot execution_time]); + if (executionTime == nil) { + executionTime = + @((int64_t)([[NSDate date] timeIntervalSince1970] * 1000)); + } + + PigeonPipelineSnapshot *pigeonSnapshot = + [PigeonPipelineSnapshot makeWithResults:pigeonResults + executionTime:executionTime]; + completion(pigeonSnapshot, nil); + }]; +} + @end diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m new file mode 100644 index 000000000000..620d8ee7bc2d --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -0,0 +1,512 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#import "include/cloud_firestore/Private/FLTPipelineParser.h" + +#if TARGET_OS_OSX +#import +#else +@import FirebaseFirestore; +#import "FIRPipelineBridge.h" +#endif + +#import + +static NSString *const kPipelineNotAvailable = + @"Pipeline API is not available. Firestore Pipelines require Firebase iOS SDK with pipeline " + "support."; + +static NSError *pipelineUnavailableError(void) { + return [NSError errorWithDomain:@"FLTFirebaseFirestore" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : kPipelineNotAvailable}]; +} + +#if __has_include("FIRPipelineBridge.h") +#define FLT_PIPELINE_AVAILABLE 1 + +static NSError *parseError(NSString *message) { + return [NSError errorWithDomain:@"FLTFirebaseFirestore" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : message}]; +} + +@interface FLTPipelineExpressionParser : NSObject +- (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error; +- (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map + error:(NSError **)error; +@end + +@implementation FLTPipelineExpressionParser + +- (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error { + NSString *name = map[@"name"]; + if (!name) { + NSDictionary *args = map[@"args"]; + if ([args isKindOfClass:[NSDictionary class]] && args[@"field"]) { + return [[FIRFieldBridge alloc] initWithName:args[@"field"]]; + } + if (error) *error = parseError(@"Expression must have a 'name' field"); + return nil; + } + + NSDictionary *args = map[@"args"]; + if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + + if ([name isEqualToString:@"field"]) { + NSString *field = args[@"field"]; + if (!field) { + if (error) *error = parseError(@"Field expression requires 'field' argument"); + return nil; + } + return [[FIRFieldBridge alloc] initWithName:field]; + } + + if ([name isEqualToString:@"constant"]) { + id value = args[@"value"]; + if (value == nil) { + if (error) *error = parseError(@"Constant requires 'value' argument"); + return nil; + } + return [[FIRConstantBridge alloc] init:value]; + } + + if ([name isEqualToString:@"alias"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Alias requires 'expression'"); + return nil; + } + // No explicit AliasedExpression type in ObjC; aliases are dict keys when building stages. + // Parse and return the inner expression; the caller uses args[@"alias"] as the dict key. + return [self parseExpression:exprMap error:error]; + } + + NSArray *binaryNames = @[ + @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", + @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo" + ]; + if ([binaryNames containsObject:name]) { + id leftMap = args[@"left"]; + id rightMap = args[@"right"]; + if (![leftMap isKindOfClass:[NSDictionary class]] || + ![rightMap isKindOfClass:[NSDictionary class]]) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires left and right expressions", name]); + return nil; + } + FIRExprBridge *left = [self parseExpression:leftMap error:error]; + FIRExprBridge *right = [self parseExpression:rightMap error:error]; + if (!left || !right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ left, right ]]; + } + + if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); + return nil; + } + FIRExprBridge *first = [self parseBooleanExpression:exprMaps[0] error:error]; + if (!first) return nil; + NSMutableArray *all = [NSMutableArray arrayWithObject:first]; + for (NSUInteger i = 1; i < exprMaps.count; i++) { + FIRExprBridge *next = [self parseBooleanExpression:exprMaps[i] error:error]; + if (!next) return nil; + [all addObject:next]; + } + return [[FIRFunctionExprBridge alloc] initWithName:name Args:all]; + } + + if ([name isEqualToString:@"not"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"not requires expression"); + return nil; + } + FIRExprBridge *expr = [self parseBooleanExpression:exprMap error:error]; + if (!expr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"not" Args:@[ expr ]]; + } + + if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); + return nil; +} + +- (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map + error:(NSError **)error { + return [self parseExpression:map error:error]; +} + +@end + +@implementation FLTPipelineParser + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + if (!stages || stages.count == 0) { + completion(nil, parseError(@"Pipeline requires at least one stage")); + return; + } + + FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; + NSMutableArray *stageBridges = [NSMutableArray array]; + NSError *parseErr = nil; + + for (NSUInteger i = 0; i < stages.count; i++) { + NSDictionary *stageMap = stages[i]; + if (![stageMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"Stage must be a map")); + return; + } + NSString *stageName = stageMap[@"stage"]; + if (![stageName isKindOfClass:[NSString class]]) { + completion(nil, parseError(@"Stage must have a 'stage' field")); + return; + } + NSDictionary *args = stageMap[@"args"]; + if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + + FIRStageBridge *stage = nil; + + if (i == 0) { + if ([stageName isEqualToString:@"collection"]) { + NSString *path = args[@"path"]; + if (!path) { + completion(nil, parseError(@"collection requires 'path'")); + return; + } + FIRCollectionReference *ref = [firestore collectionWithPath:path]; + stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; + } else if ([stageName isEqualToString:@"collection_group"]) { + NSString *path = args[@"path"]; + if (!path) { + completion(nil, parseError(@"collection_group requires 'path'")); + return; + } + stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:path]; + } else if ([stageName isEqualToString:@"database"]) { + stage = [[FIRDatabaseSourceStageBridge alloc] init]; + } else if ([stageName isEqualToString:@"documents"]) { + id argsOrArray = stageMap[@"args"]; + NSArray *docMaps = [argsOrArray isKindOfClass:[NSArray class]] ? argsOrArray : nil; + if (!docMaps || docMaps.count == 0) { + completion(nil, parseError(@"documents requires array of document refs")); + return; + } + NSMutableArray *refs = [NSMutableArray array]; + for (id docMap in docMaps) { + if (![docMap isKindOfClass:[NSDictionary class]]) continue; + NSString *path = ((NSDictionary *)docMap)[@"path"]; + if (path) [refs addObject:[firestore documentWithPath:path]]; + } + stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; + } else { + completion(nil, parseError([NSString + stringWithFormat:@"First stage must be collection, collection_group, " + @"documents, or database. Got: %@", + stageName])); + return; + } + } else { + if ([stageName isEqualToString:@"where"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"where requires expression")); + return; + } + FIRExprBridge *expr = [exprParser parseBooleanExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"limit"]) { + NSNumber *limit = args[@"limit"]; + if (![limit isKindOfClass:[NSNumber class]]) { + completion(nil, parseError(@"limit requires numeric limit")); + return; + } + stage = [[FIRLimitStageBridge alloc] initWithLimit:limit.intValue]; + } else if ([stageName isEqualToString:@"offset"]) { + NSNumber *offset = args[@"offset"]; + if (![offset isKindOfClass:[NSNumber class]]) { + completion(nil, parseError(@"offset requires numeric offset")); + return; + } + stage = [[FIROffsetStageBridge alloc] initWithOffset:offset.intValue]; + } else if ([stageName isEqualToString:@"sort"]) { + NSArray *orderingMaps = args[@"orderings"]; + if (![orderingMaps isKindOfClass:[NSArray class]] || orderingMaps.count == 0) { + completion(nil, parseError(@"sort requires at least one ordering")); + return; + } + NSMutableArray *orderings = [NSMutableArray array]; + for (id om in orderingMaps) { + if (![om isKindOfClass:[NSDictionary class]]) continue; + id exprMap = ((NSDictionary *)om)[@"expression"]; + NSString *dir = ((NSDictionary *)om)[@"order_direction"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; + FIROrderingBridge *ordering = [[FIROrderingBridge alloc] initWithExpr:expr + Direction:direction]; + [orderings addObject:ordering]; + } + if (orderings.count == 0) { + completion(nil, parseError(@"sort requires at least one ordering")); + return; + } + stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; + } else if ([stageName isEqualToString:@"select"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"select requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if (alias) { + fields[alias] = expr; + } else { + NSString *fn = em[@"name"]; + if ([fn isEqualToString:@"field"]) { + NSString *field = [em valueForKeyPath:@"args.field"]; + fields[field ?: @"_"] = expr; + } else { + fields[[NSString stringWithFormat:@"_%lu", (unsigned long)fields.count]] = expr; + } + } + } + stage = [[FIRSelectStageBridge alloc] initWithSelections:fields]; + } else if ([stageName isEqualToString:@"add_fields"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"add_fields requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if (!alias) { + completion(nil, parseError(@"add_fields expressions must have alias")); + return; + } + fields[alias] = expr; + } + stage = [[FIRAddFieldsStageBridge alloc] initWithFields:fields]; + } else if ([stageName isEqualToString:@"remove_fields"]) { + NSArray *paths = args[@"field_paths"]; + if (![paths isKindOfClass:[NSArray class]] || paths.count == 0) { + completion(nil, parseError(@"remove_fields requires field_paths")); + return; + } + stage = [[FIRRemoveFieldsStageBridge alloc] initWithFields:paths]; + } else if ([stageName isEqualToString:@"distinct"]) { + NSArray *exprMaps = args[@"expressions"]; + if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { + completion(nil, parseError(@"distinct requires at least one expression")); + return; + } + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + for (NSUInteger j = 0; j < exprMaps.count; j++) { + id em = exprMaps[j]; + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; + } + stage = [[FIRDistinctStageBridge alloc] initWithGroups:fields]; + } else if ([stageName isEqualToString:@"replace_with"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + completion(nil, parseError(@"replace_with requires expression")); + return; + } + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) { + completion(nil, parseErr); + return; + } + stage = [[FIRReplaceWithStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"union"]) { + NSArray *nestedStages = args[@"pipeline"]; + if (![nestedStages isKindOfClass:[NSArray class]] || nestedStages.count == 0) { + completion(nil, parseError(@"union requires non-empty pipeline")); + return; + } + id otherPipeline = [self buildPipelineWithFirestore:firestore + stages:nestedStages + error:&parseErr]; + if (!otherPipeline) { + completion(nil, parseErr); + return; + } + stage = [[FIRUnionStageBridge alloc] initWithOther:otherPipeline]; + } else if ([stageName isEqualToString:@"sample"]) { + NSString *type = args[@"type"]; + id val = args[@"value"]; + if ([type isEqualToString:@"percentage"]) { + double v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val doubleValue] : 0; + stage = [[FIRSampleStageBridge alloc] initWithPercentage:v]; + } else { + int v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val intValue] : 0; + stage = [[FIRSampleStageBridge alloc] initWithCount:v]; + } + } else { + completion( + nil, parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName])); + return; + } + } + + if (stage) [stageBridges addObject:stage]; + } + + if (stageBridges.count == 0) { + completion(nil, parseError(@"No valid stages")); + return; + } + + FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges + db:firestore]; + [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { + if (execError) { + completion(nil, execError); + return; + } + completion(snapshot, nil); + }]; +} + ++ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { + FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; + NSMutableArray *stageBridges = [NSMutableArray array]; + + for (NSUInteger i = 0; i < stages.count; i++) { + NSDictionary *stageMap = stages[i]; + if (![stageMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Stage must be a map"); + return nil; + } + NSString *stageName = stageMap[@"stage"]; + id argsObj = stageMap[@"args"]; + NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; + NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; + + FIRStageBridge *stage = nil; + + if (i == 0) { + if ([stageName isEqualToString:@"collection"]) { + NSString *path = args[@"path"]; + FIRCollectionReference *ref = [firestore collectionWithPath:path]; + stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; + } else if ([stageName isEqualToString:@"collection_group"]) { + stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:args[@"path"]]; + } else if ([stageName isEqualToString:@"database"]) { + stage = [[FIRDatabaseSourceStageBridge alloc] init]; + } else if ([stageName isEqualToString:@"documents"]) { + NSArray *docMaps = argsArray ?: @[]; + NSMutableArray *refs = [NSMutableArray array]; + for (id docMap in docMaps) { + if ([docMap isKindOfClass:[NSDictionary class]] && ((NSDictionary *)docMap)[@"path"]) + [refs addObject:[firestore documentWithPath:((NSDictionary *)docMap)[@"path"]]]; + } + stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; + } + } else { + NSError *parseErr = nil; + if ([stageName isEqualToString:@"where"]) { + FIRExprBridge *expr = [exprParser parseBooleanExpression:args[@"expression"] + error:&parseErr]; + if (expr) stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; + } else if ([stageName isEqualToString:@"limit"]) { + stage = [[FIRLimitStageBridge alloc] initWithLimit:[args[@"limit"] intValue]]; + } else if ([stageName isEqualToString:@"offset"]) { + stage = [[FIROffsetStageBridge alloc] initWithOffset:[args[@"offset"] intValue]]; + } else if ([stageName isEqualToString:@"sort"]) { + NSArray *orderingMaps = args[@"orderings"]; + if ([orderingMaps isKindOfClass:[NSArray class]] && orderingMaps.count > 0) { + NSMutableArray *orderings = [NSMutableArray array]; + for (id om in orderingMaps) { + if (![om isKindOfClass:[NSDictionary class]]) continue; + id exprMap = ((NSDictionary *)om)[@"expression"]; + NSString *dir = ((NSDictionary *)om)[@"order_direction"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!expr) break; + NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; + [orderings addObject:[[FIROrderingBridge alloc] initWithExpr:expr Direction:direction]]; + } + if (orderings.count > 0) { + stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; + } + } + } else if ([stageName isEqualToString:@"union"]) { + id other = [self buildPipelineWithFirestore:firestore + stages:args[@"pipeline"] + error:&parseErr]; + if (other) stage = [[FIRUnionStageBridge alloc] initWithOther:other]; + } + if (parseErr && error) *error = parseErr; + } + + if (stage) [stageBridges addObject:stage]; + } + + if (stageBridges.count == 0) { + if (error && !*error) *error = parseError(@"No valid stages"); + return nil; + } + + return [[FIRPipelineBridge alloc] initWithStages:stageBridges db:firestore]; +} + +@end + +#else + +@implementation FLTPipelineParser + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + completion(nil, pipelineUnavailableError()); +} + +@end + +#endif diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 0672709a2921..b14d9a2393a8 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -5,6 +5,8 @@ // See also: https://pub.dev/packages/pigeon #import "FirestoreMessages.g.h" +#import "FLTFirebaseFirestoreReader.h" +#import "FLTFirebaseFirestoreWriter.h" #if TARGET_OS_OSX #import @@ -359,7 +361,7 @@ + (instancetype)makeWithType:(DocumentChangeType)type pigeonResult.type = type; pigeonResult.document = document; pigeonResult.oldIndex = oldIndex; - pigeonResult.newIndex = newIndex; + pigeonResult.index = newIndex; return pigeonResult; } + (PigeonDocumentChange *)fromList:(NSArray *)list { @@ -370,8 +372,8 @@ + (PigeonDocumentChange *)fromList:(NSArray *)list { NSAssert(pigeonResult.document != nil, @""); pigeonResult.oldIndex = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.oldIndex != nil, @""); - pigeonResult.newIndex = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.newIndex != nil, @""); + pigeonResult.index = GetNullableObjectAtIndex(list, 3); + NSAssert(pigeonResult.index != nil, @""); return pigeonResult; } + (nullable PigeonDocumentChange *)nullableFromList:(NSArray *)list { @@ -382,7 +384,7 @@ - (NSArray *)toList { @(self.type), (self.document ? [self.document toList] : [NSNull null]), (self.oldIndex ?: [NSNull null]), - (self.newIndex ?: [NSNull null]), + (self.index ?: [NSNull null]), ]; } @end @@ -719,7 +721,7 @@ - (NSArray *)toList { } @end -@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader +@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader @end @implementation FirebaseFirestoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { @@ -760,7 +762,7 @@ - (nullable id)readValueOfType:(UInt8)type { } @end -@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter +@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter @end @implementation FirebaseFirestoreHostApiCodecWriter - (void)writeValue:(id)value { diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h new file mode 100644 index 000000000000..97c77f0e2a88 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Private/FLTPipelineParser.h @@ -0,0 +1,23 @@ +/* + * Copyright 2026, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#import + +@class FIRFirestore; + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTPipelineParser : NSObject + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion: + (void (^)(id _Nullable snapshot, NSError *_Nullable error))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h index 28885bdc9132..a435fda97bbd 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h @@ -231,7 +231,7 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, assign) DocumentChangeType type; @property(nonatomic, strong) PigeonDocumentSnapshot *document; @property(nonatomic, strong) NSNumber *oldIndex; -@property(nonatomic, strong) NSNumber *newIndex; +@property(nonatomic, strong) NSNumber *index; @end @interface PigeonQuerySnapshot : NSObject diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index b52f92e5c3a2..10175dffd191 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -1190,7 +1190,8 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + return cloud_firestore_windows::FirestoreCodec::ReadValueOfType(type, + stream); } } @@ -1319,7 +1320,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } } - flutter::StandardCodecSerializer::WriteValue(value, stream); + cloud_firestore_windows::FirestoreCodec::WriteValue(value, stream); } /// The codec used by FirebaseFirestoreHostApi. diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 3589afb9db14..739c8b2486ae 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -15,6 +15,8 @@ #include #include +#include "firestore_codec.h" + namespace cloud_firestore_windows { // Generated class from Pigeon. @@ -237,10 +239,11 @@ class PigeonSnapshotMetadata { bool is_from_cache() const; void set_is_from_cache(bool value_arg); - private: static PigeonSnapshotMetadata FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class PigeonDocumentSnapshot; friend class PigeonQuerySnapshot; friend class FirebaseFirestoreHostApi; @@ -271,10 +274,11 @@ class PigeonDocumentSnapshot { const PigeonSnapshotMetadata& metadata() const; void set_metadata(const PigeonSnapshotMetadata& value_arg); - private: static PigeonDocumentSnapshot FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class PigeonDocumentChange; friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; @@ -303,10 +307,11 @@ class PigeonDocumentChange { int64_t new_index() const; void set_new_index(int64_t value_arg); - private: static PigeonDocumentChange FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; + + private: friend class FirebaseFirestoreHostApi; friend class FirebaseFirestoreHostApiCodecSerializer; DocumentChangeType type_; @@ -672,7 +677,7 @@ class AggregateQueryResponse { }; class FirebaseFirestoreHostApiCodecSerializer - : public flutter::StandardCodecSerializer { + : public cloud_firestore_windows::FirestoreCodec { public: FirebaseFirestoreHostApiCodecSerializer(); inline static FirebaseFirestoreHostApiCodecSerializer& GetInstance() { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh index f6f0a2d4aef5..e8950e42ef53 100755 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/generate_pigeon.sh @@ -15,7 +15,7 @@ sed -i '' 's/private static class FirebaseFirestoreHostApiCodec extends Standard echo "Android modification complete." # Fix iOS files -FILE_NAME="../../cloud_firestore/ios/Classes/FirestoreMessages.g.m" +FILE_NAME="../../cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m" sed -i '' '/#import "FirestoreMessages.g.h"/a\ #import "FLTFirebaseFirestoreReader.h"\ #import "FLTFirebaseFirestoreWriter.h" @@ -26,7 +26,7 @@ sed -i '' 's/(self\.newIndex \?: \[NSNull null\]),/(self.index ?: [NSNull null]) sed -i '' 's/@interface FirebaseFirestoreHostApiCodecReader : FlutterStandardReader/@interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader/' $FILE_NAME sed -i '' 's/@interface FirebaseFirestoreHostApiCodecWriter : FlutterStandardWriter/@interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter/' $FILE_NAME -FILE_NAME="../../cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h" +FILE_NAME="../../cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/include/cloud_firestore/Public/FirestoreMessages.g.h" sed -i '' 's/@property(nonatomic, strong) NSNumber \*newIndex;/@property(nonatomic, strong) NSNumber \*index;/' $FILE_NAME echo "iOS modification complete." diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart index 67978a0a8f2c..e9ed58eafc5c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/pigeon/test_api.dart @@ -6,7 +6,7 @@ // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -115,7 +115,10 @@ abstract class TestFirebaseFirestoreHostApi { Future loadBundle(FirestorePigeonFirebaseApp app, Uint8List bundle); Future namedQueryGet( - FirestorePigeonFirebaseApp app, String name, PigeonGetOptions options); + FirestorePigeonFirebaseApp app, + String name, + PigeonGetOptions options, + ); Future clearPersistence(FirestorePigeonFirebaseApp app); @@ -128,82 +131,112 @@ abstract class TestFirebaseFirestoreHostApi { Future waitForPendingWrites(FirestorePigeonFirebaseApp app); Future setIndexConfiguration( - FirestorePigeonFirebaseApp app, String indexConfiguration); + FirestorePigeonFirebaseApp app, + String indexConfiguration, + ); Future setLoggingEnabled(bool loggingEnabled); Future snapshotsInSyncSetup(FirestorePigeonFirebaseApp app); Future transactionCreate( - FirestorePigeonFirebaseApp app, int timeout, int maxAttempts); + FirestorePigeonFirebaseApp app, + int timeout, + int maxAttempts, + ); Future transactionStoreResult( - String transactionId, - PigeonTransactionResult resultType, - List? commands); + String transactionId, + PigeonTransactionResult resultType, + List? commands, + ); Future transactionGet( - FirestorePigeonFirebaseApp app, String transactionId, String path); + FirestorePigeonFirebaseApp app, + String transactionId, + String path, + ); Future documentReferenceSet( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceUpdate( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceGet( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future documentReferenceDelete( - FirestorePigeonFirebaseApp app, DocumentReferenceRequest request); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest request, + ); Future queryGet( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + ); Future> aggregateQuery( - FirestorePigeonFirebaseApp app, - String path, - PigeonQueryParameters parameters, - AggregateSource source, - List queries, - bool isCollectionGroup); + FirestorePigeonFirebaseApp app, + String path, + PigeonQueryParameters parameters, + AggregateSource source, + List queries, + bool isCollectionGroup, + ); Future writeBatchCommit( - FirestorePigeonFirebaseApp app, List writes); + FirestorePigeonFirebaseApp app, + List writes, + ); Future querySnapshot( - FirestorePigeonFirebaseApp app, - String path, - bool isCollectionGroup, - PigeonQueryParameters parameters, - PigeonGetOptions options, - bool includeMetadataChanges, - ListenSource source); + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + bool includeMetadataChanges, + ListenSource source, + ); Future documentReferenceSnapshot( - FirestorePigeonFirebaseApp app, - DocumentReferenceRequest parameters, - bool includeMetadataChanges, - ListenSource source); + FirestorePigeonFirebaseApp app, + DocumentReferenceRequest parameters, + bool includeMetadataChanges, + ListenSource source, + ); Future persistenceCacheIndexManagerRequest( - FirestorePigeonFirebaseApp app, - PersistenceCacheIndexManagerRequest request); + FirestorePigeonFirebaseApp app, + PersistenceCacheIndexManagerRequest request, + ); - Future executePipeline(FirestorePigeonFirebaseApp app, - List?> stages, Map? options); + Future executePipeline( + FirestorePigeonFirebaseApp app, + List?> stages, + Map? options, + ); - static void setup(TestFirebaseFirestoreHostApi? api, - {BinaryMessenger? binaryMessenger}) { + static void setup( + TestFirebaseFirestoreHostApi? api, { + BinaryMessenger? binaryMessenger, + }) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -211,16 +244,22 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null FirestorePigeonFirebaseApp.', + ); final Uint8List? arg_bundle = (args[1] as Uint8List?); - assert(arg_bundle != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.'); + assert( + arg_bundle != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.loadBundle was null, expected non-null Uint8List.', + ); final String output = await api.loadBundle(arg_app!, arg_bundle!); return [output]; }); @@ -228,9 +267,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -238,19 +278,27 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_name = (args[1] as String?); - assert(arg_name != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.'); + assert( + arg_name != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null String.', + ); final PigeonGetOptions? arg_options = (args[2] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.'); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.namedQueryGet was null, expected non-null PigeonGetOptions.', + ); final PigeonQuerySnapshot output = await api.namedQueryGet(arg_app!, arg_name!, arg_options!); return [output]; @@ -259,9 +307,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -269,13 +318,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.clearPersistence was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.clearPersistence(arg_app!); return []; }); @@ -283,9 +336,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -293,13 +347,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.disableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.disableNetwork(arg_app!); return []; }); @@ -307,9 +365,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -317,13 +376,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.enableNetwork was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.enableNetwork(arg_app!); return []; }); @@ -331,9 +394,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -341,13 +405,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.terminate was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.terminate(arg_app!); return []; }); @@ -355,9 +423,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -365,13 +434,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.waitForPendingWrites was null, expected non-null FirestorePigeonFirebaseApp.', + ); await api.waitForPendingWrites(arg_app!); return []; }); @@ -379,9 +452,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -389,16 +463,22 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_indexConfiguration = (args[1] as String?); - assert(arg_indexConfiguration != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.'); + assert( + arg_indexConfiguration != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setIndexConfiguration was null, expected non-null String.', + ); await api.setIndexConfiguration(arg_app!, arg_indexConfiguration!); return []; }); @@ -406,9 +486,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -416,12 +497,16 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null.', + ); final List args = (message as List?)!; final bool? arg_loggingEnabled = (args[0] as bool?); - assert(arg_loggingEnabled != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.'); + assert( + arg_loggingEnabled != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.setLoggingEnabled was null, expected non-null bool.', + ); await api.setLoggingEnabled(arg_loggingEnabled!); return []; }); @@ -429,9 +514,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -439,13 +525,17 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.snapshotsInSyncSetup was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String output = await api.snapshotsInSyncSetup(arg_app!); return [output]; }); @@ -453,9 +543,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -463,30 +554,42 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null FirestorePigeonFirebaseApp.', + ); final int? arg_timeout = (args[1] as int?); - assert(arg_timeout != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); + assert( + arg_timeout != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', + ); final int? arg_maxAttempts = (args[2] as int?); - assert(arg_maxAttempts != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.'); + assert( + arg_maxAttempts != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionCreate was null, expected non-null int.', + ); final String output = await api.transactionCreate( - arg_app!, arg_timeout!, arg_maxAttempts!); + arg_app!, + arg_timeout!, + arg_maxAttempts!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -494,30 +597,40 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null.', + ); final List args = (message as List?)!; final String? arg_transactionId = (args[0] as String?); - assert(arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.'); + assert( + arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null String.', + ); final PigeonTransactionResult? arg_resultType = args[1] == null ? null : PigeonTransactionResult.values[args[1]! as int]; - assert(arg_resultType != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.'); + assert( + arg_resultType != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionStoreResult was null, expected non-null PigeonTransactionResult.', + ); final List? arg_commands = (args[2] as List?)?.cast(); await api.transactionStoreResult( - arg_transactionId!, arg_resultType!, arg_commands); + arg_transactionId!, + arg_resultType!, + arg_commands, + ); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -525,19 +638,27 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_transactionId = (args[1] as String?); - assert(arg_transactionId != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); + assert( + arg_transactionId != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', + ); final String? arg_path = (args[2] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.transactionGet was null, expected non-null String.', + ); final PigeonDocumentSnapshot output = await api.transactionGet(arg_app!, arg_transactionId!, arg_path!); return [output]; @@ -546,9 +667,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -556,17 +678,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSet was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceSet(arg_app!, arg_request!); return []; }); @@ -574,9 +702,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -584,17 +713,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceUpdate was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceUpdate(arg_app!, arg_request!); return []; }); @@ -602,9 +737,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -612,17 +748,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceGet was null, expected non-null DocumentReferenceRequest.', + ); final PigeonDocumentSnapshot output = await api.documentReferenceGet(arg_app!, arg_request!); return [output]; @@ -631,9 +773,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -641,17 +784,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_request = (args[1] as DocumentReferenceRequest?); - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceDelete was null, expected non-null DocumentReferenceRequest.', + ); await api.documentReferenceDelete(arg_app!, arg_request!); return []; }); @@ -659,9 +808,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -669,37 +819,55 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null String.', + ); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null bool.', + ); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonQueryParameters.', + ); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.'); - final PigeonQuerySnapshot output = await api.queryGet(arg_app!, - arg_path!, arg_isCollectionGroup!, arg_parameters!, arg_options!); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.queryGet was null, expected non-null PigeonGetOptions.', + ); + final PigeonQuerySnapshot output = await api.queryGet( + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -707,47 +875,63 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null String.', + ); final PigeonQueryParameters? arg_parameters = (args[2] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null PigeonQueryParameters.', + ); final AggregateSource? arg_source = args[3] == null ? null : AggregateSource.values[args[3]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.'); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null AggregateSource.', + ); final List? arg_queries = (args[4] as List?)?.cast(); - assert(arg_queries != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.'); + assert( + arg_queries != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null List.', + ); final bool? arg_isCollectionGroup = (args[5] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.', + ); final List output = await api.aggregateQuery( - arg_app!, - arg_path!, - arg_parameters!, - arg_source!, - arg_queries!, - arg_isCollectionGroup!); + arg_app!, + arg_path!, + arg_parameters!, + arg_source!, + arg_queries!, + arg_isCollectionGroup!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -755,17 +939,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null FirestorePigeonFirebaseApp.', + ); final List? arg_writes = (args[1] as List?)?.cast(); - assert(arg_writes != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.'); + assert( + arg_writes != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit was null, expected non-null List.', + ); await api.writeBatchCommit(arg_app!, arg_writes!); return []; }); @@ -773,9 +963,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -783,50 +974,68 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null FirestorePigeonFirebaseApp.', + ); final String? arg_path = (args[1] as String?); - assert(arg_path != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.'); + assert( + arg_path != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null String.', + ); final bool? arg_isCollectionGroup = (args[2] as bool?); - assert(arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', + ); final PigeonQueryParameters? arg_parameters = (args[3] as PigeonQueryParameters?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonQueryParameters.', + ); final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); - assert(arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.'); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null PigeonGetOptions.', + ); final bool? arg_includeMetadataChanges = (args[5] as bool?); - assert(arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.'); + assert( + arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null bool.', + ); final ListenSource? arg_source = args[6] == null ? null : ListenSource.values[args[6]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.'); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.querySnapshot was null, expected non-null ListenSource.', + ); final String output = await api.querySnapshot( - arg_app!, - arg_path!, - arg_isCollectionGroup!, - arg_parameters!, - arg_options!, - arg_includeMetadataChanges!, - arg_source!); + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + arg_includeMetadataChanges!, + arg_source!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -834,35 +1043,50 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null FirestorePigeonFirebaseApp.', + ); final DocumentReferenceRequest? arg_parameters = (args[1] as DocumentReferenceRequest?); - assert(arg_parameters != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.'); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null DocumentReferenceRequest.', + ); final bool? arg_includeMetadataChanges = (args[2] as bool?); - assert(arg_includeMetadataChanges != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.'); + assert( + arg_includeMetadataChanges != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null bool.', + ); final ListenSource? arg_source = args[3] == null ? null : ListenSource.values[args[3]! as int]; - assert(arg_source != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.'); - final String output = await api.documentReferenceSnapshot(arg_app!, - arg_parameters!, arg_includeMetadataChanges!, arg_source!); + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.documentReferenceSnapshot was null, expected non-null ListenSource.', + ); + final String output = await api.documentReferenceSnapshot( + arg_app!, + arg_parameters!, + arg_includeMetadataChanges!, + arg_source!, + ); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -870,19 +1094,25 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null FirestorePigeonFirebaseApp.', + ); final PersistenceCacheIndexManagerRequest? arg_request = args[1] == null ? null : PersistenceCacheIndexManagerRequest.values[args[1]! as int]; - assert(arg_request != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.'); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.persistenceCacheIndexManagerRequest was null, expected non-null PersistenceCacheIndexManagerRequest.', + ); await api.persistenceCacheIndexManagerRequest(arg_app!, arg_request!); return []; }); @@ -890,9 +1120,10 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', - codec, - binaryMessenger: binaryMessenger); + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline', + codec, + binaryMessenger: binaryMessenger, + ); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, null); @@ -900,17 +1131,23 @@ abstract class TestFirebaseFirestoreHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null.', + ); final List args = (message as List?)!; final FirestorePigeonFirebaseApp? arg_app = (args[0] as FirestorePigeonFirebaseApp?); - assert(arg_app != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.'); + assert( + arg_app != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null FirestorePigeonFirebaseApp.', + ); final List?>? arg_stages = (args[1] as List?)?.cast?>(); - assert(arg_stages != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.'); + assert( + arg_stages != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline was null, expected non-null List?>.', + ); final Map? arg_options = (args[2] as Map?)?.cast(); final PigeonPipelineSnapshot output = From d7bc27c470350031900e3a4b6f731adf53797020 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 14:11:05 +0000 Subject: [PATCH 11/20] refactor: move aggregate classes to pipeline_aggregate.dart --- .../lib/src/pipeline_aggregate.dart | 95 ++++++++++++++++++ .../lib/src/pipeline_expression.dart | 99 ------------------- 2 files changed, 95 insertions(+), 99 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart index 403d26b7f3a4..c3ab5a96de41 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_aggregate.dart @@ -75,6 +75,101 @@ class Count extends PipelineAggregateFunction { } } +/// Sums numeric values of the specified expression +class Sum extends PipelineAggregateFunction { + final Expression expression; + + Sum(this.expression); + + @override + String get name => 'sum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Calculates average of numeric values of the specified expression +class Average extends PipelineAggregateFunction { + final Expression expression; + + Average(this.expression); + + @override + String get name => 'average'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Counts distinct values of the specified expression +class CountDistinct extends PipelineAggregateFunction { + final Expression expression; + + CountDistinct(this.expression); + + @override + String get name => 'count_distinct'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds minimum value of the specified expression +class Minimum extends PipelineAggregateFunction { + final Expression expression; + + Minimum(this.expression); + + @override + String get name => 'minimum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + +/// Finds maximum value of the specified expression +class Maximum extends PipelineAggregateFunction { + final Expression expression; + + Maximum(this.expression); + + @override + String get name => 'maximum'; + + @override + Map toMap() { + final map = super.toMap(); + map['args'] = { + 'expression': expression.toMap(), + }; + return map; + } +} + /// Represents an aggregate stage with functions and optional grouping class AggregateStage implements PipelineSerializable { final List accumulators; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart index b8a9fc5d9200..6f740f548c54 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_expression.dart @@ -2905,102 +2905,3 @@ class _RawFunctionExpression extends FunctionExpression { }; } } - -// ============================================================================ -// AGGREGATE FUNCTION CLASSES -// ============================================================================ - -/// Sums numeric values of the specified expression -class Sum extends PipelineAggregateFunction { - final Expression expression; - - Sum(this.expression); - - @override - String get name => 'sum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Calculates average of numeric values of the specified expression -class Average extends PipelineAggregateFunction { - final Expression expression; - - Average(this.expression); - - @override - String get name => 'average'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Counts distinct values of the specified expression -class CountDistinct extends PipelineAggregateFunction { - final Expression expression; - - CountDistinct(this.expression); - - @override - String get name => 'count_distinct'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Finds minimum value of the specified expression -class Minimum extends PipelineAggregateFunction { - final Expression expression; - - Minimum(this.expression); - - @override - String get name => 'minimum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} - -/// Finds maximum value of the specified expression -class Maximum extends PipelineAggregateFunction { - final Expression expression; - - Maximum(this.expression); - - @override - String get name => 'maximum'; - - @override - Map toMap() { - final map = super.toMap(); - map['args'] = { - 'expression': expression.toMap(), - }; - return map; - } -} From 85d87e65451bb65adb159bf0dd779b626a84cca3 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 14:12:44 +0000 Subject: [PATCH 12/20] feat: enhance expression parsing in FLTPipelineParser to support additional binary and unary operations --- .../cloud_firestore/FLTPipelineParser.m | 532 ++++++++++++------ 1 file changed, 362 insertions(+), 170 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 620d8ee7bc2d..b4f46889cf87 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -85,11 +85,22 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS return [self parseExpression:exprMap error:error]; } - NSArray *binaryNames = @[ - @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", - @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo" - ]; - if ([binaryNames containsObject:name]) { + // Map Dart names to iOS SDK names where they differ + NSString *sdkName = name; + if ([name isEqualToString:@"bit_xor"]) sdkName = @"xor"; + + // ------------------------------------------------------------------------- + // Binary expressions (left + right): comparisons, arithmetic, xor + // ------------------------------------------------------------------------- + static NSArray *binaryNames = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + binaryNames = @[ + @"equal", @"not_equal", @"greater_than", @"greater_than_or_equal", @"less_than", + @"less_than_or_equal", @"add", @"subtract", @"multiply", @"divide", @"modulo", @"xor" + ]; + }); + if ([binaryNames containsObject:sdkName] || [name isEqualToString:@"bit_xor"]) { id leftMap = args[@"left"]; id rightMap = args[@"right"]; if (![leftMap isKindOfClass:[NSDictionary class]] || @@ -102,9 +113,29 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS FIRExprBridge *left = [self parseExpression:leftMap error:error]; FIRExprBridge *right = [self parseExpression:rightMap error:error]; if (!left || !right) return nil; - return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ left, right ]]; + return [[FIRFunctionExprBridge alloc] initWithName:sdkName Args:@[ left, right ]]; } + // ------------------------------------------------------------------------- + // Unary expressions (single expression): exists, is_error, is_absent, not + // ------------------------------------------------------------------------- + NSArray *unaryNames = @[ @"exists", @"is_error", @"is_absent", @"not" ]; + if ([unaryNames containsObject:name]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError([NSString stringWithFormat:@"%@ requires expression", name]); + return nil; + } + FIRExprBridge *expr = [name isEqualToString:@"not"] + ? [self parseBooleanExpression:exprMap error:error] + : [self parseExpression:exprMap error:error]; + if (!expr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ expr ]]; + } + + // ------------------------------------------------------------------------- + // N-ary logical (expressions array): and, or + // ------------------------------------------------------------------------- if ([name isEqualToString:@"and"] || [name isEqualToString:@"or"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { @@ -113,26 +144,101 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); return nil; } - FIRExprBridge *first = [self parseBooleanExpression:exprMaps[0] error:error]; - if (!first) return nil; - NSMutableArray *all = [NSMutableArray arrayWithObject:first]; - for (NSUInteger i = 1; i < exprMaps.count; i++) { - FIRExprBridge *next = [self parseBooleanExpression:exprMaps[i] error:error]; - if (!next) return nil; - [all addObject:next]; + NSMutableArray *all = [NSMutableArray array]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *e = [self parseBooleanExpression:em error:error]; + if (!e) return nil; + [all addObject:e]; + } + if (all.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires at least one expression", name]); + return nil; } return [[FIRFunctionExprBridge alloc] initWithName:name Args:all]; } - if ([name isEqualToString:@"not"]) { - id exprMap = args[@"expression"]; - if (![exprMap isKindOfClass:[NSDictionary class]]) { - if (error) *error = parseError(@"not requires expression"); + // ------------------------------------------------------------------------- + // value + values[]: equal_any, not_equal_any + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"equal_any"] || [name isEqualToString:@"not_equal_any"]) { + id valueMap = args[@"value"]; + NSArray *valuesMaps = args[@"values"]; + if (![valueMap isKindOfClass:[NSDictionary class]] || + ![valuesMaps isKindOfClass:[NSArray class]] || valuesMaps.count == 0) { + if (error) + *error = + parseError([NSString stringWithFormat:@"%@ requires value and non-empty values", name]); return nil; } - FIRExprBridge *expr = [self parseBooleanExpression:exprMap error:error]; - if (!expr) return nil; - return [[FIRFunctionExprBridge alloc] initWithName:@"not" Args:@[ expr ]]; + FIRExprBridge *valueExpr = [self parseExpression:valueMap error:error]; + if (!valueExpr) return nil; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id vm in valuesMaps) { + if (![vm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *ve = [self parseExpression:vm error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) + *error = parseError([NSString stringWithFormat:@"%@ requires at least one value", name]); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:valueExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; + } + + // ------------------------------------------------------------------------- + // array + element: array_contains + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"array_contains"]) { + id arrayMap = args[@"array"]; + id elementMap = args[@"element"]; + if (![arrayMap isKindOfClass:[NSDictionary class]] || + ![elementMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"array_contains requires array and element"); + return nil; + } + FIRExprBridge *arrayExpr = [self parseExpression:arrayMap error:error]; + FIRExprBridge *elementExpr = [self parseExpression:elementMap error:error]; + if (!arrayExpr || !elementExpr) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:name Args:@[ arrayExpr, elementExpr ]]; + } + + // ------------------------------------------------------------------------- + // array + values[]: array_contains_all, array_contains_any + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"array_contains_all"] || + [name isEqualToString:@"array_contains_any"]) { + id arrayMap = args[@"array"]; + NSArray *valuesMaps = args[@"values"]; + if (![valuesMaps isKindOfClass:[NSArray class]]) valuesMaps = args[@"elements"]; + if (![arrayMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError([NSString stringWithFormat:@"%@ requires array", name]); + return nil; + } + FIRExprBridge *arrayExpr = [self parseExpression:arrayMap error:error]; + if (!arrayExpr) return nil; + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:arrayExpr]; + if ([valuesMaps isKindOfClass:[NSArray class]]) { + for (id vm in valuesMaps) { + if (![vm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *ve = [self parseExpression:vm error:error]; + if (!ve) return nil; + [argsArray addObject:ve]; + } + } + if (argsArray.count < 2) { + if (error) + *error = parseError( + [NSString stringWithFormat:@"%@ requires array and at least one value", name]); + return nil; + } + return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; } if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); @@ -148,16 +254,10 @@ - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map @implementation FLTPipelineParser -+ (void)executePipelineWithFirestore:(FIRFirestore *)firestore - stages:(NSArray *> *)stages - options:(nullable NSDictionary *)options - completion:(void (^)(id _Nullable snapshot, - NSError *_Nullable error))completion { - if (!stages || stages.count == 0) { - completion(nil, parseError(@"Pipeline requires at least one stage")); - return; - } - ++ (NSArray *) + parseStagesWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; NSMutableArray *stageBridges = [NSMutableArray array]; NSError *parseErr = nil; @@ -165,16 +265,17 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore for (NSUInteger i = 0; i < stages.count; i++) { NSDictionary *stageMap = stages[i]; if (![stageMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"Stage must be a map")); - return; + if (error) *error = parseError(@"Stage must be a map"); + return nil; } NSString *stageName = stageMap[@"stage"]; if (![stageName isKindOfClass:[NSString class]]) { - completion(nil, parseError(@"Stage must have a 'stage' field")); - return; + if (error) *error = parseError(@"Stage must have a 'stage' field"); + return nil; } - NSDictionary *args = stageMap[@"args"]; - if (![args isKindOfClass:[NSDictionary class]]) args = @{}; + id argsObj = stageMap[@"args"]; + NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; + NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; FIRStageBridge *stage = nil; @@ -182,26 +283,25 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if ([stageName isEqualToString:@"collection"]) { NSString *path = args[@"path"]; if (!path) { - completion(nil, parseError(@"collection requires 'path'")); - return; + if (error) *error = parseError(@"collection requires 'path'"); + return nil; } FIRCollectionReference *ref = [firestore collectionWithPath:path]; stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; } else if ([stageName isEqualToString:@"collection_group"]) { NSString *path = args[@"path"]; if (!path) { - completion(nil, parseError(@"collection_group requires 'path'")); - return; + if (error) *error = parseError(@"collection_group requires 'path'"); + return nil; } stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:path]; } else if ([stageName isEqualToString:@"database"]) { stage = [[FIRDatabaseSourceStageBridge alloc] init]; } else if ([stageName isEqualToString:@"documents"]) { - id argsOrArray = stageMap[@"args"]; - NSArray *docMaps = [argsOrArray isKindOfClass:[NSArray class]] ? argsOrArray : nil; + NSArray *docMaps = argsArray; if (!docMaps || docMaps.count == 0) { - completion(nil, parseError(@"documents requires array of document refs")); - return; + if (error) *error = parseError(@"documents requires array of document refs"); + return nil; } NSMutableArray *refs = [NSMutableArray array]; for (id docMap in docMaps) { @@ -211,44 +311,45 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; } else { - completion(nil, parseError([NSString - stringWithFormat:@"First stage must be collection, collection_group, " - @"documents, or database. Got: %@", - stageName])); - return; + if (error) + *error = parseError( + [NSString stringWithFormat:@"First stage must be collection, collection_group, " + @"documents, or database. Got: %@", + stageName]); + return nil; } } else { if ([stageName isEqualToString:@"where"]) { id exprMap = args[@"expression"]; if (![exprMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"where requires expression")); - return; + if (error) *error = parseError(@"where requires expression"); + return nil; } FIRExprBridge *expr = [exprParser parseBooleanExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; } else if ([stageName isEqualToString:@"limit"]) { NSNumber *limit = args[@"limit"]; if (![limit isKindOfClass:[NSNumber class]]) { - completion(nil, parseError(@"limit requires numeric limit")); - return; + if (error) *error = parseError(@"limit requires numeric limit"); + return nil; } stage = [[FIRLimitStageBridge alloc] initWithLimit:limit.intValue]; } else if ([stageName isEqualToString:@"offset"]) { NSNumber *offset = args[@"offset"]; if (![offset isKindOfClass:[NSNumber class]]) { - completion(nil, parseError(@"offset requires numeric offset")); - return; + if (error) *error = parseError(@"offset requires numeric offset"); + return nil; } stage = [[FIROffsetStageBridge alloc] initWithOffset:offset.intValue]; } else if ([stageName isEqualToString:@"sort"]) { NSArray *orderingMaps = args[@"orderings"]; if (![orderingMaps isKindOfClass:[NSArray class]] || orderingMaps.count == 0) { - completion(nil, parseError(@"sort requires at least one ordering")); - return; + if (error) *error = parseError(@"sort requires at least one ordering"); + return nil; } NSMutableArray *orderings = [NSMutableArray array]; for (id om in orderingMaps) { @@ -258,8 +359,8 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if (![exprMap isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; FIROrderingBridge *ordering = [[FIROrderingBridge alloc] initWithExpr:expr @@ -267,23 +368,23 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore [orderings addObject:ordering]; } if (orderings.count == 0) { - completion(nil, parseError(@"sort requires at least one ordering")); - return; + if (error) *error = parseError(@"sort requires at least one ordering"); + return nil; } stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; } else if ([stageName isEqualToString:@"select"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"select requires at least one expression")); - return; + if (error) *error = parseError(@"select requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *alias = [em valueForKeyPath:@"args.alias"]; if (alias) { @@ -302,21 +403,21 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"add_fields"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"add_fields requires at least one expression")); - return; + if (error) *error = parseError(@"add_fields requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } NSString *alias = [em valueForKeyPath:@"args.alias"]; if (!alias) { - completion(nil, parseError(@"add_fields expressions must have alias")); - return; + if (error) *error = parseError(@"add_fields expressions must have alias"); + return nil; } fields[alias] = expr; } @@ -324,15 +425,15 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"remove_fields"]) { NSArray *paths = args[@"field_paths"]; if (![paths isKindOfClass:[NSArray class]] || paths.count == 0) { - completion(nil, parseError(@"remove_fields requires field_paths")); - return; + if (error) *error = parseError(@"remove_fields requires field_paths"); + return nil; } stage = [[FIRRemoveFieldsStageBridge alloc] initWithFields:paths]; } else if ([stageName isEqualToString:@"distinct"]) { NSArray *exprMaps = args[@"expressions"]; if (![exprMaps isKindOfClass:[NSArray class]] || exprMaps.count == 0) { - completion(nil, parseError(@"distinct requires at least one expression")); - return; + if (error) *error = parseError(@"distinct requires at least one expression"); + return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; for (NSUInteger j = 0; j < exprMaps.count; j++) { @@ -340,8 +441,8 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; } @@ -349,27 +450,27 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } else if ([stageName isEqualToString:@"replace_with"]) { id exprMap = args[@"expression"]; if (![exprMap isKindOfClass:[NSDictionary class]]) { - completion(nil, parseError(@"replace_with requires expression")); - return; + if (error) *error = parseError(@"replace_with requires expression"); + return nil; } FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; if (!expr) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRReplaceWithStageBridge alloc] initWithExpr:expr]; } else if ([stageName isEqualToString:@"union"]) { NSArray *nestedStages = args[@"pipeline"]; if (![nestedStages isKindOfClass:[NSArray class]] || nestedStages.count == 0) { - completion(nil, parseError(@"union requires non-empty pipeline")); - return; + if (error) *error = parseError(@"union requires non-empty pipeline"); + return nil; } id otherPipeline = [self buildPipelineWithFirestore:firestore stages:nestedStages error:&parseErr]; if (!otherPipeline) { - completion(nil, parseErr); - return; + if (error) *error = parseErr; + return nil; } stage = [[FIRUnionStageBridge alloc] initWithOther:otherPipeline]; } else if ([stageName isEqualToString:@"sample"]) { @@ -382,10 +483,55 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore int v = [val isKindOfClass:[NSNumber class]] ? [(NSNumber *)val intValue] : 0; stage = [[FIRSampleStageBridge alloc] initWithCount:v]; } + } else if ([stageName isEqualToString:@"aggregate"]) { + stage = [self parseAggregateStageWithArgs:args exprParser:exprParser error:error]; + } else if ([stageName isEqualToString:@"unnest"]) { + id exprMap = args[@"expression"]; + if (![exprMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"unnest requires expression"); + return nil; + } + FIRExprBridge *fieldExpr = nil; + FIRExprBridge *aliasExpr = nil; + NSDictionary *exprDict = (NSDictionary *)exprMap; + NSString *aliasStr = nil; + if ([exprDict[@"name"] isEqualToString:@"alias"]) { + NSDictionary *aliasArgs = exprDict[@"args"]; + if ([aliasArgs isKindOfClass:[NSDictionary class]] && aliasArgs[@"expression"]) { + fieldExpr = [exprParser parseExpression:aliasArgs[@"expression"] error:&parseErr]; + if (!fieldExpr) { + if (error) *error = parseErr; + return nil; + } + aliasStr = + [aliasArgs[@"alias"] isKindOfClass:[NSString class]] ? aliasArgs[@"alias"] : nil; + } + } + if (!fieldExpr) { + fieldExpr = [exprParser parseExpression:exprMap error:&parseErr]; + if (!fieldExpr) { + if (error) *error = parseErr; + return nil; + } + if (!aliasStr && [exprDict[@"name"] isEqualToString:@"field"]) { + NSDictionary *fieldArgs = exprDict[@"args"]; + aliasStr = + [fieldArgs[@"field"] isKindOfClass:[NSString class]] ? fieldArgs[@"field"] : @"_"; + } + } + if (!aliasStr) aliasStr = @"_"; + aliasExpr = [[FIRFieldBridge alloc] initWithName:aliasStr]; + NSString *indexFieldStr = + [args[@"index_field"] isKindOfClass:[NSString class]] ? args[@"index_field"] : nil; + FIRExprBridge *indexFieldExpr = + (indexFieldStr.length > 0) ? [[FIRFieldBridge alloc] initWithName:indexFieldStr] : nil; + stage = [[FIRUnnestStageBridge alloc] initWithField:fieldExpr + alias:aliasExpr + indexField:indexFieldExpr]; } else { - completion( - nil, parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName])); - return; + if (error) + *error = parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName]); + return nil; } } @@ -393,103 +539,149 @@ + (void)executePipelineWithFirestore:(FIRFirestore *)firestore } if (stageBridges.count == 0) { - completion(nil, parseError(@"No valid stages")); - return; + if (error && !*error) *error = parseError(@"No valid stages"); + return nil; } - FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges - db:firestore]; - [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { - if (execError) { - completion(nil, execError); - return; + return stageBridges; +} + ++ (FIRAggregateFunctionBridge *)aggregateFunctionFromMap:(NSDictionary *)funcMap + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSString *name = funcMap[@"name"]; + if (![name isKindOfClass:[NSString class]]) { + if (error) *error = parseError(@"Aggregate function must have a 'name'"); + return nil; + } + // Map Dart aggregate function names to iOS SDK names (count_all -> count with no args; minimum -> + // min; maximum -> max) + NSString *iosName = name; + if ([name isEqualToString:@"count_all"]) { + iosName = @"count"; + } else if ([name isEqualToString:@"minimum"]) { + iosName = @"min"; + } else if ([name isEqualToString:@"maximum"]) { + iosName = @"max"; + } + NSDictionary *argsDict = funcMap[@"args"]; + NSMutableArray *argsArray = [NSMutableArray array]; + if ([argsDict isKindOfClass:[NSDictionary class]]) { + id exprMap = argsDict[@"expression"]; + if ([exprMap isKindOfClass:[NSDictionary class]]) { + FIRExprBridge *expr = [exprParser parseExpression:exprMap error:error]; + if (!expr) return nil; + [argsArray addObject:expr]; } - completion(snapshot, nil); - }]; + } + return [[FIRAggregateFunctionBridge alloc] initWithName:iosName Args:argsArray]; } -+ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore - stages:(NSArray *> *)stages - error:(NSError **)error { - FLTPipelineExpressionParser *exprParser = [[FLTPipelineExpressionParser alloc] init]; - NSMutableArray *stageBridges = [NSMutableArray array]; ++ (FIRStageBridge *)parseAggregateStageWithArgs:(NSDictionary *)args + exprParser:(FLTPipelineExpressionParser *)exprParser + error:(NSError **)error { + NSError *parseErr = nil; + NSArray *accumulatorMaps = nil; + NSArray *groupMaps = nil; - for (NSUInteger i = 0; i < stages.count; i++) { - NSDictionary *stageMap = stages[i]; + if (args[@"aggregate_stage"]) { + NSDictionary *stageMap = args[@"aggregate_stage"]; if (![stageMap isKindOfClass:[NSDictionary class]]) { - if (error) *error = parseError(@"Stage must be a map"); + if (error) *error = parseError(@"aggregate_stage must be a map"); return nil; } - NSString *stageName = stageMap[@"stage"]; - id argsObj = stageMap[@"args"]; - NSDictionary *args = [argsObj isKindOfClass:[NSDictionary class]] ? argsObj : @{}; - NSArray *argsArray = [argsObj isKindOfClass:[NSArray class]] ? argsObj : nil; + accumulatorMaps = stageMap[@"accumulators"]; + groupMaps = stageMap[@"groups"]; + } + if (!accumulatorMaps || ![accumulatorMaps isKindOfClass:[NSArray class]]) { + accumulatorMaps = args[@"aggregate_functions"]; + } + if (![accumulatorMaps isKindOfClass:[NSArray class]] || accumulatorMaps.count == 0) { + if (error) *error = parseError(@"aggregate requires accumulators or aggregate_functions"); + return nil; + } - FIRStageBridge *stage = nil; + NSMutableDictionary *accumulators = + [NSMutableDictionary dictionary]; + for (id accMap in accumulatorMaps) { + if (![accMap isKindOfClass:[NSDictionary class]]) continue; + NSString *alias = nil; + NSDictionary *funcMap = nil; + if ([accMap[@"name"] isEqualToString:@"alias"]) { + NSDictionary *accArgs = accMap[@"args"]; + if (![accArgs isKindOfClass:[NSDictionary class]]) continue; + alias = accArgs[@"alias"]; + funcMap = accArgs[@"aggregate_function"]; + } + if (![alias isKindOfClass:[NSString class]] || ![funcMap isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"Each accumulator must have alias and aggregate_function"); + return nil; + } + FIRAggregateFunctionBridge *func = [self aggregateFunctionFromMap:funcMap + exprParser:exprParser + error:&parseErr]; + if (!func) { + if (error) *error = parseErr; + return nil; + } + accumulators[alias] = func; + } + if (accumulators.count == 0) { + if (error) *error = parseError(@"aggregate requires at least one valid accumulator"); + return nil; + } - if (i == 0) { - if ([stageName isEqualToString:@"collection"]) { - NSString *path = args[@"path"]; - FIRCollectionReference *ref = [firestore collectionWithPath:path]; - stage = [[FIRCollectionSourceStageBridge alloc] initWithRef:ref firestore:firestore]; - } else if ([stageName isEqualToString:@"collection_group"]) { - stage = [[FIRCollectionGroupSourceStageBridge alloc] initWithCollectionId:args[@"path"]]; - } else if ([stageName isEqualToString:@"database"]) { - stage = [[FIRDatabaseSourceStageBridge alloc] init]; - } else if ([stageName isEqualToString:@"documents"]) { - NSArray *docMaps = argsArray ?: @[]; - NSMutableArray *refs = [NSMutableArray array]; - for (id docMap in docMaps) { - if ([docMap isKindOfClass:[NSDictionary class]] && ((NSDictionary *)docMap)[@"path"]) - [refs addObject:[firestore documentWithPath:((NSDictionary *)docMap)[@"path"]]]; - } - stage = [[FIRDocumentsSourceStageBridge alloc] initWithDocuments:refs firestore:firestore]; - } - } else { - NSError *parseErr = nil; - if ([stageName isEqualToString:@"where"]) { - FIRExprBridge *expr = [exprParser parseBooleanExpression:args[@"expression"] - error:&parseErr]; - if (expr) stage = [[FIRWhereStageBridge alloc] initWithExpr:expr]; - } else if ([stageName isEqualToString:@"limit"]) { - stage = [[FIRLimitStageBridge alloc] initWithLimit:[args[@"limit"] intValue]]; - } else if ([stageName isEqualToString:@"offset"]) { - stage = [[FIROffsetStageBridge alloc] initWithOffset:[args[@"offset"] intValue]]; - } else if ([stageName isEqualToString:@"sort"]) { - NSArray *orderingMaps = args[@"orderings"]; - if ([orderingMaps isKindOfClass:[NSArray class]] && orderingMaps.count > 0) { - NSMutableArray *orderings = [NSMutableArray array]; - for (id om in orderingMaps) { - if (![om isKindOfClass:[NSDictionary class]]) continue; - id exprMap = ((NSDictionary *)om)[@"expression"]; - NSString *dir = ((NSDictionary *)om)[@"order_direction"]; - if (![exprMap isKindOfClass:[NSDictionary class]]) continue; - FIRExprBridge *expr = [exprParser parseExpression:exprMap error:&parseErr]; - if (!expr) break; - NSString *direction = [dir isEqualToString:@"asc"] ? @"ascending" : @"descending"; - [orderings addObject:[[FIROrderingBridge alloc] initWithExpr:expr Direction:direction]]; - } - if (orderings.count > 0) { - stage = [[FIRSorStageBridge alloc] initWithOrderings:orderings]; - } - } - } else if ([stageName isEqualToString:@"union"]) { - id other = [self buildPipelineWithFirestore:firestore - stages:args[@"pipeline"] - error:&parseErr]; - if (other) stage = [[FIRUnionStageBridge alloc] initWithOther:other]; - } - if (parseErr && error) *error = parseErr; + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + if ([groupMaps isKindOfClass:[NSArray class]] && groupMaps.count > 0) { + for (NSUInteger g = 0; g < groupMaps.count; g++) { + id gm = groupMaps[g]; + if (![gm isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *expr = [exprParser parseExpression:gm error:&parseErr]; + if (!expr) continue; + groups[[NSString stringWithFormat:@"_%lu", (unsigned long)g]] = expr; } + } - if (stage) [stageBridges addObject:stage]; + return [[FIRAggregateStageBridge alloc] initWithAccumulators:accumulators groups:groups]; +} + ++ (void)executePipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + options:(nullable NSDictionary *)options + completion:(void (^)(id _Nullable snapshot, + NSError *_Nullable error))completion { + if (!stages || stages.count == 0) { + completion(nil, parseError(@"Pipeline requires at least one stage")); + return; } - if (stageBridges.count == 0) { - if (error && !*error) *error = parseError(@"No valid stages"); - return nil; + NSError *parseErr = nil; + NSArray *stageBridges = [self parseStagesWithFirestore:firestore + stages:stages + error:&parseErr]; + if (!stageBridges) { + completion(nil, parseErr); + return; } + FIRPipelineBridge *pipeline = [[FIRPipelineBridge alloc] initWithStages:stageBridges + db:firestore]; + [pipeline executeWithCompletion:^(id snapshot, NSError *execError) { + if (execError) { + completion(nil, execError); + return; + } + completion(snapshot, nil); + }]; +} + ++ (id)buildPipelineWithFirestore:(FIRFirestore *)firestore + stages:(NSArray *> *)stages + error:(NSError **)error { + NSArray *stageBridges = [self parseStagesWithFirestore:firestore + stages:stages + error:error]; + if (!stageBridges) return nil; return [[FIRPipelineBridge alloc] initWithStages:stageBridges db:firestore]; } From 6699324438612578ff7d7554b80f6afce6bfdfcc Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 15:33:59 +0000 Subject: [PATCH 13/20] fix: update args type in _UnnestStage to use a more specific map type --- .../cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart index 11c4ec624da1..ab06a32dc51e 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/pipeline_stage.dart @@ -363,7 +363,7 @@ final class _UnnestStage extends PipelineStage { Map toMap() { final map = { 'stage': name, - 'args': { + 'args': { 'expression': expression.toMap(), }, }; From 59c0b886a7f9bbddc4be5f97c32b88a88bd8c111 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Wed, 25 Feb 2026 15:34:21 +0000 Subject: [PATCH 14/20] feat: add filter expression parsing to FLTPipelineParser for enhanced query capabilities --- .../cloud_firestore/FLTPipelineParser.m | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index b4f46889cf87..986628021869 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -38,6 +38,7 @@ @interface FLTPipelineExpressionParser : NSObject - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NSError **)error; - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map error:(NSError **)error; +- (FIRExprBridge *)rightExprFromValue:(id)value error:(NSError **)error; @end @implementation FLTPipelineExpressionParser @@ -241,10 +242,160 @@ - (FIRExprBridge *)parseExpression:(NSDictionary *)map error:(NS return [[FIRFunctionExprBridge alloc] initWithName:name Args:argsArray]; } + // ------------------------------------------------------------------------- + // PipelineFilter (name "filter"): operator-based (and/or) or field-based + // ------------------------------------------------------------------------- + if ([name isEqualToString:@"filter"]) { + return [self parseFilterExpressionWithArgs:args error:error]; + } + if (error) *error = parseError([NSString stringWithFormat:@"Unsupported expression: %@", name]); return nil; } +- (FIRExprBridge *)rightExprFromValue:(id)value error:(NSError **)error { + if ([value isKindOfClass:[NSDictionary class]]) { + return [self parseExpression:(NSDictionary *)value error:error]; + } + return [[FIRConstantBridge alloc] init:value]; +} + +- (FIRExprBridge *)parseFilterExpressionWithArgs:(NSDictionary *)args error:(NSError **)error { + // Operator-based: and/or with expressions array (from PipelineFilter.and / .or) + NSString *operator= args[@"operator"]; + NSArray *exprMaps = args[@"expressions"]; + if ([operator isKindOfClass:[NSString class]] && [exprMaps isKindOfClass:[NSArray class]]) { + if (exprMaps.count == 0) { + if (error) *error = parseError(@"filter with operator requires at least one expression"); + return nil; + } + if (exprMaps.count == 1) { + id em = exprMaps[0]; + if (![em isKindOfClass:[NSDictionary class]]) { + if (error) *error = parseError(@"filter expressions must be maps"); + return nil; + } + return [self parseBooleanExpression:(NSDictionary *)em error:error]; + } + NSMutableArray *all = [NSMutableArray array]; + for (id em in exprMaps) { + if (![em isKindOfClass:[NSDictionary class]]) continue; + FIRExprBridge *e = [self parseBooleanExpression:(NSDictionary *)em error:error]; + if (!e) return nil; + [all addObject:e]; + } + if (all.count == 0) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:operator Args:all]; + } + + // Field-based: field + isEqualTo, isGreaterThan, etc. + NSString *fieldName = args[@"field"]; + if (![fieldName isKindOfClass:[NSString class]]) { + if (error) *error = parseError(@"filter requires operator+expressions or field"); + return nil; + } + FIRExprBridge *fieldExpr = [[FIRFieldBridge alloc] initWithName:fieldName]; + + static NSArray *filterComparisonKeys = nil; + static dispatch_once_t filterOnce; + dispatch_once(&filterOnce, ^{ + filterComparisonKeys = @[ + @"isEqualTo", @"isNotEqualTo", @"isGreaterThan", @"isGreaterThanOrEqualTo", @"isLessThan", + @"isLessThanOrEqualTo", @"arrayContains", @"arrayContainsAny", @"whereIn", @"whereNotIn", + @"isNull", @"isNotNull" + ]; + }); + for (NSString *key in filterComparisonKeys) { + id value = args[key]; + if (value == nil) continue; + + if ([key isEqualToString:@"isEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isNotEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isGreaterThan"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"greater_than" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isGreaterThanOrEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"greater_than_or_equal" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isLessThan"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"less_than" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isLessThanOrEqualTo"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"less_than_or_equal" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"arrayContains"]) { + FIRExprBridge *right = [self rightExprFromValue:value error:error]; + if (!right) return nil; + return [[FIRFunctionExprBridge alloc] initWithName:@"array_contains" + Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"arrayContainsAny"] || [key isEqualToString:@"whereIn"]) { + NSArray *valuesList = [value isKindOfClass:[NSArray class]] ? value : @[]; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id v in valuesList) { + FIRExprBridge *ve = [self rightExprFromValue:v error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) *error = parseError(@"arrayContainsAny/whereIn requires non-empty list"); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:fieldExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal_any" Args:argsArray]; + } + if ([key isEqualToString:@"whereNotIn"]) { + NSArray *valuesList = [value isKindOfClass:[NSArray class]] ? value : @[]; + NSMutableArray *valueExprs = [NSMutableArray array]; + for (id v in valuesList) { + FIRExprBridge *ve = [self rightExprFromValue:v error:error]; + if (!ve) return nil; + [valueExprs addObject:ve]; + } + if (valueExprs.count == 0) { + if (error) *error = parseError(@"whereNotIn requires non-empty list"); + return nil; + } + NSMutableArray *argsArray = [NSMutableArray arrayWithObject:fieldExpr]; + [argsArray addObjectsFromArray:valueExprs]; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal_any" Args:argsArray]; + } + if ([key isEqualToString:@"isNull"]) { + FIRExprBridge *right = [[FIRConstantBridge alloc] init:[NSNull null]]; + return [[FIRFunctionExprBridge alloc] initWithName:@"equal" Args:@[ fieldExpr, right ]]; + } + if ([key isEqualToString:@"isNotNull"]) { + FIRExprBridge *right = [[FIRConstantBridge alloc] init:[NSNull null]]; + return [[FIRFunctionExprBridge alloc] initWithName:@"not_equal" Args:@[ fieldExpr, right ]]; + } + } + + if (error) + *error = + parseError(@"filter requires at least one comparison (isEqualTo, isGreaterThan, etc.)"); + return nil; +} + - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map error:(NSError **)error { return [self parseExpression:map error:error]; From 642c0de56f96684759be4eaafe36697c837432f6 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 27 Feb 2026 11:31:05 +0000 Subject: [PATCH 15/20] fix: handle null values in parseConstantValue --- .../plugins/firebase/firestore/utils/ExpressionHelpers.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java index b3ef31dbb038..7e2c4020aa6d 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/utils/ExpressionHelpers.java @@ -77,7 +77,11 @@ static BooleanExpression parseOrExpression( * types: String, Number, Boolean, Date, Timestamp, GeoPoint, byte[], Blob, DocumentReference, * VectorValue */ - static Expression parseConstantValue(@NonNull Object value) { + static Expression parseConstantValue(Object value) { + + if (value == null) { + return Expression.nullValue(); + } if (value instanceof String) { return Expression.constant((String) value); From 0a06aea2cff655c898a8c0abf9fd42525fa1ab41 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Fri, 27 Feb 2026 14:00:38 +0000 Subject: [PATCH 16/20] feat: introduce keyForExpressionMap method in FLTPipelineParser for improved expression handling --- .../cloud_firestore/FLTPipelineParser.m | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m index 986628021869..3018ce5820fa 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m @@ -405,6 +405,25 @@ - (FIRExprBridge *)parseBooleanExpression:(NSDictionary *)map @implementation FLTPipelineParser +/// Returns the key (alias or field name) for an expression map in select/distinct stages. +/// Uses args.alias if present; otherwise for "field" expressions uses args.field. Returns nil if +/// no key can be determined (caller should error). ++ (NSString *)keyForExpressionMap:(NSDictionary *)em error:(NSError **)error { + NSString *alias = [em valueForKeyPath:@"args.alias"]; + if ([alias isKindOfClass:[NSString class]] && alias.length > 0) { + return alias; + } + if ([em[@"name"] isEqualToString:@"field"]) { + NSString *field = [em valueForKeyPath:@"args.field"]; + if ([field isKindOfClass:[NSString class]]) return field; + if (error) *error = parseError(@"field expression must have args.field"); + return nil; + } + if (error) + *error = parseError(@"select/distinct expression must have alias or be a field reference"); + return nil; +} + + (NSArray *) parseStagesWithFirestore:(FIRFirestore *)firestore stages:(NSArray *> *)stages @@ -537,18 +556,9 @@ @implementation FLTPipelineParser if (error) *error = parseErr; return nil; } - NSString *alias = [em valueForKeyPath:@"args.alias"]; - if (alias) { - fields[alias] = expr; - } else { - NSString *fn = em[@"name"]; - if ([fn isEqualToString:@"field"]) { - NSString *field = [em valueForKeyPath:@"args.field"]; - fields[field ?: @"_"] = expr; - } else { - fields[[NSString stringWithFormat:@"_%lu", (unsigned long)fields.count]] = expr; - } - } + NSString *key = [self keyForExpressionMap:em error:error]; + if (!key) return nil; + fields[key] = expr; } stage = [[FIRSelectStageBridge alloc] initWithSelections:fields]; } else if ([stageName isEqualToString:@"add_fields"]) { @@ -587,15 +597,16 @@ @implementation FLTPipelineParser return nil; } NSMutableDictionary *fields = [NSMutableDictionary dictionary]; - for (NSUInteger j = 0; j < exprMaps.count; j++) { - id em = exprMaps[j]; + for (id em in exprMaps) { if (![em isKindOfClass:[NSDictionary class]]) continue; FIRExprBridge *expr = [exprParser parseExpression:em error:&parseErr]; if (!expr) { if (error) *error = parseErr; return nil; } - fields[[NSString stringWithFormat:@"_%lu", (unsigned long)j]] = expr; + NSString *key = [self keyForExpressionMap:em error:error]; + if (!key) return nil; + fields[key] = expr; } stage = [[FIRDistinctStageBridge alloc] initWithGroups:fields]; } else if ([stageName isEqualToString:@"replace_with"]) { From 1b29c4d432597d12e08990825647f0ac9467a8f3 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:31:36 +0000 Subject: [PATCH 17/20] feat: bump Firebase JS SDK to 12.9.0 (#18043) --- .../firebase_core_web/lib/src/firebase_sdk_version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart index 6a43bfd5f478..3e04c6928134 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart @@ -6,4 +6,4 @@ part of '../firebase_core_web.dart'; /// The currently supported Firebase JS SDK version. -const String supportedFirebaseJsSdkVersion = '12.7.0'; +const String supportedFirebaseJsSdkVersion = '12.9.0'; From fd07be0c0e3ab3457ef670e6b6069dc104938a08 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Fri, 27 Feb 2026 09:57:33 -0800 Subject: [PATCH 18/20] fix: use builtin GITHUB_TOKEN instead of explicit secret (#18031) * use builtin GITHUB_TOKEN instead of explicit secret * add explicit permission --- .github/workflows/pr_title.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml index 317338e169a3..453064704526 100644 --- a/.github/workflows/pr_title.yaml +++ b/.github/workflows/pr_title.yaml @@ -9,8 +9,10 @@ on: jobs: validate: + permissions: + pull-requests: read runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From fed585f5a9b65d683cefdc7fa97ed2692e4ec817 Mon Sep 17 00:00:00 2001 From: Aashish <112133849+aashishpatil-g@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:35:30 -0800 Subject: [PATCH 19/20] refactor(fdc): Support for entityId path extensions and hardening (#17988) Support for reading entity ids from paths in response extension Hardening SQLite to use transactions for edits Setup schema versioning to handle future schema updates. Handle edge cases - multi-dimensional arrays, mixed type arrays, scalar arrays --- .../firebase_data_connect/generate_proto.sh | 7 +- .../cache/{cache_manager.dart => cache.dart} | 43 ++-- .../lib/src/cache/cache_data_types.dart | 121 +++++++++- .../lib/src/cache/cache_provider.dart | 9 +- .../src/cache/in_memory_cache_provider.dart | 14 +- .../lib/src/cache/result_tree_processor.dart | 121 +++++----- .../lib/src/cache/sqlite_cache_provider.dart | 127 +++++++--- .../lib/src/common/common_library.dart | 9 +- .../lib/src/common/dataconnect_error.dart | 27 +++ .../lib/src/core/ref.dart | 2 +- .../lib/src/firebase_data_connect.dart | 2 +- .../src/generated/connector_service.pb.dart | 74 ++++-- .../generated/connector_service.pbenum.dart | 13 - .../generated/connector_service.pbgrpc.dart | 13 - .../generated/connector_service.pbjson.dart | 37 +-- .../google/protobuf/duration.pb.dart | 166 +++++++++++++ .../google/protobuf/duration.pbenum.dart | 10 + .../google/protobuf/duration.pbjson.dart | 28 +++ .../generated/google/protobuf/struct.pb.dart | 13 - .../google/protobuf/struct.pbenum.dart | 13 - .../google/protobuf/struct.pbjson.dart | 13 - .../lib/src/generated/graphql_error.pb.dart | 13 - .../src/generated/graphql_error.pbenum.dart | 13 - .../src/generated/graphql_error.pbjson.dart | 13 - .../graphql_response_extensions.pb.dart | 222 ++++++++++++++++++ .../graphql_response_extensions.pbenum.dart | 10 + .../graphql_response_extensions.pbjson.dart | 65 +++++ .../lib/src/network/grpc_transport.dart | 14 +- .../lib/src/network/rest_transport.dart | 9 +- .../protos/connector_service.proto | 48 ++-- .../graphql_response_extensions.proto | 59 +++++ .../protos/google/duration.proto | 116 +++++++++ .../firebase_data_connect/pubspec.yaml | 1 + .../test/src/cache/cache_manager_test.dart | 102 ++++++-- .../src/cache/result_tree_processor_test.dart | 43 +++- 35 files changed, 1257 insertions(+), 333 deletions(-) rename packages/firebase_data_connect/firebase_data_connect/lib/src/cache/{cache_manager.dart => cache.dart} (80%) create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart create mode 100644 packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto create mode 100644 packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh b/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh index b8d3232eef71..5b3fc45dcaed 100755 --- a/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh +++ b/packages/firebase_data_connect/firebase_data_connect/generate_proto.sh @@ -1,4 +1,9 @@ #!/bin/bash + +# Uses dart protoc_plugin version 21.1.2. There are compilation issues with newer plugin versions. +# https://github.com/google/protobuf.dart/releases/tag/protoc_plugin-v21.1.2 +# Run `pub global activate protoc_plugin 21.1.2` + rm -rf lib/src/generated mkdir lib/src/generated -protoc --dart_out=grpc:lib/src/generated -I./protos/firebase -I./protos/google connector_service.proto google/protobuf/struct.proto graphql_error.proto --proto_path=./protos +protoc --dart_out=grpc:lib/src/generated -I./protos/firebase -I./protos/google connector_service.proto google/protobuf/struct.proto google/protobuf/duration.proto graphql_error.proto graphql_response_extensions.proto --proto_path=./protos diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart similarity index 80% rename from packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart rename to packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart index b2cc1506161f..3983df6c5842 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_manager.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache.dart @@ -50,9 +50,13 @@ class Cache { Stream> get impactedQueries => _impactedQueryController.stream; String _constructCacheIdentifier() { - final rawIdentifier = - '${_settings.storage}-${dataConnect.app.options.projectId}-${dataConnect.app.name}-${dataConnect.connectorConfig.serviceId}-${dataConnect.connectorConfig.connector}-${dataConnect.connectorConfig.location}-${dataConnect.auth?.currentUser?.uid ?? 'anon'}-${dataConnect.transport.transportOptions.host}'; - return convertToSha256(rawIdentifier); + final rawPrefix = + '${_settings.storage}-${dataConnect.app.options.projectId}-${dataConnect.app.name}-${dataConnect.connectorConfig.serviceId}-${dataConnect.connectorConfig.connector}-${dataConnect.connectorConfig.location}-${dataConnect.transport.transportOptions.host}'; + final prefixSha = convertToSha256(rawPrefix); + final rawSuffix = dataConnect.auth?.currentUser?.uid ?? 'anon'; + final suffixSha = convertToSha256(rawSuffix); + + return '$prefixSha-$suffixSha'; } void _initializeProvider() { @@ -92,23 +96,32 @@ class Cache { return; } - final dehydrationResult = await _resultTreeProcessor.dehydrate( - queryId, serverResponse.data, _cacheProvider!); + final Map paths = + serverResponse.extensions != null + ? ExtensionResponse.fromJson(serverResponse.extensions!) + .flattenPathMetadata() + : {}; + + final dehydrationResult = await _resultTreeProcessor.dehydrateResults( + queryId, serverResponse.data, _cacheProvider!, paths); EntityNode rootNode = dehydrationResult.dehydratedTree; Map dehydratedMap = rootNode.toJson(mode: EncodingMode.dehydrated); // if we have server ttl, that overrides maxAge from cacheSettings - Duration ttl = - serverResponse.ttl != null ? serverResponse.ttl! : _settings.maxAge; + Duration ttl = serverResponse.extensions != null && + serverResponse.extensions!['ttl'] != null + ? Duration(seconds: serverResponse.extensions!['ttl'] as int) + : (serverResponse.ttl ?? _settings.maxAge); + final resultTree = ResultTree( data: dehydratedMap, ttl: ttl, cachedAt: DateTime.now(), lastAccessed: DateTime.now()); - _cacheProvider!.saveResultTree(queryId, resultTree); + _cacheProvider!.setResultTree(queryId, resultTree); Set impactedQueryIds = dehydrationResult.impactedQueryIds; impactedQueryIds.remove(queryId); // remove query being cached @@ -116,7 +129,8 @@ class Cache { } /// Fetches a cached result. - Future?> get(String queryId, bool allowStale) async { + Future?> resultTree( + String queryId, bool allowStale) async { if (_cacheProvider == null) { return null; } @@ -137,23 +151,20 @@ class Cache { } resultTree.lastAccessed = DateTime.now(); - _cacheProvider!.saveResultTree(queryId, resultTree); + _cacheProvider!.setResultTree(queryId, resultTree); EntityNode rootNode = EntityNode.fromJson(resultTree.data, _cacheProvider!); + Map hydratedJson = - rootNode.toJson(); //default mode for toJson is hydrate + await _resultTreeProcessor.hydrateResults(rootNode, _cacheProvider!); + return hydratedJson; } return null; } - /// Invalidates the cache. - Future invalidate() async { - _cacheProvider?.clear(); - } - void dispose() { _impactedQueryController.close(); } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart index ae80c237817a..0768da15232c 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart @@ -15,41 +15,144 @@ import 'dart:convert'; import 'package:firebase_data_connect/src/cache/cache_provider.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:firebase_data_connect/src/common/common_library.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart' show kIsWeb, listEquals; /// Type of storage to use for the cache enum CacheStorage { persistent, memory } -const String kGlobalIDKey = 'cacheId'; +const String kGlobalIDKey = 'guid'; + +@immutable +class DataConnectPath { + final List components; + + DataConnectPath([List? components]) + : components = components ?? []; + + DataConnectPath appending(DataConnectPathSegment segment) { + return DataConnectPath([...components, segment]); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectPath && + runtimeType == other.runtimeType && + listEquals(components, other.components); + + @override + int get hashCode => Object.hashAll(components); + + @override + String toString() => 'DataConnectPath($components)'; +} + +/// Additional information about object / field identified by a path +class PathMetadata { + final DataConnectPath path; + final String? entityId; + + PathMetadata({required this.path, this.entityId}); + + @override + String toString() { + return '$path : ${entityId ?? "null"}'; + } +} + +/// Represents the server response contained within the extension response +class PathMetadataResponse { + final List path; + final String? entityId; + final List? entityIds; + + PathMetadataResponse({required this.path, this.entityId, this.entityIds}); + + factory PathMetadataResponse.fromJson(Map json) { + return PathMetadataResponse( + path: (json['path'] as List).map(_parsePathSegment).toList(), + entityId: json['entityId'] as String?, + entityIds: (json['entityIds'] as List?)?.cast(), + ); + } +} + +DataConnectPathSegment _parsePathSegment(dynamic segment) { + if (segment is String) { + return DataConnectFieldPathSegment(segment); + } else if (segment is double || segment is int) { + int index = (segment is double) ? segment.toInt() : segment; + return DataConnectListIndexPathSegment(index); + } + throw ArgumentError('Invalid path segment type: ${segment.runtimeType}'); +} + +/// Represents the extension section within the server response +class ExtensionResponse { + final Duration? maxAge; + final List dataConnect; + + ExtensionResponse({this.maxAge, required this.dataConnect}); + + factory ExtensionResponse.fromJson(Map json) { + return ExtensionResponse( + maxAge: + json['ttl'] != null ? Duration(seconds: json['ttl'] as int) : null, + dataConnect: (json['dataConnect'] as List?) + ?.map((e) => + PathMetadataResponse.fromJson(e as Map)) + .toList() ?? + [], + ); + } + + Map flattenPathMetadata() { + final Map result = {}; + for (final pmr in dataConnect) { + if (pmr.entityId != null) { + final pm = PathMetadata( + path: DataConnectPath(pmr.path), entityId: pmr.entityId); + result[pm.path] = pm; + } + + if (pmr.entityIds != null) { + for (var i = 0; i < pmr.entityIds!.length; i++) { + final entityId = pmr.entityIds![i]; + final indexPath = DataConnectPath(pmr.path) + .appending(DataConnectListIndexPathSegment(i)); + final pm = PathMetadata(path: indexPath, entityId: entityId); + result[pm.path] = pm; + } + } + } + return result; + } +} /// Configuration for the cache class CacheSettings { /// The type of storage to use (e.g., "persistent", "memory") final CacheStorage storage; - /// The maximum size of the cache in bytes - final int maxSizeBytes; - /// Duration for which cache is used before revalidation with server final Duration maxAge; // Internal const constructor const CacheSettings._internal({ required this.storage, - required this.maxSizeBytes, required this.maxAge, }); // Factory constructor to handle the logic factory CacheSettings({ CacheStorage? storage, - int? maxSizeBytes, Duration maxAge = Duration.zero, }) { return CacheSettings._internal( storage: storage ?? (kIsWeb ? CacheStorage.memory : CacheStorage.persistent), - maxSizeBytes: maxSizeBytes ?? (kIsWeb ? 40000000 : 100000000), maxAge: maxAge, ); } @@ -203,7 +306,7 @@ class EntityNode { Map json, CacheProvider cacheProvider) { EntityDataObject? entity; if (json[kGlobalIDKey] != null) { - entity = cacheProvider.getEntityDataObject(json[kGlobalIDKey]); + entity = cacheProvider.getEntityData(json[kGlobalIDKey]); } Map? scalars; diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart index 9f65aa127820..484e1e390a45 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_provider.dart @@ -25,19 +25,16 @@ abstract class CacheProvider { Future initialize(); /// Stores a `ResultTree` object. - void saveResultTree(String queryId, ResultTree resultTree); + void setResultTree(String queryId, ResultTree resultTree); /// Retrieves a `ResultTree` object. ResultTree? getResultTree(String queryId); /// Stores an `EntityDataObject` object. - void saveEntityDataObject(EntityDataObject edo); + void updateEntityData(EntityDataObject edo); /// Retrieves an `EntityDataObject` object. - EntityDataObject getEntityDataObject(String guid); - - /// Manages the cache size and eviction policies. - void manageCacheSize(); + EntityDataObject getEntityData(String guid); /// Clears all data from the cache. void clear(); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart index b625921bbe05..cd44d4e25ac5 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/in_memory_cache_provider.dart @@ -16,6 +16,7 @@ import 'cache_data_types.dart'; import 'cache_provider.dart'; /// An in-memory implementation of the `CacheProvider`. +/// This is used for the web platform class InMemoryCacheProvider implements CacheProvider { final Map _resultTrees = {}; final Map _edos = {}; @@ -31,12 +32,12 @@ class InMemoryCacheProvider implements CacheProvider { @override Future initialize() async { - // nothing to be intialized. + // nothing to be intialized return true; } @override - void saveResultTree(String queryId, ResultTree resultTree) { + void setResultTree(String queryId, ResultTree resultTree) { _resultTrees[queryId] = resultTree; } @@ -46,20 +47,15 @@ class InMemoryCacheProvider implements CacheProvider { } @override - void saveEntityDataObject(EntityDataObject edo) { + void updateEntityData(EntityDataObject edo) { _edos[edo.guid] = edo; } @override - EntityDataObject getEntityDataObject(String guid) { + EntityDataObject getEntityData(String guid) { return _edos.putIfAbsent(guid, () => EntityDataObject(guid: guid)); } - @override - void manageCacheSize() { - // In-memory cache doesn't have a size limit in this implementation. - } - @override void clear() { _resultTrees.clear(); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart index 34b557c31d71..ce4bf1bad24a 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/result_tree_processor.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:developer' as developer; + import '../common/common_library.dart'; import 'cache_data_types.dart'; import 'cache_provider.dart'; @@ -27,76 +29,109 @@ class DehydrationResult { class ResultTreeProcessor { /// Takes a server response, traverses the data, creates or updates `EntityDataObject`s, /// and builds a dehydrated `EntityNode` tree. - Future dehydrate(String queryId, - Map serverResponse, CacheProvider cacheProvider) async { + Future dehydrateResults( + String queryId, + Map serverResponse, + CacheProvider cacheProvider, + Map paths) async { final impactedQueryIds = {}; Map jsonData = serverResponse; if (serverResponse.containsKey('data')) { jsonData = serverResponse['data']; } - final rootNode = - _dehydrateNode(queryId, jsonData, cacheProvider, impactedQueryIds); + final rootNode = _dehydrateNode(queryId, jsonData, cacheProvider, + impactedQueryIds, DataConnectPath(), paths); return DehydrationResult(rootNode, impactedQueryIds); } - EntityNode _dehydrateNode(String queryId, dynamic data, - CacheProvider cacheProvider, Set impactedQueryIds) { + EntityNode _dehydrateNode( + String queryId, + dynamic data, + CacheProvider cacheProvider, + Set impactedQueryIds, + DataConnectPath path, + Map paths) { if (data is Map) { - // data contains a unique entity id. we can normalize - final guid = data[kGlobalIDKey] as String?; + // Look up entityId for current path + String? guid; + if (paths.containsKey(path)) { + guid = paths[path]?.entityId; + } - final serverValues = {}; + final scalarValues = {}; // scalars final nestedObjects = {}; final nestedObjectLists = >{}; for (final entry in data.entries) { final key = entry.key; final value = entry.value; - if (value is Map) { - EntityNode en = - _dehydrateNode(queryId, value, cacheProvider, impactedQueryIds); + //developer.log('detected Map for $key'); + EntityNode en = _dehydrateNode( + queryId, + value, + cacheProvider, + impactedQueryIds, + path.appending(DataConnectFieldPathSegment(key)), + paths); nestedObjects[key] = en; } else if (value is List) { + //developer.log('detected List for $key'); final nodeList = []; final scalarValueList = []; - for (final item in value) { + for (var i = 0; i < value.length; i++) { + final item = value[i]; if (item is Map) { nodeList.add(_dehydrateNode( - queryId, item, cacheProvider, impactedQueryIds)); + queryId, + item, + cacheProvider, + impactedQueryIds, + path + .appending(DataConnectFieldPathSegment(key)) + .appending(DataConnectListIndexPathSegment(i)), + paths)); } else { // assuming scalar - we don't handle array of arrays scalarValueList.add(item); } } - - // we either do object lists or scalar lists stored with scalars - // we don't handle mixed lists. - if (nodeList.isNotEmpty) { + // we either normalize object lists or scalar lists stored with scalars + // we don't normalize mixed lists. We store them as-is for reconstruction from cache. + if (nodeList.isNotEmpty && scalarValueList.isNotEmpty) { + // mixed type array - we directly store the json as-is + developer + .log('detected mixed type array for key $key. storing as-is'); + scalarValues[key] = value; + } else if (nodeList.isNotEmpty) { nestedObjectLists[key] = nodeList; + } else if (scalarValueList.isNotEmpty) { + scalarValues[key] = scalarValueList; } else { - serverValues[key] = scalarValueList; + // we have empty array. save key as scalar since we can't determine type + scalarValues[key] = value; } + // end list handling } else { - serverValues[key] = value; + //developer.log('detected Scalar for $key'); + scalarValues[key] = value; } } if (guid != null) { - final existingEdo = cacheProvider.getEntityDataObject(guid); - existingEdo.setServerValues(serverValues, queryId); - cacheProvider.saveEntityDataObject(existingEdo); + final existingEdo = cacheProvider.getEntityData(guid); + existingEdo.setServerValues(scalarValues, queryId); + cacheProvider.updateEntityData(existingEdo); impactedQueryIds.addAll(existingEdo.referencedFrom); - return EntityNode( entity: existingEdo, nestedObjects: nestedObjects, nestedObjectLists: nestedObjectLists); } else { return EntityNode( - scalarValues: serverValues, + scalarValues: scalarValues, nestedObjects: nestedObjects, nestedObjectLists: nestedObjectLists); } @@ -108,40 +143,8 @@ class ResultTreeProcessor { /// Takes a dehydrated `EntityNode` tree, fetches the corresponding `EntityDataObject`s /// from the `CacheProvider`, and reconstructs the original data structure. - Future> hydrate( + Future> hydrateResults( EntityNode dehydratedTree, CacheProvider cacheProvider) async { - return await _hydrateNode(dehydratedTree, cacheProvider) - as Map; - } - - Future _hydrateNode( - EntityNode node, CacheProvider cacheProvider) async { - final Map data = {}; - if (node.entity != null) { - final edo = cacheProvider.getEntityDataObject(node.entity!.guid); - data.addAll(edo.fields()); - } - - if (node.scalarValues != null) { - data.addAll(node.scalarValues!); - } - - if (node.nestedObjects != null) { - for (final entry in node.nestedObjects!.entries) { - data[entry.key] = await _hydrateNode(entry.value, cacheProvider); - } - } - - if (node.nestedObjectLists != null) { - for (final entry in node.nestedObjectLists!.entries) { - final list = []; - for (final item in entry.value) { - list.add(await _hydrateNode(item, cacheProvider)); - } - data[entry.key] = list; - } - } - - return data; + return dehydratedTree.toJson(); //default mode for toJson is hydrate } } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart index 3de7c1b44c95..1493f32a05ac 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/cache/sqlite_cache_provider.dart @@ -39,7 +39,19 @@ class SQLite3CacheProvider implements CacheProvider { final path = join(dbPath.path, '$_identifier.db'); _db = sqlite3.open(path); } - _createTables(); + + int curVersion = _getDatabaseVersion(); + if (curVersion == 0) { + _createTables(); + } else { + int major = curVersion ~/ 1000000; + if (major != 1) { + developer.log( + 'Unsupported schema major version $major detected. Expected 1'); + return false; + } + } + return true; } catch (e) { developer.log('Error initializing SQLiteProvider $e'); @@ -47,19 +59,37 @@ class SQLite3CacheProvider implements CacheProvider { } } + int _getDatabaseVersion() { + final resultSet = _db.select('PRAGMA user_version;'); + return resultSet.first.columnAt(0) as int; + } + + void _setDatabaseVersion(int version) { + _db.execute('PRAGMA user_version = $version;'); + } + void _createTables() { - _db.execute(''' - CREATE TABLE IF NOT EXISTS $resultTreeTable ( - query_id TEXT PRIMARY KEY, - result_tree TEXT - ); - '''); - _db.execute(''' - CREATE TABLE IF NOT EXISTS $entityDataTable ( - guid TEXT PRIMARY KEY, - entity_data_object TEXT - ); - '''); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute(''' + CREATE TABLE IF NOT EXISTS $resultTreeTable ( + query_id TEXT PRIMARY KEY NOT NULL, + last_accessed REAL NOT NULL, + data TEXT NOT NULL + ); + '''); + _db.execute(''' + CREATE TABLE IF NOT EXISTS $entityDataTable ( + entity_guid TEXT PRIMARY KEY NOT NULL, + data TEXT NOT NULL + ); + '''); + _setDatabaseVersion(1000000); // 1.0.0 + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override @@ -69,57 +99,84 @@ class SQLite3CacheProvider implements CacheProvider { @override void clear() { - _db.execute('DELETE FROM $resultTreeTable'); - _db.execute('DELETE FROM $entityDataTable'); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute('DELETE FROM $resultTreeTable'); + _db.execute('DELETE FROM $entityDataTable'); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override - EntityDataObject getEntityDataObject(String guid) { + EntityDataObject getEntityData(String guid) { final resultSet = _db.select( - 'SELECT entity_data_object FROM $entityDataTable WHERE guid = ?', + 'SELECT data FROM $entityDataTable WHERE entity_guid = ?', [guid], ); if (resultSet.isEmpty) { - // not found lets create an empty one. + // not found lets create an empty one EntityDataObject edo = EntityDataObject(guid: guid); return edo; } - return EntityDataObject.fromRawJson( - resultSet.first['entity_data_object'] as String); + return EntityDataObject.fromRawJson(resultSet.first['data'] as String); } @override ResultTree? getResultTree(String queryId) { final resultSet = _db.select( - 'SELECT result_tree FROM $resultTreeTable WHERE query_id = ?', + 'SELECT data FROM $resultTreeTable WHERE query_id = ?', [queryId], ); if (resultSet.isEmpty) { return null; } - return ResultTree.fromRawJson(resultSet.first['result_tree'] as String); + _updateLastAccessedTime(queryId); + return ResultTree.fromRawJson(resultSet.first['data'] as String); } - @override - void manageCacheSize() { - // TODO: implement manageCacheSize + void _updateLastAccessedTime(String queryId) { + _db.execute( + 'UPDATE $resultTreeTable SET last_accessed = ? WHERE query_id = ?', + [DateTime.now().millisecondsSinceEpoch / 1000.0, queryId], + ); } @override - void saveEntityDataObject(EntityDataObject edo) { + void updateEntityData(EntityDataObject edo) { String rawJson = edo.toRawJson(); - _db.execute( - 'INSERT OR REPLACE INTO $entityDataTable (guid, entity_data_object) VALUES (?, ?)', - [edo.guid, rawJson], - ); + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute( + 'INSERT OR REPLACE INTO $entityDataTable (entity_guid, data) VALUES (?, ?)', + [edo.guid, rawJson], + ); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } @override - void saveResultTree(String queryId, ResultTree resultTree) { - _db.execute( - 'INSERT OR REPLACE INTO $resultTreeTable (query_id, result_tree) VALUES (?, ?)', - [queryId, resultTree.toRawJson()], - ); + void setResultTree(String queryId, ResultTree resultTree) { + _db.execute('BEGIN TRANSACTION'); + try { + _db.execute( + 'INSERT OR REPLACE INTO $resultTreeTable (query_id, last_accessed, data) VALUES (?, ?, ?)', + [ + queryId, + DateTime.now().millisecondsSinceEpoch / 1000.0, + resultTree.toRawJson() + ], + ); + _db.execute('COMMIT'); + } catch (_) { + _db.execute('ROLLBACK'); + rethrow; + } } } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart index e24e7a7e3b89..9247287f5adf 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart @@ -61,11 +61,18 @@ class TransportOptions { bool? isSecure; } +/// Encapsulates the response from server class ServerResponse { + /// Data returned from server final Map data; + + /// duration for which the results are considered not stale Duration? ttl; - ServerResponse(this.data); + /// Additional data provided in extensions + final Map? extensions; + + ServerResponse(this.data, {this.extensions}); } /// Interface for transports connecting to the DataConnect backend. diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart index 3928a9706536..43b7fd964418 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_error.dart @@ -58,16 +58,43 @@ class DataConnectOperationFailureResponseErrorInfo { } /// Path where error occurred. +@immutable sealed class DataConnectPathSegment {} class DataConnectFieldPathSegment extends DataConnectPathSegment { final String field; DataConnectFieldPathSegment(this.field); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectFieldPathSegment && + runtimeType == other.runtimeType && + field == other.field; + + @override + int get hashCode => field.hashCode; + + @override + String toString() => field; } class DataConnectListIndexPathSegment extends DataConnectPathSegment { final int index; DataConnectListIndexPathSegment(this.index); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataConnectListIndexPathSegment && + runtimeType == other.runtimeType && + index == other.index; + + @override + int get hashCode => index.hashCode; + + @override + String toString() => index.toString(); } typedef Serializer = String Function(Variables vars); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart index e23a00134b70..5b359f6a15b3 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart @@ -251,7 +251,7 @@ class QueryRef extends OperationRef { final cacheManager = dataConnect.cacheManager!; bool allowStale = fetchPolicy == QueryFetchPolicy.cacheOnly; //if its cache only, we always allow stale - final cachedData = await cacheManager.get(_queryId, allowStale); + final cachedData = await cacheManager.resultTree(_queryId, allowStale); if (cachedData != null) { try { diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart index 43cc4fb63e1a..1684e88ed8b8 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart @@ -25,7 +25,7 @@ import './network/transport_library.dart' if (dart.library.html) './network/rest_library.dart'; import 'cache/cache_data_types.dart'; -import 'cache/cache_manager.dart'; +import 'cache/cache.dart'; /// DataConnect class class FirebaseDataConnect extends FirebasePluginPlatform { diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart index 54aed178ad40..1f38718bd4c9 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto @@ -27,7 +14,8 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; import 'google/protobuf/struct.pb.dart' as $1; -import 'graphql_error.pb.dart' as $2; +import 'graphql_error.pb.dart' as $3; +import 'graphql_response_extensions.pb.dart' as $4; /// The ExecuteQuery request to Firebase Data Connect. class ExecuteQueryRequest extends $pb.GeneratedMessage { @@ -257,7 +245,8 @@ class ExecuteMutationRequest extends $pb.GeneratedMessage { class ExecuteQueryResponse extends $pb.GeneratedMessage { factory ExecuteQueryResponse({ $1.Struct? data, - $core.Iterable<$2.GraphqlError>? errors, + $core.Iterable<$3.GraphqlError>? errors, + $4.GraphqlResponseExtensions? extensions, }) { final $result = create(); if (data != null) { @@ -266,6 +255,9 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { if (errors != null) { $result.errors.addAll(errors); } + if (extensions != null) { + $result.extensions = extensions; + } return $result; } ExecuteQueryResponse._() : super(); @@ -283,9 +275,11 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { createEmptyInstance: create) ..aOM<$1.Struct>(1, _omitFieldNames ? '' : 'data', subBuilder: $1.Struct.create) - ..pc<$2.GraphqlError>( + ..pc<$3.GraphqlError>( 2, _omitFieldNames ? '' : 'errors', $pb.PbFieldType.PM, - subBuilder: $2.GraphqlError.create) + subBuilder: $3.GraphqlError.create) + ..aOM<$4.GraphqlResponseExtensions>(3, _omitFieldNames ? '' : 'extensions', + subBuilder: $4.GraphqlResponseExtensions.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -329,14 +323,30 @@ class ExecuteQueryResponse extends $pb.GeneratedMessage { /// Errors of this response. @$pb.TagNumber(2) - $core.List<$2.GraphqlError> get errors => $_getList(1); + $core.List<$3.GraphqlError> get errors => $_getList(1); + + /// Additional response information. + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions get extensions => $_getN(2); + @$pb.TagNumber(3) + set extensions($4.GraphqlResponseExtensions v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasExtensions() => $_has(2); + @$pb.TagNumber(3) + void clearExtensions() => clearField(3); + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions ensureExtensions() => $_ensure(2); } /// The ExecuteMutation response from Firebase Data Connect. class ExecuteMutationResponse extends $pb.GeneratedMessage { factory ExecuteMutationResponse({ $1.Struct? data, - $core.Iterable<$2.GraphqlError>? errors, + $core.Iterable<$3.GraphqlError>? errors, + $4.GraphqlResponseExtensions? extensions, }) { final $result = create(); if (data != null) { @@ -345,6 +355,9 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { if (errors != null) { $result.errors.addAll(errors); } + if (extensions != null) { + $result.extensions = extensions; + } return $result; } ExecuteMutationResponse._() : super(); @@ -362,9 +375,11 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { createEmptyInstance: create) ..aOM<$1.Struct>(1, _omitFieldNames ? '' : 'data', subBuilder: $1.Struct.create) - ..pc<$2.GraphqlError>( + ..pc<$3.GraphqlError>( 2, _omitFieldNames ? '' : 'errors', $pb.PbFieldType.PM, - subBuilder: $2.GraphqlError.create) + subBuilder: $3.GraphqlError.create) + ..aOM<$4.GraphqlResponseExtensions>(3, _omitFieldNames ? '' : 'extensions', + subBuilder: $4.GraphqlResponseExtensions.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -409,7 +424,22 @@ class ExecuteMutationResponse extends $pb.GeneratedMessage { /// Errors of this response. @$pb.TagNumber(2) - $core.List<$2.GraphqlError> get errors => $_getList(1); + $core.List<$3.GraphqlError> get errors => $_getList(1); + + /// Additional response information. + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions get extensions => $_getN(2); + @$pb.TagNumber(3) + set extensions($4.GraphqlResponseExtensions v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasExtensions() => $_has(2); + @$pb.TagNumber(3) + void clearExtensions() => clearField(3); + @$pb.TagNumber(3) + $4.GraphqlResponseExtensions ensureExtensions() => $_ensure(2); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart index d53ea6876082..aabd1a01d514 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart index b6704e57cca1..8b1732a117ae 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbgrpc.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart index 834e9aad147e..03a497345922 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/connector_service.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: connector_service.proto @@ -108,6 +95,14 @@ const ExecuteQueryResponse$json = { '6': '.google.firebase.dataconnect.v1.GraphqlError', '10': 'errors' }, + { + '1': 'extensions', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.firebase.dataconnect.v1.GraphqlResponseExtensions', + '10': 'extensions' + }, ], }; @@ -115,7 +110,9 @@ const ExecuteQueryResponse$json = { final $typed_data.Uint8List executeQueryResponseDescriptor = $convert.base64Decode( 'ChRFeGVjdXRlUXVlcnlSZXNwb25zZRIrCgRkYXRhGAEgASgLMhcuZ29vZ2xlLnByb3RvYnVmLl' 'N0cnVjdFIEZGF0YRJECgZlcnJvcnMYAiADKAsyLC5nb29nbGUuZmlyZWJhc2UuZGF0YWNvbm5l' - 'Y3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnM='); + 'Y3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnMSWQoKZXh0ZW5zaW9ucxgDIAEoCzI5Lmdvb2dsZS' + '5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zUgpleHRl' + 'bnNpb25z'); @$core.Deprecated('Use executeMutationResponseDescriptor instead') const ExecuteMutationResponse$json = { @@ -137,6 +134,14 @@ const ExecuteMutationResponse$json = { '6': '.google.firebase.dataconnect.v1.GraphqlError', '10': 'errors' }, + { + '1': 'extensions', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.firebase.dataconnect.v1.GraphqlResponseExtensions', + '10': 'extensions' + }, ], }; @@ -144,4 +149,6 @@ const ExecuteMutationResponse$json = { final $typed_data.Uint8List executeMutationResponseDescriptor = $convert.base64Decode( 'ChdFeGVjdXRlTXV0YXRpb25SZXNwb25zZRIrCgRkYXRhGAEgASgLMhcuZ29vZ2xlLnByb3RvYn' 'VmLlN0cnVjdFIEZGF0YRJECgZlcnJvcnMYAiADKAsyLC5nb29nbGUuZmlyZWJhc2UuZGF0YWNv' - 'bm5lY3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnM='); + 'bm5lY3QudjEuR3JhcGhxbEVycm9yUgZlcnJvcnMSWQoKZXh0ZW5zaW9ucxgDIAEoCzI5Lmdvb2' + 'dsZS5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zUgpl' + 'eHRlbnNpb25z'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart new file mode 100644 index 000000000000..4bcbcd32a4c2 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pb.dart @@ -0,0 +1,166 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin; + +/// A Duration represents a signed, fixed-length span of time represented +/// as a count of seconds and fractions of seconds at nanosecond +/// resolution. It is independent of any calendar and concepts like "day" +/// or "month". It is related to Timestamp in that the difference between +/// two Timestamp values is a Duration and it can be added or subtracted +/// from a Timestamp. Range is approximately +-10,000 years. +/// +/// # Examples +/// +/// Example 1: Compute Duration from two Timestamps in pseudo code. +/// +/// Timestamp start = ...; +/// Timestamp end = ...; +/// Duration duration = ...; +/// +/// duration.seconds = end.seconds - start.seconds; +/// duration.nanos = end.nanos - start.nanos; +/// +/// if (duration.seconds < 0 && duration.nanos > 0) { +/// duration.seconds += 1; +/// duration.nanos -= 1000000000; +/// } else if (duration.seconds > 0 && duration.nanos < 0) { +/// duration.seconds -= 1; +/// duration.nanos += 1000000000; +/// } +/// +/// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +/// +/// Timestamp start = ...; +/// Duration duration = ...; +/// Timestamp end = ...; +/// +/// end.seconds = start.seconds + duration.seconds; +/// end.nanos = start.nanos + duration.nanos; +/// +/// if (end.nanos < 0) { +/// end.seconds -= 1; +/// end.nanos += 1000000000; +/// } else if (end.nanos >= 1000000000) { +/// end.seconds += 1; +/// end.nanos -= 1000000000; +/// } +/// +/// Example 3: Compute Duration from datetime.timedelta in Python. +/// +/// td = datetime.timedelta(days=3, minutes=10) +/// duration = Duration() +/// duration.FromTimedelta(td) +/// +/// # JSON Mapping +/// +/// In JSON format, the Duration type is encoded as a string rather than an +/// object, where the string ends in the suffix "s" (indicating seconds) and +/// is preceded by the number of seconds, with nanoseconds expressed as +/// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +/// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +/// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +/// microsecond should be expressed in JSON format as "3.000001s". +class Duration extends $pb.GeneratedMessage with $mixin.DurationMixin { + factory Duration({ + $fixnum.Int64? seconds, + $core.int? nanos, + }) { + final $result = create(); + if (seconds != null) { + $result.seconds = seconds; + } + if (nanos != null) { + $result.nanos = nanos; + } + return $result; + } + Duration._() : super(); + factory Duration.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory Duration.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Duration', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), + createEmptyInstance: create, + toProto3Json: $mixin.DurationMixin.toProto3JsonHelper, + fromProto3Json: $mixin.DurationMixin.fromProto3JsonHelper) + ..aInt64(1, _omitFieldNames ? '' : 'seconds') + ..a<$core.int>(2, _omitFieldNames ? '' : 'nanos', $pb.PbFieldType.O3) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Duration clone() => Duration()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Duration copyWith(void Function(Duration) updates) => + super.copyWith((message) => updates(message as Duration)) as Duration; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Duration create() => Duration._(); + Duration createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Duration getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Duration? _defaultInstance; + + /// Signed seconds of the span of time. Must be from -315,576,000,000 + /// to +315,576,000,000 inclusive. Note: these bounds are computed from: + /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + @$pb.TagNumber(1) + $fixnum.Int64 get seconds => $_getI64(0); + @$pb.TagNumber(1) + set seconds($fixnum.Int64 v) { + $_setInt64(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasSeconds() => $_has(0); + @$pb.TagNumber(1) + void clearSeconds() => clearField(1); + + /// Signed fractions of a second at nanosecond resolution of the span + /// of time. Durations less than one second are represented with a 0 + /// `seconds` field and a positive or negative `nanos` field. For durations + /// of one second or more, a non-zero value for the `nanos` field must be + /// of the same sign as the `seconds` field. Must be from -999,999,999 + /// to +999,999,999 inclusive. + @$pb.TagNumber(2) + $core.int get nanos => $_getIZ(1); + @$pb.TagNumber(2) + set nanos($core.int v) { + $_setSignedInt32(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasNanos() => $_has(1); + @$pb.TagNumber(2) + void clearNanos() => clearField(2); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart new file mode 100644 index 000000000000..1a2c58d81056 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbenum.dart @@ -0,0 +1,10 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart new file mode 100644 index 000000000000..5847acb2d458 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/duration.pbjson.dart @@ -0,0 +1,28 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/duration.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use durationDescriptor instead') +const Duration$json = { + '1': 'Duration', + '2': [ + {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'}, + {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, + ], +}; + +/// Descriptor for `Duration`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List durationDescriptor = $convert.base64Decode( + 'CghEdXJhdGlvbhIYCgdzZWNvbmRzGAEgASgDUgdzZWNvbmRzEhQKBW5hbm9zGAIgASgFUgVuYW' + '5vcw=='); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart index 7b9093d681ff..42d55e426602 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart index b5acd2512df2..7f9bf0cbf322 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart index 3f53dbf0a988..c0693f570058 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/google/protobuf/struct.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: google/protobuf/struct.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart index a50398e29488..2def4cc62994 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pb.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart index 9f28e16d3c23..53454c94a217 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbenum.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart index ae48a28388dc..9a90ffc79685 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_error.pbjson.dart @@ -1,16 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. // // Generated code. Do not modify. // source: graphql_error.proto diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart new file mode 100644 index 000000000000..c86ba89dbd75 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pb.dart @@ -0,0 +1,222 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'google/protobuf/duration.pb.dart' as $2; +import 'google/protobuf/struct.pb.dart' as $1; + +/// Data Connect specific properties for a path under response.data. +/// (-- Design doc: http://go/fdc-caching-wire-protocol --) +class GraphqlResponseExtensions_DataConnectProperties + extends $pb.GeneratedMessage { + factory GraphqlResponseExtensions_DataConnectProperties({ + $1.ListValue? path, + $core.String? entityId, + $core.Iterable<$core.String>? entityIds, + $2.Duration? maxAge, + }) { + final $result = create(); + if (path != null) { + $result.path = path; + } + if (entityId != null) { + $result.entityId = entityId; + } + if (entityIds != null) { + $result.entityIds.addAll(entityIds); + } + if (maxAge != null) { + $result.maxAge = maxAge; + } + return $result; + } + GraphqlResponseExtensions_DataConnectProperties._() : super(); + factory GraphqlResponseExtensions_DataConnectProperties.fromBuffer( + $core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory GraphqlResponseExtensions_DataConnectProperties.fromJson( + $core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames + ? '' + : 'GraphqlResponseExtensions.DataConnectProperties', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'google.firebase.dataconnect.v1'), + createEmptyInstance: create) + ..aOM<$1.ListValue>(1, _omitFieldNames ? '' : 'path', + subBuilder: $1.ListValue.create) + ..aOS(2, _omitFieldNames ? '' : 'entityId') + ..pPS(3, _omitFieldNames ? '' : 'entityIds') + ..aOM<$2.Duration>(4, _omitFieldNames ? '' : 'maxAge', + subBuilder: $2.Duration.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions_DataConnectProperties clone() => + GraphqlResponseExtensions_DataConnectProperties()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions_DataConnectProperties copyWith( + void Function(GraphqlResponseExtensions_DataConnectProperties) + updates) => + super.copyWith((message) => updates( + message as GraphqlResponseExtensions_DataConnectProperties)) + as GraphqlResponseExtensions_DataConnectProperties; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions_DataConnectProperties create() => + GraphqlResponseExtensions_DataConnectProperties._(); + GraphqlResponseExtensions_DataConnectProperties createEmptyInstance() => + create(); + static $pb.PbList + createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions_DataConnectProperties getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor< + GraphqlResponseExtensions_DataConnectProperties>(create); + static GraphqlResponseExtensions_DataConnectProperties? _defaultInstance; + + /// The path under response.data where the rest of the fields apply. + /// Each element may be a string (field name) or number (array index). + /// The root of response.data is denoted by the empty list `[]`. + /// (-- To simplify client logic, the server should never set this to null. + /// i.e. Use `[]` if the properties below apply to everything in data. --) + @$pb.TagNumber(1) + $1.ListValue get path => $_getN(0); + @$pb.TagNumber(1) + set path($1.ListValue v) { + setField(1, v); + } + + @$pb.TagNumber(1) + $core.bool hasPath() => $_has(0); + @$pb.TagNumber(1) + void clearPath() => clearField(1); + @$pb.TagNumber(1) + $1.ListValue ensurePath() => $_ensure(0); + + /// A single Entity ID. Set if the path points to a single entity. + @$pb.TagNumber(2) + $core.String get entityId => $_getSZ(1); + @$pb.TagNumber(2) + set entityId($core.String v) { + $_setString(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasEntityId() => $_has(1); + @$pb.TagNumber(2) + void clearEntityId() => clearField(2); + + /// A list of Entity IDs. Set if the path points to an array of entities. An + /// ID is present for each element of the array at the corresponding index. + @$pb.TagNumber(3) + $core.List<$core.String> get entityIds => $_getList(2); + + /// The server-suggested duration before data under path is considered stale. + /// (-- Right now, this field is never set. For future plans, see + /// http://go/fdc-sdk-caching-config#heading=h.rmvncy2rao3g --) + @$pb.TagNumber(4) + $2.Duration get maxAge => $_getN(3); + @$pb.TagNumber(4) + set maxAge($2.Duration v) { + setField(4, v); + } + + @$pb.TagNumber(4) + $core.bool hasMaxAge() => $_has(3); + @$pb.TagNumber(4) + void clearMaxAge() => clearField(4); + @$pb.TagNumber(4) + $2.Duration ensureMaxAge() => $_ensure(3); +} + +/// GraphqlResponseExtensions contains additional information of +/// `GraphqlResponse` or `ExecuteQueryResponse`. +class GraphqlResponseExtensions extends $pb.GeneratedMessage { + factory GraphqlResponseExtensions({ + $core.Iterable? + dataConnect, + }) { + final $result = create(); + if (dataConnect != null) { + $result.dataConnect.addAll(dataConnect); + } + return $result; + } + GraphqlResponseExtensions._() : super(); + factory GraphqlResponseExtensions.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory GraphqlResponseExtensions.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GraphqlResponseExtensions', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'google.firebase.dataconnect.v1'), + createEmptyInstance: create) + ..pc( + 1, _omitFieldNames ? '' : 'dataConnect', $pb.PbFieldType.PM, + subBuilder: GraphqlResponseExtensions_DataConnectProperties.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions clone() => + GraphqlResponseExtensions()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + GraphqlResponseExtensions copyWith( + void Function(GraphqlResponseExtensions) updates) => + super.copyWith((message) => updates(message as GraphqlResponseExtensions)) + as GraphqlResponseExtensions; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions create() => GraphqlResponseExtensions._(); + GraphqlResponseExtensions createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static GraphqlResponseExtensions getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GraphqlResponseExtensions? _defaultInstance; + + /// Data Connect specific GraphQL extension, a list of paths and properties. + /// (-- Future fields should go inside to avoid name conflicts with other GQL + /// extensions in the wild unless we're implementing a common 3P pattern in + /// extensions such as versioning and telemetry. --) + @$pb.TagNumber(1) + $core.List get dataConnect => + $_getList(0); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart new file mode 100644 index 000000000000..924da2c849bb --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbenum.dart @@ -0,0 +1,10 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart new file mode 100644 index 000000000000..a1022d1267e2 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/generated/graphql_response_extensions.pbjson.dart @@ -0,0 +1,65 @@ +// +// Generated code. Do not modify. +// source: graphql_response_extensions.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use graphqlResponseExtensionsDescriptor instead') +const GraphqlResponseExtensions$json = { + '1': 'GraphqlResponseExtensions', + '2': [ + { + '1': 'data_connect', + '3': 1, + '4': 3, + '5': 11, + '6': + '.google.firebase.dataconnect.v1.GraphqlResponseExtensions.DataConnectProperties', + '10': 'dataConnect' + }, + ], + '3': [GraphqlResponseExtensions_DataConnectProperties$json], +}; + +@$core.Deprecated('Use graphqlResponseExtensionsDescriptor instead') +const GraphqlResponseExtensions_DataConnectProperties$json = { + '1': 'DataConnectProperties', + '2': [ + { + '1': 'path', + '3': 1, + '4': 1, + '5': 11, + '6': '.google.protobuf.ListValue', + '10': 'path' + }, + {'1': 'entity_id', '3': 2, '4': 1, '5': 9, '10': 'entityId'}, + {'1': 'entity_ids', '3': 3, '4': 3, '5': 9, '10': 'entityIds'}, + { + '1': 'max_age', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Duration', + '10': 'maxAge' + }, + ], +}; + +/// Descriptor for `GraphqlResponseExtensions`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List graphqlResponseExtensionsDescriptor = $convert.base64Decode( + 'ChlHcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zEnIKDGRhdGFfY29ubmVjdBgBIAMoCzJPLmdvb2' + 'dsZS5maXJlYmFzZS5kYXRhY29ubmVjdC52MS5HcmFwaHFsUmVzcG9uc2VFeHRlbnNpb25zLkRh' + 'dGFDb25uZWN0UHJvcGVydGllc1ILZGF0YUNvbm5lY3QatwEKFURhdGFDb25uZWN0UHJvcGVydG' + 'llcxIuCgRwYXRoGAEgASgLMhouZ29vZ2xlLnByb3RvYnVmLkxpc3RWYWx1ZVIEcGF0aBIbCgll' + 'bnRpdHlfaWQYAiABKAlSCGVudGl0eUlkEh0KCmVudGl0eV9pZHMYAyADKAlSCWVudGl0eUlkcx' + 'IyCgdtYXhfYWdlGAQgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uUgZtYXhBZ2U='); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart index e0c3e660333b..180bb209168b 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart @@ -173,6 +173,9 @@ ServerResponse handleResponse(CommonResponse commonResponse) { Map? jsond = commonResponse.data as Map?; String jsonEncoded = jsonEncode(commonResponse.data); + Map? jsonExt = + commonResponse.extensions as Map?; + if (commonResponse.errors.isNotEmpty) { Map? data = jsonDecode(jsonEncoded) as Map?; @@ -202,7 +205,7 @@ ServerResponse handleResponse(CommonResponse commonResponse) { // no errors - return a standard response if (jsond != null) { - return ServerResponse(jsond); + return ServerResponse(jsond, extensions: jsonExt); } else { return ServerResponse({}); } @@ -219,20 +222,21 @@ DataConnectTransport getTransport( GRPCTransport(transportOptions, options, appId, sdkType, appCheck); class CommonResponse { - CommonResponse(this.deserializer, this.data, this.errors); + CommonResponse(this.deserializer, this.data, this.errors, this.extensions); static CommonResponse fromExecuteMutation( Deserializer deserializer, ExecuteMutationResponse response) { return CommonResponse( - deserializer, response.data.toProto3Json(), response.errors); + deserializer, response.data.toProto3Json(), response.errors, null); } static CommonResponse fromExecuteQuery( Deserializer deserializer, ExecuteQueryResponse response) { - return CommonResponse( - deserializer, response.data.toProto3Json(), response.errors); + return CommonResponse(deserializer, response.data.toProto3Json(), + response.errors, response.extensions.toProto3Json()); } final Deserializer deserializer; final Object? data; final List errors; + final Object? extensions; } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart index 0ee037744ffd..708d6fa02010 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart @@ -117,6 +117,7 @@ class RestTransport implements DataConnectTransport { ); Map bodyJson = jsonDecode(r.body) as Map; + if (r.statusCode != 200) { String message = bodyJson.containsKey('message') ? bodyJson['message']! : r.body; @@ -127,7 +128,13 @@ class RestTransport implements DataConnectTransport { "Received a status code of ${r.statusCode} with a message '$message'", ); } - return ServerResponse(bodyJson); + final Map? extensions = + bodyJson['extensions'] as Map?; + final serverResponse = ServerResponse(bodyJson, extensions: extensions); + if (extensions != null && extensions.containsKey('ttl')) { + serverResponse.ttl = Duration(seconds: extensions['ttl'] as int); + } + return serverResponse; } on Exception catch (e) { if (e is DataConnectError) { rethrow; diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto b/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto index a93b79234362..0d8e28c5221a 100644 --- a/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto +++ b/packages/firebase_data_connect/firebase_data_connect/protos/connector_service.proto @@ -1,30 +1,29 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// Adapted from http://google3/google/firebase/dataconnect/v1main/connector_service.proto;rcl=596717236 +// Adapted from third_party/firebase/dataconnect/emulator/server/api/connector_service.proto syntax = "proto3"; package google.firebase.dataconnect.v1; import "google/api/field_behavior.proto"; -import "graphql_error.proto"; import "google/protobuf/struct.proto"; +import "graphql_error.proto"; +import "graphql_response_extensions.proto"; -option java_package = "google.firebase.dataconnect.proto"; +option java_package = "com.google.firebase.dataconnect.api"; option java_multiple_files = true; // Firebase Data Connect provides means to deploy a set of predefined GraphQL @@ -40,12 +39,11 @@ option java_multiple_files = true; // token. service ConnectorService { // Execute a predefined query in a Connector. - rpc ExecuteQuery(ExecuteQueryRequest) returns (ExecuteQueryResponse) { - } + rpc ExecuteQuery(ExecuteQueryRequest) returns (ExecuteQueryResponse) {} // Execute a predefined mutation in a Connector. - rpc ExecuteMutation(ExecuteMutationRequest) returns (ExecuteMutationResponse) { - } + rpc ExecuteMutation(ExecuteMutationRequest) + returns (ExecuteMutationResponse) {} } // The ExecuteQuery request to Firebase Data Connect. @@ -94,6 +92,8 @@ message ExecuteQueryResponse { google.protobuf.Struct data = 1; // Errors of this response. repeated GraphqlError errors = 2; + // Additional response information. + GraphqlResponseExtensions extensions = 3; } // The ExecuteMutation response from Firebase Data Connect. @@ -102,4 +102,6 @@ message ExecuteMutationResponse { google.protobuf.Struct data = 1; // Errors of this response. repeated GraphqlError errors = 2; -} \ No newline at end of file + // Additional response information. + GraphqlResponseExtensions extensions = 3; +} diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto b/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto new file mode 100644 index 000000000000..e04e1927ada4 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/protos/firebase/graphql_response_extensions.proto @@ -0,0 +1,59 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from third_party/firebase/dataconnect/emulator/server/api/graphql_response_extensions.proto + +syntax = "proto3"; + +package google.firebase.dataconnect.v1; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +option java_multiple_files = true; +option java_outer_classname = "GraphqlResponseExtensionsProto"; +option java_package = "com.google.firebase.dataconnect.api"; + +// GraphqlResponseExtensions contains additional information of +// `GraphqlResponse` or `ExecuteQueryResponse`. +message GraphqlResponseExtensions { + // Data Connect specific properties for a path under response.data. + // (-- Design doc: http://go/fdc-caching-wire-protocol --) + message DataConnectProperties { + // The path under response.data where the rest of the fields apply. + // Each element may be a string (field name) or number (array index). + // The root of response.data is denoted by the empty list `[]`. + // (-- To simplify client logic, the server should never set this to null. + // i.e. Use `[]` if the properties below apply to everything in data. --) + google.protobuf.ListValue path = 1; + + // A single Entity ID. Set if the path points to a single entity. + string entity_id = 2; + + // A list of Entity IDs. Set if the path points to an array of entities. An + // ID is present for each element of the array at the corresponding index. + repeated string entity_ids = 3; + + // The server-suggested duration before data under path is considered stale. + // (-- Right now, this field is never set. For future plans, see + // http://go/fdc-sdk-caching-config#heading=h.rmvncy2rao3g --) + google.protobuf.Duration max_age = 4; + } + // Data Connect specific GraphQL extension, a list of paths and properties. + // (-- Future fields should go inside to avoid name conflicts with other GQL + // extensions in the wild unless we're implementing a common 3P pattern in + // extensions such as versioning and telemetry. --) + repeated DataConnectProperties data_connect = 1; +} + diff --git a/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto b/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto new file mode 100644 index 000000000000..cb7cf0e926cc --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/protos/google/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} \ No newline at end of file diff --git a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml index e71288430a1e..971425f165dc 100644 --- a/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml +++ b/packages/firebase_data_connect/firebase_data_connect/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: firebase_auth: ^6.1.3 firebase_core: ^4.3.0 firebase_core_platform_interface: ^6.0.2 + fixnum: ^1.1.1 flutter: sdk: flutter grpc: ^3.2.4 diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart index db40791ad2a0..dec53744b7e3 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/cache_manager_test.dart @@ -18,7 +18,7 @@ import 'package:firebase_data_connect/src/core/ref.dart'; import 'package:firebase_data_connect/src/network/rest_library.dart'; import 'package:firebase_data_connect/src/common/common_library.dart'; import 'package:firebase_data_connect/src/cache/cache_data_types.dart'; -import 'package:firebase_data_connect/src/cache/cache_manager.dart'; +import 'package:firebase_data_connect/src/cache/cache.dart'; import 'package:firebase_data_connect/src/cache/cache_provider.dart'; import 'package:firebase_data_connect/src/cache/in_memory_cache_provider.dart'; import 'package:firebase_data_connect/src/cache/sqlite_cache_provider.dart'; @@ -51,18 +51,27 @@ void main() { const String simpleQueryResponse = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":4}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; + final Map simpleQueryExtensions = { + 'dataConnect': [ + { + 'path': ['items'], + 'entityIds': ['123', '345'] + } + ] + }; + // query that updates the price for cacheId 123 to 11 const String simpleQueryResponseUpdate = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":11}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":11}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; @@ -70,10 +79,19 @@ void main() { // query two has same object as query one so should refer to same Entity. const String simpleQueryTwoResponse = ''' {"data": { - "item": { "desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4 } + "item": { "desc":"itemDesc1","name":"itemOne","price":4 } }} '''; + final Map simpleQueryTwoExtensions = { + 'dataConnect': [ + { + 'path': ['item'], + 'entityId': '123' + } + ] + }; + group('Cache Provider Tests', () { setUp(() async { mockApp = MockFirebaseApp(); @@ -126,9 +144,11 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - await cache.update('itemsSimple', ServerResponse(jsonData)); + await cache.update('itemsSimple', + ServerResponse(jsonData, extensions: simpleQueryExtensions)); - Map? cachedData = await cache.get('itemsSimple', true); + Map? cachedData = + await cache.resultTree('itemsSimple', true); expect(jsonData['data'], cachedData); }); // test set get @@ -144,8 +164,8 @@ void main() { edo.updateServerValue('name', 'test', null); edo.updateServerValue('desc', 'testDesc', null); - cp.saveEntityDataObject(edo); - EntityDataObject edo2 = cp.getEntityDataObject('1234'); + cp.updateEntityData(edo); + EntityDataObject edo2 = cp.getEntityData('1234'); expect(edo.fields().length, edo2.fields().length); expect(edo.fields()['name'], edo2.fields()['name']); @@ -162,21 +182,24 @@ void main() { Map jsonDataOne = jsonDecode(simpleQueryResponse) as Map; - await cache.update(queryOneId, ServerResponse(jsonDataOne)); + await cache.update(queryOneId, + ServerResponse(jsonDataOne, extensions: simpleQueryExtensions)); Map jsonDataTwo = jsonDecode(simpleQueryTwoResponse) as Map; - await cache.update(queryTwoId, ServerResponse(jsonDataTwo)); + await cache.update(queryTwoId, + ServerResponse(jsonDataTwo, extensions: simpleQueryTwoExtensions)); Map jsonDataOneUpdate = jsonDecode(simpleQueryResponseUpdate) as Map; - await cache.update(queryOneId, ServerResponse(jsonDataOneUpdate)); + await cache.update(queryOneId, + ServerResponse(jsonDataOneUpdate, extensions: simpleQueryExtensions)); // shared object should be updated. // now reload query two from cache and check object value. // it should be updated Map? jsonDataTwoUpdated = - await cache.get(queryTwoId, true); + await cache.resultTree(queryTwoId, true); if (jsonDataTwoUpdated == null) { fail('No query two found in cache'); } @@ -194,16 +217,16 @@ void main() { await cp.initialize(); String oid = '1234'; - EntityDataObject edo = cp.getEntityDataObject(oid); + EntityDataObject edo = cp.getEntityData(oid); String testValue = 'testValue'; String testProp = 'testProp'; edo.updateServerValue(testProp, testValue, null); - cp.saveEntityDataObject(edo); + cp.updateEntityData(edo); - EntityDataObject edo2 = cp.getEntityDataObject(oid); + EntityDataObject edo2 = cp.getEntityData(oid); String value = edo2.fields()[testProp]; expect(testValue, value); @@ -221,7 +244,8 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - await cache.update('itemsSimple', ServerResponse(jsonData)); + await cache.update('itemsSimple', + ServerResponse(jsonData, extensions: simpleQueryExtensions)); QueryRef ref = QueryRef( dataConnect, @@ -254,5 +278,47 @@ void main() { expect(resultDelayed.source, DataSource.server); }); }); + + test('Test AnyValue Caching', () async { + if (dataConnect.cacheManager == null) { + fail('No cache available'); + } + + Cache cache = dataConnect.cacheManager!; + + const String anyValueSingleData = ''' + {"data": {"anyValueItem": + { "name": "AnyItem B", + "blob": {"values":["A", 45, {"embedKey": "embedVal"}, ["A", "AA"]]} + } + }} + '''; + + final Map anyValueSingleExt = { + 'dataConnect': [ + { + 'path': ['anyValueItem'], + 'entityId': 'AnyValueItemSingle_ID' + } + ] + }; + + Map jsonData = + jsonDecode(anyValueSingleData) as Map; + + await cache.update('queryAnyValue', + ServerResponse(jsonData, extensions: anyValueSingleExt)); + + Map? cachedData = + await cache.resultTree('queryAnyValue', true); + + expect(cachedData?['anyValueItem']?['name'], 'AnyItem B'); + List values = cachedData?['anyValueItem']?['blob']?['values']; + expect(values.length, 4); + expect(values[0], 'A'); + expect(values[1], 45); + expect(values[2], {'embedKey': 'embedVal'}); + expect(values[3], ['A', 'AA']); + }); }); // test group } //main diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart index 6d3c71d09a17..9b347425c7dc 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/cache/result_tree_processor_test.dart @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:firebase_data_connect/src/cache/cache_data_types.dart'; import 'package:firebase_data_connect/src/cache/result_tree_processor.dart'; +import 'package:firebase_data_connect/src/common/common_library.dart'; import 'package:flutter_test/flutter_test.dart'; import 'dart:convert'; @@ -23,19 +25,46 @@ void main() { const String simpleQueryResponse = ''' {"data": {"items":[ - {"desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4}, - {"desc":"itemDesc2","name":"itemTwo", "cacheId":"345","price":7} + {"desc":"itemDesc1","name":"itemOne","price":4}, + {"desc":"itemDesc2","name":"itemTwo","price":7} ]}} '''; + final Map simpleQueryPaths = { + DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(0) + ]): PathMetadata( + path: DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(0) + ]), + entityId: '123'), + DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(1) + ]): PathMetadata( + path: DataConnectPath([ + DataConnectFieldPathSegment('items'), + DataConnectListIndexPathSegment(1) + ]), + entityId: '345'), + }; + // query two has same object as query one so should refer to same Entity. const String simpleQueryResponseTwo = ''' {"data": { - "item": { "desc":"itemDesc1","name":"itemOne", "cacheId":"123","price":4 } + "item": { "desc":"itemDesc1","name":"itemOne","price":4 } }} '''; + final Map simpleQueryTwoPaths = { + DataConnectPath([DataConnectFieldPathSegment('item')]): PathMetadata( + path: DataConnectPath([DataConnectFieldPathSegment('item')]), + entityId: '123'), + }; + group('CacheProviderTests', () { // Dehydrate two queries sharing a single object. // Confirm that same EntityDataObject is present in both the dehydrated queries @@ -45,8 +74,8 @@ void main() { Map jsonData = jsonDecode(simpleQueryResponse) as Map; - DehydrationResult result = - await rp.dehydrate('itemsSimple', jsonData['data'], cp); + DehydrationResult result = await rp.dehydrateResults( + 'itemsSimple', jsonData['data'], cp, simpleQueryPaths); expect(result.dehydratedTree.nestedObjectLists?.length, 1); expect(result.dehydratedTree.nestedObjectLists?['items']?.length, 2); expect(result.dehydratedTree.nestedObjectLists?['items']?.first.entity, @@ -54,8 +83,8 @@ void main() { Map jsonDataTwo = jsonDecode(simpleQueryResponseTwo) as Map; - DehydrationResult resultTwo = - await rp.dehydrate('itemsSimpleTwo', jsonDataTwo, cp); + DehydrationResult resultTwo = await rp.dehydrateResults( + 'itemsSimpleTwo', jsonDataTwo['data'], cp, simpleQueryTwoPaths); List? guids = result.dehydratedTree.nestedObjectLists?['items'] ?.map((item) => item.entity?.guid) From 2334cf01fa30696f006cd52cb13756905269b487 Mon Sep 17 00:00:00 2001 From: Jude Selase Kwashie <64037520+SelaseKay@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:09:26 +0000 Subject: [PATCH 20/20] chore: fix formatting (#18044) * feat: bump Firebase JS SDK to 12.9.0 * chore: fix formatting * chore: revert JS SDK bump --- .../cloud_firestore/FirestoreMessages.g.m | 75 +++++---- .../firebase_auth/firebase_auth_messages.g.m | 142 +++++++++--------- .../Sources/firebase_core/messages.g.m | 26 ++-- .../FLTFirebaseMessagingPlugin.m | 8 +- 4 files changed, 129 insertions(+), 122 deletions(-) diff --git a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m index 34e717b694a7..16dce2073a10 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FirestoreMessages.g.m @@ -934,11 +934,12 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setIndexConfigurationApp: - indexConfiguration:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(setIndexConfigurationApp:indexConfiguration:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + setIndexConfigurationApp:indexConfiguration:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(setIndexConfigurationApp:indexConfiguration:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1006,11 +1007,11 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(transactionCreateApp: - timeout:maxAttempts:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(transactionCreateApp:timeout:maxAttempts:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(transactionCreateApp:timeout:maxAttempts:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(transactionCreateApp:timeout:maxAttempts:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1034,8 +1035,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (transactionStoreResultTransactionId:resultType:commands:completion:)], + NSCAssert([api respondsToSelector:@selector(transactionStoreResultTransactionId:resultType: + commands:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(transactionStoreResultTransactionId:resultType:commands:completion:)", api); @@ -1062,11 +1063,11 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(transactionGetApp: - transactionId:path:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(transactionGetApp:transactionId:path:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(transactionGetApp:transactionId:path:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(transactionGetApp:transactionId:path:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1192,8 +1193,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (queryGetApp:path:isCollectionGroup:parameters:options:completion:)], + NSCAssert([api respondsToSelector:@selector(queryGetApp:path:isCollectionGroup:parameters: + options:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(queryGetApp:path:isCollectionGroup:parameters:options:completion:)", api); @@ -1225,9 +1226,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (aggregateQueryApp: - path:parameters:source:queries:isCollectionGroup:completion:)], + NSCAssert([api respondsToSelector:@selector(aggregateQueryApp:path:parameters:source:queries: + isCollectionGroup:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(aggregateQueryApp:path:parameters:source:queries:isCollectionGroup:" @"completion:)", @@ -1287,14 +1287,13 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (querySnapshotApp: - path:isCollectionGroup:parameters:options:includeMetadataChanges - :source:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(querySnapshotApp:path:isCollectionGroup:parameters:options:" - @"includeMetadataChanges:source:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(querySnapshotApp:path:isCollectionGroup:parameters: + options:includeMetadataChanges:source:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(querySnapshotApp:path:isCollectionGroup:parameters:options:" + @"includeMetadataChanges:source:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1326,9 +1325,8 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (documentReferenceSnapshotApp: - parameters:includeMetadataChanges:source:completion:)], + NSCAssert([api respondsToSelector:@selector(documentReferenceSnapshotApp:parameters: + includeMetadataChanges:source:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " @"@selector(documentReferenceSnapshotApp:parameters:includeMetadataChanges:source:" @"completion:)", @@ -1359,11 +1357,12 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (persistenceCacheIndexManagerRequestApp:request:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(persistenceCacheIndexManagerRequestApp:request:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + persistenceCacheIndexManagerRequestApp:request:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(persistenceCacheIndexManagerRequestApp:request:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m index 8a32f1224ae6..365ff70d690c 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m @@ -1065,11 +1065,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(confirmPasswordResetApp: - code:newPassword:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(confirmPasswordResetApp:code:newPassword:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(confirmPasswordResetApp:code:newPassword:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(confirmPasswordResetApp:code:newPassword:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1096,11 +1096,13 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (createUserWithEmailAndPasswordApp:email:password:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(createUserWithEmailAndPasswordApp:email:password:completion:)", - api); + NSCAssert( + [api + respondsToSelector:@selector( + createUserWithEmailAndPasswordApp:email:password:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(createUserWithEmailAndPasswordApp:email:password:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1213,11 +1215,12 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (signInWithEmailAndPasswordApp:email:password:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithEmailAndPasswordApp:email:password:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + signInWithEmailAndPasswordApp:email:password:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithEmailAndPasswordApp:email:password:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1245,11 +1248,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(signInWithEmailLinkApp: - email:emailLink:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithEmailLinkApp:email:emailLink:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(signInWithEmailLinkApp:email:emailLink:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithEmailLinkApp:email:emailLink:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1277,11 +1280,11 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(signInWithProviderApp: - signInProvider:completion:)], - @"FirebaseAuthHostApi api (%@) doesn't respond to " - @"@selector(signInWithProviderApp:signInProvider:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(signInWithProviderApp:signInProvider:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(signInWithProviderApp:signInProvider:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1361,8 +1364,8 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (sendPasswordResetEmailApp:email:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector: + @selector(sendPasswordResetEmailApp:email:actionCodeSettings:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(sendPasswordResetEmailApp:email:actionCodeSettings:completion:)", api); @@ -1392,8 +1395,8 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:FirebaseAuthHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector: + @selector(sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(sendSignInLinkToEmailApp:email:actionCodeSettings:completion:)", api); @@ -1535,7 +1538,7 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng codec:FirebaseAuthHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(revokeTokenWithAuthorizationCodeApp: - authorizationCode:completion:)], + authorizationCode:completion:)], @"FirebaseAuthHostApi api (%@) doesn't respond to " @"@selector(revokeTokenWithAuthorizationCodeApp:authorizationCode:completion:)", api); @@ -1844,11 +1847,11 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(reauthenticateWithCredentialApp: - input:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(reauthenticateWithCredentialApp:input:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(reauthenticateWithCredentialApp:input:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(reauthenticateWithCredentialApp:input:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1874,11 +1877,12 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(reauthenticateWithProviderApp: - signInProvider:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(reauthenticateWithProviderApp:signInProvider:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + reauthenticateWithProviderApp:signInProvider:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(reauthenticateWithProviderApp:signInProvider:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -1929,11 +1933,12 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(sendEmailVerificationApp: - actionCodeSettings:completion:)], - @"FirebaseAuthUserHostApi api (%@) doesn't respond to " - @"@selector(sendEmailVerificationApp:actionCodeSettings:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + sendEmailVerificationApp:actionCodeSettings:completion:)], + @"FirebaseAuthUserHostApi api (%@) doesn't respond to " + @"@selector(sendEmailVerificationApp:actionCodeSettings:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2099,8 +2104,8 @@ void SetUpFirebaseAuthUserHostApiWithSuffix(id binaryMes binaryMessenger:binaryMessenger codec:FirebaseAuthUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (verifyBeforeUpdateEmailApp:newEmail:actionCodeSettings:completion:)], + NSCAssert([api respondsToSelector:@selector(verifyBeforeUpdateEmailApp:newEmail: + actionCodeSettings:completion:)], @"FirebaseAuthUserHostApi api (%@) doesn't respond to " @"@selector(verifyBeforeUpdateEmailApp:newEmail:actionCodeSettings:completion:)", api); @@ -2204,11 +2209,11 @@ void SetUpMultiFactorUserHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(enrollPhoneApp: - assertion:displayName:completion:)], - @"MultiFactorUserHostApi api (%@) doesn't respond to " - @"@selector(enrollPhoneApp:assertion:displayName:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(enrollPhoneApp:assertion:displayName:completion:)], + @"MultiFactorUserHostApi api (%@) doesn't respond to " + @"@selector(enrollPhoneApp:assertion:displayName:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2234,11 +2239,11 @@ void SetUpMultiFactorUserHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorUserHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(enrollTotpApp: - assertionId:displayName:completion:)], - @"MultiFactorUserHostApi api (%@) doesn't respond to " - @"@selector(enrollTotpApp:assertionId:displayName:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(enrollTotpApp:assertionId:displayName:completion:)], + @"MultiFactorUserHostApi api (%@) doesn't respond to " + @"@selector(enrollTotpApp:assertionId:displayName:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); @@ -2430,8 +2435,8 @@ void SetUpMultiFactoResolverHostApiWithSuffix(id binaryM binaryMessenger:binaryMessenger codec:MultiFactoResolverHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (resolveSignInResolverId:assertion:totpAssertionId:completion:)], + NSCAssert([api respondsToSelector: + @selector(resolveSignInResolverId:assertion:totpAssertionId:completion:)], @"MultiFactoResolverHostApi api (%@) doesn't respond to " @"@selector(resolveSignInResolverId:assertion:totpAssertionId:completion:)", api); @@ -2549,8 +2554,8 @@ void SetUpMultiFactorTotpHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorTotpHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAssertionForEnrollmentSecretKey: - oneTimePassword:completion:)], + NSCAssert([api respondsToSelector: + @selector(getAssertionForEnrollmentSecretKey:oneTimePassword:completion:)], @"MultiFactorTotpHostApi api (%@) doesn't respond to " @"@selector(getAssertionForEnrollmentSecretKey:oneTimePassword:completion:)", api); @@ -2579,8 +2584,8 @@ void SetUpMultiFactorTotpHostApiWithSuffix(id binaryMess binaryMessenger:binaryMessenger codec:MultiFactorTotpHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAssertionForSignInEnrollmentId: - oneTimePassword:completion:)], + NSCAssert([api respondsToSelector: + @selector(getAssertionForSignInEnrollmentId:oneTimePassword:completion:)], @"MultiFactorTotpHostApi api (%@) doesn't respond to " @"@selector(getAssertionForSignInEnrollmentId:oneTimePassword:completion:)", api); @@ -2627,11 +2632,12 @@ void SetUpMultiFactorTotpSecretHostApiWithSuffix(id bina binaryMessenger:binaryMessenger codec:MultiFactorTotpSecretHostApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(generateQrCodeUrlSecretKey: - accountName:issuer:completion:)], - @"MultiFactorTotpSecretHostApi api (%@) doesn't respond to " - @"@selector(generateQrCodeUrlSecretKey:accountName:issuer:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector( + generateQrCodeUrlSecretKey:accountName:issuer:completion:)], + @"MultiFactorTotpSecretHostApi api (%@) doesn't respond to " + @"@selector(generateQrCodeUrlSecretKey:accountName:issuer:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_secretKey = GetNullableObjectAtIndex(args, 0); diff --git a/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m b/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m index ff9ac9933bc0..cf3e439b92ac 100644 --- a/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m +++ b/packages/firebase_core/firebase_core/ios/firebase_core/Sources/firebase_core/messages.g.m @@ -224,11 +224,11 @@ void SetUpFirebaseCoreHostApiWithSuffix(id binaryMesseng binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(initializeAppAppName: - initializeAppRequest:completion:)], - @"FirebaseCoreHostApi api (%@) doesn't respond to " - @"@selector(initializeAppAppName:initializeAppRequest:completion:)", - api); + NSCAssert( + [api respondsToSelector:@selector(initializeAppAppName:initializeAppRequest:completion:)], + @"FirebaseCoreHostApi api (%@) doesn't respond to " + @"@selector(initializeAppAppName:initializeAppRequest:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_appName = GetNullableObjectAtIndex(args, 0); @@ -313,11 +313,13 @@ void SetUpFirebaseAppHostApiWithSuffix(id binaryMessenge binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (setAutomaticDataCollectionEnabledAppName:enabled:completion:)], - @"FirebaseAppHostApi api (%@) doesn't respond to " - @"@selector(setAutomaticDataCollectionEnabledAppName:enabled:completion:)", - api); + NSCAssert( + [api + respondsToSelector:@selector( + setAutomaticDataCollectionEnabledAppName:enabled:completion:)], + @"FirebaseAppHostApi api (%@) doesn't respond to " + @"@selector(setAutomaticDataCollectionEnabledAppName:enabled:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_appName = GetNullableObjectAtIndex(args, 0); @@ -342,8 +344,8 @@ void SetUpFirebaseAppHostApiWithSuffix(id binaryMessenge binaryMessenger:binaryMessenger codec:nullGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector - (setAutomaticResourceManagementEnabledAppName:enabled:completion:)], + NSCAssert([api respondsToSelector:@selector(setAutomaticResourceManagementEnabledAppName: + enabled:completion:)], @"FirebaseAppHostApi api (%@) doesn't respond to " @"@selector(setAutomaticResourceManagementEnabledAppName:enabled:completion:)", api); diff --git a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m index 3d8c9298a4e9..76879a85c526 100644 --- a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m +++ b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m @@ -299,12 +299,12 @@ - (void)setupNotificationHandlingWithRemoteNotification: respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]; _originalNotificationCenterDelegateRespondsTo.willPresentNotification = (unsigned int)[_originalNotificationCenterDelegate - respondsToSelector:@selector(userNotificationCenter: - willPresentNotification:withCompletionHandler:)]; + respondsToSelector:@selector(userNotificationCenter:willPresentNotification: + withCompletionHandler:)]; _originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse = (unsigned int)[_originalNotificationCenterDelegate - respondsToSelector:@selector(userNotificationCenter: - didReceiveNotificationResponse:withCompletionHandler:)]; + respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse: + withCompletionHandler:)]; } }