-
Notifications
You must be signed in to change notification settings - Fork 937
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
Adding Proto-based equality and comparison #2678
Conversation
4079cff
to
7e97b72
Compare
7e97b72
to
14931c2
Compare
} | ||
} | ||
|
||
function compareBooleans(b1: boolean, b2: boolean): number { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2020 :-)
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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));
}
There was a problem hiding this comment.
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
if ('nullValue' in obj) { |
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 }, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/deserialize/normalize/ ?
There was a problem hiding this comment.
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') { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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).