Skip to content

Commit 6c8ded1

Browse files
milaGGLehsannas
authored andcommitted
Add decimal128 support (#579)
* initial code * add unit tests * add integration tests * update tests * update tests * Update BsonTypesTest.java * add more tests * fix a bug in comparing decimal128 value * resolve comments * update the comment regarding decimal128 NotIn&NaN test * update the Quadruple class * resolve comments * hide the Quadruple, QuadrupleBuilder from api.txt
1 parent 4417c2b commit 6c8ded1

27 files changed

+2696
-425
lines changed

firebase-firestore/api.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ package com.google.firebase.firestore {
7575
method public String getPath();
7676
}
7777

78+
public final class Decimal128Value {
79+
ctor public Decimal128Value(String);
80+
field public final String! stringValue;
81+
}
82+
7883
public class DocumentChange {
7984
method public com.google.firebase.firestore.QueryDocumentSnapshot getDocument();
8085
method public int getNewIndex();
@@ -135,6 +140,7 @@ package com.google.firebase.firestore {
135140
method public java.util.Map<java.lang.String!,java.lang.Object!>? getData(com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior);
136141
method public java.util.Date? getDate(String);
137142
method public java.util.Date? getDate(String, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior);
143+
method public com.google.firebase.firestore.Decimal128Value? getDecimal128Value(String);
138144
method public com.google.firebase.firestore.DocumentReference? getDocumentReference(String);
139145
method public Double? getDouble(String);
140146
method public com.google.firebase.firestore.GeoPoint? getGeoPoint(String);

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/BsonTypesTest.java

Lines changed: 207 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocsOnNightly;
2020
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor;
2121
import static com.google.firebase.firestore.testutil.TestUtil.map;
22+
import static java.lang.Double.NaN;
23+
import static java.lang.Double.POSITIVE_INFINITY;
2224
import static org.junit.Assert.assertEquals;
2325
import static org.junit.Assert.assertNotNull;
2426
import static org.junit.Assert.assertNull;
@@ -54,6 +56,7 @@ public void writeAndReadBsonTypes() throws ExecutionException, InterruptedExcept
5456
"bsonTimestamp", new BsonTimestamp(1, 2),
5557
"bsonBinary", BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}),
5658
"int32", new Int32Value(1),
59+
"decimal128", new Decimal128Value("1.2e3"),
5760
"minKey", MinKey.instance(),
5861
"maxKey", MaxKey.instance())));
5962

@@ -75,6 +78,7 @@ public void writeAndReadBsonTypes() throws ExecutionException, InterruptedExcept
7578
expected.put("bsonTimestamp", new BsonTimestamp(1, 3));
7679
expected.put("bsonBinary", BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}));
7780
expected.put("int32", new Int32Value(2));
81+
expected.put("decimal128", new Decimal128Value("1.2e3"));
7882
expected.put("minKey", MinKey.instance());
7983
expected.put("maxKey", MaxKey.instance());
8084

@@ -85,6 +89,7 @@ public void writeAndReadBsonTypes() throws ExecutionException, InterruptedExcept
8589
assertTrue(actual.get("bsonTimestamp") instanceof BsonTimestamp);
8690
assertTrue(actual.get("bsonBinary") instanceof BsonBinaryData);
8791
assertTrue(actual.get("int32") instanceof Int32Value);
92+
assertTrue(actual.get("decimal128") instanceof Decimal128Value);
8893
assertTrue(actual.get("minKey") instanceof MinKey);
8994
assertTrue(actual.get("maxKey") instanceof MaxKey);
9095
assertEquals(expected, actual.getData());
@@ -101,13 +106,22 @@ public void writeAndReadBsonTypeOffline() throws ExecutionException, Interrupted
101106
Map<String, Object> expected = new HashMap<>();
102107
docRef.set(
103108
map(
104-
"bsonObjectId", new BsonObjectId("507f191e810c19729de860ea"),
105-
"regex", new RegexValue("^foo", "i"),
106-
"bsonTimestamp", new BsonTimestamp(1, 2),
107-
"bsonBinary", BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}),
108-
"int32", new Int32Value(1),
109-
"minKey", MinKey.instance(),
110-
"maxKey", MaxKey.instance()));
109+
"bsonObjectId",
110+
new BsonObjectId("507f191e810c19729de860ea"),
111+
"regex",
112+
new RegexValue("^foo", "i"),
113+
"bsonTimestamp",
114+
new BsonTimestamp(1, 2),
115+
"bsonBinary",
116+
BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}),
117+
"int32",
118+
new Int32Value(1),
119+
"decimal128",
120+
new Decimal128Value("1.2e3"),
121+
"minKey",
122+
MinKey.instance(),
123+
"maxKey",
124+
MaxKey.instance()));
111125

