Skip to content

Commit 1b897bb

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 1b897bb

27 files changed

+2705
-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: 216 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,80 @@ 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+
// Pre-populate the cache with all docs
426+
waitFor(randomColl.get());
427+
428+
Query orderedQuery =
429+
randomColl
430+
.orderBy("key", Direction.DESCENDING)
431+
.whereGreaterThan("key", new Decimal128Value("-1.2e3"));
432+
assertSDKQueryResultsConsistentWithBackend(
433+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "b"));
434+
435+
orderedQuery =
436+
randomColl
437+
.orderBy("key", Direction.DESCENDING)
438+
.whereGreaterThan("key", new Decimal128Value("-1.2e-3"));
439+
assertSDKQueryResultsConsistentWithBackend(
440+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "b"));
441+
442+
orderedQuery =
443+
randomColl
444+
.orderBy("key", Direction.DESCENDING)
445+
.whereNotEqualTo("key", new Decimal128Value("0.0"));
446+
assertSDKQueryResultsConsistentWithBackend(
447+
randomColl, orderedQuery, docs, Arrays.asList("f", "c", "a", "e", "d"));
448+
449+
orderedQuery = randomColl.whereNotEqualTo("key", new Decimal128Value("NaN"));
450+
assertSDKQueryResultsConsistentWithBackend(
451+
randomColl, orderedQuery, docs, Arrays.asList("e", "a", "b", "c", "f"));
452+
453+
orderedQuery =
454+
randomColl
455+
.orderBy("key", Direction.DESCENDING)
456+
.whereEqualTo("key", new Decimal128Value("1.2e3"));
457+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("c"));
458+
459+
orderedQuery =
460+
randomColl
461+
.orderBy("key", Direction.DESCENDING)
462+
.whereNotEqualTo("key", new Decimal128Value("1.2e3"));
463+
assertSDKQueryResultsConsistentWithBackend(
464+
randomColl, orderedQuery, docs, Arrays.asList("f", "b", "a", "e", "d"));
465+
466+
// Note: server is sending NaN incorrectly, but the SDK NotInFilter.matches gracefully handles
467+
// it and removes the incorrect doc "d".
468+
orderedQuery =
469+
randomColl
470+
.orderBy("key", Direction.DESCENDING)
471+
.whereNotIn(
472+
"key",
473+
Arrays.asList(
474+
new Decimal128Value("1.2e3"),
475+
new Decimal128Value("Infinity"),
476+
new Decimal128Value("NaN")));
477+
assertSDKQueryResultsConsistentWithBackend(
478+
randomColl, orderedQuery, docs, Arrays.asList("b", "a", "e"));
479+
}
480+
388481
@Test
389482
public void filterAndOrderMinKey() throws Exception {
390483
Map<String, Map<String, Object>> docs =
@@ -478,6 +571,108 @@ public void filterNullValueWithBsonTypes() throws Exception {
478571
randomColl, query, docs, Arrays.asList("a", "d", "e"));
479572
}
480573

574+
@Test
575+
public void filterAndOrderNumericalValues() throws Exception {
576+
Map<String, Map<String, Object>> docs =
577+
map(
578+
"a",
579+
map("key", new Decimal128Value("-1.2e3")), // -1200
580+
"b",
581+
map("key", new Int32Value(0)),
582+
"c",
583+
map("key", new Decimal128Value("1")),
584+
"d",
585+
map("key", new Int32Value(1)),
586+
"e",
587+
map("key", 1L),
588+
"f",
589+
map("key", 1.0),
590+
"g",
591+
map("key", new Decimal128Value("1.2e-3")), // 0.0012
592+
"h",
593+
map("key", new Int32Value(2)),
594+
"i",
595+
map("key", new Decimal128Value("NaN")),
596+
"j",
597+
map("key", new Decimal128Value("-Infinity")),
598+
"k",
599+
map("key", NaN),
600+
"l",
601+
map("key", POSITIVE_INFINITY));
602+
CollectionReference randomColl = testCollectionWithDocsOnNightly(docs);
603+
604+
// Pre-populate the cache with all docs
605+
waitFor(randomColl.get());
606+
607+
Query orderedQuery = randomColl.orderBy("key", Direction.DESCENDING);
608+
assertSDKQueryResultsConsistentWithBackend(
609+
randomColl,
610+
orderedQuery,
611+
docs,
612+
Arrays.asList(
613+
"l", // Infinity
614+
"h", // 2
615+
"f", // 1.0
616+
"e", // 1
617+
"d", // 1
618+
"c", // 1
619+
"g", // 0.0012
620+
"b", // 0
621+
"a", // -1200
622+
"j", // -Infinity
623+
"k", // NaN
624+
"i" // NaN
625+
));
626+
627+
orderedQuery =
628+
randomColl
629+
.orderBy("key", Direction.DESCENDING)
630+
.whereNotEqualTo("key", new Decimal128Value("1.0"));
631+
assertSDKQueryResultsConsistentWithBackend(
632+
randomColl, orderedQuery, docs, Arrays.asList("l", "h", "g", "b", "a", "j", "k", "i"));
633+
634+
orderedQuery = randomColl.orderBy("key", Direction.DESCENDING).whereEqualTo("key", 1);
635+
assertSDKQueryResultsConsistentWithBackend(
636+
randomColl, orderedQuery, docs, Arrays.asList("f", "e", "d", "c"));
637+
}
638+
639+
@Test
640+
public void decimal128ValuesWithNo2sComplementRepresentation() throws Exception {
641+
// For decimal128 values with no 2's complement representation, it is considered not equal to
642+
// a double with the same value, e.g, 1.1.
643+
Map<String, Map<String, Object>> docs =
644+
map(
645+
"a",
646+
map("key", new Decimal128Value("-1.1e-3")), // -0.0011
647+
"b",
648+
map("key", new Decimal128Value("1.1")),
649+
"c",
650+
map("key", 1.1),
651+
"d",
652+
map("key", 1.0),
653+
"e",
654+
map("key", new Decimal128Value("1.1e-3")) // 0.0011
655+
);
656+
CollectionReference randomColl = testCollectionWithDocsOnNightly(docs);
657+
658+
// Pre-populate the cache with all docs
659+
waitFor(randomColl.get());
660+
661+
Query orderedQuery = randomColl.whereEqualTo("key", new Decimal128Value("1.1"));
662+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("b"));
663+
664+
orderedQuery = randomColl.whereNotEqualTo("key", new Decimal128Value("1.1"));
665+
assertSDKQueryResultsConsistentWithBackend(
666+
randomColl, orderedQuery, docs, Arrays.asList("a", "e", "d", "c"));
667+
668+
orderedQuery = randomColl.whereEqualTo("key", 1.1);
669+
assertSDKQueryResultsConsistentWithBackend(randomColl, orderedQuery, docs, Arrays.asList("c"));
670+
671+
orderedQuery = randomColl.whereNotEqualTo("key", 1.1);
672+
assertSDKQueryResultsConsistentWithBackend(
673+
randomColl, orderedQuery, docs, Arrays.asList("a", "e", "d", "b"));
674+
}
675+
481676
@Test
482677
public void orderBsonTypesTogether() throws Exception {
483678
Map<String, Map<String, Object>> docs =
@@ -512,6 +707,12 @@ public void orderBsonTypesTogether() throws Exception {
512707
map("key", new Int32Value(1)),
513708
"int32Value3",
514709
map("key", new Int32Value(0)),
710+
"decimal128Value1",
711+
map("key", new Decimal128Value("-1.2e3")),
712+
"decimal128Value2",
713+
map("key", new Decimal128Value("-0.0")),
714+
"decimal128Value3",
715+
map("key", new Decimal128Value("1.2e3")),
515716
"minKey1",
516717
map("key", MinKey.instance()),
517718
"minKey2",
@@ -539,9 +740,15 @@ public void orderBsonTypesTogether() throws Exception {
539740
"bsonTimestamp1",
540741
"bsonTimestamp2",
541742
"bsonTimestamp3",
743+
// Int32Value and Decimal128Value are sorted together
744+
"decimal128Value3",
542745
"int32Value2",
746+
// Int32Value of 0 equals to Decimal128Value of 0, and falls to document key as second
747+
// order
543748
"int32Value3",
749+
"decimal128Value2",
544750
"int32Value1",
751+
"decimal128Value1",
545752
"minKey2",
546753
"minKey1");
547754

0 commit comments

Comments
 (0)