|
39 | 39 | import com.google.firebase.firestore.remote.WatchChange.WatchTargetChange;
|
40 | 40 | import com.google.firebase.firestore.remote.WatchChange.WatchTargetChangeType;
|
41 | 41 | import com.google.firebase.firestore.testutil.TestTargetMetadataProvider;
|
| 42 | +import com.google.firestore.v1.BitSequence; |
| 43 | +import com.google.firestore.v1.BloomFilter; |
42 | 44 | import com.google.protobuf.ByteString;
|
43 | 45 | import java.util.ArrayList;
|
44 | 46 | import java.util.Collections;
|
@@ -456,6 +458,120 @@ public void testExistenceFilterMismatchClearsTarget() {
|
456 | 458 | assertEquals(0, event.getDocumentUpdates().size());
|
457 | 459 | }
|
458 | 460 |
|
| 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 | + |
459 | 575 | @Test
|
460 | 576 | public void testExistenceFilterMismatchRemovesCurrentChanges() {
|
461 | 577 | Map<Integer, TargetData> targetMap = activeQueries(1);
|
|
0 commit comments