@@ -117,9 +117,23 @@ export class ParsedUpdateData {
117
117
enum UserDataSource {
118
118
Set ,
119
119
Update ,
120
+ MergeSet ,
120
121
QueryValue // from a where clause or cursor bound
121
122
}
122
123
124
+ function isWrite ( dataSource : UserDataSource ) {
125
+ switch ( dataSource ) {
126
+ case UserDataSource . Set : // fall through
127
+ case UserDataSource . MergeSet : // fall through
128
+ case UserDataSource . Update :
129
+ return true ;
130
+ case UserDataSource . QueryValue :
131
+ return false ;
132
+ default :
133
+ throw fail ( `Unexpected case for UserDataSource: ${ dataSource } ` ) ;
134
+ }
135
+ }
136
+
123
137
/** A "context" object passed around while parsing user data. */
124
138
class ParseContext {
125
139
readonly fieldTransforms : FieldTransform [ ] ;
@@ -230,7 +244,7 @@ class ParseContext {
230
244
}
231
245
232
246
private validatePathSegment ( segment : string ) {
233
- if ( this . isWrite ( ) && RESERVED_FIELD_REGEX . test ( segment ) ) {
247
+ if ( isWrite ( this . dataSource ) && RESERVED_FIELD_REGEX . test ( segment ) ) {
234
248
throw this . createError ( 'Document fields cannot begin and end with __' ) ;
235
249
}
236
250
}
@@ -273,40 +287,43 @@ export class DocumentKeyReference {
273
287
export class UserDataConverter {
274
288
constructor ( private preConverter : DataPreConverter ) { }
275
289
276
- /** Parse document data (e.g. from a set() call). */
277
- parseSetData (
278
- methodName : string ,
279
- input : AnyJs ,
280
- options : firestore . SetOptions
281
- ) : ParsedSetData {
290
+ /** Parse document data from a non-merge set() call.*/
291
+ parseSetData ( methodName : string , input : AnyJs ) : ParsedSetData {
282
292
const context = new ParseContext (
283
293
UserDataSource . Set ,
284
294
methodName ,
285
295
FieldPath . EMPTY_PATH
286
296
) ;
287
297
validatePlainObject ( 'Data must be an object, but it was:' , context , input ) ;
288
298
289
- const merge = options . merge !== undefined ? options . merge : false ;
299
+ let updateData = this . parseData ( input , context ) ;
290
300
291
- let updateData = ObjectValue . EMPTY ;
292
-
293
- objUtils . forEach ( input as Dict < AnyJs > , ( key , value ) => {
294
- const path = new ExternalFieldPath ( key ) . _internalPath ;
295
-
296
- const childContext = context . childContextForFieldPath ( path ) ;
297
- value = this . runPreConverter ( value , childContext ) ;
301
+ return new ParsedSetData (
302
+ updateData as ObjectValue ,
303
+ /* fieldMask= */ null ,
304
+ context . fieldTransforms
305
+ ) ;
306
+ }
298
307
299
- const parsedValue = this . parseData ( value , childContext ) ;
300
- if ( parsedValue ) {
301
- updateData = updateData . set ( path , parsedValue ) ;
302
- }
303
- } ) ;
308
+ /** Parse document data from a set() call with '{merge:true}'. */
309
+ parseMergeData ( methodName : string , input : AnyJs ) : ParsedSetData {
310
+ const context = new ParseContext (
311
+ UserDataSource . MergeSet ,
312
+ methodName ,
313
+ FieldPath . EMPTY_PATH
314
+ ) ;
315
+ validatePlainObject ( 'Data must be an object, but it was:' , context , input ) ;
304
316
305
- const fieldMask = merge ? new FieldMask ( context . fieldMask ) : null ;
306
- return new ParsedSetData ( updateData , fieldMask , context . fieldTransforms ) ;
317
+ let updateData = this . parseData ( input , context ) ;
318
+ const fieldMask = new FieldMask ( context . fieldMask ) ;
319
+ return new ParsedSetData (
320
+ updateData as ObjectValue ,
321
+ fieldMask ,
322
+ context . fieldTransforms
323
+ ) ;
307
324
}
308
325
309
- /** Parse update data (e.g. from an update() call) . */
326
+ /** Parse update data from an update() call. */
310
327
parseUpdateData ( methodName : string , input : AnyJs ) : ParsedUpdateData {
311
328
const context = new ParseContext (
312
329
UserDataSource . Update ,
@@ -523,12 +540,9 @@ export class UserDataConverter {
523
540
return new RefValue ( value . databaseId , value . key ) ;
524
541
} else if ( value instanceof FieldValueImpl ) {
525
542
if ( value instanceof DeleteFieldValueImpl ) {
526
- // We shouldn't encounter delete sentinels here. Provide a good error.
527
- if ( context . dataSource !== UserDataSource . Update ) {
528
- throw context . createError (
529
- 'FieldValue.delete() can only be used with update()'
530
- ) ;
531
- } else {
543
+ if ( context . dataSource == UserDataSource . MergeSet ) {
544
+ return null ;
545
+ } else if ( context . dataSource === UserDataSource . Update ) {
532
546
assert (
533
547
context . path == null || context . path . length > 0 ,
534
548
'FieldValue.delete() at the top level should have already' +
@@ -538,12 +552,14 @@ export class UserDataConverter {
538
552
'FieldValue.delete() can only appear at the top level ' +
539
553
'of your update data'
540
554
) ;
555
+ } else {
556
+ // We shouldn't encounter delete sentinels for queries or non-merge set() calls.
557
+ throw context . createError (
558
+ 'FieldValue.delete() can only be used with update() and set() with {merge:true}'
559
+ ) ;
541
560
}
542
561
} else if ( value instanceof ServerTimestampFieldValueImpl ) {
543
- if (
544
- context . dataSource !== UserDataSource . Set &&
545
- context . dataSource !== UserDataSource . Update
546
- ) {
562
+ if ( ! isWrite ( context . dataSource ) ) {
547
563
throw context . createError (
548
564
'FieldValue.serverTimestamp() can only be used with set()' +
549
565
' and update()'
0 commit comments