112126
docRef.update(
113127
map(
@@ -123,6 +137,7 @@ public void writeAndReadBsonTypeOffline() throws ExecutionException, Interrupted
123137
expected.put("bsonTimestamp", new BsonTimestamp(1, 3));
124138
expected.put("bsonBinary", BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}));
125139
expected.put("int32", new Int32Value(1));
140+
expected.put("decimal128", new Decimal128Value("1.2e3"));
126141
expected.put("minKey", MinKey.instance());
127142
expected.put("maxKey", MaxKey.instance());
128143

@@ -133,6 +148,7 @@ public void writeAndReadBsonTypeOffline() throws ExecutionException, Interrupted
133148
assertTrue(actual.get("bsonTimestamp") instanceof BsonTimestamp);
134149
assertTrue(actual.get("bsonBinary") instanceof BsonBinaryData);
135150
assertTrue(actual.get("int32") instanceof Int32Value);
151+
assertTrue(actual.get("decimal128") instanceof Decimal128Value);
136152
assertTrue(actual.get("minKey") instanceof MinKey);
137153
assertTrue(actual.get("maxKey") instanceof MaxKey);
138154
assertEquals(expected, actual.getData());
@@ -174,6 +190,8 @@ public void listenToDocumentsWithBsonTypes() throws Throwable {
174190
BsonBinaryData.fromBytes(1, new byte[] {1, 2, 3}),
175191
"int32",
176192
new Int32Value(1),
193+
"decimal128",
194+
new Decimal128Value("1.2e3"),
177195
"minKey",
178196
MinKey.instance(),
179197
"maxKey",
@@ -192,6 +210,9 @@ public void listenToDocumentsWithBsonTypes() throws Throwable {
192210
assertEquals(
193211
docSnap.getBsonTimestamp("bsonTimestamp"), new BsonTimestamp(1, 2));
194212
assertEquals(docSnap.getInt32Value("int32"), new Int32Value(1));
213+
assertEquals(
214+
docSnap.getDecimal128Value("decimal128"),
215+
new Decimal128Value("1.2e3"));
195216
assertEquals(docSnap.getMinKey("minKey"), MinKey.instance());
196217
assertEquals(docSnap.getMaxKey("maxKey"), MaxKey.instance());
197218

@@ -266,15 +287,13 @@ public void filterAndOrderBsonObjectIds() throws Exception {
266287
randomColl
267288
.orderBy("key", Direction.DESCENDING)
268289
.whereGreaterThan("key", new BsonObjectId("507f191e810c19729de860ea"));
269-
270290
assertSDKQueryResultsConsistentWithBackend(
271291
randomColl, orderedQuery, docs, Arrays.asList("c", "b"));
272292

273293
orderedQuery =
274294
randomColl
275295
.orderBy("key", Direction.DESCENDING)
276296
.whereNotEqualTo("key", new BsonObjectId("507f191e810c19729de860eb"));
277-
278297
assertSDKQueryResultsConsistentWithBackend(
279298
randomColl, orderedQuery, docs, Arrays.asList("c", "a"));
280299
}
@@ -385,6 +404,77 @@ public void filterAndOrderInt32() throws Exception {
385404
randomColl, orderedQuery, docs, Arrays.asList("c", "a"));
386405
}
387406

407+
@Test
408+
public void filterAndOrderDecimal128() throws Exception {
409+
Map<String, Map<String, Object>> docs =
410+
map(
411+
"a",
412+
map("key", new Decimal128Value("-1.2e3")),
413+
"b",
414+
map("key", new Decimal128Value("0")),
415+
"c",
416+
map("key", new Decimal128Value("1.2e3")),
417+
"d",
418+
map("key", new Decimal128Value("NaN")),
419+
"e",
420+
map("key", new Decimal128Value("-Infinity")),
421+
"f",
422+
map("key", new Decimal128Value("Infinity")));
423+
CollectionReference randomColl = testCollectionWithDocsOnNightly(docs);
424+
425+
Query orderedQuery =
426+
randomColl
427+
.orderBy("key", Direction.DESCENDING)
428+
.whereGreaterThan("key", new Decimal128Value("-1.2e3"));
429+
assertSDKQueryResultsConsistentWithBackend(
430+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "b"));
431+
432+
orderedQuery =
433+
randomColl
434+
.orderBy("key", Direction.DESCENDING)
435+
.whereGreaterThan("key", new Decimal128Value("-1.2e-3"));
436+
assertSDKQueryResultsConsistentWithBackend(
437+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "b"));
438+
439+
orderedQuery =
440+
randomColl
441+
.orderBy("key", Direction.DESCENDING)
442+
.whereNotEqualTo("key", new Decimal128Value("0.0"));
443+
assertSDKQueryResultsConsistentWithBackend(
444+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "a", "e", "d"));
445+
446+
orderedQuery = randomColl.whereNotEqualTo("key", new Decimal128Value("NaN"));
447+
assertSDKQueryResultsConsistentWithBackend(
448+
randomColl, orderedQuery, docs, Arrays.asList("e", "a", "b", "c", "f"));
449+
450+
orderedQuery =
451+
randomColl
452+
.orderBy("key", Direction.DESCENDING)
453+
.whereEqualTo("key", new Decimal128Value("1.2e3"));
454+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("c"));
455+
456+
orderedQuery =
457+
randomColl
458+
.orderBy("key", Direction.DESCENDING)
459+
.whereNotEqualTo("key", new Decimal128Value("1.2e3"));
460+
assertSDKQueryResultsConsistentWithBackend(
461+
randomColl, orderedQuery, docs, Arrays.asList("f", "b", "a", "e", "d"));
462+
463+
// Note: server is sending NaN incorrectly, but the SDK NotInFilter.matches gracefully handles
464+
// it and removes the incorrect doc "d".
465+
orderedQuery =
466+
randomColl
467+
.orderBy("key", Direction.DESCENDING)
468+
.whereNotIn(
469+
"key",
470+
Arrays.asList(
471+
new Decimal128Value("1.2e3"),
472+
new Decimal128Value("Infinity"),
473+
new Decimal128Value("NaN")));
474+
assertSDKQueryResultsConsistentWithBackend(
475+
randomColl, orderedQuery, docs, Arrays.asList("b", "a", "e"));
476+
}
477+
388478
@Test
389479
public void filterAndOrderMinKey() throws Exception {
390480
Map<String, Map<String, Object>> docs =
@@ -478,6 +568,102 @@ public void filterNullValueWithBsonTypes() throws Exception {
478568
randomColl, query, docs, Arrays.asList("a", "d", "e"));
479569
}
480570

