Skip to content
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

Add Numeric Add #1368

Merged
merged 10 commits into from
Mar 6, 2019
Merged

Add Numeric Add #1368

merged 10 commits into from
Mar 6, 2019

Conversation

schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Nov 8, 2018

Port of firebase/firebase-android-sdk#105

Unlike Android, this PR doesn't treat INT64 overflows, as JavaScript's precision is limited to 53 bit. We likely can't properly expose the full backend semantics to our customers since we eventually surface the results as a number.

This limitation is currently - along with the rest of the API - going through API review.

Copy link
Contributor

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few nits!

* are subject to precision loss. Furthermore, once processed by the Firestore
* backend, all integer operations are capped between -2^63 and 2^63-1.
*
* If field is not an integer or double, or if the field does not yet exist,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"If field" => "If the field"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

* Returns a special value that can be used with set() or update() that tells
* the server to add the given value to the field's current value.
*
* If either the operand or the current field value is of type double, both
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW it's tempting to try to avoid using "double" because JavaScript just has Numbers (and so we should just talk about integer and non-integer numbers). But the backend has doubles, so I guess this needs to cover that. Anyway, it's going to be subtly confusing no matter how we explain it, so this is probably fine. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this section to:

     * If either the operand or the current field value uses floating point
     * precision, all arithmetic will follow IEEE 754 semantics. If both values
     * are integers, values outside of JavaScript's safe number range
     * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject
     * to precision loss. Furthermore, once processed by the Firestore backend,
     * all integer operations are capped between -2^63 and 2^63-1.
     *
     * If the current field value is not of type number, or if the field does not
     * yet exist, the transformation will set the field to the given value.

@@ -50,6 +53,11 @@ export abstract class FieldValueImpl implements firestore.FieldValue {
return new ArrayRemoveFieldValueImpl(elements);
}

static numericAdd(n: number): FieldValueImpl {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use validateExactNumberOfArgs() to verify we got 1 arg. delete() and serverTimestamp() should probably be verifying no args.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I added a validateNoArgs and used it in delete() and serverTimestamp().

* non-idempotent write before the write is removed from the queue.
*
* These mutations are never sent to the backend and serialized via
* JsonProtoSerializer.toMutation().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of sounds like you're saying they're never serialized via JsonProtoSerializer.toMutation(). In general it seems awkward to combine these two statements into one sentence. They don't seem very related.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the last part of this.

const keys = mutations.reduce(
(keys, m) => keys.add(m.key),
documentKeySet()
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Nice/Oh... Nice/g

const fieldMask = mutation.fieldMask;
if (fieldMask) {
if (maybeDoc instanceof Document) {
const baseValues = fieldMask.applyTo(maybeDoc.data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you port the latest Android? (Assign baseValues using ternary operator and include comment about Precondition.exists(true)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -228,6 +228,7 @@ export class MemoryEagerDelegate implements ReferenceDelegate {
return cache
.getMatchingKeysForTargetId(txn, queryData.targetId)
.next(keys => {
console.log('Found matching keys ' + keys.size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't tslint yell about this? (and throughout the rest of this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm. Sorry. I had a bunch of GitHub trouble where it wouldn't show my latest changes. Looks like I rebasing my stashed changes from last week solved my troubles :/

return new DoubleValue(sum);
}

// If the existing value is not a number, use the value of the transform as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this in an else block? (since I don't like early return and conceptually I think there are 3 possible cases here and we should just structure the code as such)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -595,7 +595,7 @@ describeSpec('Writes:', [], () => {

specTest(
'Writes are released when there are no queries left',
['eager-gc'],
['eager-gc','exclusive'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mila must have pushed to this branch. Investigating.

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback addressed, and random changes reverted.

* Returns a special value that can be used with set() or update() that tells
* the server to add the given value to the field's current value.
*
* If either the operand or the current field value is of type double, both
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this section to:

     * If either the operand or the current field value uses floating point
     * precision, all arithmetic will follow IEEE 754 semantics. If both values
     * are integers, values outside of JavaScript's safe number range
     * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject
     * to precision loss. Furthermore, once processed by the Firestore backend,
     * all integer operations are capped between -2^63 and 2^63-1.
     *
     * If the current field value is not of type number, or if the field does not
     * yet exist, the transformation will set the field to the given value.

* are subject to precision loss. Furthermore, once processed by the Firestore
* backend, all integer operations are capped between -2^63 and 2^63-1.
*
* If field is not an integer or double, or if the field does not yet exist,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -50,6 +53,11 @@ export abstract class FieldValueImpl implements firestore.FieldValue {
return new ArrayRemoveFieldValueImpl(elements);
}

static numericAdd(n: number): FieldValueImpl {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I added a validateNoArgs and used it in delete() and serverTimestamp().

* non-idempotent write before the write is removed from the queue.
*
* These mutations are never sent to the backend and serialized via
* JsonProtoSerializer.toMutation().
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the last part of this.

const keys = mutations.reduce(
(keys, m) => keys.add(m.key),
documentKeySet()
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Nice/Oh... Nice/g

@@ -228,6 +228,7 @@ export class MemoryEagerDelegate implements ReferenceDelegate {
return cache
.getMatchingKeysForTargetId(txn, queryData.targetId)
.next(keys => {
console.log('Found matching keys ' + keys.size);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm. Sorry. I had a bunch of GitHub trouble where it wouldn't show my latest changes. Looks like I rebasing my stashed changes from last week solved my troubles :/

return new DoubleValue(sum);
}

// If the existing value is not a number, use the value of the transform as
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -595,7 +595,7 @@ describeSpec('Writes:', [], () => {

specTest(
'Writes are released when there are no queries left',
['eager-gc'],
['eager-gc','exclusive'],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mila must have pushed to this branch. Investigating.

const fieldMask = mutation.fieldMask;
if (fieldMask) {
if (maybeDoc instanceof Document) {
const baseValues = fieldMask.applyTo(maybeDoc.data);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, changes look good!

@@ -83,6 +91,12 @@ export class ArrayRemoveFieldValueImpl extends FieldValueImpl {
}
}

export class NumericAddFieldValueImpl extends FieldValueImpl {
constructor(readonly _operand: number) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do public readonly to make the intent clear.

@@ -644,6 +646,15 @@ export class UserDataConverter {
context.fieldTransforms.push(
new FieldTransform(context.path, arrayRemove)
);
} else if (value instanceof NumericAddFieldValueImpl) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative if you are not matching super class.

switch( value.constructor) {
  case NumericAddFieldValueImpl: 
....
}

@schmidt-sebastian schmidt-sebastian changed the title Add Numeric Add DO NOT MERGE Add Numeric Add Nov 12, 2018
@schmidt-sebastian
Copy link
Contributor Author

FYI: Pushed commit to rename numericAdd() to increment().

@schmidt-sebastian
Copy link
Contributor Author

PR updated with new Protobuf naming.

@mohshraim
Copy link

Hello @schmidt-sebastian
We are waiting this important feature..
Any chance to have it in near future beta release?
Thanks

@mikelehen
Copy link
Contributor

@mohshraim We're waiting on the backend changes to fully deploy. Sorry for the delay.

@schmidt-sebastian schmidt-sebastian changed the title DO NOT MERGE Add Numeric Add Add Numeric Add Mar 6, 2019
@schmidt-sebastian schmidt-sebastian merged commit d364569 into master Mar 6, 2019
@wilhuff wilhuff deleted the mrschmidt-increment3 branch March 11, 2019 21:01
@firebase firebase locked and limited conversation to collaborators Oct 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants