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 c5a4ff04ced4..a08e5ded90d0 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 @@ -4,6 +4,10 @@ package io.flutter.plugins.firebase.firestore; +import static com.google.firebase.firestore.AggregateField.average; +import static com.google.firebase.firestore.AggregateField.count; +import static com.google.firebase.firestore.AggregateField.sum; + import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -11,6 +15,7 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; import com.google.firebase.FirebaseApp; +import com.google.firebase.firestore.AggregateField; import com.google.firebase.firestore.AggregateQuery; import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.DocumentReference; @@ -729,23 +734,93 @@ public void queryGet( } @Override - public void aggregateQueryCount( + public void aggregateQuery( @NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app, @NonNull String path, @NonNull GeneratedAndroidFirebaseFirestore.PigeonQueryParameters parameters, @NonNull GeneratedAndroidFirebaseFirestore.AggregateSource source, + @NonNull List queries, @NonNull Boolean isCollectionGroup, - @NonNull GeneratedAndroidFirebaseFirestore.Result result) { + @NonNull + GeneratedAndroidFirebaseFirestore.Result< + List> + result) { + Query query = + PigeonParser.parseQuery(getFirestoreFromPigeon(app), path, isCollectionGroup, parameters); + + AggregateQuery aggregateQuery; + ArrayList aggregateFields = new ArrayList<>(); + + for (GeneratedAndroidFirebaseFirestore.AggregateQuery queryRequest : queries) { + switch (queryRequest.getType()) { + case COUNT: + aggregateFields.add(count()); + break; + case SUM: + assert queryRequest.getField() != null; + aggregateFields.add(sum(queryRequest.getField())); + break; + case AVERAGE: + assert queryRequest.getField() != null; + aggregateFields.add(average(queryRequest.getField())); + break; + } + } + + assert query != null; + aggregateQuery = + query.aggregate( + aggregateFields.get(0), + aggregateFields.subList(1, aggregateFields.size()).toArray(new AggregateField[0])); + cachedThreadPool.execute( () -> { try { - Query query = - PigeonParser.parseQuery( - getFirestoreFromPigeon(app), path, isCollectionGroup, parameters); - AggregateQuery aggregateQuery = query.count(); AggregateQuerySnapshot aggregateQuerySnapshot = Tasks.await(aggregateQuery.get(PigeonParser.parseAggregateSource(source))); - result.success((double) aggregateQuerySnapshot.getCount()); + + ArrayList aggregateResponse = + new ArrayList<>(); + for (GeneratedAndroidFirebaseFirestore.AggregateQuery queryRequest : queries) { + switch (queryRequest.getType()) { + case COUNT: + GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder builder = + new GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder(); + builder.setType(GeneratedAndroidFirebaseFirestore.AggregateType.COUNT); + builder.setValue((double) aggregateQuerySnapshot.getCount()); + + aggregateResponse.add(builder.build()); + break; + case SUM: + assert queryRequest.getField() != null; + GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder builderSum = + new GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder(); + builderSum.setType(GeneratedAndroidFirebaseFirestore.AggregateType.SUM); + builderSum.setValue( + ((Number) + Objects.requireNonNull( + aggregateQuerySnapshot.get(sum(queryRequest.getField())))) + .doubleValue()); + builderSum.setField(queryRequest.getField()); + + aggregateResponse.add(builderSum.build()); + break; + case AVERAGE: + assert queryRequest.getField() != null; + GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder builderAverage = + new GeneratedAndroidFirebaseFirestore.AggregateQueryResponse.Builder(); + builderAverage.setType(GeneratedAndroidFirebaseFirestore.AggregateType.AVERAGE); + builderAverage.setValue( + Objects.requireNonNull( + aggregateQuerySnapshot.get(average(queryRequest.getField())))); + builderAverage.setField(queryRequest.getField()); + + aggregateResponse.add(builderAverage.build()); + break; + } + } + + result.success(aggregateResponse); } 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 3fb1605da974..122314a75ec7 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 @@ -161,6 +161,18 @@ private PigeonTransactionType(final int index) { } } + public enum AggregateType { + COUNT(0), + SUM(1), + AVERAGE(2); + + final int index; + + private AggregateType(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; @@ -1407,6 +1419,170 @@ public ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class AggregateQuery { + private @NonNull AggregateType type; + + public @NonNull AggregateType getType() { + return type; + } + + public void setType(@NonNull AggregateType setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"type\" is null."); + } + this.type = setterArg; + } + + private @Nullable String field; + + public @Nullable String getField() { + return field; + } + + public void setField(@Nullable String setterArg) { + this.field = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + AggregateQuery() {} + + public static final class Builder { + + private @Nullable AggregateType type; + + public @NonNull Builder setType(@NonNull AggregateType setterArg) { + this.type = setterArg; + return this; + } + + private @Nullable String field; + + public @NonNull Builder setField(@Nullable String setterArg) { + this.field = setterArg; + return this; + } + + public @NonNull AggregateQuery build() { + AggregateQuery pigeonReturn = new AggregateQuery(); + pigeonReturn.setType(type); + pigeonReturn.setField(field); + return pigeonReturn; + } + } + + @NonNull + public ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(type == null ? null : type.index); + toListResult.add(field); + return toListResult; + } + + static @NonNull AggregateQuery fromList(@NonNull ArrayList list) { + AggregateQuery pigeonResult = new AggregateQuery(); + Object type = list.get(0); + pigeonResult.setType(AggregateType.values()[(int) type]); + Object field = list.get(1); + pigeonResult.setField((String) field); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class AggregateQueryResponse { + private @NonNull AggregateType type; + + public @NonNull AggregateType getType() { + return type; + } + + public void setType(@NonNull AggregateType setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"type\" is null."); + } + this.type = setterArg; + } + + private @Nullable String field; + + public @Nullable String getField() { + return field; + } + + public void setField(@Nullable String setterArg) { + this.field = setterArg; + } + + private @NonNull Double value; + + public @NonNull Double getValue() { + return value; + } + + public void setValue(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"value\" is null."); + } + this.value = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + AggregateQueryResponse() {} + + public static final class Builder { + + private @Nullable AggregateType type; + + public @NonNull Builder setType(@NonNull AggregateType setterArg) { + this.type = setterArg; + return this; + } + + private @Nullable String field; + + public @NonNull Builder setField(@Nullable String setterArg) { + this.field = setterArg; + return this; + } + + private @Nullable Double value; + + public @NonNull Builder setValue(@NonNull Double setterArg) { + this.value = setterArg; + return this; + } + + public @NonNull AggregateQueryResponse build() { + AggregateQueryResponse pigeonReturn = new AggregateQueryResponse(); + pigeonReturn.setType(type); + pigeonReturn.setField(field); + pigeonReturn.setValue(value); + return pigeonReturn; + } + } + + @NonNull + public ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(type == null ? null : type.index); + toListResult.add(field); + toListResult.add(value); + return toListResult; + } + + static @NonNull AggregateQueryResponse fromList(@NonNull ArrayList list) { + AggregateQueryResponse pigeonResult = new AggregateQueryResponse(); + Object type = list.get(0); + pigeonResult.setType(AggregateType.values()[(int) type]); + Object field = list.get(1); + pigeonResult.setField((String) field); + Object value = list.get(2); + pigeonResult.setValue((Double) value); + return pigeonResult; + } + } + public interface Result { @SuppressWarnings("UnknownNullness") void success(T result); @@ -1424,26 +1600,30 @@ private FirebaseFirestoreHostApiCodec() {} protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return DocumentReferenceRequest.fromList((ArrayList) readValue(buffer)); + return AggregateQuery.fromList((ArrayList) readValue(buffer)); case (byte) 129: - return FirestorePigeonFirebaseApp.fromList((ArrayList) readValue(buffer)); + return AggregateQueryResponse.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return PigeonDocumentChange.fromList((ArrayList) readValue(buffer)); + return DocumentReferenceRequest.fromList((ArrayList) readValue(buffer)); case (byte) 131: - return PigeonDocumentOption.fromList((ArrayList) readValue(buffer)); + return FirestorePigeonFirebaseApp.fromList((ArrayList) readValue(buffer)); case (byte) 132: - return PigeonDocumentSnapshot.fromList((ArrayList) readValue(buffer)); + return PigeonDocumentChange.fromList((ArrayList) readValue(buffer)); case (byte) 133: - return PigeonFirebaseSettings.fromList((ArrayList) readValue(buffer)); + return PigeonDocumentOption.fromList((ArrayList) readValue(buffer)); case (byte) 134: - return PigeonGetOptions.fromList((ArrayList) readValue(buffer)); + return PigeonDocumentSnapshot.fromList((ArrayList) readValue(buffer)); case (byte) 135: - return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); + return PigeonFirebaseSettings.fromList((ArrayList) readValue(buffer)); case (byte) 136: - return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + return PigeonGetOptions.fromList((ArrayList) readValue(buffer)); case (byte) 137: - return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + return PigeonQueryParameters.fromList((ArrayList) readValue(buffer)); case (byte) 138: + return PigeonQuerySnapshot.fromList((ArrayList) readValue(buffer)); + case (byte) 139: + return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); + case (byte) 140: return PigeonTransactionCommand.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -1452,38 +1632,44 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof DocumentReferenceRequest) { + if (value instanceof AggregateQuery) { stream.write(128); + writeValue(stream, ((AggregateQuery) value).toList()); + } else if (value instanceof AggregateQueryResponse) { + stream.write(129); + writeValue(stream, ((AggregateQueryResponse) value).toList()); + } else if (value instanceof DocumentReferenceRequest) { + stream.write(130); writeValue(stream, ((DocumentReferenceRequest) value).toList()); } else if (value instanceof FirestorePigeonFirebaseApp) { - stream.write(129); + stream.write(131); writeValue(stream, ((FirestorePigeonFirebaseApp) value).toList()); } else if (value instanceof PigeonDocumentChange) { - stream.write(130); + stream.write(132); writeValue(stream, ((PigeonDocumentChange) value).toList()); } else if (value instanceof PigeonDocumentOption) { - stream.write(131); + stream.write(133); writeValue(stream, ((PigeonDocumentOption) value).toList()); } else if (value instanceof PigeonDocumentSnapshot) { - stream.write(132); + stream.write(134); writeValue(stream, ((PigeonDocumentSnapshot) value).toList()); } else if (value instanceof PigeonFirebaseSettings) { - stream.write(133); + stream.write(135); writeValue(stream, ((PigeonFirebaseSettings) value).toList()); } else if (value instanceof PigeonGetOptions) { - stream.write(134); + stream.write(136); writeValue(stream, ((PigeonGetOptions) value).toList()); } else if (value instanceof PigeonQueryParameters) { - stream.write(135); + stream.write(137); writeValue(stream, ((PigeonQueryParameters) value).toList()); } else if (value instanceof PigeonQuerySnapshot) { - stream.write(136); + stream.write(138); writeValue(stream, ((PigeonQuerySnapshot) value).toList()); } else if (value instanceof PigeonSnapshotMetadata) { - stream.write(137); + stream.write(139); writeValue(stream, ((PigeonSnapshotMetadata) value).toList()); } else if (value instanceof PigeonTransactionCommand) { - stream.write(138); + stream.write(140); writeValue(stream, ((PigeonTransactionCommand) value).toList()); } else { super.writeValue(stream, value); @@ -1572,13 +1758,14 @@ void queryGet( @NonNull PigeonGetOptions options, @NonNull Result result); - void aggregateQueryCount( + void aggregateQuery( @NonNull FirestorePigeonFirebaseApp app, @NonNull String path, @NonNull PigeonQueryParameters parameters, @NonNull AggregateSource source, + @NonNull List queries, @NonNull Boolean isCollectionGroup, - @NonNull Result result); + @NonNull Result> result); void writeBatchCommit( @NonNull FirestorePigeonFirebaseApp app, @@ -2203,7 +2390,7 @@ public void error(Throwable error) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQueryCount", + "dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery", getCodec()); if (api != null) { channel.setMessageHandler( @@ -2214,10 +2401,11 @@ public void error(Throwable error) { String pathArg = (String) args.get(1); PigeonQueryParameters parametersArg = (PigeonQueryParameters) args.get(2); AggregateSource sourceArg = AggregateSource.values()[(int) args.get(3)]; - Boolean isCollectionGroupArg = (Boolean) args.get(4); - Result resultCallback = - new Result() { - public void success(Double result) { + List queriesArg = (List) args.get(4); + Boolean isCollectionGroupArg = (Boolean) args.get(5); + Result> resultCallback = + new Result>() { + public void success(List result) { wrapped.add(0, result); reply.reply(wrapped); } @@ -2228,11 +2416,12 @@ public void error(Throwable error) { } }; - api.aggregateQueryCount( + api.aggregateQuery( appArg, pathArg, parametersArg, sourceArg, + queriesArg, isCollectionGroupArg, resultCallback); }); diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart index ff5f11120aeb..3bdb7f3d2377 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/query_e2e.dart @@ -3775,7 +3775,9 @@ void runQueryTests() { }, timeout: const Timeout.factor(3), ); + }); + group('Aggregate Queries', () { testWidgets( 'count()', (_) async { @@ -3819,6 +3821,153 @@ void runQueryTests() { }, ); + testWidgets( + 'sum()', + (_) async { + final collection = await initializeTest('sum'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = collection.aggregate(sum('foo')); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.getSum('foo'), + 3, + ); + }, + ); + + testWidgets( + 'sum() with query', + (_) async { + final collection = await initializeTest('sum'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = + collection.where('foo', isEqualTo: 1).aggregate(sum('foo')); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.getSum('foo'), + 1, + ); + }, + ); + + testWidgets( + 'average()', + (_) async { + final collection = await initializeTest('avg'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = collection.aggregate(average('foo')); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.getAverage('foo'), + 1.5, + ); + }, + ); + + testWidgets( + 'average() with query', + (_) async { + final collection = await initializeTest('avg'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = + collection.where('foo', isEqualTo: 1).aggregate(average('foo')); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.getAverage('foo'), + 1, + ); + }, + ); + + testWidgets( + 'chaining aggregate queries', + (_) async { + final collection = await initializeTest('chaining'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = + collection.aggregate(count(), sum('foo'), average('foo')); + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.count, + 2, + ); + + expect( + snapshot.getSum('foo'), + 3, + ); + + expect( + snapshot.getAverage('foo'), + 1.5, + ); + }, + ); + + testWidgets('chaining multiples aggregate queries', (_) async { + final collection = await initializeTest('chaining'); + + await Future.wait([ + collection.add({'foo': 1}), + collection.add({'foo': 2}), + ]); + + AggregateQuery query = collection + .where('foo', isEqualTo: 1) + .aggregate(count(), sum('foo'), average('foo')); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.count, + 1, + ); + + expect( + snapshot.getSum('foo'), + 1, + ); + + expect( + snapshot.getAverage('foo'), + 1, + ); + }); + testWidgets( 'count() with collectionGroup', (_) async { diff --git a/packages/cloud_firestore/cloud_firestore/example/lib/main.dart b/packages/cloud_firestore/cloud_firestore/example/lib/main.dart index 05de981c19a4..ad671e8cdb32 100755 --- a/packages/cloud_firestore/cloud_firestore/example/lib/main.dart +++ b/packages/cloud_firestore/cloud_firestore/example/lib/main.dart @@ -152,13 +152,64 @@ class _FilmListState extends State { }, ), PopupMenuButton( - onSelected: (_) => _resetLikes(), + onSelected: (value) async { + switch (value) { + case 'reset_likes': + return _resetLikes(); + case 'aggregate': + // Count the number of movies + final _count = await FirebaseFirestore.instance + .collection('firestore-example-app') + .count() + .get(); + + print('Count: ${_count.count}'); + + // Average the number of likes + final _average = await FirebaseFirestore.instance + .collection('firestore-example-app') + .aggregate(average('likes')) + .get(); + + print('Average: ${_average.getAverage('likes')}'); + + // Sum the number of likes + final _sum = await FirebaseFirestore.instance + .collection('firestore-example-app') + .aggregate(sum('likes')) + .get(); + + print('Sum: ${_sum.getSum('likes')}'); + + // In one query + final _all = await FirebaseFirestore.instance + .collection('firestore-example-app') + .aggregate( + average('likes'), + sum('likes'), + count(), + ) + .get(); + + print('Average: ${_all.getAverage('likes')} ' + 'Sum: ${_all.getSum('likes')} ' + 'Count: ${_all.count}'); + + return; + default: + return; + } + }, itemBuilder: (BuildContext context) { return [ const PopupMenuItem( value: 'reset_likes', child: Text('Reset like counts (WriteBatch)'), ), + const PopupMenuItem( + value: 'aggregate', + child: Text('Get aggregate data'), + ), ]; }, ), diff --git a/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m b/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m index 9542ea5a32a9..370b72316b2d 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m @@ -270,41 +270,6 @@ - (FlutterError *)convertToFlutterError:(NSError *)error { return [FlutterError errorWithCode:code message:message details:details]; } -- (void)aggregateQueryCountApp:(nonnull FirestorePigeonFirebaseApp *)app - path:(nonnull NSString *)path - parameters:(nonnull PigeonQueryParameters *)parameters - source:(AggregateSource)source - isCollectionGroup:(NSNumber *)isCollectionGroup - completion: - (nonnull void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - FIRFirestore *firestore = [self getFIRFirestoreFromAppNameFromPigeon:app]; - - FIRQuery *query = [FirestorePigeonParser parseQueryWithParameters:parameters - firestore:firestore - path:path - isCollectionGroup:[isCollectionGroup boolValue]]; - if (query == nil) { - completion(nil, [FlutterError errorWithCode:@"error-parsing" - message:@"An error occurred while parsing query arguments, " - @"this is most likely an error with this SDK." - details:nil]); - return; - } - - FIRAggregateQuery *aggregateQuery = [query count]; - - [aggregateQuery aggregationWithSource:FIRAggregateSourceServer - completion:^(FIRAggregateQuerySnapshot *_Nullable snapshot, - NSError *_Nullable error) { - if (error != nil) { - completion(nil, [self convertToFlutterError:error]); - } else { - double myDoubleValue = [snapshot.count doubleValue]; - completion([NSNumber numberWithDouble:myDoubleValue], nil); - } - }]; -} - - (void)clearPersistenceApp:(nonnull FirestorePigeonFirebaseApp *)app completion:(nonnull void (^)(FlutterError *_Nullable))completion { FIRFirestore *firestore = [self getFIRFirestoreFromAppNameFromPigeon:app]; @@ -761,4 +726,107 @@ - (void)transactionCreateApp:(nonnull FirestorePigeonFirebaseApp *)app nil); } +- (void)aggregateQueryApp:(nonnull FirestorePigeonFirebaseApp *)app + path:(nonnull NSString *)path + parameters:(nonnull PigeonQueryParameters *)parameters + source:(AggregateSource)source + queries:(nonnull NSArray *)queries + isCollectionGroup:(NSNumber *)isCollectionGroup + completion:(nonnull void (^)(NSArray *_Nullable, + FlutterError *_Nullable))completion { + FIRFirestore *firestore = [self getFIRFirestoreFromAppNameFromPigeon:app]; + + FIRQuery *query = [FirestorePigeonParser parseQueryWithParameters:parameters + firestore:firestore + path:path + isCollectionGroup:[isCollectionGroup boolValue]]; + if (query == nil) { + completion(nil, [FlutterError errorWithCode:@"error-parsing" + message:@"An error occurred while parsing query arguments, " + @"this is most likely an error with this SDK." + details:nil]); + return; + } + + NSMutableArray *aggregateFields = + [[NSMutableArray alloc] init]; + + for (AggregateQuery *queryRequest in queries) { + switch ([queryRequest type]) { + case AggregateTypeCount: + [aggregateFields addObject:[FIRAggregateField aggregateFieldForCount]]; + break; + case AggregateTypeSum: + [aggregateFields + addObject:[FIRAggregateField aggregateFieldForSumOfField:[queryRequest field]]]; + break; + case AggregateTypeAverage: + [aggregateFields + addObject:[FIRAggregateField aggregateFieldForAverageOfField:[queryRequest field]]]; + break; + default: + // Handle the default case + break; + } + } + + FIRAggregateQuery *aggregateQuery = [query aggregate:aggregateFields]; + + [aggregateQuery + aggregationWithSource:FIRAggregateSourceServer + completion:^(FIRAggregateQuerySnapshot *_Nullable snapshot, + NSError *_Nullable error) { + if (error != nil) { + completion(nil, [self convertToFlutterError:error]); + return; + } + NSMutableArray *aggregateResponses = + [[NSMutableArray alloc] init]; + + for (AggregateQuery *queryRequest in queries) { + switch (queryRequest.type) { + case AggregateTypeCount: { + double doubleValue = [snapshot.count doubleValue]; + + [aggregateResponses + addObject:[AggregateQueryResponse + makeWithType:AggregateTypeCount + field:nil + value:[NSNumber numberWithDouble:doubleValue]]]; + break; + } + case AggregateTypeSum: { + double doubleValue = [[snapshot + valueForAggregateField:[FIRAggregateField + aggregateFieldForSumOfField:[queryRequest + field]]] + doubleValue]; + + [aggregateResponses + addObject:[AggregateQueryResponse + makeWithType:AggregateTypeSum + field:queryRequest.field + value:[NSNumber numberWithDouble:doubleValue]]]; + break; + } + case AggregateTypeAverage: { + double doubleValue = [[snapshot + valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField: + [queryRequest field]]] doubleValue]; + + [aggregateResponses + addObject:[AggregateQueryResponse + makeWithType:AggregateTypeAverage + field:queryRequest.field + value:[NSNumber numberWithDouble:doubleValue]]]; + break; + } + } + } + + completion(aggregateResponses, nil); + }]; +} + @end diff --git a/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m b/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m index 19f11d4d0595..eb82ef9ce4c3 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m @@ -81,6 +81,16 @@ - (instancetype)initWithValue:(PigeonTransactionType)value { } @end +@implementation AggregateTypeBox +- (instancetype)initWithValue:(AggregateType)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ @@ -160,6 +170,18 @@ + (nullable PigeonQueryParameters *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface AggregateQuery () ++ (AggregateQuery *)fromList:(NSArray *)list; ++ (nullable AggregateQuery *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface AggregateQueryResponse () ++ (AggregateQueryResponse *)fromList:(NSArray *)list; ++ (nullable AggregateQueryResponse *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @implementation PigeonFirebaseSettings + (instancetype)makeWithPersistenceEnabled:(nullable NSNumber *)persistenceEnabled host:(nullable NSString *)host @@ -546,32 +568,90 @@ - (NSArray *)toList { } @end +@implementation AggregateQuery ++ (instancetype)makeWithType:(AggregateType)type field:(nullable NSString *)field { + AggregateQuery *pigeonResult = [[AggregateQuery alloc] init]; + pigeonResult.type = type; + pigeonResult.field = field; + return pigeonResult; +} ++ (AggregateQuery *)fromList:(NSArray *)list { + AggregateQuery *pigeonResult = [[AggregateQuery alloc] init]; + pigeonResult.type = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.field = GetNullableObjectAtIndex(list, 1); + return pigeonResult; +} ++ (nullable AggregateQuery *)nullableFromList:(NSArray *)list { + return (list) ? [AggregateQuery fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.type), + (self.field ?: [NSNull null]), + ]; +} +@end + +@implementation AggregateQueryResponse ++ (instancetype)makeWithType:(AggregateType)type + field:(nullable NSString *)field + value:(NSNumber *)value { + AggregateQueryResponse *pigeonResult = [[AggregateQueryResponse alloc] init]; + pigeonResult.type = type; + pigeonResult.field = field; + pigeonResult.value = value; + return pigeonResult; +} ++ (AggregateQueryResponse *)fromList:(NSArray *)list { + AggregateQueryResponse *pigeonResult = [[AggregateQueryResponse alloc] init]; + pigeonResult.type = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.field = GetNullableObjectAtIndex(list, 1); + pigeonResult.value = GetNullableObjectAtIndex(list, 2); + NSAssert(pigeonResult.value != nil, @""); + return pigeonResult; +} ++ (nullable AggregateQueryResponse *)nullableFromList:(NSArray *)list { + return (list) ? [AggregateQueryResponse fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.type), + (self.field ?: [NSNull null]), + (self.value ?: [NSNull null]), + ]; +} +@end + @interface FirebaseFirestoreHostApiCodecReader : FLTFirebaseFirestoreReader @end @implementation FirebaseFirestoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: - return [DocumentReferenceRequest fromList:[self readValue]]; + return [AggregateQuery fromList:[self readValue]]; case 129: - return [FirestorePigeonFirebaseApp fromList:[self readValue]]; + return [AggregateQueryResponse fromList:[self readValue]]; case 130: - return [PigeonDocumentChange fromList:[self readValue]]; + return [DocumentReferenceRequest fromList:[self readValue]]; case 131: - return [PigeonDocumentOption fromList:[self readValue]]; + return [FirestorePigeonFirebaseApp fromList:[self readValue]]; case 132: - return [PigeonDocumentSnapshot fromList:[self readValue]]; + return [PigeonDocumentChange fromList:[self readValue]]; case 133: - return [PigeonFirebaseSettings fromList:[self readValue]]; + return [PigeonDocumentOption fromList:[self readValue]]; case 134: - return [PigeonGetOptions fromList:[self readValue]]; + return [PigeonDocumentSnapshot fromList:[self readValue]]; case 135: - return [PigeonQueryParameters fromList:[self readValue]]; + return [PigeonFirebaseSettings fromList:[self readValue]]; case 136: - return [PigeonQuerySnapshot fromList:[self readValue]]; + return [PigeonGetOptions fromList:[self readValue]]; case 137: - return [PigeonSnapshotMetadata fromList:[self readValue]]; + return [PigeonQueryParameters fromList:[self readValue]]; case 138: + return [PigeonQuerySnapshot fromList:[self readValue]]; + case 139: + return [PigeonSnapshotMetadata fromList:[self readValue]]; + case 140: return [PigeonTransactionCommand fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -583,39 +663,45 @@ @interface FirebaseFirestoreHostApiCodecWriter : FLTFirebaseFirestoreWriter @end @implementation FirebaseFirestoreHostApiCodecWriter - (void)writeValue:(id)value { - if ([value isKindOfClass:[DocumentReferenceRequest class]]) { + if ([value isKindOfClass:[AggregateQuery class]]) { [self writeByte:128]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FirestorePigeonFirebaseApp class]]) { + } else if ([value isKindOfClass:[AggregateQueryResponse class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonDocumentChange class]]) { + } else if ([value isKindOfClass:[DocumentReferenceRequest class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonDocumentOption class]]) { + } else if ([value isKindOfClass:[FirestorePigeonFirebaseApp class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonDocumentSnapshot class]]) { + } else if ([value isKindOfClass:[PigeonDocumentChange class]]) { [self writeByte:132]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonFirebaseSettings class]]) { + } else if ([value isKindOfClass:[PigeonDocumentOption class]]) { [self writeByte:133]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonGetOptions class]]) { + } else if ([value isKindOfClass:[PigeonDocumentSnapshot class]]) { [self writeByte:134]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { + } else if ([value isKindOfClass:[PigeonFirebaseSettings class]]) { [self writeByte:135]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { + } else if ([value isKindOfClass:[PigeonGetOptions class]]) { [self writeByte:136]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + } else if ([value isKindOfClass:[PigeonQueryParameters class]]) { [self writeByte:137]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + } else if ([value isKindOfClass:[PigeonQuerySnapshot class]]) { [self writeByte:138]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonSnapshotMetadata class]]) { + [self writeByte:139]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { + [self writeByte:140]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1108,31 +1194,35 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.cloud_firestore_platform_interface." - @"FirebaseFirestoreHostApi.aggregateQueryCount" + @"FirebaseFirestoreHostApi.aggregateQuery" binaryMessenger:binaryMessenger codec:FirebaseFirestoreHostApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector - (aggregateQueryCountApp:path:parameters:source:isCollectionGroup:completion:)], - @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(aggregateQueryCountApp:path:parameters:source:isCollectionGroup:completion:)", - api); + 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:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); NSString *arg_path = GetNullableObjectAtIndex(args, 1); PigeonQueryParameters *arg_parameters = GetNullableObjectAtIndex(args, 2); AggregateSource arg_source = [GetNullableObjectAtIndex(args, 3) integerValue]; - NSNumber *arg_isCollectionGroup = GetNullableObjectAtIndex(args, 4); - [api aggregateQueryCountApp:arg_app - path:arg_path - parameters:arg_parameters - source:arg_source - isCollectionGroup:arg_isCollectionGroup - completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { - callback(wrapResult(output, error)); - }]; + NSArray *arg_queries = GetNullableObjectAtIndex(args, 4); + NSNumber *arg_isCollectionGroup = GetNullableObjectAtIndex(args, 5); + [api aggregateQueryApp:arg_app + path:arg_path + parameters:arg_parameters + source:arg_source + queries:arg_queries + isCollectionGroup:arg_isCollectionGroup + completion:^(NSArray *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; }]; } else { [channel setMessageHandler:nil]; diff --git a/packages/cloud_firestore/cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h b/packages/cloud_firestore/cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h index 508aa9de8b35..0814b8f0f546 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/Public/FirestoreMessages.g.h @@ -110,6 +110,18 @@ typedef NS_ENUM(NSUInteger, PigeonTransactionType) { - (instancetype)initWithValue:(PigeonTransactionType)value; @end +typedef NS_ENUM(NSUInteger, AggregateType) { + AggregateTypeCount = 0, + AggregateTypeSum = 1, + AggregateTypeAverage = 2, +}; + +/// Wrapper for AggregateType to allow for nullability. +@interface AggregateTypeBox : NSObject +@property(nonatomic, assign) AggregateType value; +- (instancetype)initWithValue:(AggregateType)value; +@end + @class PigeonFirebaseSettings; @class FirestorePigeonFirebaseApp; @class PigeonSnapshotMetadata; @@ -121,6 +133,8 @@ typedef NS_ENUM(NSUInteger, PigeonTransactionType) { @class PigeonTransactionCommand; @class DocumentReferenceRequest; @class PigeonQueryParameters; +@class AggregateQuery; +@class AggregateQueryResponse; @interface PigeonFirebaseSettings : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. @@ -257,6 +271,25 @@ typedef NS_ENUM(NSUInteger, PigeonTransactionType) { @property(nonatomic, strong, nullable) NSDictionary *filters; @end +@interface AggregateQuery : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithType:(AggregateType)type field:(nullable NSString *)field; +@property(nonatomic, assign) AggregateType type; +@property(nonatomic, copy, nullable) NSString *field; +@end + +@interface AggregateQueryResponse : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithType:(AggregateType)type + field:(nullable NSString *)field + value:(NSNumber *)value; +@property(nonatomic, assign) AggregateType type; +@property(nonatomic, copy, nullable) NSString *field; +@property(nonatomic, strong) NSNumber *value; +@end + /// The codec used by FirebaseFirestoreHostApi. NSObject *FirebaseFirestoreHostApiGetCodec(void); @@ -318,12 +351,14 @@ NSObject *FirebaseFirestoreHostApiGetCodec(void); parameters:(PigeonQueryParameters *)parameters options:(PigeonGetOptions *)options completion:(void (^)(PigeonQuerySnapshot *_Nullable, FlutterError *_Nullable))completion; -- (void)aggregateQueryCountApp:(FirestorePigeonFirebaseApp *)app - path:(NSString *)path - parameters:(PigeonQueryParameters *)parameters - source:(AggregateSource)source - isCollectionGroup:(NSNumber *)isCollectionGroup - completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +- (void)aggregateQueryApp:(FirestorePigeonFirebaseApp *)app + path:(NSString *)path + parameters:(PigeonQueryParameters *)parameters + source:(AggregateSource)source + queries:(NSArray *)queries + isCollectionGroup:(NSNumber *)isCollectionGroup + completion:(void (^)(NSArray *_Nullable, + FlutterError *_Nullable))completion; - (void)writeBatchCommitApp:(FirestorePigeonFirebaseApp *)app writes:(NSArray *)writes completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 8322d550cbfc..350dbb286d67 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -38,7 +38,10 @@ export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte Order, ArrayConfig, QueryScope, - LoadBundleTaskState; + LoadBundleTaskState, + average, + count, + sum; export 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' show FirebaseException; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart index e356a37ec07d..97c385537b7d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart @@ -23,4 +23,10 @@ class AggregateQuery { }) async { return AggregateQuerySnapshot._(await _delegate.get(source: source), query); } + + /// Represents an [AggregateQuery] over the data at a particular location for retrieving metadata + /// without retrieving the actual documents. + AggregateQuery count() { + return AggregateQuery._(_delegate.count(), query); + } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart index be3d3c612b61..af9aa5db1ac6 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart @@ -16,5 +16,11 @@ class AggregateQuerySnapshot { final Query query; /// Returns the count of the documents that match the query. - int get count => _delegate.count; + int? get count => _delegate.count; + + /// Returns the sum of the values of the documents that match the query. + double? getSum(String field) => _delegate.getSum(field); + + /// Returns the average of the values of the documents that match the query. + double? getAverage(String field) => _delegate.getAverage(field); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index 0e8099479689..8057b578dd9e 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -192,6 +192,41 @@ abstract class Query { }); AggregateQuery count(); + + /// Calculates the specified aggregations over the documents in the + /// result set of the given query, without actually downloading the documents. + AggregateQuery aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]); } /// Represents a [Query] over the data at a particular location. @@ -852,6 +887,78 @@ class _JsonQuery implements Query> { AggregateQuery count() { return AggregateQuery._(_delegate.count(), this); } + + /// Calculates the specified aggregations over the documents in the + /// result set of the given query, without actually downloading the documents. + @override + AggregateQuery aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]) { + return AggregateQuery._( + _delegate.aggregate( + aggregateField1, + aggregateField2, + aggregateField3, + aggregateField4, + aggregateField5, + aggregateField6, + aggregateField7, + aggregateField8, + aggregateField9, + aggregateField10, + aggregateField11, + aggregateField12, + aggregateField13, + aggregateField14, + aggregateField15, + aggregateField16, + aggregateField17, + aggregateField18, + aggregateField19, + aggregateField20, + aggregateField21, + aggregateField22, + aggregateField23, + aggregateField24, + aggregateField25, + aggregateField26, + aggregateField27, + aggregateField28, + aggregateField29, + aggregateField30, + ), + this, + ); + } } class _WithConverterQuery implements Query { @@ -1021,4 +1128,73 @@ class _WithConverterQuery implements Query { AggregateQuery count() { return _originalQuery.count(); } + + /// Calculates the specified aggregations over the documents in the + /// result set of the given query, without actually downloading the documents. + @override + AggregateQuery aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]) { + return _originalQuery.aggregate( + aggregateField1, + aggregateField2, + aggregateField3, + aggregateField4, + aggregateField5, + aggregateField6, + aggregateField7, + aggregateField8, + aggregateField9, + aggregateField10, + aggregateField11, + aggregateField12, + aggregateField13, + aggregateField14, + aggregateField15, + aggregateField16, + aggregateField17, + aggregateField18, + aggregateField19, + aggregateField20, + aggregateField21, + aggregateField22, + aggregateField23, + aggregateField24, + aggregateField25, + aggregateField26, + aggregateField27, + aggregateField28, + aggregateField29, + aggregateField30, + ); + } } diff --git a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp index 37b841a39b5c..9df63ce1713f 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.cpp @@ -1384,8 +1384,6 @@ void CloudFirestorePlugin::QueryGet( }); } -using firebase::firestore::AggregateQuery; - firebase::firestore::AggregateSource GetAggregateSourceFromPigeon( const AggregateSource& source) { switch (source) { @@ -1395,24 +1393,71 @@ firebase::firestore::AggregateSource GetAggregateSourceFromPigeon( } } -void CloudFirestorePlugin::AggregateQueryCount( +void CloudFirestorePlugin::AggregateQuery( const FirestorePigeonFirebaseApp& app, const std::string& path, const PigeonQueryParameters& parameters, const AggregateSource& source, - bool is_collection_group, - std::function reply)> result) { + const flutter::EncodableList& queries, bool is_collection_group, + std::function reply)> result) { Firestore* firestore = GetFirestoreFromPigeon(app); Query query = ParseQuery(firestore, path, is_collection_group, parameters); - AggregateQuery aggregate_query = query.Count(); + + // C++ SDK does not support average and sum + firebase::firestore::AggregateQuery aggregate_query; + + for (auto& queryRequest : queries) { + const cloud_firestore_windows::AggregateQuery& queryRequestTyped = + std::any_cast( + std::get(queryRequest)); + + switch (queryRequestTyped.type()) { + case AggregateType::count: + aggregate_query = query.Count(); + break; + case AggregateType::sum: + std::cout << "Sum is not supported on C++" << std::endl; + break; + case AggregateType::average: + std::cout << "Average is not supported on C++" << std::endl; + break; + } + } Future future = aggregate_query.Get(GetAggregateSourceFromPigeon(source)); future.OnCompletion( - [result](const Future& completed_future) { + [result, + queries](const Future& completed_future) { if (completed_future.error() == firebase::firestore::kErrorOk) { const AggregateQuerySnapshot* aggregateQuerySnapshot = completed_future.result(); - result(static_cast(aggregateQuerySnapshot->count())); + EncodableList aggregateResponses; + + for (auto& queryRequest : queries) { + const cloud_firestore_windows::AggregateQuery& queryRequestTyped = + std::any_cast( + std::get(queryRequest)); + + switch (queryRequestTyped.type()) { + case AggregateType::count: { + double doubleValue = + static_cast(aggregateQuerySnapshot->count()); + aggregateResponses.push_back(CustomEncodableValue( + AggregateQueryResponse(AggregateType::count, doubleValue))); + break; + } + case AggregateType::sum: { + std::cout << "Sum is not supported on C++" << std::endl; + break; + } + case AggregateType::average: { + std::cout << "Average is not supported on C++" << std::endl; + break; + } + } + } + + result(aggregateResponses); } else { result(CloudFirestorePlugin::ParseError(completed_future)); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.h b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.h index 8f1024cdb1af..72d9c15d8939 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.h +++ b/packages/cloud_firestore/cloud_firestore/windows/cloud_firestore_plugin.h @@ -111,11 +111,12 @@ class CloudFirestorePlugin : public flutter::Plugin, bool is_collection_group, const PigeonQueryParameters& parameters, const PigeonGetOptions& options, std::function reply)> result) override; - virtual void AggregateQueryCount( + virtual void AggregateQuery( const FirestorePigeonFirebaseApp& app, const std::string& path, const PigeonQueryParameters& parameters, const AggregateSource& source, - bool is_collection_group, - std::function reply)> result) override; + const flutter::EncodableList& queries, bool is_collection_group, + std::function reply)> result) + override; virtual void WriteBatchCommit( const FirestorePigeonFirebaseApp& app, const flutter::EncodableList& writes, diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index 4f832a23164b..fcf06964eab7 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -893,6 +893,105 @@ PigeonQueryParameters PigeonQueryParameters::FromEncodableList( return decoded; } +// AggregateQuery + +AggregateQuery::AggregateQuery(const AggregateType& type) : type_(type) {} + +AggregateQuery::AggregateQuery(const AggregateType& type, + const std::string* field) + : type_(type), + field_(field ? std::optional(*field) : std::nullopt) {} + +const AggregateType& AggregateQuery::type() const { return type_; } + +void AggregateQuery::set_type(const AggregateType& value_arg) { + type_ = value_arg; +} + +const std::string* AggregateQuery::field() const { + return field_ ? &(*field_) : nullptr; +} + +void AggregateQuery::set_field(const std::string_view* value_arg) { + field_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AggregateQuery::set_field(std::string_view value_arg) { + field_ = value_arg; +} + +EncodableList AggregateQuery::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue((int)type_)); + list.push_back(field_ ? EncodableValue(*field_) : EncodableValue()); + return list; +} + +AggregateQuery AggregateQuery::FromEncodableList(const EncodableList& list) { + AggregateQuery decoded((AggregateType)(std::get(list[0]))); + auto& encodable_field = list[1]; + if (!encodable_field.IsNull()) { + decoded.set_field(std::get(encodable_field)); + } + return decoded; +} + +// AggregateQueryResponse + +AggregateQueryResponse::AggregateQueryResponse(const AggregateType& type, + double value) + : type_(type), value_(value) {} + +AggregateQueryResponse::AggregateQueryResponse(const AggregateType& type, + const std::string* field, + double value) + : type_(type), + field_(field ? std::optional(*field) : std::nullopt), + value_(value) {} + +const AggregateType& AggregateQueryResponse::type() const { return type_; } + +void AggregateQueryResponse::set_type(const AggregateType& value_arg) { + type_ = value_arg; +} + +const std::string* AggregateQueryResponse::field() const { + return field_ ? &(*field_) : nullptr; +} + +void AggregateQueryResponse::set_field(const std::string_view* value_arg) { + field_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AggregateQueryResponse::set_field(std::string_view value_arg) { + field_ = value_arg; +} + +double AggregateQueryResponse::value() const { return value_; } + +void AggregateQueryResponse::set_value(double value_arg) { value_ = value_arg; } + +EncodableList AggregateQueryResponse::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue((int)type_)); + list.push_back(field_ ? EncodableValue(*field_) : EncodableValue()); + list.push_back(EncodableValue(value_)); + return list; +} + +AggregateQueryResponse AggregateQueryResponse::FromEncodableList( + const EncodableList& list) { + AggregateQueryResponse decoded((AggregateType)(std::get(list[0])), + std::get(list[2])); + auto& encodable_field = list[1]; + if (!encodable_field.IsNull()) { + decoded.set_field(std::get(encodable_field)); + } + return decoded; +} + FirebaseFirestoreHostApiCodecSerializer:: FirebaseFirestoreHostApiCodecSerializer() {} @@ -900,36 +999,42 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { case 128: - return CustomEncodableValue(DocumentReferenceRequest::FromEncodableList( + return CustomEncodableValue(AggregateQuery::FromEncodableList( std::get(ReadValue(stream)))); case 129: - return CustomEncodableValue(FirestorePigeonFirebaseApp::FromEncodableList( + return CustomEncodableValue(AggregateQueryResponse::FromEncodableList( std::get(ReadValue(stream)))); case 130: - return CustomEncodableValue(PigeonDocumentChange::FromEncodableList( + return CustomEncodableValue(DocumentReferenceRequest::FromEncodableList( std::get(ReadValue(stream)))); case 131: - return CustomEncodableValue(PigeonDocumentOption::FromEncodableList( + return CustomEncodableValue(FirestorePigeonFirebaseApp::FromEncodableList( std::get(ReadValue(stream)))); case 132: - return CustomEncodableValue(PigeonDocumentSnapshot::FromEncodableList( + return CustomEncodableValue(PigeonDocumentChange::FromEncodableList( std::get(ReadValue(stream)))); case 133: - return CustomEncodableValue(PigeonFirebaseSettings::FromEncodableList( + return CustomEncodableValue(PigeonDocumentOption::FromEncodableList( std::get(ReadValue(stream)))); case 134: - return CustomEncodableValue(PigeonGetOptions::FromEncodableList( + return CustomEncodableValue(PigeonDocumentSnapshot::FromEncodableList( std::get(ReadValue(stream)))); case 135: - return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( + return CustomEncodableValue(PigeonFirebaseSettings::FromEncodableList( std::get(ReadValue(stream)))); case 136: - return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + return CustomEncodableValue(PigeonGetOptions::FromEncodableList( std::get(ReadValue(stream)))); case 137: - return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + return CustomEncodableValue(PigeonQueryParameters::FromEncodableList( std::get(ReadValue(stream)))); case 138: + return CustomEncodableValue(PigeonQuerySnapshot::FromEncodableList( + std::get(ReadValue(stream)))); + case 139: + return CustomEncodableValue(PigeonSnapshotMetadata::FromEncodableList( + std::get(ReadValue(stream)))); + case 140: return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); default: @@ -942,8 +1047,24 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( const EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { - if (custom_value->type() == typeid(DocumentReferenceRequest)) { + if (custom_value->type() == typeid(AggregateQuery)) { stream->WriteByte(128); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(AggregateQueryResponse)) { + stream->WriteByte(129); + WriteValue( + EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(DocumentReferenceRequest)) { + stream->WriteByte(130); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -951,7 +1072,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(FirestorePigeonFirebaseApp)) { - stream->WriteByte(129); + stream->WriteByte(131); WriteValue(EncodableValue( std::any_cast(*custom_value) .ToEncodableList()), @@ -959,7 +1080,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonDocumentChange)) { - stream->WriteByte(130); + stream->WriteByte(132); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -967,7 +1088,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonDocumentOption)) { - stream->WriteByte(131); + stream->WriteByte(133); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -975,7 +1096,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonDocumentSnapshot)) { - stream->WriteByte(132); + stream->WriteByte(134); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -983,7 +1104,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonFirebaseSettings)) { - stream->WriteByte(133); + stream->WriteByte(135); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -991,7 +1112,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonGetOptions)) { - stream->WriteByte(134); + stream->WriteByte(136); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -999,7 +1120,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonQueryParameters)) { - stream->WriteByte(135); + stream->WriteByte(137); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1007,7 +1128,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonQuerySnapshot)) { - stream->WriteByte(136); + stream->WriteByte(138); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1015,7 +1136,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonSnapshotMetadata)) { - stream->WriteByte(137); + stream->WriteByte(139); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1023,7 +1144,7 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PigeonTransactionCommand)) { - stream->WriteByte(138); + stream->WriteByte(140); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -1872,7 +1993,7 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, auto channel = std::make_unique>( binary_messenger, "dev.flutter.pigeon.cloud_firestore_platform_interface." - "FirebaseFirestoreHostApi.aggregateQueryCount", + "FirebaseFirestoreHostApi.aggregateQuery", &GetCodec()); if (api != nullptr) { channel->SetMessageHandler( @@ -1909,25 +2030,32 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, } const AggregateSource& source_arg = (AggregateSource)encodable_source_arg.LongValue(); - const auto& encodable_is_collection_group_arg = args.at(4); + const auto& encodable_queries_arg = args.at(4); + if (encodable_queries_arg.IsNull()) { + reply(WrapError("queries_arg unexpectedly null.")); + return; + } + const auto& queries_arg = + std::get(encodable_queries_arg); + const auto& encodable_is_collection_group_arg = args.at(5); if (encodable_is_collection_group_arg.IsNull()) { reply(WrapError("is_collection_group_arg unexpectedly null.")); return; } const auto& is_collection_group_arg = std::get(encodable_is_collection_group_arg); - api->AggregateQueryCount( - app_arg, path_arg, parameters_arg, source_arg, - is_collection_group_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back( - EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + api->AggregateQuery(app_arg, path_arg, parameters_arg, source_arg, + queries_arg, is_collection_group_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue( + std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index cbc3d3e91765..79b3618b614f 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -122,6 +122,8 @@ enum class PigeonTransactionType { deleteType = 3 }; +enum class AggregateType { count = 0, sum = 1, average = 2 }; + // Generated class from Pigeon that represents data sent in messages. class PigeonFirebaseSettings { public: @@ -523,6 +525,62 @@ class PigeonQueryParameters { std::optional filters_; }; +// Generated class from Pigeon that represents data sent in messages. +class AggregateQuery { + public: + // Constructs an object setting all non-nullable fields. + explicit AggregateQuery(const AggregateType& type); + + // Constructs an object setting all fields. + explicit AggregateQuery(const AggregateType& type, const std::string* field); + + const AggregateType& type() const; + void set_type(const AggregateType& value_arg); + + const std::string* field() const; + void set_field(const std::string_view* value_arg); + void set_field(std::string_view value_arg); + + private: + static AggregateQuery FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + AggregateType type_; + std::optional field_; +}; + +// Generated class from Pigeon that represents data sent in messages. +class AggregateQueryResponse { + public: + // Constructs an object setting all non-nullable fields. + explicit AggregateQueryResponse(const AggregateType& type, double value); + + // Constructs an object setting all fields. + explicit AggregateQueryResponse(const AggregateType& type, + const std::string* field, double value); + + const AggregateType& type() const; + void set_type(const AggregateType& value_arg); + + const std::string* field() const; + void set_field(const std::string_view* value_arg); + void set_field(std::string_view value_arg); + + double value() const; + void set_value(double value_arg); + + private: + static AggregateQueryResponse FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + AggregateType type_; + std::optional field_; + double value_; +}; + class FirebaseFirestoreHostApiCodecSerializer : public cloud_firestore_windows::FirestoreCodec { public: @@ -613,11 +671,11 @@ class FirebaseFirestoreHostApi { bool is_collection_group, const PigeonQueryParameters& parameters, const PigeonGetOptions& options, std::function reply)> result) = 0; - virtual void AggregateQueryCount( + virtual void AggregateQuery( const FirestorePigeonFirebaseApp& app, const std::string& path, const PigeonQueryParameters& parameters, const AggregateSource& source, - bool is_collection_group, - std::function reply)> result) = 0; + const flutter::EncodableList& queries, bool is_collection_group, + std::function reply)> result) = 0; virtual void WriteBatchCommit( const FirestorePigeonFirebaseApp& app, const flutter::EncodableList& writes, diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart index 034232f9399b..0eb3d8012441 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart @@ -12,6 +12,7 @@ class MethodChannelAggregateQuery extends AggregateQueryPlatform { this._pigeonParameters, this._path, this._pigeonApp, + this._aggregateQueries, this._isCollectionGroupQuery, ) : super(query); @@ -20,21 +21,89 @@ class MethodChannelAggregateQuery extends AggregateQueryPlatform { final PigeonQueryParameters _pigeonParameters; final bool _isCollectionGroupQuery; + final List _aggregateQueries; + @override Future get({ required AggregateSource source, }) async { final data = - await MethodChannelFirebaseFirestore.pigeonChannel.aggregateQueryCount( + await MethodChannelFirebaseFirestore.pigeonChannel.aggregateQuery( _pigeonApp, _path, _pigeonParameters, source, + _aggregateQueries, _isCollectionGroupQuery, ); + int? count; + List sum = []; + List average = []; + for (final query in data) { + if (query == null) continue; + switch (query.type) { + case AggregateType.count: + count = query.value.toInt(); + break; + case AggregateType.sum: + sum.add(query); + break; + case AggregateType.average: + average.add(query); + break; + } + } + return AggregateQuerySnapshotPlatform( - count: data.toInt(), + count: count, + sum: sum, + average: average, + ); + } + + @override + AggregateQueryPlatform count() { + return MethodChannelAggregateQuery( + query, + _pigeonParameters, + _path, + _pigeonApp, + [ + ..._aggregateQueries, + AggregateQuery(type: AggregateType.count), + ], + _isCollectionGroupQuery, + ); + } + + @override + AggregateQueryPlatform sum(String field) { + return MethodChannelAggregateQuery( + query, + _pigeonParameters, + _path, + _pigeonApp, + [ + ..._aggregateQueries, + AggregateQuery(type: AggregateType.sum, field: field), + ], + _isCollectionGroupQuery, + ); + } + + @override + AggregateQueryPlatform average(String field) { + return MethodChannelAggregateQuery( + query, + _pigeonParameters, + _path, + _pigeonApp, + [ + ..._aggregateQueries, + AggregateQuery(type: AggregateType.average, field: field), + ], + _isCollectionGroupQuery, ); } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart index a8db09aa5511..6e4ae0a648da 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart @@ -8,6 +8,8 @@ import 'dart:async'; import 'package:_flutterfire_internals/_flutterfire_internals.dart'; import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; import 'package:cloud_firestore_platform_interface/src/internal/pointer.dart'; +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_query.dart' + as query; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; @@ -272,6 +274,143 @@ class MethodChannelQuery extends QueryPlatform { _pigeonParameters, _pointer.path, pigeonApp, + [ + AggregateQuery( + type: AggregateType.count, + ) + ], + isCollectionGroupQuery, + ); + } + + @override + AggregateQueryPlatform aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]) { + final fields = [ + aggregateField1, + aggregateField2, + aggregateField3, + aggregateField4, + aggregateField5, + aggregateField6, + aggregateField7, + aggregateField8, + aggregateField9, + aggregateField10, + aggregateField11, + aggregateField12, + aggregateField13, + aggregateField14, + aggregateField15, + aggregateField16, + aggregateField17, + aggregateField18, + aggregateField19, + aggregateField20, + aggregateField21, + aggregateField22, + aggregateField23, + aggregateField24, + aggregateField25, + aggregateField26, + aggregateField27, + aggregateField28, + aggregateField29, + aggregateField30, + ].whereType(); + return MethodChannelAggregateQuery( + this, + _pigeonParameters, + _pointer.path, + pigeonApp, + fields.map( + (e) { + if (e is query.count) { + return AggregateQuery( + type: AggregateType.count, + ); + } else if (e is query.sum) { + return AggregateQuery( + type: AggregateType.sum, + field: e.field, + ); + } else if (e is query.average) { + return AggregateQuery( + type: AggregateType.average, + field: e.field, + ); + } else { + throw ArgumentError( + 'Unsupported aggregate method ${e.runtimeType}'); + } + }, + ).toList(), + isCollectionGroupQuery, + ); + } + + // This method is not exposed in the public API, but can be used internally + @override + AggregateQueryPlatform sum(String field) { + return MethodChannelAggregateQuery( + this, + _pigeonParameters, + _pointer.path, + pigeonApp, + [ + AggregateQuery( + type: AggregateType.sum, + field: field, + ) + ], + isCollectionGroupQuery, + ); + } + + // This method is not exposed in the public API, but can be used internally + @override + AggregateQueryPlatform average(String field) { + return MethodChannelAggregateQuery( + this, + _pigeonParameters, + _pointer.path, + pigeonApp, + [ + AggregateQuery( + type: AggregateType.average, + field: field, + ) + ], isCollectionGroupQuery, ); } 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 29702bf08e8a..930a900b391a 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 @@ -75,6 +75,12 @@ enum PigeonTransactionType { deleteType, } +enum AggregateType { + count, + sum, + average, +} + class PigeonFirebaseSettings { PigeonFirebaseSettings({ this.persistenceEnabled, @@ -469,43 +475,106 @@ class PigeonQueryParameters { } } +class AggregateQuery { + AggregateQuery({ + required this.type, + this.field, + }); + + AggregateType type; + + String? field; + + Object encode() { + return [ + type.index, + field, + ]; + } + + static AggregateQuery decode(Object result) { + result as List; + return AggregateQuery( + type: AggregateType.values[result[0]! as int], + field: result[1] as String?, + ); + } +} + +class AggregateQueryResponse { + AggregateQueryResponse({ + required this.type, + this.field, + required this.value, + }); + + AggregateType type; + + String? field; + + double value; + + Object encode() { + return [ + type.index, + field, + value, + ]; + } + + static AggregateQueryResponse decode(Object result) { + result as List; + return AggregateQueryResponse( + type: AggregateType.values[result[0]! as int], + field: result[1] as String?, + value: result[2]! as double, + ); + } +} + class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { const _FirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is DocumentReferenceRequest) { + if (value is AggregateQuery) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is FirestorePigeonFirebaseApp) { + } else if (value is AggregateQueryResponse) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentChange) { + } else if (value is DocumentReferenceRequest) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentOption) { + } else if (value is FirestorePigeonFirebaseApp) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentSnapshot) { + } else if (value is PigeonDocumentChange) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is PigeonFirebaseSettings) { + } else if (value is PigeonDocumentOption) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is PigeonGetOptions) { + } else if (value is PigeonDocumentSnapshot) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonFirebaseSettings) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(140); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -515,26 +584,30 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return DocumentReferenceRequest.decode(readValue(buffer)!); + return AggregateQuery.decode(readValue(buffer)!); case 129: - return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); + return AggregateQueryResponse.decode(readValue(buffer)!); case 130: - return PigeonDocumentChange.decode(readValue(buffer)!); + return DocumentReferenceRequest.decode(readValue(buffer)!); case 131: - return PigeonDocumentOption.decode(readValue(buffer)!); + return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); case 132: - return PigeonDocumentSnapshot.decode(readValue(buffer)!); + return PigeonDocumentChange.decode(readValue(buffer)!); case 133: - return PigeonFirebaseSettings.decode(readValue(buffer)!); + return PigeonDocumentOption.decode(readValue(buffer)!); case 134: - return PigeonGetOptions.decode(readValue(buffer)!); + return PigeonDocumentSnapshot.decode(readValue(buffer)!); case 135: - return PigeonQueryParameters.decode(readValue(buffer)!); + return PigeonFirebaseSettings.decode(readValue(buffer)!); case 136: - return PigeonQuerySnapshot.decode(readValue(buffer)!); + return PigeonGetOptions.decode(readValue(buffer)!); case 137: - return PigeonSnapshotMetadata.decode(readValue(buffer)!); + return PigeonQueryParameters.decode(readValue(buffer)!); case 138: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 139: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 140: return PigeonTransactionCommand.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -1069,15 +1142,16 @@ class FirebaseFirestoreHostApi { } } - Future aggregateQueryCount( + Future> aggregateQuery( 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.aggregateQueryCount', + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', codec, binaryMessenger: _binaryMessenger, ); @@ -1086,6 +1160,7 @@ class FirebaseFirestoreHostApi { arg_path, arg_parameters, arg_source.index, + arg_queries, arg_isCollectionGroup, ]) as List?; if (replyList == null) { @@ -1105,7 +1180,7 @@ class FirebaseFirestoreHostApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as double?)!; + return (replyList[0] as List?)!.cast(); } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart index 8681d1b4a6ea..b42e55710666 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart @@ -32,4 +32,21 @@ abstract class AggregateQueryPlatform extends PlatformInterface { }) async { throw UnimplementedError('get() is not implemented'); } + + /// Returns an [AggregateQuerySnapshotPlatform] with the count of the documents that match the query. + AggregateQueryPlatform count() { + throw UnimplementedError('count() is not implemented'); + } + + /// Returns an [AggregateQuerySnapshotPlatform] with the sum of the values of the documents that match the query. + AggregateQueryPlatform sum( + String field, + ) { + throw UnimplementedError('sum() is not implemented'); + } + + /// Returns an [AggregateQuerySnapshotPlatform] with the average of the values of the documents that match the query. + AggregateQueryPlatform average(String field) { + throw UnimplementedError('average() is not implemented'); + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart index 9615a9e80d9c..7d4f938773e3 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart @@ -2,12 +2,19 @@ // 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:collection/collection.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; /// [AggregateQuerySnapshotPlatform] represents a response to an [AggregateQueryPlatform] request. class AggregateQuerySnapshotPlatform extends PlatformInterface { - AggregateQuerySnapshotPlatform({required int count}) - : _count = count, + AggregateQuerySnapshotPlatform({ + required int? count, + required List sum, + required List average, + }) : _count = count, + _sum = sum, + _average = average, super(token: _token); static final Object _token = Object(); @@ -22,8 +29,26 @@ class AggregateQuerySnapshotPlatform extends PlatformInterface { PlatformInterface.verifyToken(instance, _token); } - final int _count; + final int? _count; + + final List _average; + + final List _sum; /// Returns the count of the documents that match the query. - int get count => _count; + int? get count => _count; + + /// Returns the average of the documents that match the query. + /// The key is the field name and the value is the average. + double? getAverage(String field) { + return _average + .firstWhereOrNull((element) => element.field == field) + ?.value; + } + + /// Returns the sum of the documents that match the query. + /// The key is the field name and the value is the sum. + double? getSum(String field) { + return _sum.firstWhereOrNull((element) => element.field == field)?.value; + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart index 45dc344c3ba5..c64623efc54a 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart @@ -238,4 +238,80 @@ abstract class QueryPlatform extends PlatformInterface { AggregateQueryPlatform count() { throw UnimplementedError('count() is not implemented'); } + + AggregateQueryPlatform aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]) { + throw UnimplementedError('aggregate() is not implemented'); + } + + /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for + /// metadata + /// + /// This method is not exposed in the public API, but can be used internally + AggregateQueryPlatform sum(String field) { + throw UnimplementedError('sum() is not implemented'); + } + + /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for + /// metadata + /// + /// This method is not exposed in the public API, but can be used internally + AggregateQueryPlatform average(String field) { + throw UnimplementedError('average() is not implemented'); + } +} + +abstract class AggregateField {} + +/// Create a CountAggregateField object that can be used to compute +/// the count of documents in the result set of a query. +// ignore: camel_case_types +class count extends AggregateField {} + +/// Create an object that can be used to compute the sum of a specified field +/// over a range of documents in the result set of a query. +// ignore: camel_case_types +class sum extends AggregateField { + sum(this.field); + + final String field; +} + +/// Create an object that can be used to compute the average of a specified field +/// over a range of documents in the result set of a query. +// ignore: camel_case_types +class average extends AggregateField { + average(this.field); + + final String field; } 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 232a24da9ea2..405d5c000251 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -240,6 +240,34 @@ class PigeonQueryParameters { final Map? filters; } +enum AggregateType { + count, + sum, + average, +} + +class AggregateQuery { + const AggregateQuery({ + required this.type, + required this.field, + }); + + final AggregateType type; + final String? field; +} + +class AggregateQueryResponse { + const AggregateQueryResponse({ + required this.type, + required this.value, + required this.field, + }); + + final AggregateType type; + final String? field; + final double value; +} + @HostApi(dartHostTestHandler: 'TestFirebaseFirestoreHostApi') abstract class FirebaseFirestoreHostApi { @async @@ -351,11 +379,12 @@ abstract class FirebaseFirestoreHostApi { ); @async - double aggregateQueryCount( + List aggregateQuery( FirestorePigeonFirebaseApp app, String path, PigeonQueryParameters parameters, AggregateSource source, + List queries, bool isCollectionGroup, ); 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 9be5576fc2a7..649a5ff1793d 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 @@ -17,39 +17,45 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { const _TestFirebaseFirestoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is DocumentReferenceRequest) { + if (value is AggregateQuery) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is FirestorePigeonFirebaseApp) { + } else if (value is AggregateQueryResponse) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentChange) { + } else if (value is DocumentReferenceRequest) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentOption) { + } else if (value is FirestorePigeonFirebaseApp) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is PigeonDocumentSnapshot) { + } else if (value is PigeonDocumentChange) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is PigeonFirebaseSettings) { + } else if (value is PigeonDocumentOption) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is PigeonGetOptions) { + } else if (value is PigeonDocumentSnapshot) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is PigeonQueryParameters) { + } else if (value is PigeonFirebaseSettings) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is PigeonQuerySnapshot) { + } else if (value is PigeonGetOptions) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is PigeonSnapshotMetadata) { + } else if (value is PigeonQueryParameters) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is PigeonTransactionCommand) { + } else if (value is PigeonQuerySnapshot) { buffer.putUint8(138); writeValue(buffer, value.encode()); + } else if (value is PigeonSnapshotMetadata) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is PigeonTransactionCommand) { + buffer.putUint8(140); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -59,26 +65,30 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return DocumentReferenceRequest.decode(readValue(buffer)!); + return AggregateQuery.decode(readValue(buffer)!); case 129: - return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); + return AggregateQueryResponse.decode(readValue(buffer)!); case 130: - return PigeonDocumentChange.decode(readValue(buffer)!); + return DocumentReferenceRequest.decode(readValue(buffer)!); case 131: - return PigeonDocumentOption.decode(readValue(buffer)!); + return FirestorePigeonFirebaseApp.decode(readValue(buffer)!); case 132: - return PigeonDocumentSnapshot.decode(readValue(buffer)!); + return PigeonDocumentChange.decode(readValue(buffer)!); case 133: - return PigeonFirebaseSettings.decode(readValue(buffer)!); + return PigeonDocumentOption.decode(readValue(buffer)!); case 134: - return PigeonGetOptions.decode(readValue(buffer)!); + return PigeonDocumentSnapshot.decode(readValue(buffer)!); case 135: - return PigeonQueryParameters.decode(readValue(buffer)!); + return PigeonFirebaseSettings.decode(readValue(buffer)!); case 136: - return PigeonQuerySnapshot.decode(readValue(buffer)!); + return PigeonGetOptions.decode(readValue(buffer)!); case 137: - return PigeonSnapshotMetadata.decode(readValue(buffer)!); + return PigeonQueryParameters.decode(readValue(buffer)!); case 138: + return PigeonQuerySnapshot.decode(readValue(buffer)!); + case 139: + return PigeonSnapshotMetadata.decode(readValue(buffer)!); + case 140: return PigeonTransactionCommand.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -165,11 +175,12 @@ abstract class TestFirebaseFirestoreHostApi { PigeonGetOptions options, ); - Future aggregateQueryCount( + Future> aggregateQuery( FirestorePigeonFirebaseApp app, String path, PigeonQueryParameters parameters, AggregateSource source, + List queries, bool isCollectionGroup, ); @@ -830,7 +841,7 @@ abstract class TestFirebaseFirestoreHostApi { } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQueryCount', + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery', codec, binaryMessenger: binaryMessenger, ); @@ -843,42 +854,49 @@ abstract class TestFirebaseFirestoreHostApi { (Object? message) async { assert( message != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQueryCount was 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.aggregateQueryCount was null, expected non-null FirestorePigeonFirebaseApp.', + '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.aggregateQueryCount was null, expected non-null String.', + '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.aggregateQueryCount was null, expected non-null PigeonQueryParameters.', + '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.aggregateQueryCount was null, expected non-null AggregateSource.', + '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.', ); - final bool? arg_isCollectionGroup = (args[4] as bool?); + final bool? arg_isCollectionGroup = (args[5] as bool?); assert( arg_isCollectionGroup != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQueryCount was null, expected non-null bool.', + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.aggregateQuery was null, expected non-null bool.', ); - final double output = await api.aggregateQueryCount( + final List output = await api.aggregateQuery( arg_app!, arg_path!, arg_parameters!, arg_source!, + arg_queries!, arg_isCollectionGroup!, ); return [output]; diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart index 63d3ee40074c..759cb22226e3 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart @@ -13,18 +13,96 @@ class AggregateQueryWeb extends AggregateQueryPlatform { /// [AggregateQueryWeb] represents the data at a particular location for retrieving metadata /// without retrieving the actual documents. - AggregateQueryWeb(QueryPlatform query, _webQuery) - : _delegate = firestore_interop.AggregateQuery(_webQuery), + AggregateQueryWeb( + QueryPlatform query, + _webQuery, + this._aggregateQueries, + ) : _delegate = firestore_interop.AggregateQuery(_webQuery), + _webQuery = _webQuery, super(query); + final List _aggregateQueries; + final firestore_interop.Query _webQuery; + /// Returns an [AggregateQuerySnapshotPlatform] with the count of the documents that match the query. @override Future get({ required AggregateSource source, }) async { // Note: There isn't a source option on the web platform - firestore_interop.AggregateQuerySnapshot snapshot = await _delegate.get(); + firestore_interop.AggregateQuerySnapshot snapshot = + await _delegate.get(_aggregateQueries); + + List sum = []; + List average = []; + + for (final query in _aggregateQueries) { + switch (query.type) { + case AggregateType.sum: + sum.add( + AggregateQueryResponse( + type: AggregateType.sum, + value: snapshot.getDataValue(query), + field: query.field, + ), + ); + break; + case AggregateType.average: + average.add( + AggregateQueryResponse( + type: AggregateType.average, + value: snapshot.getDataValue(query), + field: query.field, + ), + ); + break; + default: + break; + } + } + + return AggregateQuerySnapshotPlatform( + count: snapshot.count, + sum: sum, + average: average, + ); + } + + @override + AggregateQueryPlatform count() { + return AggregateQueryWeb( + query, + _webQuery, + [ + ..._aggregateQueries, + AggregateQuery( + type: AggregateType.count, + ), + ], + ); + } + + @override + AggregateQueryPlatform sum(String field) { + return AggregateQueryWeb( + query, + _webQuery, + [ + ..._aggregateQueries, + AggregateQuery(type: AggregateType.sum, field: field), + ], + ); + } - return AggregateQuerySnapshotPlatform(count: snapshot.count); + @override + AggregateQueryPlatform average(String field) { + return AggregateQueryWeb( + query, + _webQuery, + [ + ..._aggregateQueries, + AggregateQuery(type: AggregateType.average, field: field), + ], + ); } } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart index 4d842bfb045f..dac2ed528af5 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart @@ -9,6 +9,8 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart' + as platform_interface; import 'package:cloud_firestore_web/src/utils/encode_utility.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_web/firebase_core_web_interop.dart' @@ -833,9 +835,35 @@ abstract class FieldValue { class AggregateQuery { AggregateQuery(Query query) : _jsQuery = query.jsObject; final firestore_interop.QueryJsImpl _jsQuery; - Future get() async { + + static String name(platform_interface.AggregateQuery query) { + return '${query.type.name}_${query.field}'; + } + + Future get( + List aggregateQueries, + ) async { + // Create a map of the requests + final Map requests = {}; + for (final platform_interface.AggregateQuery aggregateQuery + in aggregateQueries) { + switch (aggregateQuery.type) { + case AggregateType.count: + requests['count'] = firestore_interop.count(); + break; + case AggregateType.sum: + requests[name(aggregateQuery)] = + firestore_interop.sum(aggregateQuery.field!); + break; + case AggregateType.average: + requests[name(aggregateQuery)] = + firestore_interop.average(aggregateQuery.field!); + break; + } + } + return handleThenable( - firestore_interop.getCountFromServer(_jsQuery)) + firestore_interop.getAggregateFromServer(_jsQuery, jsify(requests))) .then(AggregateQuerySnapshot.getInstance); } } @@ -857,5 +885,8 @@ class AggregateQuerySnapshot : _data = Map.from(dartify(jsObject.data())), super.fromJsObject(jsObject); - int get count => _data['count']! as int; + int? get count => _data['count'] as int?; + + double getDataValue(platform_interface.AggregateQuery query) => + _data[AggregateQuery.name(query)]! as double; } diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart index 8483c4e504b9..12f1d8e77aa2 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart @@ -665,11 +665,26 @@ external Object get arrayRemove; @JS() external Object get arrayUnion; +@JS() +external Object count(); + +@JS() +external Object average(String field); + +@JS() +external Object sum(String field); + @JS() external PromiseJsImpl getCountFromServer( QueryJsImpl query, ); +@JS() +external PromiseJsImpl getAggregateFromServer( + QueryJsImpl query, + Object specs, +); + @JS('AggregateQuerySnapshot') abstract class AggregateQuerySnapshotJsImpl { external Map data(); diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart index f8deceeac48f..459bda5736dc 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart @@ -3,6 +3,8 @@ // 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' + as platform_interface; import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; import 'package:cloud_firestore_web/src/utils/encode_utility.dart'; import 'package:collection/collection.dart'; @@ -255,6 +257,134 @@ class QueryWeb extends QueryPlatform { @override AggregateQueryPlatform count() { - return AggregateQueryWeb(this, _buildWebQueryWithParameters()); + return AggregateQueryWeb( + this, + _buildWebQueryWithParameters(), + [ + AggregateQuery( + type: AggregateType.count, + ) + ], + ); + } + + @override + AggregateQueryPlatform aggregate( + AggregateField aggregateField1, [ + AggregateField? aggregateField2, + AggregateField? aggregateField3, + AggregateField? aggregateField4, + AggregateField? aggregateField5, + AggregateField? aggregateField6, + AggregateField? aggregateField7, + AggregateField? aggregateField8, + AggregateField? aggregateField9, + AggregateField? aggregateField10, + AggregateField? aggregateField11, + AggregateField? aggregateField12, + AggregateField? aggregateField13, + AggregateField? aggregateField14, + AggregateField? aggregateField15, + AggregateField? aggregateField16, + AggregateField? aggregateField17, + AggregateField? aggregateField18, + AggregateField? aggregateField19, + AggregateField? aggregateField20, + AggregateField? aggregateField21, + AggregateField? aggregateField22, + AggregateField? aggregateField23, + AggregateField? aggregateField24, + AggregateField? aggregateField25, + AggregateField? aggregateField26, + AggregateField? aggregateField27, + AggregateField? aggregateField28, + AggregateField? aggregateField29, + AggregateField? aggregateField30, + ]) { + final fields = [ + aggregateField1, + aggregateField2, + aggregateField3, + aggregateField4, + aggregateField5, + aggregateField6, + aggregateField7, + aggregateField8, + aggregateField9, + aggregateField10, + aggregateField11, + aggregateField12, + aggregateField13, + aggregateField14, + aggregateField15, + aggregateField16, + aggregateField17, + aggregateField18, + aggregateField19, + aggregateField20, + aggregateField21, + aggregateField22, + aggregateField23, + aggregateField24, + aggregateField25, + aggregateField26, + aggregateField27, + aggregateField28, + aggregateField29, + aggregateField30, + ].whereType(); + return AggregateQueryWeb( + this, + _buildWebQueryWithParameters(), + fields.map((e) { + if (e is platform_interface.count) { + return AggregateQuery( + type: AggregateType.count, + ); + } else if (e is platform_interface.sum) { + return AggregateQuery( + type: AggregateType.sum, + field: e.field, + ); + } else if (e is platform_interface.average) { + return AggregateQuery( + type: AggregateType.average, + field: e.field, + ); + } else { + throw UnsupportedError( + 'Unsupported aggregate field type ${e.runtimeType}', + ); + } + }).toList(), + ); + } + + @override + AggregateQueryPlatform sum(String field) { + return AggregateQueryWeb( + this, + _buildWebQueryWithParameters(), + [ + AggregateQuery( + type: AggregateType.sum, + field: field, + ) + ], + ); + } + + @override + AggregateQueryPlatform average(String field) { + return AggregateQueryWeb( + this, + _buildWebQueryWithParameters(), + [ + AggregateQuery( + type: AggregateType.average, + field: field, + ) + ], + ); } }