571+
@Test
572+
public void filterAndOrderNumericalValues() throws Exception {
573+
Map<String, Map<String, Object>> docs =
574+
map(
575+
"a",
576+
map("key", new Decimal128Value("-1.2e3")), // -1200
577+
"b",
578+
map("key", new Int32Value(0)),
579+
"c",
580+
map("key", new Decimal128Value("1")),
581+
"d",
582+
map("key", new Int32Value(1)),
583+
"e",
584+
map("key", 1L),
585+
"f",
586+
map("key", 1.0),
587+
"g",
588+
map("key", new Decimal128Value("1.2e-3")), // 0.0012
589+
"h",
590+
map("key", new Int32Value(2)),
591+
"i",
592+
map("key", new Decimal128Value("NaN")),
593+
"j",
594+
map("key", new Decimal128Value("-Infinity")),
595+
"k",
596+
map("key", NaN),
597+
"l",
598+
map("key", POSITIVE_INFINITY));
599+
CollectionReference randomColl = testCollectionWithDocsOnNightly(docs);
600+
601+
Query orderedQuery = randomColl.orderBy("key", Direction.DESCENDING);
602+
assertSDKQueryResultsConsistentWithBackend(
603+
randomColl,
604+
orderedQuery,
605+
docs,
606+
Arrays.asList(
607+
"l", // Infinity
608+
"h", // 2
609+
"f", // 1.0
610+
"e", // 1
611+
"d", // 1
612+
"c", // 1
613+
"g", // 0.0012
614+
"b", // 0
615+
"a", // -1200
616+
"j", // -Infinity
617+
"k", // NaN
618+
"i" // NaN
619+
));
620+
621+
orderedQuery =
622+
randomColl
623+
.orderBy("key", Direction.DESCENDING)
624+
.whereNotEqualTo("key", new Decimal128Value("1.0"));
625+
assertSDKQueryResultsConsistentWithBackend(
626+
randomColl, orderedQuery, docs, Arrays.asList("l", "h", "g", "b", "a", "j", "k", "i"));
627+
628+
orderedQuery = randomColl.orderBy("key", Direction.DESCENDING).whereEqualTo("key", 1);
629+
assertSDKQueryResultsConsistentWithBackend(
630+
randomColl, orderedQuery, docs, Arrays.asList("f", "e", "d", "c"));
631+
}
632+
633+
@Test
634+
public void decimal128ValuesWithNo2sComplementRepresentation() throws Exception {
635+
// For decimal128 values with no 2's complement representation, it is considered not equal to
636+
// a double with the same value, e.g, 1.1.
637+
Map<String, Map<String, Object>> docs =
638+
map(
639+
"a",
640+
map("key", new Decimal128Value("-1.1e-3")), // -0.0011
641+
"b",
642+
map("key", new Decimal128Value("1.1")),
643+
"c",
644+
map("key", 1.1),
645+
"d",
646+
map("key", 1.0),
647+
"e",
648+
map("key", new Decimal128Value("1.1e-3")) // 0.0011
649+
);
650+
CollectionReference randomColl = testCollectionWithDocsOnNightly(docs);
651+
652+
Query orderedQuery = randomColl.whereEqualTo("key", new Decimal128Value("1.1"));
653+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("b"));
654+
655+
orderedQuery = randomColl.whereNotEqualTo("key", new Decimal128Value("1.1"));
656+
assertSDKQueryResultsConsistentWithBackend(
657+
randomColl, orderedQuery, docs, Arrays.asList("a", "e", "d", "c"));
658+
659+
orderedQuery = randomColl.whereEqualTo("key", 1.1);
660+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("c"));
661+
662+
orderedQuery = randomColl.whereNotEqualTo("key", 1.1);
663+
assertSDKQueryResultsConsistentWithBackend(
664+
randomColl, orderedQuery, docs, Arrays.asList("a", "e", "d", "b"));
665+
}
666+
481667
@Test
482668
public void orderBsonTypesTogether() throws Exception {
483669
Map<String, Map<String, Object>> docs =
@@ -512,6 +698,12 @@ public void orderBsonTypesTogether() throws Exception {
512698
map("key", new Int32Value(1)),
513699
"int32Value3",
514700
map("key", new Int32Value(0)),
701+
"decimal128Value1",
702+
map("key", new Decimal128Value("-1.2e3")),
703+
"decimal128Value2",
704+
map("key", new Decimal128Value("-0.0")),
705+
"decimal128Value3",
706+
map("key", new Decimal128Value("1.2e3")),
515707
"minKey1",
516708
map("key", MinKey.instance()),
517709
"minKey2",
@@ -539,9 +731,15 @@ public void orderBsonTypesTogether() throws Exception {
539731
"bsonTimestamp1",
540732
"bsonTimestamp2",
541733
"bsonTimestamp3",
734+
// Int32Value and Decimal128Value are sorted together
735+
"decimal128Value3",
542736
"int32Value2",
737+
// Int32Value of 0 equals to Decimal128Value of 0, and falls to document key as second
738+
// order
543739
"int32Value3",
740+
"decimal128Value2",
544741
"int32Value1",
742+
"decimal128Value1",
545743
"minKey2",
546744
"minKey1");
547745

0 commit comments

Comments
 (0)