Skip to content

Commit 5d3f44d

Browse files
Adding Proto-based equality and comparison
This will be used (and tested) in the follow-up CL that adds the FieldValue tpes.
1 parent baa0a89 commit 5d3f44d

File tree

5 files changed

+294
-29
lines changed

5 files changed

+294
-29
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/Blob.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,6 @@ public int hashCode() {
8282

8383
@Override
8484
public int compareTo(@NonNull Blob other) {
85-
int size = Math.min(bytes.size(), other.bytes.size());
86-
for (int i = 0; i < size; i++) {
87-
// Make sure the bytes are unsigned
88-
int thisByte = bytes.byteAt(i) & 0xff;
89-
int otherByte = other.bytes.byteAt(i) & 0xff;
90-
if (thisByte < otherByte) {
91-
return -1;
92-
} else if (thisByte > otherByte) {
93-
return 1;
94-
}
95-
// Byte values are equal, continue with comparison
96-
}
97-
return Util.compareIntegers(bytes.size(), other.bytes.size());
85+
return Util.compareByteString(bytes, other.bytes);
9886
}
9987
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteMutationQueue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public List<MutationBatch> getAllMutationBatchesAffectingDocumentKeys(
318318
Collections.sort(
319319
result,
320320
(MutationBatch lhs, MutationBatch rhs) ->
321-
Util.compareInts(lhs.getBatchId(), rhs.getBatchId()));
321+
Util.compareIntegers(lhs.getBatchId(), rhs.getBatchId()));
322322
}
323323
return result;
324324
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/value/FieldValue.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@
4242
*/
4343
public abstract class FieldValue implements Comparable<FieldValue> {
4444
/** The order of types in Firestore; this order is defined by the backend. */
45-
static final int TYPE_ORDER_NULL = 0;
45+
public static final int TYPE_ORDER_NULL = 0;
4646

47-
static final int TYPE_ORDER_BOOLEAN = 1;
48-
static final int TYPE_ORDER_NUMBER = 2;
49-
static final int TYPE_ORDER_TIMESTAMP = 3;
50-
static final int TYPE_ORDER_STRING = 4;
51-
static final int TYPE_ORDER_BLOB = 5;
52-
static final int TYPE_ORDER_REFERENCE = 6;
53-
static final int TYPE_ORDER_GEOPOINT = 7;
54-
static final int TYPE_ORDER_ARRAY = 8;
55-
static final int TYPE_ORDER_OBJECT = 9;
47+
public static final int TYPE_ORDER_BOOLEAN = 1;
48+
public static final int TYPE_ORDER_NUMBER = 2;
49+
public static final int TYPE_ORDER_TIMESTAMP = 3;
50+
public static final int TYPE_ORDER_STRING = 4;
51+
public static final int TYPE_ORDER_BLOB = 5;
52+
public static final int TYPE_ORDER_REFERENCE = 6;
53+
public static final int TYPE_ORDER_GEOPOINT = 7;
54+
public static final int TYPE_ORDER_ARRAY = 8;
55+
public static final int TYPE_ORDER_OBJECT = 9;
5656

5757
public abstract int typeOrder();
5858

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.util;
16+
17+
import static com.google.firebase.firestore.util.Assert.fail;
18+
import static com.google.firebase.firestore.util.Assert.hardAssert;
19+
20+
import androidx.annotation.Nullable;
21+
import com.google.common.base.Splitter;
22+
import com.google.firebase.firestore.model.value.FieldValue;
23+
import com.google.firestore.v1.ArrayValue;
24+
import com.google.firestore.v1.MapValue;
25+
import com.google.firestore.v1.Value;
26+
import java.util.Iterator;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.TreeMap;
30+
31+
public class ProtoValueUtil {
32+
33+
public static int typeOrder(Value value) {
34+
35+
switch (value.getValueTypeCase()) {
36+
case NULL_VALUE:
37+
return FieldValue.TYPE_ORDER_NULL;
38+
case BOOLEAN_VALUE:
39+
return FieldValue.TYPE_ORDER_BOOLEAN;
40+
case INTEGER_VALUE:
41+
return FieldValue.TYPE_ORDER_NUMBER;
42+
case DOUBLE_VALUE:
43+
return FieldValue.TYPE_ORDER_NUMBER;
44+
case TIMESTAMP_VALUE:
45+
return FieldValue.TYPE_ORDER_TIMESTAMP;
46+
case STRING_VALUE:
47+
return FieldValue.TYPE_ORDER_STRING;
48+
case BYTES_VALUE:
49+
return FieldValue.TYPE_ORDER_BLOB;
50+
case REFERENCE_VALUE:
51+
return FieldValue.TYPE_ORDER_REFERENCE;
52+
case GEO_POINT_VALUE:
53+
return FieldValue.TYPE_ORDER_GEOPOINT;
54+
case ARRAY_VALUE:
55+
return FieldValue.TYPE_ORDER_ARRAY;
56+
case MAP_VALUE:
57+
return FieldValue.TYPE_ORDER_OBJECT;
58+
default:
59+
throw fail("Invalid value type: " + value.getValueTypeCase());
60+
}
61+
}
62+
63+
/** Returns whether `value` is non-null and corresponds to the given type order. */
64+
public static boolean isType(@Nullable Value value, int typeOrder) {
65+
return value != null && typeOrder(value) == typeOrder;
66+
}
67+
68+
public static boolean equals(Value left, Value right) {
69+
int leftType = typeOrder(left);
70+
int rightType = typeOrder(right);
71+
if (leftType != rightType) {
72+
return false;
73+
}
74+
75+
switch (leftType) {
76+
case FieldValue.TYPE_ORDER_ARRAY:
77+
return arrayEquals(left, right);
78+
case FieldValue.TYPE_ORDER_NUMBER:
79+
return numberEquals(left, right);
80+
case FieldValue.TYPE_ORDER_OBJECT:
81+
return objectEquals(left, right);
82+
default:
83+
return left.equals(right);
84+
}
85+
}
86+
87+
private static boolean objectEquals(Value left, Value right) {
88+
MapValue leftMap = left.getMapValue();
89+
MapValue rightMap = right.getMapValue();
90+
91+
if (leftMap.getFieldsCount() != rightMap.getFieldsCount()) {
92+
return false;
93+
}
94+
95+
for (Map.Entry<String, Value> entry : leftMap.getFieldsMap().entrySet()) {
96+
Value otherEntry = rightMap.getFieldsMap().get(entry.getKey());
97+
if (!entry.getValue().equals(otherEntry)) {
98+
return false;
99+
}
100+
}
101+
102+
return true;
103+
}
104+
105+
private static boolean numberEquals(Value left, Value right) {
106+
if (left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE
107+
&& right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
108+
return left.equals(right);
109+
} else if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE
110+
&& right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
111+
return Double.doubleToLongBits(left.getDoubleValue())
112+
== Double.doubleToLongBits(right.getDoubleValue());
113+
}
114+
115+
return false;
116+
}
117+
118+
private static boolean arrayEquals(Value left, Value right) {
119+
ArrayValue leftArray = left.getArrayValue();
120+
ArrayValue rightArray = right.getArrayValue();
121+
122+
if (leftArray.getValuesCount() != rightArray.getValuesCount()) {
123+
return false;
124+
}
125+
126+
for (int i = 0; i < leftArray.getValuesCount(); ++i) {
127+
if (!equals(leftArray.getValues(i), rightArray.getValues(i))) {
128+
return false;
129+
}
130+
}
131+
132+
return true;
133+
}
134+
135+
public static int compare(Value left, Value right) {
136+
int leftType = typeOrder(left);
137+
int rightType = typeOrder(right);
138+
139+
if (leftType != rightType) {
140+
return Util.compareIntegers(leftType, rightType);
141+
}
142+
143+
switch (leftType) {
144+
case FieldValue.TYPE_ORDER_NULL:
145+
return 0;
146+
case FieldValue.TYPE_ORDER_BOOLEAN:
147+
return Util.compareBooleans(left.getBooleanValue(), right.getBooleanValue());
148+
case FieldValue.TYPE_ORDER_NUMBER:
149+
return compareNumbers(left, right);
150+
case FieldValue.TYPE_ORDER_TIMESTAMP:
151+
return compareTimestamps(left, right);
152+
case FieldValue.TYPE_ORDER_STRING:
153+
return left.getStringValue().compareTo(right.getStringValue());
154+
case FieldValue.TYPE_ORDER_BLOB:
155+
return Util.compareByteString(left.getBytesValue(), right.getBytesValue());
156+
case FieldValue.TYPE_ORDER_REFERENCE:
157+
return compareReferences(left, right);
158+
case FieldValue.TYPE_ORDER_GEOPOINT:
159+
return compareGeoPoints(left, right);
160+
case FieldValue.TYPE_ORDER_ARRAY:
161+
return compareArrays(left, right);
162+
case FieldValue.TYPE_ORDER_OBJECT:
163+
return compareMaps(left, right);
164+
default:
165+
throw fail("Invalid value type: " + leftType);
166+
}
167+
}
168+
169+
private static int compareMaps(Value left, Value right) {
170+
Iterator<Map.Entry<String, Value>> iterator1 =
171+
new TreeMap<>(left.getMapValue().getFieldsMap()).entrySet().iterator();
172+
Iterator<Map.Entry<String, Value>> iterator2 =
173+
new TreeMap<>(right.getMapValue().getFieldsMap()).entrySet().iterator();
174+
while (iterator1.hasNext() && iterator2.hasNext()) {
175+
Map.Entry<String, Value> entry1 = iterator1.next();
176+
Map.Entry<String, Value> entry2 = iterator2.next();
177+
int keyCompare = entry1.getKey().compareTo(entry2.getKey());
178+
if (keyCompare != 0) {
179+
return keyCompare;
180+
}
181+
int valueCompare = compare(entry1.getValue(), entry2.getValue());
182+
if (valueCompare != 0) {
183+
return valueCompare;
184+
}
185+
}
186+
187+
// Only equal if both iterators are exhausted.
188+
return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext());
189+
}
190+
191+
private static int compareArrays(Value left, Value right) {
192+
int minLength =
193+
Math.min(left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
194+
for (int i = 0; i < minLength; i++) {
195+
int cmp = compare(left.getArrayValue().getValues(i), right.getArrayValue().getValues(i));
196+
if (cmp != 0) {
197+
return cmp;
198+
}
199+
}
200+
return Util.compareIntegers(
201+
left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
202+
}
203+
204+
private static int compareGeoPoints(Value left, Value right) {
205+
int comparison =
206+
Util.compareDoubles(
207+
left.getGeoPointValue().getLatitude(), right.getGeoPointValue().getLatitude());
208+
if (comparison == 0) {
209+
return Util.compareDoubles(
210+
left.getGeoPointValue().getLongitude(), right.getGeoPointValue().getLongitude());
211+
}
212+
return comparison;
213+
}
214+
215+
private static int compareReferences(Value left, Value right) {
216+
List<String> leftSegments = Splitter.on('/').splitToList(left.getReferenceValue());
217+
List<String> rightSegments = Splitter.on('/').splitToList(right.getReferenceValue());
218+
int minLength = Math.min(leftSegments.size(), rightSegments.size());
219+
for (int i = 0; i < minLength; i++) {
220+
int cmp = leftSegments.get(i).compareTo(rightSegments.get(i));
221+
if (cmp != 0) {
222+
return cmp;
223+
}
224+
}
225+
return Util.compareIntegers(leftSegments.size(), rightSegments.size());
226+
}
227+
228+
private static int compareTimestamps(Value left, Value right) {
229+
if (left.getTimestampValue().getSeconds() == right.getTimestampValue().getSeconds()) {
230+
return Integer.signum(
231+
left.getTimestampValue().getNanos() - right.getTimestampValue().getNanos());
232+
}
233+
return Long.signum(
234+
left.getTimestampValue().getSeconds() - right.getTimestampValue().getSeconds());
235+
}
236+
237+
private static int compareNumbers(Value left, Value right) {
238+
if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
239+
double thisDouble = left.getDoubleValue();
240+
if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
241+
return Util.compareDoubles(thisDouble, right.getDoubleValue());
242+
} else {
243+
hardAssert(
244+
right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE,
245+
"Unexpected value type: %s",
246+
right);
247+
return Util.compareMixed(thisDouble, right.getIntegerValue());
248+
}
249+
} else {
250+
hardAssert(
251+
left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE,
252+
"Unexpected value type: %s",
253+
left);
254+
long thisLong = left.getIntegerValue();
255+
if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
256+
return Util.compareLongs(thisLong, right.getIntegerValue());
257+
} else {
258+
hardAssert(
259+
right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE,
260+
"Unexpected value type: %s",
261+
right);
262+
return -1 * Util.compareMixed(right.getDoubleValue(), thisLong);
263+
}
264+
}
265+
}
266+
}

firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,6 @@ public static int compareLongs(long i1, long i2) {
8686
return NumberComparisonHelper.compareLongs(i1, i2);
8787
}
8888

89-
/** Utility function to compare ints. See {@link #compareLongs} for rationale. */
90-
public static int compareInts(int i1, int i2) {
91-
return NumberComparisonHelper.compareLongs(i1, i2);
92-
}
93-
9489
/** Utility function to compare doubles (using Firestore semantics for NaN). */
9590
public static int compareDoubles(double i1, double i2) {
9691
return NumberComparisonHelper.firestoreCompareDoubles(i1, i2);
@@ -222,4 +217,20 @@ public static void crashMainThread(RuntimeException exception) {
222217
throw exception;
223218
});
224219
}
220+
221+
public static int compareByteString(ByteString left, ByteString right) {
222+
int size = Math.min(left.size(), right.size());
223+
for (int i = 0; i < size; i++) {
224+
// Make sure the bytes are unsigned
225+
int thisByte = left.byteAt(i) & 0xff;
226+
int otherByte = right.byteAt(i) & 0xff;
227+
if (thisByte < otherByte) {
228+
return -1;
229+
} else if (thisByte > otherByte) {
230+
return 1;
231+
}
232+
// Byte values are equal, continue with comparison
233+
}
234+
return Util.compareIntegers(left.size(), right.size());
235+
}
225236
}

0 commit comments

Comments
 (0)