aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Firestore/Example/SwiftBuildTest/main.swift4
-rw-r--r--Firestore/Example/Tests/Model/FSTMutationTests.mm62
-rw-r--r--Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm6
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.h8
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.mm24
-rw-r--r--Firestore/Source/API/FIRFieldValue+Internal.h12
-rw-r--r--Firestore/Source/API/FIRFieldValue.mm38
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.mm45
-rw-r--r--Firestore/Source/Public/FIRFieldValue.h23
-rw-r--r--Firestore/core/src/firebase/firestore/model/transform_operations.h67
-rw-r--r--Firestore/core/test/firebase/firestore/model/transform_operations_test.cc3
11 files changed, 272 insertions, 20 deletions
diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift
index 3087085..691adf6 100644
--- a/Firestore/Example/SwiftBuildTest/main.swift
+++ b/Firestore/Example/SwiftBuildTest/main.swift
@@ -100,6 +100,10 @@ func writeDocument(at docRef: DocumentReference) {
let updateData = [
"bar.baz": 42,
FieldPath(["foobar"]): 42,
+ "server_timestamp": FieldValue.serverTimestamp(),
+ "array_union": FieldValue.arrayUnion(["a", "b"]),
+ "array_remove": FieldValue.arrayRemove(["a", "b"]),
+ "field_delete": FieldValue.delete(),
] as [AnyHashable: Any]
docRef.setData(setData)
diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm
index 60cb7b8..56bf1c2 100644
--- a/Firestore/Example/Tests/Model/FSTMutationTests.mm
+++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm
@@ -16,9 +16,12 @@
#import "Firestore/Source/Model/FSTMutation.h"
+#import <FirebaseFirestore/FIRFieldValue.h>
#import <FirebaseFirestore/FIRTimestamp.h>
#import <XCTest/XCTest.h>
+#include <vector>
+
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
@@ -26,13 +29,19 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::ArrayTransform;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
using firebase::firestore::model::Precondition;
+using firebase::firestore::model::TransformOperation;
@interface FSTMutationTests : XCTestCase
@end
@@ -104,11 +113,12 @@ using firebase::firestore::model::Precondition;
XCTAssertEqualObjects(patchedDoc, baseDoc);
}
-- (void)testAppliesLocalTransformsToDocuments {
+- (void)testAppliesLocalServerTimestampTransformToDocuments {
NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
- FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]);
+ FSTMutation *transform = FSTTestTransformMutation(
+ @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
FSTMaybeDocument *transformedDoc =
[transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
@@ -130,11 +140,57 @@ using firebase::firestore::model::Precondition;
XCTAssertEqualObjects(transformedDoc, expectedDoc);
}
+// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
+// unit tests for it currently. We could consider removing this test once we have integration tests.
+- (void)testCreateArrayUnionTransform {
+ FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
+ @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
+ @"bar.baz" : [FIRFieldValue fieldValueForArrayUnion:@[ @YES, @[ @1, @2 ], @{@"a" : @"b"} ]]
+ });
+ XCTAssertEqual(transform.fieldTransforms.size(), 2);
+
+ const FieldTransform &first = transform.fieldTransforms[0];
+ XCTAssertEqual(first.path(), FieldPath({"foo"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
+ ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
+ }
+
+ const FieldTransform &second = transform.fieldTransforms[1];
+ XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements {
+ FSTTestFieldValue(@YES), FSTTestFieldValue(@[ @1, @2 ]), FSTTestFieldValue(@{@"a" : @"b"})
+ };
+ ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(second.transformation()), expected);
+ }
+}
+
+// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
+// unit tests for it currently. We could consider removing this test once we have integration tests.
+- (void)testCreateArrayRemoveTransform {
+ FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
+ @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]],
+ });
+ XCTAssertEqual(transform.fieldTransforms.size(), 1);
+
+ const FieldTransform &first = transform.fieldTransforms[0];
+ XCTAssertEqual(first.path(), FieldPath({"foo"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
+ const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
+ }
+}
+
- (void)testAppliesServerAckedTransformsToDocuments {
NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
- FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]);
+ FSTMutation *transform = FSTTestTransformMutation(
+ @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
initWithVersion:FSTTestVersion(1)
diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
index 454b108..a648cd8 100644
--- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
@@ -17,6 +17,7 @@
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import <FirebaseFirestore/FIRFieldPath.h>
+#import <FirebaseFirestore/FIRFieldValue.h>
#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <FirebaseFirestore/FIRGeoPoint.h>
#import <FirebaseFirestore/FIRTimestamp.h>
@@ -367,7 +368,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesTransformMutation {
- FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @[ @"a", @"bar.baz" ]);
+ FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @{
+ @"a" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"bar.baz" : [FIRFieldValue fieldValueForServerTimestamp]
+ });
GCFSWrite *proto = [GCFSWrite message];
proto.transform = [GCFSDocumentTransform message];
proto.transform.document = [self.serializer encodedDocumentKey:mutation.key];
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h
index d05c1f1..9b5f96a 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.h
+++ b/Firestore/Example/Tests/Util/FSTHelpers.h
@@ -245,8 +245,12 @@ FSTPatchMutation *FSTTestPatchMutation(
NSDictionary<NSString *, id> *values,
const std::vector<firebase::firestore::model::FieldPath> &updateMask);
-FSTTransformMutation *FSTTestTransformMutation(NSString *path,
- NSArray<NSString *> *serverTimestampFields);
+/**
+ * Creates a FSTTransformMutation by parsing any FIRFieldValue sentinels in the provided data. The
+ * data is expected to use dotted-notation for nested fields (i.e.
+ * @{ @"foo.bar": [FIRFieldValue ...] } and must not contain any non-sentinel data.
+ */
+FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary<NSString *, id> *data);
/** Creates a delete mutation for the document key at the given path. */
FSTDeleteMutation *FSTTestDeleteMutation(NSString *path);
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm
index 78b41a4..e1ed18c 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.mm
+++ b/Firestore/Example/Tests/Util/FSTHelpers.mm
@@ -122,7 +122,7 @@ NSDateComponents *FSTTestDateComponents(
return comps;
}
-FSTFieldValue *FSTTestFieldValue(id _Nullable value) {
+FSTUserDataConverter *FSTTestUserDataConverter() {
// This owns the DatabaseIds since we do not have FirestoreClient instance to own them.
static DatabaseId database_id{"project", DatabaseId::kDefault};
FSTUserDataConverter *converter =
@@ -130,6 +130,11 @@ FSTFieldValue *FSTTestFieldValue(id _Nullable value) {
preConverter:^id _Nullable(id _Nullable input) {
return input;
}];
+ return converter;
+}
+
+FSTFieldValue *FSTTestFieldValue(id _Nullable value) {
+ FSTUserDataConverter *converter = FSTTestUserDataConverter();
// HACK: We use parsedQueryValue: since it accepts scalars as well as arrays / objects, and
// our tests currently use FSTTestFieldValue() pretty generically so we don't know the intent.
return [converter parsedQueryValue:value];
@@ -279,17 +284,14 @@ FSTPatchMutation *FSTTestPatchMutation(const absl::string_view path,
precondition:Precondition::Exists(true)];
}
-// For now this only creates TransformMutations with server timestamps.
-FSTTransformMutation *FSTTestTransformMutation(NSString *path,
- NSArray<NSString *> *serverTimestampFields) {
+FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary<NSString *, id> *data) {
FSTDocumentKey *key = [FSTDocumentKey keyWithPath:testutil::Resource(util::MakeStringView(path))];
- std::vector<FieldTransform> fieldTransforms;
- for (NSString *field in serverTimestampFields) {
- FieldPath fieldPath = testutil::Field(util::MakeStringView(field));
- auto transformOp = absl::make_unique<ServerTimestampTransform>(ServerTimestampTransform::Get());
- fieldTransforms.emplace_back(std::move(fieldPath), std::move(transformOp));
- }
- return [[FSTTransformMutation alloc] initWithKey:key fieldTransforms:std::move(fieldTransforms)];
+ FSTUserDataConverter *converter = FSTTestUserDataConverter();
+ FSTParsedUpdateData *result = [converter parsedUpdateData:data];
+ FSTCAssert(result.data.value.count == 0,
+ @"FSTTestTransformMutation() only expects transforms; no other data");
+ return [[FSTTransformMutation alloc] initWithKey:key
+ fieldTransforms:std::move(result.fieldTransforms)];
}
FSTDeleteMutation *FSTTestDeleteMutation(NSString *path) {
diff --git a/Firestore/Source/API/FIRFieldValue+Internal.h b/Firestore/Source/API/FIRFieldValue+Internal.h
index 2eb927e..1618cd4 100644
--- a/Firestore/Source/API/FIRFieldValue+Internal.h
+++ b/Firestore/Source/API/FIRFieldValue+Internal.h
@@ -42,4 +42,16 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
@end
+/** FIRFieldValue class for array unions. */
+@interface FSTArrayUnionFieldValue : FIRFieldValue
+- (instancetype)init NS_UNAVAILABLE;
+@property(strong, nonatomic, readonly) NSArray<id> *elements;
+@end
+
+/** FIRFieldValue class for array removes. */
+@interface FSTArrayRemoveFieldValue : FIRFieldValue
+- (instancetype)init NS_UNAVAILABLE;
+@property(strong, nonatomic, readonly) NSArray<id> *elements;
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFieldValue.mm b/Firestore/Source/API/FIRFieldValue.mm
index 0d0a9c4..e0ed8c7 100644
--- a/Firestore/Source/API/FIRFieldValue.mm
+++ b/Firestore/Source/API/FIRFieldValue.mm
@@ -82,6 +82,36 @@ NS_ASSUME_NONNULL_BEGIN
@end
+#pragma mark - FSTArrayUnionFieldValue
+
+@interface FSTArrayUnionFieldValue ()
+- (instancetype)initWithElements:(NSArray<id> *)elements;
+@end
+
+@implementation FSTArrayUnionFieldValue
+- (instancetype)initWithElements:(NSArray<id> *)elements {
+ if (self = [super initPrivate]) {
+ _elements = elements;
+ }
+ return self;
+}
+@end
+
+#pragma mark - FSTArrayRemoveFieldValue
+
+@interface FSTArrayRemoveFieldValue ()
+- (instancetype)initWithElements:(NSArray<id> *)elements;
+@end
+
+@implementation FSTArrayRemoveFieldValue
+- (instancetype)initWithElements:(NSArray<id> *)elements {
+ if (self = [super initPrivate]) {
+ _elements = elements;
+ }
+ return self;
+}
+@end
+
#pragma mark - FIRFieldValue
@implementation FIRFieldValue
@@ -99,6 +129,14 @@ NS_ASSUME_NONNULL_BEGIN
return [FSTServerTimestampFieldValue serverTimestampFieldValue];
}
++ (instancetype)fieldValueForArrayUnion:(NSArray<id> *)elements {
+ return [[FSTArrayUnionFieldValue alloc] initWithElements:elements];
+}
+
++ (instancetype)fieldValueForArrayRemove:(NSArray<id> *)elements {
+ return [[FSTArrayRemoveFieldValue alloc] initWithElements:elements];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm
index dff9235..90d68d8 100644
--- a/Firestore/Source/API/FSTUserDataConverter.mm
+++ b/Firestore/Source/API/FSTUserDataConverter.mm
@@ -44,6 +44,7 @@
#include "absl/memory/memory.h"
namespace util = firebase::firestore::util;
+using firebase::firestore::model::ArrayTransform;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldMask;
@@ -165,7 +166,11 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
FSTUserDataSourceSet,
FSTUserDataSourceMergeSet,
FSTUserDataSourceUpdate,
- FSTUserDataSourceQueryValue, // from a where clause or cursor bound.
+ /**
+ * Indicates the source is a where clause, cursor bound, arrayUnion() element, etc. In particular,
+ * this will result in [FSTParseContext isWrite] returning NO.
+ */
+ FSTUserDataSourceArgument,
};
#pragma mark - FSTParseContext
@@ -318,7 +323,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
case FSTUserDataSourceMergeSet: // Falls through.
case FSTUserDataSourceUpdate:
return YES;
- case FSTUserDataSourceQueryValue:
+ case FSTUserDataSourceArgument:
return NO;
default:
FSTThrowInvalidArgument(@"Unexpected case for FSTUserDataSource: %d", self.dataSource);
@@ -489,7 +494,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
- (FSTFieldValue *)parsedQueryValue:(id)input {
FSTParseContext *context =
- [FSTParseContext contextWithSource:FSTUserDataSourceQueryValue
+ [FSTParseContext contextWithSource:FSTUserDataSourceArgument
path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
FSTFieldValue *_Nullable parsed = [self parseData:input context:context];
FSTAssert(parsed, @"Parsed data should not be nil.");
@@ -593,10 +598,28 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
@"SetOptions.merge()%@",
[context fieldDescription]);
}
+
} else if ([fieldValue isKindOfClass:[FSTServerTimestampFieldValue class]]) {
[context appendToFieldTransformsWithFieldPath:*context.path
transformOperation:absl::make_unique<ServerTimestampTransform>(
ServerTimestampTransform::Get())];
+
+ } else if ([fieldValue isKindOfClass:[FSTArrayUnionFieldValue class]]) {
+ std::vector<FSTFieldValue *> parsedElements =
+ [self parseArrayTransformElements:((FSTArrayUnionFieldValue *)fieldValue).elements];
+ auto array_union =
+ absl::make_unique<ArrayTransform>(TransformOperation::Type::ArrayUnion, parsedElements);
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:std::move(array_union)];
+
+ } else if ([fieldValue isKindOfClass:[FSTArrayRemoveFieldValue class]]) {
+ std::vector<FSTFieldValue *> parsedElements =
+ [self parseArrayTransformElements:((FSTArrayRemoveFieldValue *)fieldValue).elements];
+ auto array_remove =
+ absl::make_unique<ArrayTransform>(TransformOperation::Type::ArrayRemove, parsedElements);
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:std::move(array_remove)];
+
} else {
FSTFail(@"Unknown FIRFieldValue type: %@", NSStringFromClass([fieldValue class]));
}
@@ -753,6 +776,22 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
}
}
+- (std::vector<FSTFieldValue *>)parseArrayTransformElements:(NSArray<id> *)elements {
+ std::vector<FSTFieldValue *> results;
+ for (id element in elements) {
+ // Although array transforms are used with writes, the actual elements being unioned or removed
+ // are not considered writes since they cannot contain any FieldValue sentinels, etc.
+ FSTParseContext *context =
+ [FSTParseContext contextWithSource:FSTUserDataSourceArgument
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
+ FSTFieldValue *parsedElement = [self parseData:element context:context];
+ FSTAssert(parsedElement && context.fieldTransforms->size() == 0,
+ @"Failed to properly parse array transform element: %@", element);
+ results.push_back(parsedElement);
+ }
+ return results;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRFieldValue.h b/Firestore/Source/Public/FIRFieldValue.h
index 11a0da0..d896587 100644
--- a/Firestore/Source/Public/FIRFieldValue.h
+++ b/Firestore/Source/Public/FIRFieldValue.h
@@ -38,6 +38,29 @@ NS_SWIFT_NAME(FieldValue)
*/
+ (instancetype)fieldValueForServerTimestamp NS_SWIFT_NAME(serverTimestamp());
+/**
+ * Returns a special value that can be used with setData() or updateData() that tells the server to
+ * union the given elements with any array value that already exists on the server. Each
+ * specified element that doesn't already exist in the array will be added to the end. If the
+ * field being modified is not already an array it will be overwritten with an array containing
+ * exactly the specified elements.
+ *
+ * @param elements The elements to union into the array.
+ * @return The FieldValue sentinel for use in a call to setData() or updateData().
+ */
++ (instancetype)fieldValueForArrayUnion:(NSArray<id> *)elements NS_SWIFT_NAME(arrayUnion(_:));
+
+/**
+ * Returns a special value that can be used with setData() or updateData() that tells the server to
+ * remove the given elements from any array value that already exists on the server. All
+ * instances of each element specified will be removed from the array. If the field being
+ * modified is not already an array it will be overwritten with an empty array.
+ *
+ * @param elements The elements to remove from the array.
+ * @return The FieldValue sentinel for use in a call to setData() or updateData().
+ */
++ (instancetype)fieldValueForArrayRemove:(NSArray<id> *)elements NS_SWIFT_NAME(arrayRemove(_:));
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/core/src/firebase/firestore/model/transform_operations.h b/Firestore/core/src/firebase/firestore/model/transform_operations.h
index 6ff4dbc..aad5a9b 100644
--- a/Firestore/core/src/firebase/firestore/model/transform_operations.h
+++ b/Firestore/core/src/firebase/firestore/model/transform_operations.h
@@ -17,6 +17,12 @@
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
+#include <vector>
+
+#if defined(__OBJC__)
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#endif
+
namespace firebase {
namespace firestore {
namespace model {
@@ -31,6 +37,8 @@ class TransformOperation {
/** All the different kinds to TransformOperation. */
enum class Type {
ServerTimestamp,
+ ArrayUnion,
+ ArrayRemove,
Test, // Purely for test purpose.
};
@@ -90,6 +98,65 @@ class ServerTimestampTransform : public TransformOperation {
}
};
+// TODO(mikelehen): ArrayTransform can only be used from Obj-C until we switch
+// to using FieldValue instead of FSTFieldValue.
+#if defined(__OBJC__)
+/**
+ * Transforms an array via a union or remove operation (for convenience, we use
+ * this class for both Type::ArrayUnion and Type::ArrayRemove).
+ */
+class ArrayTransform : public TransformOperation {
+ public:
+ ArrayTransform(const Type& type, const std::vector<FSTFieldValue*> elements)
+ : type_(type), elements_(elements) {
+ }
+
+ ~ArrayTransform() override {
+ }
+
+ Type type() const override {
+ return type_;
+ }
+
+ const std::vector<FSTFieldValue*>& elements() const {
+ return elements_;
+ }
+
+ bool operator==(const TransformOperation& other) const override {
+ if (other.type() != type()) {
+ return false;
+ }
+ auto array_transform = static_cast<const ArrayTransform&>(other);
+ if (array_transform.elements_.size() != elements_.size()) {
+ return false;
+ }
+ for (int i = 0; i < elements_.size(); i++) {
+ if (![array_transform.elements_[i] isEqual:elements_[i]]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const override {
+ NSUInteger result = 37;
+ result = 31 * result + (type() == Type::ArrayUnion ? 1231 : 1237);
+ for (FSTFieldValue* element : elements_) {
+ result = 31 * result + [element hash];
+ }
+ return result;
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ Type type_;
+ std::vector<FSTFieldValue*> elements_;
+};
+#endif
+
} // namespace model
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc b/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
index 0ef95db..ec0882a 100644
--- a/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
+++ b/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
@@ -46,6 +46,9 @@ TEST(TransformOperations, ServerTimestamp) {
EXPECT_NE(transform, dummy);
}
+// TODO(mikelehen): Add ArrayTransform test once it no longer depends on
+// FSTFieldValue and can be exposed to C++ code.
+
} // namespace model
} // namespace firestore
} // namespace firebase