@@ -109,6 +109,7 @@ - (instancetype)initWithData:(FSTObjectValue *)data
109
109
*/
110
110
typedef NS_ENUM (NSInteger , FSTUserDataSource) {
111
111
FSTUserDataSourceSet,
112
+ FSTUserDataSourceMergeSet,
112
113
FSTUserDataSourceUpdate,
113
114
FSTUserDataSourceQueryValue, // from a where clause or cursor bound.
114
115
};
@@ -158,6 +159,9 @@ - (instancetype)initWithSource:(FSTUserDataSource)dataSource
158
159
- (instancetype )contextForField : (NSString *)fieldName ;
159
160
- (instancetype )contextForFieldPath : (FSTFieldPath *)fieldPath ;
160
161
- (instancetype )contextForArrayIndex : (NSUInteger )index ;
162
+
163
+ /* * Returns true for the non-query parse contexts (Set, MergeSet and Update) */
164
+ - (BOOL )isWrite ;
161
165
@end
162
166
163
167
@implementation FSTParseContext
@@ -230,10 +234,6 @@ - (NSString *)fieldDescription {
230
234
}
231
235
}
232
236
233
- - (BOOL )isWrite {
234
- return _dataSource == FSTUserDataSourceSet || _dataSource == FSTUserDataSourceUpdate;
235
- }
236
-
237
237
- (void )validatePath {
238
238
// TODO(b/34871131): Remove nil check once we have proper paths for fields within arrays.
239
239
if (self.path == nil ) {
@@ -252,6 +252,19 @@ - (void)validatePathSegment:(NSString *)segment {
252
252
}
253
253
}
254
254
255
+ - (BOOL )isWrite {
256
+ switch (self.dataSource ) {
257
+ case FSTUserDataSourceSet: // Falls through.
258
+ case FSTUserDataSourceMergeSet: // Falls through.
259
+ case FSTUserDataSourceUpdate:
260
+ return YES ;
261
+ case FSTUserDataSourceQueryValue:
262
+ return NO ;
263
+ default :
264
+ FSTThrowInvalidArgument (@" Unexpected case for FSTUserDataSource: %d " , self.dataSource );
265
+ }
266
+ }
267
+
255
268
@end
256
269
257
270
#pragma mark - FSTDocumentKeyReference
@@ -288,35 +301,37 @@ - (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID
288
301
return self;
289
302
}
290
303
291
- - (FSTParsedSetData *)parsedSetData : (id )input options : (FIRSetOptions *) options {
304
+ - (FSTParsedSetData *)parsedMergeData : (id )input {
292
305
// NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust
293
306
// Obj-C to verify the type for us.
294
307
if (![input isKindOfClass: [NSDictionary class ]]) {
295
308
FSTThrowInvalidArgument (@" Data to be written must be an NSDictionary." );
296
309
}
297
310
298
311
FSTParseContext *context =
299
- [FSTParseContext contextWithSource: FSTUserDataSourceSet path: [FSTFieldPath emptyPath ]];
300
-
301
- __block FSTObjectValue *updateData = [FSTObjectValue objectValue ];
312
+ [FSTParseContext contextWithSource: FSTUserDataSourceMergeSet path: [FSTFieldPath emptyPath ]];
313
+ FSTObjectValue *updateData = (FSTObjectValue *)[self parseData: input context: context];
302
314
303
- [input enumerateKeysAndObjectsUsingBlock: ^(NSString *key, id value, BOOL *stop) {
304
- // Treat key as a complete field name (don't split on dots, etc.)
305
- FSTFieldPath *path = [[FIRFieldPath alloc ] initWithFields: @[ key ]].internalValue ;
315
+ return
316
+ [[FSTParsedSetData alloc ] initWithData: updateData
317
+ fieldMask: [[FSTFieldMask alloc ] initWithFields: context.fieldMask]
318
+ fieldTransforms: context.fieldTransforms];
319
+ }
306
320
307
- value = self.preConverter (value);
321
+ - (FSTParsedSetData *)parsedSetData : (id )input {
322
+ // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust
323
+ // Obj-C to verify the type for us.
324
+ if (![input isKindOfClass: [NSDictionary class ]]) {
325
+ FSTThrowInvalidArgument (@" Data to be written must be an NSDictionary." );
326
+ }
308
327
309
- FSTFieldValue *_Nullable parsedValue =
310
- [self parseData: value context: [context contextForFieldPath: path]];
311
- if (parsedValue) {
312
- updateData = [updateData objectBySettingValue: parsedValue forPath: path];
313
- }
314
- }];
328
+ FSTParseContext *context =
329
+ [FSTParseContext contextWithSource: FSTUserDataSourceSet path: [FSTFieldPath emptyPath ]];
330
+ FSTObjectValue *updateData = (FSTObjectValue *)[self parseData: input context: context];
315
331
316
- return [[FSTParsedSetData alloc ]
317
- initWithData: updateData
318
- fieldMask: options.merge ? [[FSTFieldMask alloc ] initWithFields: context.fieldMask] : nil
319
- fieldTransforms: context.fieldTransforms];
332
+ return [[FSTParsedSetData alloc ] initWithData: updateData
333
+ fieldMask: nil
334
+ fieldTransforms: context.fieldTransforms];
320
335
}
321
336
322
337
- (FSTParsedUpdateData *)parsedUpdateData : (id )input {
@@ -536,20 +551,23 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(FSTPars
536
551
537
552
} else if ([input isKindOfClass: [FIRFieldValue class ]]) {
538
553
if ([input isKindOfClass: [FSTDeleteFieldValue class ]]) {
539
- // We shouldn't encounter delete sentinels here. Provide a good error.
540
- if (context.dataSource != FSTUserDataSourceUpdate) {
541
- FSTThrowInvalidArgument (@" FieldValue.delete() can only be used with updateData()." );
542
- } else {
554
+ if (context.dataSource == FSTUserDataSourceMergeSet) {
555
+ return nil ;
556
+ } else if (context.dataSource == FSTUserDataSourceUpdate) {
543
557
FSTAssert (context.path .length > 0 ,
544
558
@" FieldValue.delete() at the top level should have already been handled." );
545
559
FSTThrowInvalidArgument (
546
560
@" FieldValue.delete() can only appear at the top level of your "
547
561
" update data%@ " ,
548
562
[context fieldDescription ]);
563
+ } else {
564
+ // We shouldn't encounter delete sentinels for queries or non-merge setData calls.
565
+ FSTThrowInvalidArgument (
566
+ @" FieldValue.delete() can only be used with updateData() and setData() with "
567
+ @" SetOptions.merge()." );
549
568
}
550
569
} else if ([input isKindOfClass: [FSTServerTimestampFieldValue class ]]) {
551
- if (context.dataSource != FSTUserDataSourceSet &&
552
- context.dataSource != FSTUserDataSourceUpdate) {
570
+ if (![context isWrite ]) {
553
571
FSTThrowInvalidArgument (
554
572
@" FieldValue.serverTimestamp() can only be used with setData() and updateData()." );
555
573
}
0 commit comments