aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore
diff options
context:
space:
mode:
authorGravatar Michael Lehenbauer <mikelehen@gmail.com>2018-04-13 14:22:41 -0700
committerGravatar GitHub <noreply@github.com>2018-04-13 14:22:41 -0700
commit60d43d7b0980719beac21811da712f1cb4881308 (patch)
tree268424fabe81a547ee17b737177f24130b1f4d89 /Firestore
parentdfdab5bee001546b3f36638c00b96b381cb4c040 (diff)
Array Transforms public API and parsing
This includes the new FIRFieldValue sentinels, the FSTUserDataConverter code to parse them into internal ArrayTransform operations for use in an FSTTransformMutation, and some sanity testing in FSTMutationTests. I still need to implement FSTTransformMutation support for local application and serialization (and then integration tests).
Diffstat (limited to 'Firestore')
-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