Skip to content

Adding Proto-based equality and comparison #2678

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

Conversation

schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Feb 25, 2020

Port of firebase/firebase-android-sdk#1157

Sending this to @wilhuff since it includes some gnarly type normalization that does not exist on Android (e.g. converts string numbers to numbers).

@schmidt-sebastian schmidt-sebastian changed the base branch from mrschmidt/apiblob to mrschmidt/rewritefieldvalue February 25, 2020 04:07
@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/rewritefieldvalue-2 branch 6 times, most recently from 4079cff to 7e97b72 Compare February 25, 2020 05:32
}
}

function compareBooleans(b1: boolean, b2: boolean): number {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is unnecessary. Javascript booleans compare naturally using < so you can just use primitiveComparator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

@@ -512,7 +445,7 @@ export class JsonProtoSerializer {
return new fieldValue.GeoPointValue(new GeoPoint(latitude, longitude));
} else if ('bytesValue' in obj) {
assertPresent(obj.bytesValue, 'bytesValue');
const blob = this.fromBlob(obj.bytesValue);
const blob = normalizeByteString(obj.bytesValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is now a ByteString, so better to name this bytes.

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

@@ -0,0 +1,385 @@
/**
* @license
* Copyright 202 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

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

2020 :-)

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 thought the year was meant to represent when the file was originally created...
Updated.

* limitations under the License.
*/

import * as api from '../protos/firestore_proto_api';
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we already have an "api" package this is potentially confusing. Consider importing this as protos or something that more concretely points toward this being the messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use this exact import in a lot of places in the SDK. My naming was inspired by these imports.

Kept as is.


/** Extracts the backend's type order for the provided value. */
export function typeOrder(value: api.Value): TypeOrder {
if ('nullValue' in value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like we could take the first key in value and then look this up in a map. Something like:

const typeOrderMap = {
  'nullValue': TypeOrder.NullValue,
  // ...
}

export function typeOrder(value: api.Value): TypeOrder {
  for (const key in value) {
    const result = typeOrderMap[key];
    if (result !== undefined) {
      return result;
    }
  }
  return fail('Invalid value type: ' + JSON.stringify(value));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems like it would result in more code. I am not opposed, but the current implementation closely follows

Kept as is.

export function numberEquals(left: api.Value, right: api.Value): boolean {
if ('integerValue' in left && 'integerValue' in right) {
return (
normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the serializer just perform these normalizations once so we don't have to do it on every comparison?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's something we should potentially discuss offline. It breaks one of the goals of this field value rewrite (as it would require us to do a full pass of incoming objects), but it would simplify this code a bit and could be something that we could just do for Node.

If we do end up pre-normalizing our data, we will likely also end up with two different versions of the Value type, and we should have a strategy on how to cleanly separate the "incoming" Value type from the internal Value type.

}

function compareTimestamps(
left: string | { seconds?: string; nanos?: number },
Copy link
Contributor

Choose a reason for hiding this comment

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

This type is a mouthful. Maybe make an alias for it?

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.

const rightMap = right.fields || {};
const rightKeys = keys(leftMap);

leftKeys.sort();
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these sorts required? The protos we get from the backend are guaranteed to come with keys in order, and JS guarantees that traversing keys happens in the order they're added to the object.

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 added a comment here (that I stole from the canonical ID implementation on Android). The maps can get out of order when documents are added locally. Furthermore, I don't think iteration order is guaranteed here (on Android, iteration by insertion order is enforced by the backing LinkedHashMap).

export function normalizeTimestamp(
date: string | { seconds?: string; nanos?: number }
): { seconds: number; nanos: number } {
assert(!!date, 'Cannot deserialize null or undefined timestamp.');
Copy link
Contributor

Choose a reason for hiding this comment

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

s/deserialize/normalize/ ?

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

return value;
} else if (typeof value === 'string') {
// Proto 3 uses the string values 'NaN' and 'Infinity'.
if (value === 'NaN') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Number() parses these values just fine. Do we really need to special case them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. This is actually from the serializer, but we don't need to this code here (or there).

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Feb 25, 2020
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

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

LGTM

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Feb 25, 2020
@schmidt-sebastian schmidt-sebastian merged commit 799bc96 into mrschmidt/rewritefieldvalue Feb 25, 2020
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/rewritefieldvalue-2 branch February 28, 2020 23:03
@firebase firebase locked and limited conversation to collaborators Mar 27, 2020
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.

2 participants