Skip to content

Commit 4f18f6e

Browse files
authored
Merge bc56f21 into 79cc918
2 parents 79cc918 + bc56f21 commit 4f18f6e

File tree

5 files changed

+263
-2
lines changed

5 files changed

+263
-2
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,6 @@ public Target encodeTarget(TargetData targetData) {
501501
builder.setResumeToken(targetData.getResumeToken());
502502
}
503503

504-
// TODO(Mila) Incorporate this into the if statement above.
505504
if (targetData.getExpectedCount() != null
506505
&& (!targetData.getResumeToken().isEmpty()
507506
|| targetData.getSnapshotVersion().compareTo(SnapshotVersion.NONE) > 0)) {

firebase-firestore/src/test/java/com/google/firebase/firestore/local/LocalSerializerTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,51 @@ public void testEncodesTargetData() {
404404
assertEquals(targetData, decoded);
405405
}
406406

407+
@Test
408+
public void localSerializerShouldDropExpectedCountInTargetData() {
409+
Query query = TestUtil.query("room");
410+
int targetId = 42;
411+
long sequenceNumber = 10;
412+
SnapshotVersion snapshotVersion = TestUtil.version(1039);
413+
SnapshotVersion limboFreeVersion = TestUtil.version(1000);
414+
ByteString resumeToken = TestUtil.resumeToken(1039);
415+
416+
TargetData targetData =
417+
new TargetData(
418+
query.toTarget(),
419+
targetId,
420+
sequenceNumber,
421+
QueryPurpose.LISTEN,
422+
snapshotVersion,
423+
limboFreeVersion,
424+
resumeToken,
425+
/* expectedCount= */ 1234);
426+
427+
com.google.firestore.v1.Target.QueryTarget queryTarget =
428+
remoteSerializer.encodeQueryTarget(query.toTarget());
429+
430+
com.google.firebase.firestore.proto.Target expected =
431+
com.google.firebase.firestore.proto.Target.newBuilder()
432+
.setTargetId(targetId)
433+
.setLastListenSequenceNumber(sequenceNumber)
434+
.setSnapshotVersion(com.google.protobuf.Timestamp.newBuilder().setNanos(1039000))
435+
.setResumeToken(ByteString.copyFrom(resumeToken.toByteArray()))
436+
.setQuery(
437+
com.google.firestore.v1.Target.QueryTarget.newBuilder()
438+
.setParent(queryTarget.getParent())
439+
.setStructuredQuery(queryTarget.getStructuredQuery()))
440+
.setLastLimboFreeSnapshotVersion(
441+
com.google.protobuf.Timestamp.newBuilder().setNanos(1000000))
442+
.build();
443+
444+
assertEquals(expected, serializer.encodeTargetData(targetData));
445+
TargetData decoded = serializer.decodeTargetData(expected);
446+
// Set the expected_count in TargetData to null, as serializing a TargetData into local Target
447+
// proto will drop the expected_count and the deserialized TargetData will not include the
448+
// expected_count.
449+
assertEquals(targetData.withExpectedCount(null), decoded);
450+
}
451+
407452
@Test
408453
public void testEncodesQuery() {
409454
Target target =

firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteEventTest.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import com.google.firebase.firestore.remote.WatchChange.WatchTargetChange;
4040
import com.google.firebase.firestore.remote.WatchChange.WatchTargetChangeType;
4141
import com.google.firebase.firestore.testutil.TestTargetMetadataProvider;
42+
import com.google.firestore.v1.BitSequence;
43+
import com.google.firestore.v1.BloomFilter;
4244
import com.google.protobuf.ByteString;
4345
import java.util.ArrayList;
4446
import java.util.Collections;
@@ -456,6 +458,120 @@ public void testExistenceFilterMismatchClearsTarget() {
456458
assertEquals(0, event.getDocumentUpdates().size());
457459
}
458460

461+
@Test
462+
public void existenceFilterMismatchWithSuccessfulBloomFilterApplication() {
463+
Map<Integer, TargetData> targetMap = activeQueries(1, 2);
464+
465+
MutableDocument doc1 = doc("docs/1", 1, map("value", 1));
466+
MutableDocument doc2 = doc("docs/2", 2, map("value", 2));
467+
468+
WatchChange change1 = new DocumentChange(asList(1), emptyList(), doc1.getKey(), doc1);
469+
WatchChange change2 = new DocumentChange(asList(1), emptyList(), doc2.getKey(), doc2);
470+
WatchChange change3 = new WatchTargetChange(WatchTargetChangeType.Current, asList(1));
471+
472+
// The BloomFilter proto value below is created based on the document paths that are constructed
473+
// using the pattern: "projects/test-project/databases/test-database/documents/"+document_key.
474+
// Override the default database ID to ensure that the document path matches the pattern above.
475+
targetMetadataProvider.setDatabaseId("test-project", "test-database");
476+
WatchChangeAggregator aggregator =
477+
createAggregator(
478+
targetMap,
479+
noOutstandingResponses,
480+
keySet(doc1.getKey(), doc2.getKey()),
481+
change1,
482+
change2,
483+
change3);
484+
485+
RemoteEvent event = aggregator.createRemoteEvent(version(3));
486+
487+
assertEquals(version(3), event.getSnapshotVersion());
488+
assertEquals(2, event.getDocumentUpdates().size());
489+
assertEquals(doc1, event.getDocumentUpdates().get(doc1.getKey()));
490+
assertEquals(doc2, event.getDocumentUpdates().get(doc2.getKey()));
491+
492+
assertEquals(2, event.getTargetChanges().size());
493+
494+
TargetChange mapping1 = targetChange(resumeToken, true, null, asList(doc1, doc2), null);
495+
assertEquals(mapping1, event.getTargetChanges().get(1));
496+
497+
TargetChange mapping2 = targetChange(resumeToken, false, null, null, null);
498+
assertEquals(mapping2, event.getTargetChanges().get(2));
499+
500+
// This BloomFilter will return false on MightContain(doc1) and true on MightContain(doc2).
501+
BitSequence.Builder bitSequence = BitSequence.newBuilder();
502+
bitSequence.setPadding(1);
503+
bitSequence.setBitmap(ByteString.copyFrom(new byte[] {0x0E, 0x0F}));
504+
com.google.firestore.v1.BloomFilter.Builder bloomFilter = BloomFilter.newBuilder();
505+
bloomFilter.setBits(bitSequence);
506+
bloomFilter.setHashCount(7);
507+
508+
WatchChange.ExistenceFilterWatchChange watchChange =
509+
new WatchChange.ExistenceFilterWatchChange(1, new ExistenceFilter(1, bloomFilter.build()));
510+
aggregator.handleExistenceFilter(watchChange);
511+
512+
event = aggregator.createRemoteEvent(version(3));
513+
514+
assertEquals(1, event.getTargetChanges().size());
515+
assertEquals(0, event.getTargetMismatches().size());
516+
assertEquals(0, event.getDocumentUpdates().size());
517+
}
518+
519+
@Test
520+
public void existenceFilterMismatchWithBloomFilterFalsePositiveResult() {
521+
Map<Integer, TargetData> targetMap = activeQueries(1, 2);
522+
523+
MutableDocument doc1 = doc("docs/1", 1, map("value", 1));
524+
MutableDocument doc2 = doc("docs/2", 2, map("value", 2));
525+
526+
WatchChange change1 = new DocumentChange(asList(1), emptyList(), doc1.getKey(), doc1);
527+
WatchChange change2 = new DocumentChange(asList(1), emptyList(), doc2.getKey(), doc2);
528+
WatchChange change3 = new WatchTargetChange(WatchTargetChangeType.Current, asList(1));
529+
530+
WatchChangeAggregator aggregator =
531+
createAggregator(
532+
targetMap,
533+
noOutstandingResponses,
534+
keySet(doc1.getKey(), doc2.getKey()),
535+
change1,
536+
change2,
537+
change3);
538+
539+
RemoteEvent event = aggregator.createRemoteEvent(version(3));
540+
541+
assertEquals(version(3), event.getSnapshotVersion());
542+
assertEquals(2, event.getDocumentUpdates().size());
543+
assertEquals(doc1, event.getDocumentUpdates().get(doc1.getKey()));
544+
assertEquals(doc2, event.getDocumentUpdates().get(doc2.getKey()));
545+
546+
assertEquals(2, event.getTargetChanges().size());
547+
548+
TargetChange mapping1 = targetChange(resumeToken, true, null, asList(doc1, doc2), null);
549+
assertEquals(mapping1, event.getTargetChanges().get(1));
550+
551+
TargetChange mapping2 = targetChange(resumeToken, false, null, null, null);
552+
assertEquals(mapping2, event.getTargetChanges().get(2));
553+
554+
// With this BloomFilter, mightContain() will return true for all documents.
555+
BitSequence.Builder bitSequence = BitSequence.newBuilder();
556+
bitSequence.setPadding(7);
557+
bitSequence.setBitmap(ByteString.copyFrom(new byte[] {(byte) 0xFF, (byte) 0xFF}));
558+
com.google.firestore.v1.BloomFilter.Builder bloomFilter = BloomFilter.newBuilder();
559+
bloomFilter.setBits(bitSequence);
560+
bloomFilter.setHashCount(33);
561+
562+
WatchChange.ExistenceFilterWatchChange watchChange =
563+
new WatchChange.ExistenceFilterWatchChange(1, new ExistenceFilter(1, bloomFilter.build()));
564+
aggregator.handleExistenceFilter(watchChange);
565+
566+
event = aggregator.createRemoteEvent(version(3));
567+
568+
TargetChange mapping3 = targetChange(ByteString.EMPTY, false, null, null, asList(doc1, doc2));
569+
assertEquals(1, event.getTargetChanges().size());
570+
assertEquals(mapping3, event.getTargetChanges().get(1));
571+
assertEquals(1, event.getTargetMismatches().size());
572+
assertEquals(0, event.getDocumentUpdates().size());
573+
}
574+
459575
@Test
460576
public void testExistenceFilterMismatchRemovesCurrentChanges() {
461577
Map<Integer, TargetData> targetMap = activeQueries(1);

firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteSerializerTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,11 @@ public void testEncodesListenRequestLabels() {
509509
targetData = new TargetData(query.toTarget(), 2, 3, QueryPurpose.EXISTENCE_FILTER_MISMATCH);
510510
result = serializer.encodeListenRequestLabels(targetData);
511511
assertEquals(map("goog-listen-tags", "existence-filter-mismatch"), result);
512+
513+
targetData =
514+
new TargetData(query.toTarget(), 2, 3, QueryPurpose.EXISTENCE_FILTER_MISMATCH_BLOOM);
515+
result = serializer.encodeListenRequestLabels(targetData);
516+
assertEquals(map("goog-listen-tags", "existence-filter-mismatch-bloom"), result);
512517
}
513518

514519
@Test
@@ -1153,6 +1158,96 @@ public void testEncodesReadTime() {
11531158
serializer.decodeQueryTarget(serializer.encodeQueryTarget(q.toTarget())), q.toTarget());
11541159
}
11551160

1161+
@Test
1162+
public void encodesExpectedCountWhenResumeTokenIsPresent() {
1163+
Query q = Query.atPath(ResourcePath.fromString("docs"));
1164+
TargetData targetData =
1165+
new TargetData(q.toTarget(), 1, 2, QueryPurpose.LISTEN)
1166+
.withResumeToken(TestUtil.resumeToken(1000), SnapshotVersion.NONE)
1167+
.withExpectedCount(42);
1168+
Target actual = serializer.encodeTarget(targetData);
1169+
1170+
StructuredQuery.Builder structuredQueryBuilder =
1171+
StructuredQuery.newBuilder()
1172+
.addFrom(CollectionSelector.newBuilder().setCollectionId("docs"))
1173+
.addOrderBy(defaultKeyOrder());
1174+
1175+
QueryTarget.Builder queryBuilder =
1176+
QueryTarget.newBuilder()
1177+
.setParent("projects/p/databases/d/documents")
1178+
.setStructuredQuery(structuredQueryBuilder);
1179+
Target expected =
1180+
Target.newBuilder()
1181+
.setQuery(queryBuilder)
1182+
.setTargetId(1)
1183+
.setResumeToken(TestUtil.resumeToken(1000))
1184+
.setExpectedCount(Int32Value.newBuilder().setValue(42))
1185+
.build();
1186+
1187+
assertEquals(expected, actual);
1188+
assertEquals(
1189+
serializer.decodeQueryTarget(serializer.encodeQueryTarget(q.toTarget())), q.toTarget());
1190+
}
1191+
1192+
@Test
1193+
public void encodesExpectedCountWhenReadTimeIsPresent() {
1194+
Query q = Query.atPath(ResourcePath.fromString("docs"));
1195+
TargetData targetData =
1196+
new TargetData(q.toTarget(), 1, 2, QueryPurpose.LISTEN)
1197+
.withResumeToken(ByteString.EMPTY, version(4000000))
1198+
.withExpectedCount(42);
1199+
Target actual = serializer.encodeTarget(targetData);
1200+
1201+
StructuredQuery.Builder structuredQueryBuilder =
1202+
StructuredQuery.newBuilder()
1203+
.addFrom(CollectionSelector.newBuilder().setCollectionId("docs"))
1204+
.addOrderBy(defaultKeyOrder());
1205+
1206+
QueryTarget.Builder queryBuilder =
1207+
QueryTarget.newBuilder()
1208+
.setParent("projects/p/databases/d/documents")
1209+
.setStructuredQuery(structuredQueryBuilder);
1210+
Target expected =
1211+
Target.newBuilder()
1212+
.setQuery(queryBuilder)
1213+
.setTargetId(1)
1214+
.setReadTime(Timestamp.newBuilder().setSeconds(4))
1215+
.setExpectedCount(Int32Value.newBuilder().setValue(42))
1216+
.build();
1217+
1218+
assertEquals(expected, actual);
1219+
assertEquals(
1220+
serializer.decodeQueryTarget(serializer.encodeQueryTarget(q.toTarget())), q.toTarget());
1221+
}
1222+
1223+
@Test
1224+
public void shouldIgnoreExpectedCountWithoutResumeTokenOrReadTime() {
1225+
Query q = Query.atPath(ResourcePath.fromString("docs"));
1226+
TargetData targetData =
1227+
new TargetData(q.toTarget(), 1, 2, QueryPurpose.LISTEN).withExpectedCount(42);
1228+
Target actual = serializer.encodeTarget(targetData);
1229+
1230+
StructuredQuery.Builder structuredQueryBuilder =
1231+
StructuredQuery.newBuilder()
1232+
.addFrom(CollectionSelector.newBuilder().setCollectionId("docs"))
1233+
.addOrderBy(defaultKeyOrder());
1234+
1235+
QueryTarget.Builder queryBuilder =
1236+
QueryTarget.newBuilder()
1237+
.setParent("projects/p/databases/d/documents")
1238+
.setStructuredQuery(structuredQueryBuilder);
1239+
Target expected =
1240+
Target.newBuilder()
1241+
.setQuery(queryBuilder)
1242+
.setTargetId(1)
1243+
.setResumeToken(ByteString.EMPTY)
1244+
.build();
1245+
1246+
assertEquals(expected, actual);
1247+
assertEquals(
1248+
serializer.decodeQueryTarget(serializer.encodeQueryTarget(q.toTarget())), q.toTarget());
1249+
}
1250+
11561251
/**
11571252
* Wraps the given query in TargetData. This is useful because the APIs we're testing accept
11581253
* TargetData, but for the most part we're just testing variations on Query.

firebase-firestore/src/testUtil/java/com/google/firebase/firestore/testutil/TestTargetMetadataProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
public class TestTargetMetadataProvider implements WatchChangeAggregator.TargetMetadataProvider {
3131
final Map<Integer, ImmutableSortedSet<DocumentKey>> syncedKeys = new HashMap<>();
3232
final Map<Integer, TargetData> queryData = new HashMap<>();
33+
DatabaseId databaseId = DatabaseId.forProject("test-project");
3334

3435
@Override
3536
public ImmutableSortedSet<DocumentKey> getRemoteKeysForTarget(int targetId) {
@@ -44,7 +45,12 @@ public TargetData getTargetDataForTarget(int targetId) {
4445

4546
@Override
4647
public DatabaseId getDatabaseId() {
47-
return DatabaseId.forProject("test-project");
48+
return databaseId;
49+
}
50+
51+
/** Replaces the default project ID and database ID. */
52+
public void setDatabaseId(String projectId, String databaseId) {
53+
this.databaseId = DatabaseId.forDatabase(projectId, databaseId);
4854
}
4955

5056
/** Sets or replaces the local state for the provided query data. */

0 commit comments

Comments
 (0)