From 47163aad70719c5d689a86319033820bbac68363 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 28 Oct 2024 09:29:46 +0100 Subject: [PATCH 1/4] feat(firestore): add support for vector query --- .../GeneratedAndroidFirebaseFirestore.java | 168 ++++++++++++++++++ .../ios/Classes/FirestoreMessages.g.m | 101 +++++++++++ .../ios/Classes/Public/FirestoreMessages.g.h | 51 ++++++ .../cloud_firestore/lib/cloud_firestore.dart | 3 +- .../cloud_firestore/lib/src/query.dart | 8 + .../cloud_firestore/lib/src/vector_query.dart | 32 ++++ .../cloud_firestore/windows/messages.g.cpp | 144 ++++++++++++++- .../cloud_firestore/windows/messages.g.h | 55 +++++- .../cloud_firestore_platform_interface.dart | 3 +- .../lib/src/pigeon/messages.pigeon.dart | 97 ++++++++++ .../platform_interface_query.dart | 4 + .../platform_interface_vector_query.dart | 36 ++++ ...tform_interface_vector_query_snapshot.dart | 14 ++ .../pigeons/messages.dart | 42 +++++ .../test/pigeon/test_api.dart | 93 ++++++++++ 15 files changed, 845 insertions(+), 6 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/GeneratedAndroidFirebaseFirestore.java index f7d24bc7c7ec..13843d1f39be 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 @@ -104,6 +104,38 @@ private Source(final int index) { } } + /** An enumeration of firestore source types. */ + public enum VectorSource { + /** + * Causes Firestore to avoid the cache, generating an error if the server cannot be reached. + * Note that the cache will still be updated if the server request succeeds. Also note that + * latency-compensation still takes effect, so any pending write operations will be visible in + * the returned data (merged into the server-provided data). + */ + SERVER(0); + + final int index; + + private VectorSource(final int index) { + this.index = index; + } + } + + public enum DistanceMeasure { + /** The cosine similarity measure. */ + COSINE(0), + /** The euclidean distance measure. */ + EUCLIDEAN(1), + /** The dot product distance measure. */ + DOT_PRODUCT(2); + + final int index; + + private DistanceMeasure(final int index) { + this.index = index; + } + } + /** * The listener retrieves data and listens to updates from the local Firestore cache only. If the * cache is empty, an empty snapshot will be returned. Snapshot events will be triggered on cache @@ -856,6 +888,79 @@ public ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class VectorQueryOptions { + private @NonNull String distanceResultField; + + public @NonNull String getDistanceResultField() { + return distanceResultField; + } + + public void setDistanceResultField(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"distanceResultField\" is null."); + } + this.distanceResultField = setterArg; + } + + private @NonNull Double distanceThreshold; + + public @NonNull Double getDistanceThreshold() { + return distanceThreshold; + } + + public void setDistanceThreshold(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"distanceThreshold\" is null."); + } + this.distanceThreshold = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + VectorQueryOptions() {} + + public static final class Builder { + + private @Nullable String distanceResultField; + + public @NonNull Builder setDistanceResultField(@NonNull String setterArg) { + this.distanceResultField = setterArg; + return this; + } + + private @Nullable Double distanceThreshold; + + public @NonNull Builder setDistanceThreshold(@NonNull Double setterArg) { + this.distanceThreshold = setterArg; + return this; + } + + public @NonNull VectorQueryOptions build() { + VectorQueryOptions pigeonReturn = new VectorQueryOptions(); + pigeonReturn.setDistanceResultField(distanceResultField); + pigeonReturn.setDistanceThreshold(distanceThreshold); + return pigeonReturn; + } + } + + @NonNull + public ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(distanceResultField); + toListResult.add(distanceThreshold); + return toListResult; + } + + static @NonNull VectorQueryOptions fromList(@NonNull ArrayList list) { + VectorQueryOptions pigeonResult = new VectorQueryOptions(); + Object distanceResultField = list.get(0); + pigeonResult.setDistanceResultField((String) distanceResultField); + Object distanceThreshold = list.get(1); + pigeonResult.setDistanceThreshold((Double) distanceThreshold); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class PigeonGetOptions { private @NonNull Source source; @@ -1667,6 +1772,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return PigeonSnapshotMetadata.fromList((ArrayList) readValue(buffer)); case (byte) 140: return PigeonTransactionCommand.fromList((ArrayList) readValue(buffer)); + case (byte) 141: + return VectorQueryOptions.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -1713,6 +1820,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof PigeonTransactionCommand) { stream.write(140); writeValue(stream, ((PigeonTransactionCommand) value).toList()); + } else if (value instanceof VectorQueryOptions) { + stream.write(141); + writeValue(stream, ((VectorQueryOptions) value).toList()); } else { super.writeValue(stream, value); } @@ -1809,6 +1919,17 @@ void aggregateQuery( @NonNull Boolean isCollectionGroup, @NonNull Result> result); + void findNearest( + @NonNull FirestorePigeonFirebaseApp app, + @NonNull String path, + @NonNull Boolean isCollectionGroup, + @NonNull PigeonQueryParameters parameters, + @NonNull PigeonGetOptions options, + @NonNull VectorSource source, + @NonNull VectorQueryOptions queryOptions, + @NonNull DistanceMeasure distanceMeasure, + @NonNull Result result); + void writeBatchCommit( @NonNull FirestorePigeonFirebaseApp app, @NonNull List writes, @@ -2478,6 +2599,53 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0); + String pathArg = (String) args.get(1); + Boolean isCollectionGroupArg = (Boolean) args.get(2); + PigeonQueryParameters parametersArg = (PigeonQueryParameters) args.get(3); + PigeonGetOptions optionsArg = (PigeonGetOptions) args.get(4); + VectorSource sourceArg = VectorSource.values()[(int) args.get(5)]; + VectorQueryOptions queryOptionsArg = (VectorQueryOptions) args.get(6); + DistanceMeasure distanceMeasureArg = DistanceMeasure.values()[(int) args.get(7)]; + Result resultCallback = + new Result() { + public void success(PigeonQuerySnapshot result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.findNearest( + appArg, + pathArg, + isCollectionGroupArg, + parametersArg, + optionsArg, + sourceArg, + queryOptionsArg, + distanceMeasureArg, + resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( 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 34e717b694a7..bc22b55b4d63 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m @@ -40,6 +40,27 @@ - (instancetype)initWithValue:(Source)value { } @end +/// An enumeration of firestore source types. +@implementation VectorSourceBox +- (instancetype)initWithValue:(VectorSource)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation DistanceMeasureBox +- (instancetype)initWithValue:(DistanceMeasure)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + /// The listener retrieves data and listens to updates from the local Firestore cache only. /// If the cache is empty, an empty snapshot will be returned. /// Snapshot events will be triggered on cache updates, like local mutations or load bundles. @@ -168,6 +189,12 @@ + (nullable PigeonQuerySnapshot *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface VectorQueryOptions () ++ (VectorQueryOptions *)fromList:(NSArray *)list; ++ (nullable VectorQueryOptions *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface PigeonGetOptions () + (PigeonGetOptions *)fromList:(NSArray *)list; + (nullable PigeonGetOptions *)nullableFromList:(NSArray *)list; @@ -410,6 +437,33 @@ - (NSArray *)toList { } @end +@implementation VectorQueryOptions ++ (instancetype)makeWithDistanceResultField:(NSString *)distanceResultField + distanceThreshold:(NSNumber *)distanceThreshold { + VectorQueryOptions *pigeonResult = [[VectorQueryOptions alloc] init]; + pigeonResult.distanceResultField = distanceResultField; + pigeonResult.distanceThreshold = distanceThreshold; + return pigeonResult; +} ++ (VectorQueryOptions *)fromList:(NSArray *)list { + VectorQueryOptions *pigeonResult = [[VectorQueryOptions alloc] init]; + pigeonResult.distanceResultField = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.distanceResultField != nil, @""); + pigeonResult.distanceThreshold = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.distanceThreshold != nil, @""); + return pigeonResult; +} ++ (nullable VectorQueryOptions *)nullableFromList:(NSArray *)list { + return (list) ? [VectorQueryOptions fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.distanceResultField ?: [NSNull null]), + (self.distanceThreshold ?: [NSNull null]), + ]; +} +@end + @implementation PigeonGetOptions + (instancetype)makeWithSource:(Source)source serverTimestampBehavior:(ServerTimestampBehavior)serverTimestampBehavior { @@ -680,6 +734,8 @@ - (nullable id)readValueOfType:(UInt8)type { return [PigeonSnapshotMetadata fromList:[self readValue]]; case 140: return [PigeonTransactionCommand fromList:[self readValue]]; + case 141: + return [VectorQueryOptions fromList:[self readValue]]; default: return [super readValueOfType:type]; } @@ -729,6 +785,9 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[PigeonTransactionCommand class]]) { [self writeByte:140]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[VectorQueryOptions class]]) { + [self writeByte:141]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1255,6 +1314,48 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.cloud_firestore_platform_interface." + @"FirebaseFirestoreHostApi.findNearest" + binaryMessenger:binaryMessenger + codec:FirebaseFirestoreHostApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (findNearestApp: + path:isCollectionGroup:parameters:options:source:queryOptions + :distanceMeasure:completion:)], + @"FirebaseFirestoreHostApi api (%@) doesn't respond to " + @"@selector(findNearestApp:path:isCollectionGroup:parameters:options:source:" + @"queryOptions:distanceMeasure:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FirestorePigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); + NSString *arg_path = GetNullableObjectAtIndex(args, 1); + NSNumber *arg_isCollectionGroup = GetNullableObjectAtIndex(args, 2); + PigeonQueryParameters *arg_parameters = GetNullableObjectAtIndex(args, 3); + PigeonGetOptions *arg_options = GetNullableObjectAtIndex(args, 4); + VectorSource arg_source = [GetNullableObjectAtIndex(args, 5) integerValue]; + VectorQueryOptions *arg_queryOptions = GetNullableObjectAtIndex(args, 6); + DistanceMeasure arg_distanceMeasure = [GetNullableObjectAtIndex(args, 7) integerValue]; + [api findNearestApp:arg_app + path:arg_path + isCollectionGroup:arg_isCollectionGroup + parameters:arg_parameters + options:arg_options + source:arg_source + queryOptions:arg_queryOptions + distanceMeasure:arg_distanceMeasure + completion:^(PigeonQuerySnapshot *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.cloud_firestore_platform_interface." 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 2eabaeaef25f..b2c7c27ec4b2 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 @@ -57,6 +57,37 @@ typedef NS_ENUM(NSUInteger, Source) { - (instancetype)initWithValue:(Source)value; @end +/// An enumeration of firestore source types. +typedef NS_ENUM(NSUInteger, VectorSource) { + /// Causes Firestore to avoid the cache, generating an error if the server cannot be reached. Note + /// that the cache will still be updated if the server request succeeds. Also note that + /// latency-compensation still takes effect, so any pending write operations will be visible in + /// the + /// returned data (merged into the server-provided data). + VectorSourceServer = 0, +}; + +/// Wrapper for VectorSource to allow for nullability. +@interface VectorSourceBox : NSObject +@property(nonatomic, assign) VectorSource value; +- (instancetype)initWithValue:(VectorSource)value; +@end + +typedef NS_ENUM(NSUInteger, DistanceMeasure) { + /// The cosine similarity measure. + DistanceMeasureCosine = 0, + /// The euclidean distance measure. + DistanceMeasureEuclidean = 1, + /// The dot product distance measure. + DistanceMeasureDotProduct = 2, +}; + +/// Wrapper for DistanceMeasure to allow for nullability. +@interface DistanceMeasureBox : NSObject +@property(nonatomic, assign) DistanceMeasure value; +- (instancetype)initWithValue:(DistanceMeasure)value; +@end + /// The listener retrieves data and listens to updates from the local Firestore cache only. /// If the cache is empty, an empty snapshot will be returned. /// Snapshot events will be triggered on cache updates, like local mutations or load bundles. @@ -165,6 +196,7 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @class PigeonDocumentSnapshot; @class PigeonDocumentChange; @class PigeonQuerySnapshot; +@class VectorQueryOptions; @class PigeonGetOptions; @class PigeonDocumentOption; @class PigeonTransactionCommand; @@ -243,6 +275,15 @@ typedef NS_ENUM(NSUInteger, AggregateType) { @property(nonatomic, strong) PigeonSnapshotMetadata *metadata; @end +@interface VectorQueryOptions : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithDistanceResultField:(NSString *)distanceResultField + distanceThreshold:(NSNumber *)distanceThreshold; +@property(nonatomic, copy) NSString *distanceResultField; +@property(nonatomic, strong) NSNumber *distanceThreshold; +@end + @interface PigeonGetOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; @@ -396,6 +437,16 @@ NSObject *FirebaseFirestoreHostApiGetCodec(void); isCollectionGroup:(NSNumber *)isCollectionGroup completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; +- (void)findNearestApp:(FirestorePigeonFirebaseApp *)app + path:(NSString *)path + isCollectionGroup:(NSNumber *)isCollectionGroup + parameters:(PigeonQueryParameters *)parameters + options:(PigeonGetOptions *)options + source:(VectorSource)source + queryOptions:(VectorQueryOptions *)queryOptions + distanceMeasure:(DistanceMeasure)distanceMeasure + completion: + (void (^)(PigeonQuerySnapshot *_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 60072efbcf3b..689c9aefc32a 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -9,6 +9,7 @@ import 'dart:convert'; // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:cloud_firestore/src/vector_query.dart'; import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' @@ -59,6 +60,7 @@ part 'src/filters.dart'; part 'src/firestore.dart'; part 'src/load_bundle_task.dart'; part 'src/load_bundle_task_snapshot.dart'; +part 'src/persistent_cache_index_manager.dart'; part 'src/query.dart'; part 'src/query_document_snapshot.dart'; part 'src/query_snapshot.dart'; @@ -66,4 +68,3 @@ part 'src/snapshot_metadata.dart'; part 'src/transaction.dart'; part 'src/utils/codec_utility.dart'; part 'src/write_batch.dart'; -part 'src/persistent_cache_index_manager.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index cefa3c75883f..ecafbca65565 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -226,6 +226,14 @@ abstract class Query { AggregateField? aggregateField29, AggregateField? aggregateField30, ]); + + VectorQuery findNearest( + String field, { + required Object queryVector, // List or VectorValue + required int limit, + required DistanceMeasure distanceMeasure, + required VectorQueryOptions options, + }); } /// Represents a [Query] over the data at a particular location. diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart new file mode 100644 index 000000000000..489fbb7c8608 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart @@ -0,0 +1,32 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// [VectorQuery] represents the data at a particular location for retrieving metadata +/// without retrieving the actual documents. +class VectorQuery { + VectorQuery._(this._delegate, this.query) { + VectorQueryPlatform.verify(_delegate); + } + + /// [Query] represents the query over the data at a particular location used by the [VectorQuery] to + /// retrieve the metadata. + final Query query; + + final VectorQueryPlatform _delegate; + + /// Returns an [VectorQuerySnapshot] with the count of the documents that match the query. + Future get({ + AggregateSource source = AggregateSource.server, + }) async { + return VectorQuerySnapshot._(await _delegate.get(source: source), query); + } + + /// Represents an [VectorQuery] over the data at a particular location for retrieving metadata + /// without retrieving the actual documents. + VectorQuery count() { + return VectorQuery._(_delegate.count(), query); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index bb9f58d5775d..b21e60404866 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -397,6 +397,44 @@ PigeonQuerySnapshot PigeonQuerySnapshot::FromEncodableList( return decoded; } +// VectorQueryOptions + +VectorQueryOptions::VectorQueryOptions(const std::string& distance_result_field, + double distance_threshold) + : distance_result_field_(distance_result_field), + distance_threshold_(distance_threshold) {} + +const std::string& VectorQueryOptions::distance_result_field() const { + return distance_result_field_; +} + +void VectorQueryOptions::set_distance_result_field(std::string_view value_arg) { + distance_result_field_ = value_arg; +} + +double VectorQueryOptions::distance_threshold() const { + return distance_threshold_; +} + +void VectorQueryOptions::set_distance_threshold(double value_arg) { + distance_threshold_ = value_arg; +} + +EncodableList VectorQueryOptions::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(distance_result_field_)); + list.push_back(EncodableValue(distance_threshold_)); + return list; +} + +VectorQueryOptions VectorQueryOptions::FromEncodableList( + const EncodableList& list) { + VectorQueryOptions decoded(std::get(list[0]), + std::get(list[1])); + return decoded; +} + // PigeonGetOptions PigeonGetOptions::PigeonGetOptions( @@ -1045,6 +1083,9 @@ EncodableValue FirebaseFirestoreHostApiCodecSerializer::ReadValueOfType( case 140: return CustomEncodableValue(PigeonTransactionCommand::FromEncodableList( std::get(ReadValue(stream)))); + case 141: + return CustomEncodableValue(VectorQueryOptions::FromEncodableList( + std::get(ReadValue(stream)))); default: return cloud_firestore_windows::FirestoreCodec::ReadValueOfType(type, stream); @@ -1159,6 +1200,13 @@ void FirebaseFirestoreHostApiCodecSerializer::WriteValue( stream); return; } + if (custom_value->type() == typeid(VectorQueryOptions)) { + stream->WriteByte(141); + WriteValue(EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), + stream); + return; + } } cloud_firestore_windows::FirestoreCodec::WriteValue(value, stream); } @@ -2072,6 +2120,98 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel->SetMessageHandler(nullptr); } } + { + auto channel = std::make_unique>( + binary_messenger, + "dev.flutter.pigeon.cloud_firestore_platform_interface." + "FirebaseFirestoreHostApi.findNearest", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_arg = args.at(0); + if (encodable_app_arg.IsNull()) { + reply(WrapError("app_arg unexpectedly null.")); + return; + } + const auto& app_arg = + std::any_cast( + std::get(encodable_app_arg)); + const auto& encodable_path_arg = args.at(1); + if (encodable_path_arg.IsNull()) { + reply(WrapError("path_arg unexpectedly null.")); + return; + } + const auto& path_arg = std::get(encodable_path_arg); + const auto& encodable_is_collection_group_arg = args.at(2); + 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); + const auto& encodable_parameters_arg = args.at(3); + if (encodable_parameters_arg.IsNull()) { + reply(WrapError("parameters_arg unexpectedly null.")); + return; + } + const auto& parameters_arg = + std::any_cast( + std::get(encodable_parameters_arg)); + const auto& encodable_options_arg = args.at(4); + if (encodable_options_arg.IsNull()) { + reply(WrapError("options_arg unexpectedly null.")); + return; + } + const auto& options_arg = std::any_cast( + std::get(encodable_options_arg)); + const auto& encodable_source_arg = args.at(5); + if (encodable_source_arg.IsNull()) { + reply(WrapError("source_arg unexpectedly null.")); + return; + } + const VectorSource& source_arg = + (VectorSource)encodable_source_arg.LongValue(); + const auto& encodable_query_options_arg = args.at(6); + if (encodable_query_options_arg.IsNull()) { + reply(WrapError("query_options_arg unexpectedly null.")); + return; + } + const auto& query_options_arg = + std::any_cast( + std::get( + encodable_query_options_arg)); + const auto& encodable_distance_measure_arg = args.at(7); + if (encodable_distance_measure_arg.IsNull()) { + reply(WrapError("distance_measure_arg unexpectedly null.")); + return; + } + const DistanceMeasure& distance_measure_arg = + (DistanceMeasure)encodable_distance_measure_arg.LongValue(); + api->FindNearest(app_arg, path_arg, is_collection_group_arg, + parameters_arg, options_arg, source_arg, + query_options_arg, distance_measure_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(CustomEncodableValue( + std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } { auto channel = std::make_unique>( binary_messenger, @@ -2290,8 +2430,8 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("request_arg unexpectedly null.")); return; } - const PersistenceCacheIndexManagerRequestEnum& request_arg = - (PersistenceCacheIndexManagerRequestEnum) + const PersistenceCacheIndexManagerRequest& request_arg = + (PersistenceCacheIndexManagerRequest) encodable_request_arg.LongValue(); api->PersistenceCacheIndexManagerRequest( app_arg, request_arg, diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 8aecb887facc..1877553f8840 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -96,6 +96,27 @@ enum class Source { cache = 2 }; +// An enumeration of firestore source types. +enum class VectorSource { + // Causes Firestore to avoid the cache, generating an error if the server + // cannot be reached. Note + // that the cache will still be updated if the server request succeeds. Also + // note that + // latency-compensation still takes effect, so any pending write operations + // will be visible in the + // returned data (merged into the server-provided data). + server = 0 +}; + +enum class DistanceMeasure { + // The cosine similarity measure. + cosine = 0, + // The euclidean distance measure. + euclidean = 1, + // The dot product distance measure. + dotProduct = 2 +}; + // The listener retrieves data and listens to updates from the local Firestore // cache only. If the cache is empty, an empty snapshot will be returned. // Snapshot events will be triggered on cache updates, like local mutations or @@ -136,7 +157,7 @@ enum class AggregateSource { // [PersistenceCacheIndexManagerRequest] represents the request types for the // persistence cache index manager. -enum class PersistenceCacheIndexManagerRequestEnum { +enum class PersistenceCacheIndexManagerRequest { enableIndexAutoCreation = 0, disableIndexAutoCreation = 1, deleteAllIndexes = 2 @@ -348,6 +369,29 @@ class PigeonQuerySnapshot { PigeonSnapshotMetadata metadata_; }; +// Generated class from Pigeon that represents data sent in messages. +class VectorQueryOptions { + public: + // Constructs an object setting all fields. + explicit VectorQueryOptions(const std::string& distance_result_field, + double distance_threshold); + + const std::string& distance_result_field() const; + void set_distance_result_field(std::string_view value_arg); + + double distance_threshold() const; + void set_distance_threshold(double value_arg); + + private: + static VectorQueryOptions FromEncodableList( + const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FirebaseFirestoreHostApi; + friend class FirebaseFirestoreHostApiCodecSerializer; + std::string distance_result_field_; + double distance_threshold_; +}; + // Generated class from Pigeon that represents data sent in messages. class PigeonGetOptions { public: @@ -707,6 +751,13 @@ class FirebaseFirestoreHostApi { const PigeonQueryParameters& parameters, const AggregateSource& source, const flutter::EncodableList& queries, bool is_collection_group, std::function reply)> result) = 0; + virtual void FindNearest( + const FirestorePigeonFirebaseApp& app, const std::string& path, + bool is_collection_group, const PigeonQueryParameters& parameters, + const PigeonGetOptions& options, const VectorSource& source, + const VectorQueryOptions& query_options, + const DistanceMeasure& distance_measure, + std::function reply)> result) = 0; virtual void WriteBatchCommit( const FirestorePigeonFirebaseApp& app, const flutter::EncodableList& writes, @@ -724,7 +775,7 @@ class FirebaseFirestoreHostApi { std::function reply)> result) = 0; virtual void PersistenceCacheIndexManagerRequest( const FirestorePigeonFirebaseApp& app, - const PersistenceCacheIndexManagerRequestEnum& request, + const PersistenceCacheIndexManagerRequest& request, std::function reply)> result) = 0; // The codec used by FirebaseFirestoreHostApi. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 01f41179836c..28e07d90b303 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -30,11 +30,12 @@ export 'src/platform_interface/platform_interface_firestore.dart'; export 'src/platform_interface/platform_interface_index_definitions.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; +export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; export 'src/platform_interface/platform_interface_transaction.dart'; +export 'src/platform_interface/platform_interface_vector_query.dart'; export 'src/platform_interface/platform_interface_write_batch.dart'; -export 'src/platform_interface/platform_interface_persistent_cache_index_manager.dart'; export 'src/platform_interface/utils/load_bundle_task_state.dart'; export 'src/set_options.dart'; export 'src/settings.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/pigeon/messages.pigeon.dart index 85af6edc5260..d5027e75a197 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 @@ -46,6 +46,26 @@ enum Source { cache, } +/// An enumeration of firestore source types. +enum VectorSource { + /// Causes Firestore to avoid the cache, generating an error if the server cannot be reached. Note + /// that the cache will still be updated if the server request succeeds. Also note that + /// latency-compensation still takes effect, so any pending write operations will be visible in the + /// returned data (merged into the server-provided data). + server, +} + +enum DistanceMeasure { + /// The cosine similarity measure. + cosine, + + /// The euclidean distance measure. + euclidean, + + /// The dot product distance measure. + dotProduct, +} + /// The listener retrieves data and listens to updates from the local Firestore cache only. /// If the cache is empty, an empty snapshot will be returned. /// Snapshot events will be triggered on cache updates, like local mutations or load bundles. @@ -301,6 +321,32 @@ class PigeonQuerySnapshot { } } +class VectorQueryOptions { + VectorQueryOptions({ + required this.distanceResultField, + required this.distanceThreshold, + }); + + String distanceResultField; + + double distanceThreshold; + + Object encode() { + return [ + distanceResultField, + distanceThreshold, + ]; + } + + static VectorQueryOptions decode(Object result) { + result as List; + return VectorQueryOptions( + distanceResultField: result[0]! as String, + distanceThreshold: result[1]! as double, + ); + } +} + class PigeonGetOptions { PigeonGetOptions({ required this.source, @@ -598,6 +644,9 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { } else if (value is PigeonTransactionCommand) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is VectorQueryOptions) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -632,6 +681,8 @@ class _FirebaseFirestoreHostApiCodec extends FirestoreMessageCodec { return PigeonSnapshotMetadata.decode(readValue(buffer)!); case 140: return PigeonTransactionCommand.decode(readValue(buffer)!); + case 141: + return VectorQueryOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -1207,6 +1258,52 @@ class FirebaseFirestoreHostApi { } } + Future findNearest( + FirestorePigeonFirebaseApp arg_app, + String arg_path, + bool arg_isCollectionGroup, + PigeonQueryParameters arg_parameters, + PigeonGetOptions arg_options, + VectorSource arg_source, + VectorQueryOptions arg_queryOptions, + DistanceMeasure arg_distanceMeasure, + ) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest', + codec, + binaryMessenger: _binaryMessenger, + ); + final List? replyList = await channel.send([ + arg_app, + arg_path, + arg_isCollectionGroup, + arg_parameters, + arg_options, + arg_source.index, + arg_queryOptions, + arg_distanceMeasure.index, + ]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as PigeonQuerySnapshot?)!; + } + } + Future writeBatchCommit( FirestorePigeonFirebaseApp arg_app, List arg_writes, 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 44cea3026120..6dfcf0e46314 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 @@ -234,6 +234,10 @@ abstract class QueryPlatform extends PlatformInterface { throw UnimplementedError('whereFilter() is not implemented'); } + QueryPlatform findNearest(FilterPlatformInterface filter) { + throw UnimplementedError('whereFilter() is not implemented'); + } + /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for /// metadata AggregateQueryPlatform count() { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart new file mode 100644 index 000000000000..f2232718db01 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart @@ -0,0 +1,36 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_vector_query_snapshot.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../../cloud_firestore_platform_interface.dart'; + +/// [VectorQueryPlatform] represents the data at a particular location for retrieving metadata +/// without retrieving the actual documents. +abstract class VectorQueryPlatform extends PlatformInterface { + VectorQueryPlatform(this.query) : super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [VectorQueryPlatform]. + /// + /// This is used by the app-facing [VectorQuery] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verify(VectorQueryPlatform instance) { + PlatformInterface.verify(instance, _token); + } + + /// The [QueryPlatform] instance to which this [VectorQueryPlatform] queries against to retrieve the metadata. + final QueryPlatform query; + + /// Returns an [VectorQuerySnapshotPlatform] with the count of the documents that match the query. + Future get({ + required VectorSource source, + }) async { + throw UnimplementedError('get() is not implemented'); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart new file mode 100644 index 000000000000..8aef0b380d21 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart @@ -0,0 +1,14 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; + +/// [VectorQuerySnapshotPlatform] represents a response to an [VectorQueryPlatform] request. +class VectorQuerySnapshotPlatform extends QuerySnapshotPlatform { + VectorQuerySnapshotPlatform( + super.docs, + super.docChanges, + super.metadata, + ) : super(); +} 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 45f42dd233c7..9863bcfbb268 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -137,6 +137,36 @@ enum Source { cache, } +/// An enumeration of firestore source types. +enum VectorSource { + /// Causes Firestore to avoid the cache, generating an error if the server cannot be reached. Note + /// that the cache will still be updated if the server request succeeds. Also note that + /// latency-compensation still takes effect, so any pending write operations will be visible in the + /// returned data (merged into the server-provided data). + server, +} + +enum DistanceMeasure { + /// The cosine similarity measure. + cosine, + + /// The euclidean distance measure. + euclidean, + + /// The dot product distance measure. + dotProduct, +} + +class VectorQueryOptions { + final String distanceResultField; + final double distanceThreshold; + + VectorQueryOptions({ + required this.distanceResultField, + required this.distanceThreshold, + }); +} + /// The listener retrieves data and listens to updates from the local Firestore cache only. /// If the cache is empty, an empty snapshot will be returned. /// Snapshot events will be triggered on cache updates, like local mutations or load bundles. @@ -411,6 +441,18 @@ abstract class FirebaseFirestoreHostApi { bool isCollectionGroup, ); + @async + PigeonQuerySnapshot findNearest( + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + VectorSource source, + VectorQueryOptions queryOptions, + DistanceMeasure distanceMeasure, + ); + @async void writeBatchCommit( FirestorePigeonFirebaseApp app, 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 fda97b2fbce8..3708f07b6112 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 @@ -56,6 +56,9 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { } else if (value is PigeonTransactionCommand) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is VectorQueryOptions) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -90,6 +93,8 @@ class _TestFirebaseFirestoreHostApiCodec extends StandardMessageCodec { return PigeonSnapshotMetadata.decode(readValue(buffer)!); case 140: return PigeonTransactionCommand.decode(readValue(buffer)!); + case 141: + return VectorQueryOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -184,6 +189,17 @@ abstract class TestFirebaseFirestoreHostApi { bool isCollectionGroup, ); + Future findNearest( + FirestorePigeonFirebaseApp app, + String path, + bool isCollectionGroup, + PigeonQueryParameters parameters, + PigeonGetOptions options, + VectorSource source, + VectorQueryOptions queryOptions, + DistanceMeasure distanceMeasure, + ); + Future writeBatchCommit( FirestorePigeonFirebaseApp app, List writes, @@ -910,6 +926,83 @@ abstract class TestFirebaseFirestoreHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest', + codec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest 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.findNearest 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.findNearest was null, expected non-null String.', + ); + final bool? arg_isCollectionGroup = (args[2] as bool?); + assert( + arg_isCollectionGroup != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null bool.', + ); + final PigeonQueryParameters? arg_parameters = + (args[3] as PigeonQueryParameters?); + assert( + arg_parameters != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null PigeonQueryParameters.', + ); + final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); + assert( + arg_options != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null PigeonGetOptions.', + ); + final VectorSource? arg_source = + args[5] == null ? null : VectorSource.values[args[5]! as int]; + assert( + arg_source != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null VectorSource.', + ); + final VectorQueryOptions? arg_queryOptions = + (args[6] as VectorQueryOptions?); + assert( + arg_queryOptions != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null VectorQueryOptions.', + ); + final DistanceMeasure? arg_distanceMeasure = + args[7] == null ? null : DistanceMeasure.values[args[7]! as int]; + assert( + arg_distanceMeasure != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null DistanceMeasure.', + ); + final PigeonQuerySnapshot output = await api.findNearest( + arg_app!, + arg_path!, + arg_isCollectionGroup!, + arg_parameters!, + arg_options!, + arg_source!, + arg_queryOptions!, + arg_distanceMeasure!, + ); + return [output]; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.writeBatchCommit', From e9a3202a650ec7b101dfe89bb30bcdbd8ffe4962 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 28 Oct 2024 10:25:34 +0100 Subject: [PATCH 2/4] more --- .../GeneratedAndroidFirebaseFirestore.java | 13 ++-- .../ios/Classes/FirestoreMessages.g.m | 18 +++--- .../ios/Classes/Public/FirestoreMessages.g.h | 3 +- .../cloud_firestore/lib/cloud_firestore.dart | 4 +- .../cloud_firestore/lib/src/query.dart | 42 ++++++++++++- .../cloud_firestore/lib/src/vector_query.dart | 2 +- .../lib/src/vector_query_snapshot.dart | 13 ++++ .../cloud_firestore/windows/messages.g.cpp | 47 ++++++++------- .../cloud_firestore/windows/messages.g.h | 4 +- .../method_channel/method_channel_query.dart | 26 ++++++++ .../method_channel_vector_query.dart | 60 +++++++++++++++++++ .../lib/src/pigeon/messages.pigeon.dart | 6 +- .../platform_interface_query.dart | 17 ++++-- .../platform_interface_vector_query.dart | 9 +++ .../pigeons/messages.dart | 3 +- .../test/pigeon/test_api.dart | 22 ++++--- 16 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore/lib/src/vector_query_snapshot.dart create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart 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 13843d1f39be..26732ac221b7 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 @@ -1924,8 +1924,9 @@ void findNearest( @NonNull String path, @NonNull Boolean isCollectionGroup, @NonNull PigeonQueryParameters parameters, - @NonNull PigeonGetOptions options, + @NonNull List queryVector, @NonNull VectorSource source, + @NonNull Long limit, @NonNull VectorQueryOptions queryOptions, @NonNull DistanceMeasure distanceMeasure, @NonNull Result result); @@ -2614,10 +2615,11 @@ public void error(Throwable error) { String pathArg = (String) args.get(1); Boolean isCollectionGroupArg = (Boolean) args.get(2); PigeonQueryParameters parametersArg = (PigeonQueryParameters) args.get(3); - PigeonGetOptions optionsArg = (PigeonGetOptions) args.get(4); + List queryVectorArg = (List) args.get(4); VectorSource sourceArg = VectorSource.values()[(int) args.get(5)]; - VectorQueryOptions queryOptionsArg = (VectorQueryOptions) args.get(6); - DistanceMeasure distanceMeasureArg = DistanceMeasure.values()[(int) args.get(7)]; + Number limitArg = (Number) args.get(6); + VectorQueryOptions queryOptionsArg = (VectorQueryOptions) args.get(7); + DistanceMeasure distanceMeasureArg = DistanceMeasure.values()[(int) args.get(8)]; Result resultCallback = new Result() { public void success(PigeonQuerySnapshot result) { @@ -2636,8 +2638,9 @@ public void error(Throwable error) { pathArg, isCollectionGroupArg, parametersArg, - optionsArg, + queryVectorArg, sourceArg, + (limitArg == null) ? null : limitArg.longValue(), queryOptionsArg, distanceMeasureArg, resultCallback); 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 bc22b55b4d63..cc7e8e2cc99d 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/FirestoreMessages.g.m @@ -1323,11 +1323,11 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, if (api) { NSCAssert([api respondsToSelector:@selector (findNearestApp: - path:isCollectionGroup:parameters:options:source:queryOptions - :distanceMeasure:completion:)], + path:isCollectionGroup:parameters:queryVector:source:limit + :queryOptions:distanceMeasure:completion:)], @"FirebaseFirestoreHostApi api (%@) doesn't respond to " - @"@selector(findNearestApp:path:isCollectionGroup:parameters:options:source:" - @"queryOptions:distanceMeasure:completion:)", + @"@selector(findNearestApp:path:isCollectionGroup:parameters:queryVector:source:" + @"limit:queryOptions:distanceMeasure:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -1335,16 +1335,18 @@ void FirebaseFirestoreHostApiSetup(id binaryMessenger, NSString *arg_path = GetNullableObjectAtIndex(args, 1); NSNumber *arg_isCollectionGroup = GetNullableObjectAtIndex(args, 2); PigeonQueryParameters *arg_parameters = GetNullableObjectAtIndex(args, 3); - PigeonGetOptions *arg_options = GetNullableObjectAtIndex(args, 4); + NSArray *arg_queryVector = GetNullableObjectAtIndex(args, 4); VectorSource arg_source = [GetNullableObjectAtIndex(args, 5) integerValue]; - VectorQueryOptions *arg_queryOptions = GetNullableObjectAtIndex(args, 6); - DistanceMeasure arg_distanceMeasure = [GetNullableObjectAtIndex(args, 7) integerValue]; + NSNumber *arg_limit = GetNullableObjectAtIndex(args, 6); + VectorQueryOptions *arg_queryOptions = GetNullableObjectAtIndex(args, 7); + DistanceMeasure arg_distanceMeasure = [GetNullableObjectAtIndex(args, 8) integerValue]; [api findNearestApp:arg_app path:arg_path isCollectionGroup:arg_isCollectionGroup parameters:arg_parameters - options:arg_options + queryVector:arg_queryVector source:arg_source + limit:arg_limit queryOptions:arg_queryOptions distanceMeasure:arg_distanceMeasure completion:^(PigeonQuerySnapshot *_Nullable output, 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 b2c7c27ec4b2..c4ea951329d5 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 @@ -441,8 +441,9 @@ NSObject *FirebaseFirestoreHostApiGetCodec(void); path:(NSString *)path isCollectionGroup:(NSNumber *)isCollectionGroup parameters:(PigeonQueryParameters *)parameters - options:(PigeonGetOptions *)options + queryVector:(NSArray *)queryVector source:(VectorSource)source + limit:(NSNumber *)limit queryOptions:(VectorQueryOptions *)queryOptions distanceMeasure:(DistanceMeasure)distanceMeasure completion: diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 689c9aefc32a..a5e47f71bd8b 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -9,7 +9,6 @@ import 'dart:convert'; // ignore: unnecessary_import import 'dart:typed_data'; -import 'package:cloud_firestore/src/vector_query.dart'; import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' @@ -31,6 +30,7 @@ export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte ServerTimestampBehavior, SetOptions, ListenSource, + VectorSource, DocumentChangeType, PersistenceSettings, Settings, @@ -67,4 +67,6 @@ part 'src/query_snapshot.dart'; part 'src/snapshot_metadata.dart'; part 'src/transaction.dart'; part 'src/utils/codec_utility.dart'; +part 'src/vector_query.dart'; +part 'src/vector_query_snapshot.dart'; part 'src/write_batch.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index ecafbca65565..dc33ba1abf74 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -229,7 +229,8 @@ abstract class Query { VectorQuery findNearest( String field, { - required Object queryVector, // List or VectorValue + /// List or VectorValue + required Object queryVector, required int limit, required DistanceMeasure distanceMeasure, required VectorQueryOptions options, @@ -908,6 +909,27 @@ class _JsonQuery implements Query> { this, ); } + + @override + VectorQuery findNearest( + String field, { + /// List or VectorValue + required Object queryVector, + required int limit, + required DistanceMeasure distanceMeasure, + required VectorQueryOptions options, + }) { + return VectorQuery._( + _delegate.findNearest( + field, + queryVector: queryVector, + limit: limit, + distanceMeasure: distanceMeasure, + options: options, + ), + this, + ); + } } class _WithConverterQuery implements Query { @@ -1152,4 +1174,22 @@ class _WithConverterQuery implements Query { aggregateField30, ); } + + @override + VectorQuery findNearest( + String field, { + /// List or VectorValue + required Object queryVector, + required int limit, + required DistanceMeasure distanceMeasure, + required VectorQueryOptions options, + }) { + return _originalQuery.findNearest( + field, + queryVector: queryVector, + limit: limit, + distanceMeasure: distanceMeasure, + options: options, + ); + } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart index 489fbb7c8608..dca47387099a 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart @@ -19,7 +19,7 @@ class VectorQuery { /// Returns an [VectorQuerySnapshot] with the count of the documents that match the query. Future get({ - AggregateSource source = AggregateSource.server, + VectorSource source = VectorSource.server, }) async { return VectorQuerySnapshot._(await _delegate.get(source: source), query); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query_snapshot.dart new file mode 100644 index 000000000000..6e7da44306fd --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query_snapshot.dart @@ -0,0 +1,13 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// [VectorQuerySnapshot] represents a response to an [VectorQuery] request. +class VectorQuerySnapshot extends _JsonQuerySnapshot { + VectorQuerySnapshot._( + super.firestore, + super.delegate, + ) : super(); +} diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp index b21e60404866..8c42e72e15cc 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.cpp @@ -2161,13 +2161,13 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& parameters_arg = std::any_cast( std::get(encodable_parameters_arg)); - const auto& encodable_options_arg = args.at(4); - if (encodable_options_arg.IsNull()) { - reply(WrapError("options_arg unexpectedly null.")); + const auto& encodable_query_vector_arg = args.at(4); + if (encodable_query_vector_arg.IsNull()) { + reply(WrapError("query_vector_arg unexpectedly null.")); return; } - const auto& options_arg = std::any_cast( - std::get(encodable_options_arg)); + const auto& query_vector_arg = + std::get(encodable_query_vector_arg); const auto& encodable_source_arg = args.at(5); if (encodable_source_arg.IsNull()) { reply(WrapError("source_arg unexpectedly null.")); @@ -2175,7 +2175,13 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, } const VectorSource& source_arg = (VectorSource)encodable_source_arg.LongValue(); - const auto& encodable_query_options_arg = args.at(6); + const auto& encodable_limit_arg = args.at(6); + if (encodable_limit_arg.IsNull()) { + reply(WrapError("limit_arg unexpectedly null.")); + return; + } + const int64_t limit_arg = encodable_limit_arg.LongValue(); + const auto& encodable_query_options_arg = args.at(7); if (encodable_query_options_arg.IsNull()) { reply(WrapError("query_options_arg unexpectedly null.")); return; @@ -2184,26 +2190,27 @@ void FirebaseFirestoreHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, std::any_cast( std::get( encodable_query_options_arg)); - const auto& encodable_distance_measure_arg = args.at(7); + const auto& encodable_distance_measure_arg = args.at(8); if (encodable_distance_measure_arg.IsNull()) { reply(WrapError("distance_measure_arg unexpectedly null.")); return; } const DistanceMeasure& distance_measure_arg = (DistanceMeasure)encodable_distance_measure_arg.LongValue(); - api->FindNearest(app_arg, path_arg, is_collection_group_arg, - parameters_arg, options_arg, source_arg, - query_options_arg, distance_measure_arg, - [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(CustomEncodableValue( - std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + api->FindNearest( + app_arg, path_arg, is_collection_group_arg, parameters_arg, + query_vector_arg, source_arg, limit_arg, query_options_arg, + distance_measure_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } diff --git a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h index 1877553f8840..c7ff8a7b2ce7 100644 --- a/packages/cloud_firestore/cloud_firestore/windows/messages.g.h +++ b/packages/cloud_firestore/cloud_firestore/windows/messages.g.h @@ -754,8 +754,8 @@ class FirebaseFirestoreHostApi { virtual void FindNearest( const FirestorePigeonFirebaseApp& app, const std::string& path, bool is_collection_group, const PigeonQueryParameters& parameters, - const PigeonGetOptions& options, const VectorSource& source, - const VectorQueryOptions& query_options, + const flutter::EncodableList& query_vector, const VectorSource& source, + int64_t limit, const VectorQueryOptions& query_options, const DistanceMeasure& distance_measure, std::function reply)> result) = 0; virtual void WriteBatchCommit( 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 6ae2ec548688..d66c05284108 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,7 @@ 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/method_channel/method_channel_vector_query.dart'; import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_query.dart' as query; import 'package:collection/collection.dart'; @@ -417,6 +418,31 @@ class MethodChannelQuery extends QueryPlatform { ); } + @override + VectorQueryPlatform findNearest( + String field, { + /// List or VectorValue + required Object queryVector, + required int limit, + required DistanceMeasure distanceMeasure, + required VectorQueryOptions options, + }) { + return MethodChannelVectorQuery( + firestore, + this, + _pigeonParameters, + _pointer.path, + pigeonApp, + queryVector is List + ? queryVector + : (queryVector as VectorValue).toList(), + limit, + distanceMeasure, + options, + isCollectionGroupQuery, + ); + } + @override bool operator ==(Object other) { return runtimeType == other.runtimeType && diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart new file mode 100644 index 000000000000..491ddb5cc26f --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart @@ -0,0 +1,60 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_query_snapshot.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/utils/exception.dart'; + +import '../../cloud_firestore_platform_interface.dart'; +import 'method_channel_firestore.dart'; + +/// An implementation of [VectorQueryPlatform] for the [MethodChannel] +class MethodChannelVectorQuery extends VectorQueryPlatform { + MethodChannelVectorQuery( + this.firestore, + query, + this._pigeonParameters, + this._path, + this._pigeonApp, + this._queryVector, + this._limit, + this._distanceMeasure, + this._options, + this._isCollectionGroupQuery, + ) : super(query); + + final FirebaseFirestorePlatform firestore; + final FirestorePigeonFirebaseApp _pigeonApp; + final String _path; + final PigeonQueryParameters _pigeonParameters; + final bool _isCollectionGroupQuery; + + final int _limit; + final DistanceMeasure _distanceMeasure; + final List _queryVector; + final VectorQueryOptions _options; + + @override + Future get({ + required VectorSource source, + }) async { + try { + final PigeonQuerySnapshot result = + await MethodChannelFirebaseFirestore.pigeonChannel.findNearest( + _pigeonApp, + _path, + _isCollectionGroupQuery, + _pigeonParameters, + _queryVector, + source, + _limit, + _options, + _distanceMeasure, + ); + + return MethodChannelQuerySnapshot(firestore, result); + } catch (e, stack) { + convertPlatformException(e, stack); + } + } +} 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 d5027e75a197..acb03098f853 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 @@ -1263,8 +1263,9 @@ class FirebaseFirestoreHostApi { String arg_path, bool arg_isCollectionGroup, PigeonQueryParameters arg_parameters, - PigeonGetOptions arg_options, + List arg_queryVector, VectorSource arg_source, + int arg_limit, VectorQueryOptions arg_queryOptions, DistanceMeasure arg_distanceMeasure, ) async { @@ -1278,8 +1279,9 @@ class FirebaseFirestoreHostApi { arg_path, arg_isCollectionGroup, arg_parameters, - arg_options, + arg_queryVector, arg_source.index, + arg_limit, arg_queryOptions, arg_distanceMeasure.index, ]) as List?; 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 6dfcf0e46314..08a405d0f4bf 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 @@ -234,10 +234,6 @@ abstract class QueryPlatform extends PlatformInterface { throw UnimplementedError('whereFilter() is not implemented'); } - QueryPlatform findNearest(FilterPlatformInterface filter) { - throw UnimplementedError('whereFilter() is not implemented'); - } - /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for /// metadata AggregateQueryPlatform count() { @@ -279,6 +275,17 @@ abstract class QueryPlatform extends PlatformInterface { throw UnimplementedError('aggregate() is not implemented'); } + VectorQueryPlatform findNearest( + String field, { + /// List or VectorValue + required Object queryVector, + required int limit, + required DistanceMeasure distanceMeasure, + required VectorQueryOptions options, + }) { + throw UnimplementedError('findNearest() is not implemented'); + } + /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for /// metadata /// @@ -296,7 +303,7 @@ abstract class QueryPlatform extends PlatformInterface { } } -abstract class AggregateField {} +class AggregateField {} /// Create a CountAggregateField object that can be used to compute /// the count of documents in the result set of a query. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart index f2232718db01..1b2ffea2d31e 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart @@ -34,3 +34,12 @@ abstract class VectorQueryPlatform extends PlatformInterface { throw UnimplementedError('get() is not implemented'); } } + +class VectorValue { + final List value; + VectorValue(this.value); + + List toList() { + return value; + } +} 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 9863bcfbb268..0430ad0b6173 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/pigeons/messages.dart @@ -447,8 +447,9 @@ abstract class FirebaseFirestoreHostApi { String path, bool isCollectionGroup, PigeonQueryParameters parameters, - PigeonGetOptions options, + List queryVector, VectorSource source, + int limit, VectorQueryOptions queryOptions, DistanceMeasure distanceMeasure, ); 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 3708f07b6112..d558bb8a0d7f 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 @@ -194,8 +194,9 @@ abstract class TestFirebaseFirestoreHostApi { String path, bool isCollectionGroup, PigeonQueryParameters parameters, - PigeonGetOptions options, + List queryVector, VectorSource source, + int limit, VectorQueryOptions queryOptions, DistanceMeasure distanceMeasure, ); @@ -966,10 +967,11 @@ abstract class TestFirebaseFirestoreHostApi { arg_parameters != null, 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null PigeonQueryParameters.', ); - final PigeonGetOptions? arg_options = (args[4] as PigeonGetOptions?); + final List? arg_queryVector = + (args[4] as List?)?.cast(); assert( - arg_options != null, - 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null PigeonGetOptions.', + arg_queryVector != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null List.', ); final VectorSource? arg_source = args[5] == null ? null : VectorSource.values[args[5]! as int]; @@ -977,14 +979,19 @@ abstract class TestFirebaseFirestoreHostApi { arg_source != null, 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null VectorSource.', ); + final int? arg_limit = (args[6] as int?); + assert( + arg_limit != null, + 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null int.', + ); final VectorQueryOptions? arg_queryOptions = - (args[6] as VectorQueryOptions?); + (args[7] as VectorQueryOptions?); assert( arg_queryOptions != null, 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null VectorQueryOptions.', ); final DistanceMeasure? arg_distanceMeasure = - args[7] == null ? null : DistanceMeasure.values[args[7]! as int]; + args[8] == null ? null : DistanceMeasure.values[args[8]! as int]; assert( arg_distanceMeasure != null, 'Argument for dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.findNearest was null, expected non-null DistanceMeasure.', @@ -994,8 +1001,9 @@ abstract class TestFirebaseFirestoreHostApi { arg_path!, arg_isCollectionGroup!, arg_parameters!, - arg_options!, + arg_queryVector!, arg_source!, + arg_limit!, arg_queryOptions!, arg_distanceMeasure!, ); From 6ff9a05ca25cf47f9a31aa7c3db5b40788d69105 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Thu, 7 Nov 2024 10:57:58 +0100 Subject: [PATCH 3/4] fix typing --- .../method_channel_vector_query.dart | 7 +-- .../method_channel_vector_query_snapshot.dart | 49 +++++++++++++++++++ ...tform_interface_vector_query_snapshot.dart | 43 +++++++++++++--- 3 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart index 491ddb5cc26f..fe1c51005637 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart @@ -2,8 +2,9 @@ // 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/src/method_channel/method_channel_query_snapshot.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_vector_query_snapshot.dart'; import 'package:cloud_firestore_platform_interface/src/method_channel/utils/exception.dart'; +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_vector_query_snapshot.dart'; import '../../cloud_firestore_platform_interface.dart'; import 'method_channel_firestore.dart'; @@ -35,7 +36,7 @@ class MethodChannelVectorQuery extends VectorQueryPlatform { final VectorQueryOptions _options; @override - Future get({ + Future get({ required VectorSource source, }) async { try { @@ -52,7 +53,7 @@ class MethodChannelVectorQuery extends VectorQueryPlatform { _distanceMeasure, ); - return MethodChannelQuerySnapshot(firestore, result); + return MethodChannelVectorQuerySnapshot(firestore, result); } catch (e, stack) { convertPlatformException(e, stack); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart new file mode 100644 index 000000000000..edecfd9f95df --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart @@ -0,0 +1,49 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_vector_query_snapshot.dart'; +import 'package:collection/collection.dart'; + +import 'method_channel_document_change.dart'; + +/// An implementation of [QuerySnapshotPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelVectorQuerySnapshot extends VectorQuerySnapshotPlatform { + /// Creates a [MethodChannelVectorQuerySnapshot] from the given [data] + MethodChannelVectorQuerySnapshot( + FirebaseFirestorePlatform firestore, PigeonQuerySnapshot data) + : super( + data.documents + .map((document) { + if (document == null) { + return null; + } + return DocumentSnapshotPlatform( + firestore, + document.path, + document.data, + document.metadata, + ); + }) + .whereNotNull() + .toList(), + data.documentChanges + .map((documentChange) { + if (documentChange == null) { + return null; + } + return MethodChannelDocumentChange( + firestore, + documentChange, + ); + }) + .whereNotNull() + .toList(), + SnapshotMetadataPlatform( + data.metadata.hasPendingWrites, + data.metadata.isFromCache, + )); +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart index 8aef0b380d21..df124ce8b4f0 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart @@ -3,12 +3,43 @@ // BSD-style license that can be found in the LICENSE file. import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -/// [VectorQuerySnapshotPlatform] represents a response to an [VectorQueryPlatform] request. -class VectorQuerySnapshotPlatform extends QuerySnapshotPlatform { +/// A interface that contains zero or more [DocumentSnapshotPlatform] objects +/// representing the results of a query. +/// +/// The documents can be accessed as a list by calling [docs()] and the number of documents +/// can be determined by calling [size()]. +class VectorQuerySnapshotPlatform extends PlatformInterface { + /// Create a [VectorQuerySnapshotPlatform] VectorQuerySnapshotPlatform( - super.docs, - super.docChanges, - super.metadata, - ) : super(); + this.docs, + this.docChanges, + this.metadata, + ) : super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [VectorQuerySnapshotPlatform]. + /// + /// This is used by the app-facing [QuerySnapshot] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verify(VectorQuerySnapshotPlatform instance) { + PlatformInterface.verify(instance, _token); + } + + /// Gets a list of all the documents included in this [VectorQuerySnapshotPlatform] + final List docs; + + /// An array of the documents that changed since the last snapshot. If this + /// is the first snapshot, all documents will be in the list as Added changes. + final List docChanges; + + /// Metadata for the document + final SnapshotMetadataPlatform metadata; + + /// The number of documents in this [VectorQuerySnapshotPlatform]. + int get size => docs.length; } From d02b3b071bab5cb32e55fafec1b47506eecbe436 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Thu, 7 Nov 2024 11:18:59 +0100 Subject: [PATCH 4/4] Android --- .../FlutterFirebaseFirestorePlugin.java | 28 +++++++++++ .../cloud_firestore/lib/src/query.dart | 1 + .../cloud_firestore/lib/src/vector_query.dart | 15 +++--- .../method_channel_vector_query.dart | 7 ++- .../method_channel_vector_query_snapshot.dart | 49 ------------------- .../platform_interface_vector_query.dart | 5 +- ...tform_interface_vector_query_snapshot.dart | 45 ----------------- 7 files changed, 41 insertions(+), 109 deletions(-) delete mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart delete mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart 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 35e4697f7128..f96a91c43164 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 @@ -859,6 +859,34 @@ public void aggregateQuery( }); } + @Override + public void findNearest(@NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app, @NonNull String path, @NonNull Boolean isCollectionGroup, @NonNull GeneratedAndroidFirebaseFirestore.PigeonQueryParameters parameters, @NonNull List queryVector, @NonNull GeneratedAndroidFirebaseFirestore.VectorSource source, @NonNull Long limit, @NonNull GeneratedAndroidFirebaseFirestore.VectorQueryOptions queryOptions, @NonNull GeneratedAndroidFirebaseFirestore.DistanceMeasure distanceMeasure, @NonNull GeneratedAndroidFirebaseFirestore.Result result) { + Query query = PigeonParser.parseQuery(getFirestoreFromPigeon(app), path, isCollectionGroup, parameters); + + if (query == null) { + result.error( + new GeneratedAndroidFirebaseFirestore.FlutterError( + "invalid_query", + "An error occurred while parsing query arguments, see native logs for more information. Please report this issue.", + null)); + return; + } + + cachedThreadPool.execute( + () -> { + try { + QuerySnapshot querySnapshot = Tasks.await(query.findNearest(queryVector, PigeonParser.parseVectorSource(source), limit, PigeonParser.parseVectorQueryOptions(queryOptions), PigeonParser.parseDistanceMeasure(distanceMeasure))); + + result.success( + PigeonParser.toPigeonQuerySnapshot( + querySnapshot, + DocumentSnapshot.ServerTimestampBehavior.NONE)); + } catch (Exception e) { + ExceptionConverter.sendErrorToFlutter(result, e); + } + }); + } + @Override public void writeBatchCommit( @NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index dc33ba1abf74..9b723840b24d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -920,6 +920,7 @@ class _JsonQuery implements Query> { required VectorQueryOptions options, }) { return VectorQuery._( + firestore, _delegate.findNearest( field, queryVector: queryVector, diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart index dca47387099a..229b5ec9b9f1 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/vector_query.dart @@ -7,10 +7,12 @@ part of cloud_firestore; /// [VectorQuery] represents the data at a particular location for retrieving metadata /// without retrieving the actual documents. class VectorQuery { - VectorQuery._(this._delegate, this.query) { + VectorQuery._(this._firestore, this._delegate, this.query) { VectorQueryPlatform.verify(_delegate); } + final FirebaseFirestore _firestore; + /// [Query] represents the query over the data at a particular location used by the [VectorQuery] to /// retrieve the metadata. final Query query; @@ -21,12 +23,9 @@ class VectorQuery { Future get({ VectorSource source = VectorSource.server, }) async { - return VectorQuerySnapshot._(await _delegate.get(source: source), query); - } - - /// Represents an [VectorQuery] over the data at a particular location for retrieving metadata - /// without retrieving the actual documents. - VectorQuery count() { - return VectorQuery._(_delegate.count(), query); + return VectorQuerySnapshot._( + _firestore, + await _delegate.get(source: source), + ); } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart index fe1c51005637..af02cc5ca8f5 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query.dart @@ -2,9 +2,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/src/method_channel/method_channel_vector_query_snapshot.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_query_snapshot.dart'; import 'package:cloud_firestore_platform_interface/src/method_channel/utils/exception.dart'; -import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_vector_query_snapshot.dart'; import '../../cloud_firestore_platform_interface.dart'; import 'method_channel_firestore.dart'; @@ -36,7 +35,7 @@ class MethodChannelVectorQuery extends VectorQueryPlatform { final VectorQueryOptions _options; @override - Future get({ + Future get({ required VectorSource source, }) async { try { @@ -53,7 +52,7 @@ class MethodChannelVectorQuery extends VectorQueryPlatform { _distanceMeasure, ); - return MethodChannelVectorQuerySnapshot(firestore, result); + return MethodChannelQuerySnapshot(firestore, result); } catch (e, stack) { convertPlatformException(e, stack); } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart deleted file mode 100644 index edecfd9f95df..000000000000 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_vector_query_snapshot.dart +++ /dev/null @@ -1,49 +0,0 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2017, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; -import 'package:cloud_firestore_platform_interface/src/platform_interface/platform_interface_vector_query_snapshot.dart'; -import 'package:collection/collection.dart'; - -import 'method_channel_document_change.dart'; - -/// An implementation of [QuerySnapshotPlatform] that uses [MethodChannel] to -/// communicate with Firebase plugins. -class MethodChannelVectorQuerySnapshot extends VectorQuerySnapshotPlatform { - /// Creates a [MethodChannelVectorQuerySnapshot] from the given [data] - MethodChannelVectorQuerySnapshot( - FirebaseFirestorePlatform firestore, PigeonQuerySnapshot data) - : super( - data.documents - .map((document) { - if (document == null) { - return null; - } - return DocumentSnapshotPlatform( - firestore, - document.path, - document.data, - document.metadata, - ); - }) - .whereNotNull() - .toList(), - data.documentChanges - .map((documentChange) { - if (documentChange == null) { - return null; - } - return MethodChannelDocumentChange( - firestore, - documentChange, - ); - }) - .whereNotNull() - .toList(), - SnapshotMetadataPlatform( - data.metadata.hasPendingWrites, - data.metadata.isFromCache, - )); -} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart index 1b2ffea2d31e..11ce45c901e0 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query.dart @@ -2,7 +2,6 @@ // 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/src/platform_interface/platform_interface_vector_query_snapshot.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../../cloud_firestore_platform_interface.dart'; @@ -27,8 +26,8 @@ abstract class VectorQueryPlatform extends PlatformInterface { /// The [QueryPlatform] instance to which this [VectorQueryPlatform] queries against to retrieve the metadata. final QueryPlatform query; - /// Returns an [VectorQuerySnapshotPlatform] with the count of the documents that match the query. - Future get({ + /// Returns an [VectorQuerySnapshotPlatform] . + Future get({ required VectorSource source, }) async { throw UnimplementedError('get() is not implemented'); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart deleted file mode 100644 index df124ce8b4f0..000000000000 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_vector_query_snapshot.dart +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -/// A interface that contains zero or more [DocumentSnapshotPlatform] objects -/// representing the results of a query. -/// -/// The documents can be accessed as a list by calling [docs()] and the number of documents -/// can be determined by calling [size()]. -class VectorQuerySnapshotPlatform extends PlatformInterface { - /// Create a [VectorQuerySnapshotPlatform] - VectorQuerySnapshotPlatform( - this.docs, - this.docChanges, - this.metadata, - ) : super(token: _token); - - static final Object _token = Object(); - - /// Throws an [AssertionError] if [instance] does not extend - /// [VectorQuerySnapshotPlatform]. - /// - /// This is used by the app-facing [QuerySnapshot] to ensure that - /// the object in which it's going to delegate calls has been - /// constructed properly. - static void verify(VectorQuerySnapshotPlatform instance) { - PlatformInterface.verify(instance, _token); - } - - /// Gets a list of all the documents included in this [VectorQuerySnapshotPlatform] - final List docs; - - /// An array of the documents that changed since the last snapshot. If this - /// is the first snapshot, all documents will be in the list as Added changes. - final List docChanges; - - /// Metadata for the document - final SnapshotMetadataPlatform metadata; - - /// The number of documents in this [VectorQuerySnapshotPlatform]. - int get size => docs.length; -}