mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
* naively remove FSTPath import and source/test files. * port FieldPath, part I * port FieldPath, part II * port ResourcePath, part I * port ResourcePath, part II * the grand commit to fix build errors * use testutil:: helper instead of those from FSTHelpers * fix test and lint * use c_str in errmsg directly * fix * fix * make code clean * fix integration test I missed * fix to avoid naming collision in preprocessor * address changes * address changes * address changes * fix: fieldMask are actually shared with different context. * address changes * address changes
943 lines
26 KiB
Plaintext
943 lines
26 KiB
Plaintext
/*
|
|
* Copyright 2017 Google
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import "Firestore/Source/Model/FSTFieldValue.h"
|
|
|
|
#import "FIRTimestamp.h"
|
|
|
|
#include "Firestore/core/src/firebase/firestore/util/comparison.h"
|
|
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
|
|
|
|
#import "Firestore/Source/API/FIRGeoPoint+Internal.h"
|
|
#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
|
|
#import "Firestore/Source/Model/FSTDocumentKey.h"
|
|
#import "Firestore/Source/Util/FSTAssert.h"
|
|
#import "Firestore/Source/Util/FSTClasses.h"
|
|
|
|
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
|
|
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
|
|
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
|
|
|
|
namespace util = firebase::firestore::util;
|
|
using firebase::firestore::model::DatabaseId;
|
|
using firebase::firestore::model::FieldPath;
|
|
using firebase::firestore::util::Comparator;
|
|
using firebase::firestore::util::CompareMixedNumber;
|
|
using firebase::firestore::util::DoubleBitwiseEquals;
|
|
using firebase::firestore::util::DoubleBitwiseHash;
|
|
using firebase::firestore::util::MakeStringView;
|
|
using firebase::firestore::util::ReverseOrder;
|
|
using firebase::firestore::util::WrapCompare;
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
#pragma mark - FSTFieldValueOptions
|
|
|
|
@implementation FSTFieldValueOptions
|
|
|
|
+ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options {
|
|
if (options.serverTimestampBehavior == FSTServerTimestampBehaviorNone) {
|
|
static FSTFieldValueOptions *defaultInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
defaultInstance = [[FSTFieldValueOptions alloc]
|
|
initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
|
|
});
|
|
return defaultInstance;
|
|
} else {
|
|
return [[FSTFieldValueOptions alloc]
|
|
initWithServerTimestampBehavior:options.serverTimestampBehavior];
|
|
}
|
|
}
|
|
|
|
- (instancetype)initWithServerTimestampBehavior:
|
|
(FSTServerTimestampBehavior)serverTimestampBehavior {
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
_serverTimestampBehavior = serverTimestampBehavior;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTFieldValue
|
|
|
|
@interface FSTFieldValue ()
|
|
- (NSComparisonResult)defaultCompare:(FSTFieldValue *)other;
|
|
@end
|
|
|
|
@implementation FSTFieldValue
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
@throw FSTAbstractMethodException(); // NOLINT
|
|
}
|
|
|
|
- (id)value {
|
|
return [self valueWithOptions:[FSTFieldValueOptions
|
|
optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]];
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
@throw FSTAbstractMethodException(); // NOLINT
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
@throw FSTAbstractMethodException(); // NOLINT
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
@throw FSTAbstractMethodException(); // NOLINT
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
@throw FSTAbstractMethodException(); // NOLINT
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [[self value] description];
|
|
}
|
|
|
|
- (NSComparisonResult)defaultCompare:(FSTFieldValue *)other {
|
|
if (self.typeOrder > other.typeOrder) {
|
|
return NSOrderedDescending;
|
|
} else {
|
|
FSTAssert(self.typeOrder < other.typeOrder,
|
|
@"defaultCompare should not be used for values of same type.");
|
|
return NSOrderedAscending;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTNullValue
|
|
|
|
@implementation FSTNullValue
|
|
|
|
+ (instancetype)nullValue {
|
|
static FSTNullValue *sharedInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [[FSTNullValue alloc] init];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderNull;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return [NSNull null];
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[self class]];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return 47;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[self class]]) {
|
|
return NSOrderedSame;
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTBooleanValue
|
|
|
|
@interface FSTBooleanValue ()
|
|
@property(nonatomic, assign, readonly) BOOL internalValue;
|
|
@end
|
|
|
|
@implementation FSTBooleanValue
|
|
|
|
+ (instancetype)trueValue {
|
|
static FSTBooleanValue *sharedInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [[FSTBooleanValue alloc] initWithValue:YES];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
+ (instancetype)falseValue {
|
|
static FSTBooleanValue *sharedInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [[FSTBooleanValue alloc] initWithValue:NO];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
+ (instancetype)booleanValue:(BOOL)value {
|
|
return value ? [FSTBooleanValue trueValue] : [FSTBooleanValue falseValue];
|
|
}
|
|
|
|
- (id)initWithValue:(BOOL)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderBoolean;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return self.internalValue ? @YES : @NO;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
// Since we create shared instances for true / false, we can use reference equality.
|
|
return self == other;
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return self.internalValue ? 1231 : 1237;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTBooleanValue class]]) {
|
|
return WrapCompare<bool>(self.internalValue, ((FSTBooleanValue *)other).internalValue);
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTNumberValue
|
|
|
|
@implementation FSTNumberValue
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderNumber;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if (![other isKindOfClass:[FSTNumberValue class]]) {
|
|
return [self defaultCompare:other];
|
|
} else {
|
|
if ([self isKindOfClass:[FSTDoubleValue class]]) {
|
|
double thisDouble = ((FSTDoubleValue *)self).internalValue;
|
|
if ([other isKindOfClass:[FSTDoubleValue class]]) {
|
|
return WrapCompare(thisDouble, ((FSTDoubleValue *)other).internalValue);
|
|
} else {
|
|
FSTAssert([other isKindOfClass:[FSTIntegerValue class]], @"Unknown number value: %@",
|
|
other);
|
|
auto result = CompareMixedNumber(thisDouble, ((FSTIntegerValue *)other).internalValue);
|
|
return static_cast<NSComparisonResult>(result);
|
|
}
|
|
} else {
|
|
int64_t thisInt = ((FSTIntegerValue *)self).internalValue;
|
|
if ([other isKindOfClass:[FSTIntegerValue class]]) {
|
|
return WrapCompare(thisInt, ((FSTIntegerValue *)other).internalValue);
|
|
} else {
|
|
FSTAssert([other isKindOfClass:[FSTDoubleValue class]], @"Unknown number value: %@", other);
|
|
double otherDouble = ((FSTDoubleValue *)other).internalValue;
|
|
auto result = ReverseOrder(CompareMixedNumber(otherDouble, thisInt));
|
|
return static_cast<NSComparisonResult>(result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTIntegerValue
|
|
|
|
@interface FSTIntegerValue ()
|
|
@property(nonatomic, assign, readonly) int64_t internalValue;
|
|
@end
|
|
|
|
@implementation FSTIntegerValue
|
|
|
|
+ (instancetype)integerValue:(int64_t)value {
|
|
return [[FSTIntegerValue alloc] initWithValue:value];
|
|
}
|
|
|
|
- (id)initWithValue:(int64_t)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return @(self.internalValue);
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
// NOTE: DoubleValue and LongValue instances may compare: the same, but that doesn't make them
|
|
// equal via isEqual:
|
|
return [other isKindOfClass:[FSTIntegerValue class]] &&
|
|
self.internalValue == ((FSTIntegerValue *)other).internalValue;
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return (((NSUInteger)self.internalValue) ^ (NSUInteger)(self.internalValue >> 32));
|
|
}
|
|
|
|
// NOTE: compare: is implemented in NumberValue.
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTDoubleValue
|
|
|
|
@interface FSTDoubleValue ()
|
|
@property(nonatomic, assign, readonly) double internalValue;
|
|
@end
|
|
|
|
@implementation FSTDoubleValue
|
|
|
|
+ (instancetype)doubleValue:(double)value {
|
|
// Normalize NaNs to match the behavior on the backend (which uses Double.doubletoLongBits()).
|
|
if (isnan(value)) {
|
|
return [FSTDoubleValue nanValue];
|
|
}
|
|
return [[FSTDoubleValue alloc] initWithValue:value];
|
|
}
|
|
|
|
+ (instancetype)nanValue {
|
|
static FSTDoubleValue *sharedInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [[FSTDoubleValue alloc] initWithValue:NAN];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
- (id)initWithValue:(double)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return @(self.internalValue);
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
// NOTE: DoubleValue and LongValue instances may compare: the same, but that doesn't make them
|
|
// equal via isEqual:
|
|
|
|
// NOTE: isEqual: should compare NaN equal to itself and -0.0 not equal to 0.0.
|
|
|
|
return [other isKindOfClass:[FSTDoubleValue class]] &&
|
|
DoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue);
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return DoubleBitwiseHash(self.internalValue);
|
|
}
|
|
|
|
// NOTE: compare: is implemented in NumberValue.
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTStringValue
|
|
|
|
/**
|
|
* Specialization of Comparator for NSStrings.
|
|
*/
|
|
template <>
|
|
struct Comparator<NSString *> {
|
|
bool operator()(NSString *left, NSString *right) const {
|
|
Comparator<absl::string_view> lessThan;
|
|
return lessThan(MakeStringView(left), MakeStringView(right));
|
|
}
|
|
};
|
|
|
|
@interface FSTStringValue ()
|
|
@property(nonatomic, copy, readonly) NSString *internalValue;
|
|
@end
|
|
|
|
// TODO(b/37267885): Add truncation support
|
|
@implementation FSTStringValue
|
|
|
|
+ (instancetype)stringValue:(NSString *)value {
|
|
return [[FSTStringValue alloc] initWithValue:value];
|
|
}
|
|
|
|
- (id)initWithValue:(NSString *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = [value copy];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderString;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return self.internalValue;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[FSTStringValue class]] &&
|
|
[self.internalValue isEqualToString:((FSTStringValue *)other).internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return self.internalValue ? 1 : 0;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTStringValue class]]) {
|
|
return WrapCompare(self.internalValue, ((FSTStringValue *)other).internalValue);
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTTimestampValue
|
|
|
|
@interface FSTTimestampValue ()
|
|
@property(nonatomic, strong, readonly) FIRTimestamp *internalValue;
|
|
@end
|
|
|
|
@implementation FSTTimestampValue
|
|
|
|
+ (instancetype)timestampValue:(FIRTimestamp *)value {
|
|
return [[FSTTimestampValue alloc] initWithValue:value];
|
|
}
|
|
|
|
- (id)initWithValue:(FIRTimestamp *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value; // FIRTimestamp is immutable.
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderTimestamp;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
// For developers, we expose Timestamps as Dates.
|
|
return self.internalValue.approximateDateValue;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[FSTTimestampValue class]] &&
|
|
[self.internalValue isEqual:((FSTTimestampValue *)other).internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.internalValue hash];
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTTimestampValue class]]) {
|
|
return [self.internalValue compare:((FSTTimestampValue *)other).internalValue];
|
|
} else if ([other isKindOfClass:[FSTServerTimestampValue class]]) {
|
|
// Concrete timestamps come before server timestamps.
|
|
return NSOrderedAscending;
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTServerTimestampValue
|
|
|
|
@implementation FSTServerTimestampValue
|
|
|
|
+ (instancetype)serverTimestampValueWithLocalWriteTime:(FIRTimestamp *)localWriteTime
|
|
previousValue:(nullable FSTFieldValue *)previousValue {
|
|
return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime
|
|
previousValue:previousValue];
|
|
}
|
|
|
|
- (id)initWithLocalWriteTime:(FIRTimestamp *)localWriteTime
|
|
previousValue:(nullable FSTFieldValue *)previousValue {
|
|
self = [super init];
|
|
if (self) {
|
|
_localWriteTime = localWriteTime;
|
|
_previousValue = previousValue;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderTimestamp;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
switch (options.serverTimestampBehavior) {
|
|
case FSTServerTimestampBehaviorNone:
|
|
return [NSNull null];
|
|
case FSTServerTimestampBehaviorEstimate:
|
|
return [self.localWriteTime approximateDateValue];
|
|
case FSTServerTimestampBehaviorPrevious:
|
|
return self.previousValue ? [self.previousValue valueWithOptions:options] : [NSNull null];
|
|
default:
|
|
FSTFail(@"Unexpected server timestamp option: %d", (int)options.serverTimestampBehavior);
|
|
}
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[FSTServerTimestampValue class]] &&
|
|
[self.localWriteTime isEqual:((FSTServerTimestampValue *)other).localWriteTime];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.localWriteTime hash];
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"<ServerTimestamp localTime=%@>", self.localWriteTime];
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTServerTimestampValue class]]) {
|
|
return [self.localWriteTime compare:((FSTServerTimestampValue *)other).localWriteTime];
|
|
} else if ([other isKindOfClass:[FSTTimestampValue class]]) {
|
|
// Server timestamps come after all concrete timestamps.
|
|
return NSOrderedDescending;
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTGeoPointValue
|
|
|
|
@interface FSTGeoPointValue ()
|
|
@property(nonatomic, strong, readonly) FIRGeoPoint *internalValue;
|
|
@end
|
|
|
|
@implementation FSTGeoPointValue
|
|
|
|
+ (instancetype)geoPointValue:(FIRGeoPoint *)value {
|
|
return [[FSTGeoPointValue alloc] initWithValue:value];
|
|
}
|
|
|
|
- (id)initWithValue:(FIRGeoPoint *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value; // FIRGeoPoint is immutable.
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderGeoPoint;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return self.internalValue;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[FSTGeoPointValue class]] &&
|
|
[self.internalValue isEqual:((FSTGeoPointValue *)other).internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.internalValue hash];
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTGeoPointValue class]]) {
|
|
return [self.internalValue compare:((FSTGeoPointValue *)other).internalValue];
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTBlobValue
|
|
|
|
static NSComparisonResult CompareBytes(NSData *left, NSData *right) {
|
|
NSUInteger minLength = MIN(left.length, right.length);
|
|
int result = memcmp(left.bytes, right.bytes, minLength);
|
|
if (result < 0) {
|
|
return NSOrderedAscending;
|
|
} else if (result > 0) {
|
|
return NSOrderedDescending;
|
|
} else if (left.length < right.length) {
|
|
return NSOrderedAscending;
|
|
} else if (left.length > right.length) {
|
|
return NSOrderedDescending;
|
|
} else {
|
|
return NSOrderedSame;
|
|
}
|
|
}
|
|
|
|
@interface FSTBlobValue ()
|
|
@property(nonatomic, copy, readonly) NSData *internalValue;
|
|
@end
|
|
|
|
// TODO(b/37267885): Add truncation support
|
|
@implementation FSTBlobValue
|
|
|
|
+ (instancetype)blobValue:(NSData *)value {
|
|
return [[FSTBlobValue alloc] initWithValue:value];
|
|
}
|
|
|
|
- (id)initWithValue:(NSData *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = [value copy];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderBlob;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return self.internalValue;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
return [other isKindOfClass:[FSTBlobValue class]] &&
|
|
[self.internalValue isEqual:((FSTBlobValue *)other).internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.internalValue hash];
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTBlobValue class]]) {
|
|
return CompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue);
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTReferenceValue
|
|
|
|
@interface FSTReferenceValue ()
|
|
@property(nonatomic, strong, readonly) FSTDocumentKey *key;
|
|
@end
|
|
|
|
@implementation FSTReferenceValue
|
|
|
|
+ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(const DatabaseId *)databaseID {
|
|
return [[FSTReferenceValue alloc] initWithValue:value databaseID:databaseID];
|
|
}
|
|
|
|
- (id)initWithValue:(FSTDocumentKey *)value databaseID:(const DatabaseId *)databaseID {
|
|
self = [super init];
|
|
if (self) {
|
|
_key = value;
|
|
_databaseID = databaseID;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
return self.key;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderReference;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
if (other == self) {
|
|
return YES;
|
|
}
|
|
if (![other isKindOfClass:[FSTReferenceValue class]]) {
|
|
return NO;
|
|
}
|
|
|
|
FSTReferenceValue *otherRef = (FSTReferenceValue *)other;
|
|
return [self.key isEqualToKey:otherRef.key] && *self.databaseID == *otherRef.databaseID;
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
NSUInteger result = self.databaseID->Hash();
|
|
result = 31 * result + [self.key hash];
|
|
return result;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTReferenceValue class]]) {
|
|
FSTReferenceValue *ref = (FSTReferenceValue *)other;
|
|
NSComparisonResult cmp = [util::WrapNSStringNoCopy(self.databaseID->project_id())
|
|
compare:util::WrapNSStringNoCopy(ref.databaseID->project_id())];
|
|
if (cmp != NSOrderedSame) {
|
|
return cmp;
|
|
}
|
|
cmp = [util::WrapNSStringNoCopy(self.databaseID->database_id())
|
|
compare:util::WrapNSStringNoCopy(ref.databaseID->database_id())];
|
|
return cmp != NSOrderedSame ? cmp : [self.key compare:ref.key];
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FSTObjectValue
|
|
|
|
static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, NSString *right) {
|
|
return WrapCompare(left, right);
|
|
};
|
|
|
|
@interface FSTObjectValue ()
|
|
@property(nonatomic, strong, readonly)
|
|
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *internalValue;
|
|
@end
|
|
|
|
@implementation FSTObjectValue
|
|
|
|
+ (instancetype)objectValue {
|
|
static FSTObjectValue *sharedEmptyInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *empty =
|
|
[FSTImmutableSortedDictionary dictionaryWithComparator:StringComparator];
|
|
sharedEmptyInstance = [[FSTObjectValue alloc] initWithImmutableDictionary:empty];
|
|
});
|
|
return sharedEmptyInstance;
|
|
}
|
|
|
|
- (instancetype)initWithImmutableDictionary:
|
|
(FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
_internalValue = value; // FSTImmutableSortedDictionary is immutable.
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithDictionary:(NSDictionary<NSString *, FSTFieldValue *> *)value {
|
|
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *dictionary =
|
|
[FSTImmutableSortedDictionary dictionaryWithDictionary:value comparator:StringComparator];
|
|
return [self initWithImmutableDictionary:dictionary];
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
[self.internalValue
|
|
enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *obj, BOOL *stop) {
|
|
result[key] = [obj valueWithOptions:options];
|
|
}];
|
|
return result;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderObject;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
if (other == self) {
|
|
return YES;
|
|
}
|
|
if (![other isKindOfClass:[FSTObjectValue class]]) {
|
|
return NO;
|
|
}
|
|
|
|
FSTObjectValue *otherObj = other;
|
|
return [self.internalValue isEqual:otherObj.internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.internalValue hash];
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTObjectValue class]]) {
|
|
FSTImmutableSortedDictionary *selfDict = self.internalValue;
|
|
FSTImmutableSortedDictionary *otherDict = ((FSTObjectValue *)other).internalValue;
|
|
NSEnumerator *enumerator1 = [selfDict keyEnumerator];
|
|
NSEnumerator *enumerator2 = [otherDict keyEnumerator];
|
|
NSString *key1 = [enumerator1 nextObject];
|
|
NSString *key2 = [enumerator2 nextObject];
|
|
while (key1 && key2) {
|
|
NSComparisonResult keyCompare = [key1 compare:key2];
|
|
if (keyCompare != NSOrderedSame) {
|
|
return keyCompare;
|
|
}
|
|
NSComparisonResult valueCompare = [selfDict[key1] compare:otherDict[key2]];
|
|
if (valueCompare != NSOrderedSame) {
|
|
return valueCompare;
|
|
}
|
|
key1 = [enumerator1 nextObject];
|
|
key2 = [enumerator2 nextObject];
|
|
}
|
|
// Only equal if both enumerators are exhausted.
|
|
return WrapCompare(key1 != nil, key2 != nil);
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
- (nullable FSTFieldValue *)valueForPath:(const FieldPath &)fieldPath {
|
|
FSTFieldValue *value = self;
|
|
for (size_t i = 0, max = fieldPath.size(); value && i < max; i++) {
|
|
if (![value isMemberOfClass:[FSTObjectValue class]]) {
|
|
return nil;
|
|
}
|
|
|
|
NSString *fieldName = util::WrapNSStringNoCopy(fieldPath[i]);
|
|
value = ((FSTObjectValue *)value).internalValue[fieldName];
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value
|
|
forPath:(const FieldPath &)fieldPath {
|
|
FSTAssert(fieldPath.size() > 0, @"Cannot set value with an empty path");
|
|
|
|
NSString *childName = util::WrapNSString(fieldPath.first_segment());
|
|
if (fieldPath.size() == 1) {
|
|
// Recursive base case:
|
|
return [self objectBySettingValue:value forField:childName];
|
|
} else {
|
|
// Nested path. Recursively generate a new sub-object and then wrap a new FSTObjectValue around
|
|
// the result.
|
|
FSTFieldValue *child = [_internalValue objectForKey:childName];
|
|
FSTObjectValue *childObject;
|
|
if ([child isKindOfClass:[FSTObjectValue class]]) {
|
|
childObject = (FSTObjectValue *)child;
|
|
} else {
|
|
// If the child is not found or is a primitive type, pretend as if an empty object lived
|
|
// there.
|
|
childObject = [FSTObjectValue objectValue];
|
|
}
|
|
FSTFieldValue *newChild = [childObject objectBySettingValue:value forPath:fieldPath.PopFirst()];
|
|
return [self objectBySettingValue:newChild forField:childName];
|
|
}
|
|
}
|
|
|
|
- (FSTObjectValue *)objectByDeletingPath:(const FieldPath &)fieldPath {
|
|
FSTAssert(fieldPath.size() > 0, @"Cannot delete an empty path");
|
|
NSString *childName = util::WrapNSString(fieldPath.first_segment());
|
|
if (fieldPath.size() == 1) {
|
|
return [[FSTObjectValue alloc]
|
|
initWithImmutableDictionary:[_internalValue dictionaryByRemovingObjectForKey:childName]];
|
|
} else {
|
|
FSTFieldValue *child = _internalValue[childName];
|
|
if ([child isKindOfClass:[FSTObjectValue class]]) {
|
|
FSTObjectValue *newChild =
|
|
[((FSTObjectValue *)child) objectByDeletingPath:fieldPath.PopFirst()];
|
|
return [self objectBySettingValue:newChild forField:childName];
|
|
} else {
|
|
// If the child is not found or is a primitive type, make no modifications
|
|
return self;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forField:(NSString *)field {
|
|
return [[FSTObjectValue alloc]
|
|
initWithImmutableDictionary:[_internalValue dictionaryBySettingObject:value forKey:field]];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface FSTArrayValue ()
|
|
@property(nonatomic, strong, readonly) NSArray<FSTFieldValue *> *internalValue;
|
|
@end
|
|
|
|
#pragma mark - FSTArrayValue
|
|
|
|
@implementation FSTArrayValue
|
|
|
|
- (id)initWithValueNoCopy:(NSArray<FSTFieldValue *> *)value {
|
|
self = [super init];
|
|
if (self) {
|
|
// Does not copy, assumes the caller has already copied.
|
|
_internalValue = value;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other {
|
|
if (other == self) {
|
|
return YES;
|
|
}
|
|
if (![other isKindOfClass:[self class]]) {
|
|
return NO;
|
|
}
|
|
|
|
// NSArray's isEqual does the right thing for our purposes.
|
|
FSTArrayValue *otherArray = other;
|
|
return [self.internalValue isEqual:otherArray.internalValue];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [self.internalValue hash];
|
|
}
|
|
|
|
- (id)valueWithOptions:(FSTFieldValueOptions *)options {
|
|
NSMutableArray *result = [NSMutableArray arrayWithCapacity:_internalValue.count];
|
|
[self.internalValue enumerateObjectsUsingBlock:^(FSTFieldValue *obj, NSUInteger idx, BOOL *stop) {
|
|
[result addObject:[obj value]];
|
|
}];
|
|
return result;
|
|
}
|
|
|
|
- (FSTTypeOrder)typeOrder {
|
|
return FSTTypeOrderArray;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(FSTFieldValue *)other {
|
|
if ([other isKindOfClass:[FSTArrayValue class]]) {
|
|
NSArray<FSTFieldValue *> *selfArray = self.internalValue;
|
|
NSArray<FSTFieldValue *> *otherArray = ((FSTArrayValue *)other).internalValue;
|
|
NSUInteger minLength = MIN(selfArray.count, otherArray.count);
|
|
for (NSUInteger i = 0; i < minLength; i++) {
|
|
NSComparisonResult cmp = [selfArray[i] compare:otherArray[i]];
|
|
if (cmp != NSOrderedSame) {
|
|
return cmp;
|
|
}
|
|
}
|
|
return WrapCompare<int64_t>(selfArray.count, otherArray.count);
|
|
} else {
|
|
return [self defaultCompare:other];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|