Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(firestore): add support for VectorValue #16476

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
import com.google.firebase.firestore.GeoPoint;
import com.google.firebase.firestore.VectorValue;
import com.google.firebase.firestore.LoadBundleTaskProgress;
import com.google.firebase.firestore.MemoryCacheSettings;
import com.google.firebase.firestore.PersistentCacheSettings;
Expand Down Expand Up @@ -55,6 +56,7 @@ class FlutterFirebaseFirestoreMessageCodec extends StandardMessageCodec {
private static final byte DATA_TYPE_FIRESTORE_INSTANCE = (byte) 196;
private static final byte DATA_TYPE_FIRESTORE_QUERY = (byte) 197;
private static final byte DATA_TYPE_FIRESTORE_SETTINGS = (byte) 198;
private static final byte DATA_TYPE_VECTOR_VALUE = (byte) 199;

@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
Expand All @@ -70,6 +72,9 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) {
writeAlignment(stream, 8);
writeDouble(stream, ((GeoPoint) value).getLatitude());
writeDouble(stream, ((GeoPoint) value).getLongitude());
} else if (value instanceof VectorValue) {
stream.write(DATA_TYPE_VECTOR_VALUE);
writeValue(stream, ((VectorValue) value).toArray());
} else if (value instanceof DocumentReference) {
stream.write(DATA_TYPE_DOCUMENT_REFERENCE);
FirebaseFirestore firestore = ((DocumentReference) value).getFirestore();
Expand Down Expand Up @@ -238,6 +243,8 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
case DATA_TYPE_GEO_POINT:
readAlignment(buffer, 8);
return new GeoPoint(buffer.getDouble(), buffer.getDouble());
case DATA_TYPE_VECTOR_VALUE:
return new VectorValue(readValue(buffer));
case DATA_TYPE_DOCUMENT_REFERENCE:
FirebaseFirestore firestore = (FirebaseFirestore) readValue(buffer);
final String path = (String) readValue(buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ void runDocumentReferenceTests() {
'null': null,
'timestamp': Timestamp.now(),
'geopoint': const GeoPoint(1, 2),
'vectorValue': const VectorValue([1, 2, 3]),
'reference': firestore.doc('foo/bar'),
'nan': double.nan,
'infinity': double.infinity,
Expand Down Expand Up @@ -444,6 +445,9 @@ void runDocumentReferenceTests() {
expect(data['geopoint'], isA<GeoPoint>());
expect((data['geopoint'] as GeoPoint).latitude, equals(1));
expect((data['geopoint'] as GeoPoint).longitude, equals(2));
expect(data['vectorValue'], isA<VectorValue>());
expect(
(data['vectorValue'] as VectorValue).toArray(), equals([1, 2, 3]));
expect(data['reference'], isA<DocumentReference>());
expect((data['reference'] as DocumentReference).id, equals('bar'));
expect(data['nan'].isNaN, equals(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import 'settings_e2e.dart';
import 'snapshot_metadata_e2e.dart';
import 'timestamp_e2e.dart';
import 'transaction_e2e.dart';
import 'write_batch_e2e.dart';
import 'vector_value_e2e.dart';
import 'web_snapshot_listeners.dart';
import 'write_batch_e2e.dart';

bool kUseFirestoreEmulator = true;

Expand Down Expand Up @@ -52,6 +53,7 @@ void main() {
runDocumentReferenceTests();
runFieldValueTests();
runGeoPointTests();
runVectorValueTests();
runQueryTests();
runSnapshotMetadataTests();
runTimestampTests();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2020, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_test/flutter_test.dart';

void runVectorValueTests() {
group('$VectorValue', () {
late FirebaseFirestore /*?*/ firestore;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding e2e tests for potential edge cases, particularly for JS. For instance, not sure it would handle empty vector, single dimension, 2048 (maximum) dimensions, maximum dimensions + 1, very large values in vector, floats, negative values.


setUpAll(() async {
firestore = FirebaseFirestore.instance;
});

Future<DocumentReference<Map<String, dynamic>>> initializeTest(
String path,
) async {
String prefixedPath = 'flutter-tests/$path';
await firestore.doc(prefixedPath).delete();
return firestore.doc(prefixedPath);
}

testWidgets('sets a $VectorValue & returns one', (_) async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('vector-value');

await doc.set({
'foo': const VectorValue([10.0, -10.0])
});

DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();

VectorValue vectorValue = snapshot.data()!['foo'];
expect(vectorValue, isA<VectorValue>());
expect(vectorValue.toArray(), equals([10.0, -10.0]));
});

testWidgets('updates a $VectorValue & returns', (_) async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('vector-value-update');

await doc.set({
'foo': const VectorValue([10.0, -10.0])
});

await doc.update({
'foo': const VectorValue([-10.0, 10.0])
});

DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();

VectorValue vectorValue = snapshot.data()!['foo'];
expect(vectorValue, isA<VectorValue>());
expect(vectorValue.toArray(), equals([-10.0, 10.0]));
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ - (id)readValueOfType:(UInt8)type {
[self readBytes:&longitude length:8];
return [[FIRGeoPoint alloc] initWithLatitude:latitude longitude:longitude];
}
case FirestoreDataTypeVectorValue: {
return [[FIRVectorValue alloc] initWithArrayValue:[self readValue]];
}
case FirestoreDataTypeDocumentReference: {
FIRFirestore *firestore = [self readValue];
NSString *documentPath = [self readValue];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ - (void)writeValue:(id)value {
[self writeAlignment:8];
[self writeBytes:(UInt8 *)&latitude length:8];
[self writeBytes:(UInt8 *)&longitude length:8];
} else if ([value isKindOfClass:[FIRVectorValue class]]) {
FIRVectorValue *vector = value;
[self writeByte:FirestoreDataTypeArrayUnion];
[self writeValue:vector.internalValue];
} else if ([value isKindOfClass:[FIRDocumentReference class]]) {
FIRDocumentReference *document = value;
NSString *documentPath = [document path];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ typedef NS_ENUM(UInt8, FirestoreDataType) {
FirestoreDataTypeFirestoreInstance = 196,
FirestoreDataTypeFirestoreQuery = 197,
FirestoreDataTypeFirestoreSettings = 198,
FirestoreDataTypeVectorValue = 199,
};

@interface FLTFirebaseFirestoreReaderWriter : FlutterStandardReaderWriter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte
FieldPath,
Blob,
GeoPoint,
VectorValue,
Timestamp,
Source,
GetOptions,
Expand Down Expand Up @@ -59,11 +60,11 @@ 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';
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';
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@ 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_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';
export 'src/snapshot_metadata.dart';
export 'src/timestamp.dart';
export 'src/vector_value.dart';

/// Helper method exposed to determine whether a given [collectionPath] points to
/// a valid Firestore collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'dart:typed_data';

import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart';
import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_field_value.dart';
import 'package:cloud_firestore_platform_interface/src/vector_value.dart';
import 'package:firebase_core/firebase_core.dart';
// TODO(Lyokone): remove once we bump Flutter SDK min version to 3.3
// ignore: unnecessary_import
Expand Down Expand Up @@ -43,6 +44,7 @@ class FirestoreMessageCodec extends StandardMessageCodec {
static const int _kFirestoreInstance = 196;
static const int _kFirestoreQuery = 197;
static const int _kFirestoreSettings = 198;
static const int _kVectorValue = 199;

static const Map<FieldValueType, int> _kFieldValueCodes =
<FieldValueType, int>{
Expand Down Expand Up @@ -124,6 +126,9 @@ class FirestoreMessageCodec extends StandardMessageCodec {
buffer.putUint8(_kInfinity);
} else if (value == double.negativeInfinity) {
buffer.putUint8(_kNegativeInfinity);
} else if (value is VectorValue) {
buffer.putUint8(_kVectorValue);
writeValue(buffer, value.toArray());
} else {
super.writeValue(buffer, value);
}
Expand All @@ -148,6 +153,8 @@ class FirestoreMessageCodec extends StandardMessageCodec {
FirebaseFirestorePlatform.instanceFor(
app: app, databaseId: databaseId);
return firestore.doc(path);
case _kVectorValue:
return VectorValue(readValue(buffer)! as List<double>);
case _kBlob:
final int length = readSize(buffer);
final List<int> bytes = buffer.getUint8List(length);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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:flutter/foundation.dart';

/// Represents a vector value by an array of doubles.
@immutable
class VectorValue {
/// Create [VectorValue] instance.
const VectorValue(this._value);

final List<double> _value; // ignore: public_member_api_docs

@override
bool operator ==(Object other) =>
other is VectorValue && listEquals(other._value, _value);

@override
int get hashCode => _value.hashCode;

@override
String toString() => 'VectorValue(value: $_value)';

/// Converts a [VectorValue] to a [List] of [double].
List<double> toArray() => _value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,20 @@ extension GeoPointJsImplExtension on GeoPointJsImpl {
external JSBoolean isEqual(JSObject other);
}

@JS('VectorValue')
@staticInterop
external VectorValueJsImpl get VectorValueConstructor;

@JS('VectorValue')
@staticInterop
class VectorValueJsImpl {
external factory VectorValueJsImpl(JSArray array);
}

extension VectorValueJsImplExtension on VectorValueJsImpl {
external JSArray toArray();
}

@JS('Bytes')
@staticInterop
external BytesJsImpl get BytesConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ dynamic dartify(dynamic object) {
if (jsObject.instanceof(GeoPointConstructor as JSFunction)) {
return jsObject;
}
if (jsObject.instanceof(VectorValueConstructor as JSFunction)) {
return jsObject;
}
if (jsObject.instanceof(TimestampJsConstructor as JSFunction)) {
final castedJSObject = jsObject as TimestampJsImpl;
return Timestamp(
Expand Down Expand Up @@ -99,6 +102,12 @@ JSAny? jsify(Object? dartObject) {
return dartObject as JSAny;
}

// Cannot be done with Dart 3.2 constraints
// ignore: invalid_runtime_check_with_js_interop_types
if (dartObject is VectorValueJsImpl) {
return dartObject as JSAny;
}

if (dartObject is JSAny Function()) {
return dartObject.toJS;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ class DecodeUtility {
value.instanceof(GeoPointConstructor as JSFunction)) {
return GeoPoint((value as GeoPointJsImpl).latitude.toDartDouble,
(value as GeoPointJsImpl).longitude.toDartDouble);
// Cannot be done with Dart 3.2 constraints
// ignore: invalid_runtime_check_with_js_interop_types
} else if (value is JSObject &&
value.instanceof(VectorValueConstructor as JSFunction)) {
return VectorValue((value as VectorValueJsImpl)
.toArray()
.toDart
.map((JSAny? e) => (e! as JSNumber).toDartDouble)
.toList());
} else if (value is DateTime) {
return Timestamp.fromDate(value);
// Cannot be done with Dart 3.2 constraints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class EncodeUtility {
} else if (value is GeoPoint) {
return firestore_interop.GeoPointJsImpl(
value.latitude.toJS, value.longitude.toJS);
} else if (value is VectorValue) {
return firestore_interop.VectorValueJsImpl(
value.toArray().jsify()! as JSArray);
} else if (value is Blob) {
return firestore_interop.BytesJsImpl.fromUint8Array(value.bytes.toJS);
} else if (value is DocumentReferenceWeb) {
Expand Down
Loading