Skip to content

Fix regression for merge with increment #3048

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Unreleased
- [fixed] Fixed a regression introduced in v7.14.2 that incorrectly applied
a `FieldValue.increment` in combination with `set({...}, {merge: true})`.
- [fixed] Firestore now rejects `onSnapshot()` listeners if they cannot be
registered in IndexedDB. Previously, these errors crashed the client.
- [fixed] Firestore now rejects write operations if they cannot be persisted
Expand Down
41 changes: 29 additions & 12 deletions packages/firestore/src/api/field_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,17 @@ export class ArrayUnionFieldValueImpl extends FieldValueImpl {
// Although array transforms are used with writes, the actual elements
// being uniomed or removed are not considered writes since they cannot
// contain any FieldValue sentinels, etc.
const parseContext = context.contextWith({
dataSource: UserDataSource.Argument,
methodName: this._methodName
});
const parseContext = new ParseContext(
{
dataSource: UserDataSource.Argument,
methodName: this._methodName,
arrayElement: true
},
context.databaseId,
context.serializer
);
const parsedElements = this._elements.map(
(element, i) => parseData(element, parseContext.childContextForArray(i))!
element => parseData(element, parseContext)!
);
const arrayUnion = new ArrayUnionTransformOperation(parsedElements);
return new FieldTransform(context.path!, arrayUnion);
Expand All @@ -128,12 +133,17 @@ export class ArrayRemoveFieldValueImpl extends FieldValueImpl {
// 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.
const parseContext = context.contextWith({
dataSource: UserDataSource.Argument,
methodName: this._methodName
});
const parseContext = new ParseContext(
{
dataSource: UserDataSource.Argument,
methodName: this._methodName,
arrayElement: true
},
context.databaseId,
context.serializer
);
const parsedElements = this._elements.map(
(element, i) => parseData(element, parseContext.childContextForArray(i))!
element => parseData(element, parseContext)!
);
const arrayUnion = new ArrayRemoveTransformOperation(parsedElements);
return new FieldTransform(context.path!, arrayUnion);
Expand All @@ -151,8 +161,15 @@ export class NumericIncrementFieldValueImpl extends FieldValueImpl {
}

toFieldTransform(context: ParseContext): FieldTransform {
context.contextWith({ methodName: this._methodName });
const operand = parseData(this._operand, context)!;
const parseContext = new ParseContext(
{
dataSource: UserDataSource.Argument,
methodName: this._methodName
},
context.databaseId,
context.serializer
);
const operand = parseData(this._operand, parseContext)!;
const numericIncrement = new NumericIncrementTransformOperation(
context.serializer,
operand
Expand Down
23 changes: 13 additions & 10 deletions packages/firestore/src/api/user_data_reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,12 @@ interface ContextSettings {
* nonempty path (indicating the context represents a nested location within
* the data).
*/
readonly path: FieldPath | null;
/** Whether or not this context corresponds to an element of an array. */
readonly arrayElement: boolean;
readonly path?: FieldPath;
/**
* Whether or not this context corresponds to an element of an array.
* If not set, elements are treated as if they were outside of arrays.
*/
readonly arrayElement?: boolean;
}

/** A "context" object passed around while parsing user data. */
Expand Down Expand Up @@ -181,7 +184,7 @@ export class ParseContext {
this.fieldMask = fieldMask || [];
}

get path(): FieldPath | null {
get path(): FieldPath | undefined {
return this.settings.path;
}

Expand All @@ -201,28 +204,28 @@ export class ParseContext {
}

childContextForField(field: string): ParseContext {
const childPath = this.path == null ? null : this.path.child(field);
const childPath = this.path?.child(field);
const context = this.contextWith({ path: childPath, arrayElement: false });
context.validatePathSegment(field);
return context;
}

childContextForFieldPath(field: FieldPath): ParseContext {
const childPath = this.path == null ? null : this.path.child(field);
const childPath = this.path?.child(field);
const context = this.contextWith({ path: childPath, arrayElement: false });
context.validatePath();
return context;
}

childContextForArray(index: number): ParseContext {
// TODO(b/34871131): We don't support array paths right now; so make path
// null.
return this.contextWith({ path: null, arrayElement: true });
// undefined.
return this.contextWith({ path: undefined, arrayElement: true });
}

createError(reason: string): Error {
const fieldDescription =
this.path === null || this.path.isEmpty()
!this.path || this.path.isEmpty()
? ''
: ` (found in field ${this.path.toString()})`;
return new FirestoreError(
Expand All @@ -246,7 +249,7 @@ export class ParseContext {
private validatePath(): void {
// TODO(b/34871131): Remove null check once we have proper paths for fields
// within arrays.
if (this.path === null) {
if (!this.path) {
return;
}
for (let i = 0; i < this.path.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2018 Google Inc.
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -140,6 +140,14 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => {
});
});

it('increments with set() and merge:true', async () => {
await withTestSetup(async () => {
await writeInitialData({ sum: 1 });
await docRef.set({ sum: FieldValue.increment(1337) }, { merge: true });
await expectLocalAndRemoteValue(1338);
});
});

it('multiple double increments', async () => {
await withTestSetup(async () => {
await writeInitialData({ sum: 0.0 });
Expand Down