Skip to content

Commit a5eb895

Browse files
authored
Add allMutationsAffectingDocumentKeys to FSTMutationQueue (#1479)
* Pod updates for Cocapods 1.5.3 * Add allMutationsAffectingDocumentKeys
1 parent 4c9df5a commit a5eb895

File tree

4 files changed

+199
-22
lines changed

4 files changed

+199
-22
lines changed

Firestore/Example/Tests/Local/FSTMutationQueueTests.mm

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131

3232
#include "Firestore/core/src/firebase/firestore/auth/user.h"
3333
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
34+
#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
3435
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
3536

3637
namespace testutil = firebase::firestore::testutil;
3738
using firebase::firestore::auth::User;
3839
using firebase::firestore::model::DocumentKey;
40+
using firebase::firestore::model::DocumentKeySet;
41+
using firebase::firestore::testutil::Key;
3942

4043
NS_ASSUME_NONNULL_BEGIN
4144

@@ -284,7 +287,7 @@ - (void)testAllMutationBatchesAffectingDocumentKey {
284287

285288
self.persistence.run("testAllMutationBatchesAffectingDocumentKey", [&]() {
286289
NSArray<FSTMutation *> *mutations = @[
287-
FSTTestSetMutation(@"fob/bar",
290+
FSTTestSetMutation(@"foi/bar",
288291
@{ @"a" : @1 }),
289292
FSTTestSetMutation(@"foo/bar",
290293
@{ @"a" : @1 }),
@@ -315,6 +318,85 @@ - (void)testAllMutationBatchesAffectingDocumentKey {
315318
});
316319
}
317320

321+
- (void)testAllMutationBatchesAffectingDocumentKeys {
322+
if ([self isTestBaseClass]) return;
323+
324+
self.persistence.run("testAllMutationBatchesAffectingDocumentKey", [&]() {
325+
NSArray<FSTMutation *> *mutations = @[
326+
FSTTestSetMutation(@"fob/bar",
327+
@{ @"a" : @1 }),
328+
FSTTestSetMutation(@"foo/bar",
329+
@{ @"a" : @1 }),
330+
FSTTestPatchMutation("foo/bar",
331+
@{ @"b" : @1 }, {}),
332+
FSTTestSetMutation(@"foo/bar/suffix/key",
333+
@{ @"a" : @1 }),
334+
FSTTestSetMutation(@"foo/baz",
335+
@{ @"a" : @1 }),
336+
FSTTestSetMutation(@"food/bar",
337+
@{ @"a" : @1 })
338+
];
339+
340+
// Store all the mutations.
341+
NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
342+
for (FSTMutation *mutation in mutations) {
343+
FSTMutationBatch *batch =
344+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
345+
mutations:@[ mutation ]];
346+
[batches addObject:batch];
347+
}
348+
349+
DocumentKeySet keys{
350+
Key("foo/bar"),
351+
Key("foo/baz"),
352+
};
353+
354+
NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2], batches[4] ];
355+
NSArray<FSTMutationBatch *> *matches =
356+
[self.mutationQueue allMutationBatchesAffectingDocumentKeys:keys];
357+
358+
XCTAssertEqualObjects(matches, expected);
359+
});
360+
}
361+
362+
- (void)testAllMutationBatchesAffectingDocumentKeys_handlesOverlap {
363+
if ([self isTestBaseClass]) return;
364+
365+
self.persistence.run("testAllMutationBatchesAffectingDocumentKeys_handlesOverlap", [&]() {
366+
NSArray<FSTMutation *> *group1 = @[
367+
FSTTestSetMutation(@"foo/bar",
368+
@{ @"a" : @1 }),
369+
FSTTestSetMutation(@"foo/baz",
370+
@{ @"a" : @1 }),
371+
];
372+
FSTMutationBatch *batch1 =
373+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
374+
mutations:group1];
375+
376+
NSArray<FSTMutation *> *group2 = @[ FSTTestSetMutation(@"food/bar", @{ @"a" : @1 }) ];
377+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp] mutations:group2];
378+
379+
NSArray<FSTMutation *> *group3 = @[
380+
FSTTestSetMutation(@"foo/bar",
381+
@{ @"b" : @1 }),
382+
];
383+
FSTMutationBatch *batch3 =
384+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
385+
mutations:group3];
386+
387+
DocumentKeySet keys{
388+
Key("foo/bar"),
389+
Key("foo/baz"),
390+
};
391+
392+
NSArray<FSTMutationBatch *> *expected = @[ batch1, batch3 ];
393+
NSArray<FSTMutationBatch *> *matches =
394+
[self.mutationQueue allMutationBatchesAffectingDocumentKeys:keys];
395+
396+
XCTAssertEqualObjects(matches, expected);
397+
});
398+
}
399+
318400
- (void)testAllMutationBatchesAffectingQuery {
319401
if ([self isTestBaseClass]) return;
320402

Firestore/Source/Local/FSTLevelDBMutationQueue.mm

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
#include "Firestore/core/src/firebase/firestore/auth/user.h"
3232
#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
33-
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
3433
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
3534
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
3635
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
@@ -46,6 +45,7 @@
4645
using Firestore::StringView;
4746
using firebase::firestore::auth::User;
4847
using firebase::firestore::model::DocumentKey;
48+
using firebase::firestore::model::DocumentKeySet;
4949
using firebase::firestore::model::ResourcePath;
5050
using leveldb::DB;
5151
using leveldb::Iterator;
@@ -364,11 +364,16 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
364364
NSMutableArray *result = [NSMutableArray array];
365365
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
366366
for (; indexIterator->Valid(); indexIterator->Next()) {
367-
// Only consider rows matching exactly the specific key of interest. Note that because we order
368-
// by path first, and we order terminators before path separators, we'll encounter all the
369-
// index rows for documentKey contiguously. In particular, all the rows for documentKey will
370-
// occur before any rows for documents nested in a subcollection beneath documentKey so we can
371-
// stop as soon as we hit any such row.
367+
// Only consider rows matching exactly the specific key of interest. Index rows have this
368+
// form (with markers in brackets):
369+
//
370+
// <User>user <Path>collection <Path>doc <BatchId>2 <Terminator>
371+
// <User>user <Path>collection <Path>doc <BatchId>3 <Terminator>
372+
// <User>user <Path>collection <Path>doc <Path>sub <Path>doc <BatchId>3 <Terminator>
373+
//
374+
// Note that Path markers sort after BatchId markers so this means that when searching for
375+
// collection/doc, all the entries for it will be contiguous in the table, allowing a break
376+
// after any mismatch.
372377
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
373378
![rowKey decodeKey:indexIterator->key()] ||
374379
DocumentKey{rowKey.documentKey} != documentKey) {
@@ -396,6 +401,43 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
396401
return result;
397402
}
398403

404+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKeys:
405+
(const DocumentKeySet &)documentKeys {
406+
NSString *userID = self.userID;
407+
408+
// Take a pass through the document keys and collect the set of unique mutation batchIDs that
409+
// affect them all. Some batches can affect more than one key.
410+
std::set<FSTBatchID> batchIDs;
411+
412+
auto indexIterator = _db.currentTransaction->NewIterator();
413+
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
414+
for (const DocumentKey &documentKey : documentKeys) {
415+
std::string indexPrefix =
416+
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:userID resourcePath:documentKey.path()];
417+
for (indexIterator->Seek(indexPrefix); indexIterator->Valid(); indexIterator->Next()) {
418+
// Only consider rows matching exactly the specific key of interest. Index rows have this
419+
// form (with markers in brackets):
420+
//
421+
// <User>user <Path>collection <Path>doc <BatchId>2 <Terminator>
422+
// <User>user <Path>collection <Path>doc <BatchId>3 <Terminator>
423+
// <User>user <Path>collection <Path>doc <Path>sub <Path>doc <BatchId>3 <Terminator>
424+
//
425+
// Note that Path markers sort after BatchId markers so this means that when searching for
426+
// collection/doc, all the entries for it will be contiguous in the table, allowing a break
427+
// after any mismatch.
428+
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
429+
![rowKey decodeKey:indexIterator->key()] ||
430+
DocumentKey{rowKey.documentKey} != documentKey) {
431+
break;
432+
}
433+
434+
batchIDs.insert(rowKey.batchID);
435+
}
436+
}
437+
438+
return [self allMutationBatchesWithBatchIDs:batchIDs];
439+
}
440+
399441
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingQuery:(FSTQuery *)query {
400442
HARD_ASSERT(![query isDocumentQuery], "Document queries shouldn't go down this path");
401443
NSString *userID = self.userID;
@@ -417,11 +459,10 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
417459
// index for more than a single document so the associated batchIDs will be neither necessarily
418460
// unique nor in order. This means an efficient simultaneous scan isn't possible.
419461
std::string indexPrefix =
420-
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID resourcePath:queryPath];
462+
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:userID resourcePath:queryPath];
421463
auto indexIterator = _db.currentTransaction->NewIterator();
422464
indexIterator->Seek(indexPrefix);
423465

424-
NSMutableArray *result = [NSMutableArray array];
425466
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
426467

427468
// Collect up unique batchIDs encountered during a scan of the index. Use a set<FSTBatchID> to
@@ -430,7 +471,7 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
430471
// This method is faster than performing lookups of the keys with _db->Get and keeping a hash of
431472
// batchIDs that have already been looked up. The performance difference is minor for small
432473
// numbers of keys but > 30% faster for larger numbers of keys.
433-
std::set<FSTBatchID> uniqueBatchIds;
474+
std::set<FSTBatchID> uniqueBatchIDs;
434475
for (; indexIterator->Valid(); indexIterator->Next()) {
435476
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
436477
![rowKey decodeKey:indexIterator->key()]) {
@@ -444,14 +485,25 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
444485
continue;
445486
}
446487

447-
uniqueBatchIds.insert(rowKey.batchID);
488+
uniqueBatchIDs.insert(rowKey.batchID);
448489
}
449490

491+
return [self allMutationBatchesWithBatchIDs:uniqueBatchIDs];
492+
}
493+
494+
/**
495+
* Constructs an array of matching batches, sorted by batchID to ensure that multiple mutations
496+
* affecting the same document key are applied in order.
497+
*/
498+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesWithBatchIDs:
499+
(const std::set<FSTBatchID> &)batchIDs {
500+
NSMutableArray *result = [NSMutableArray array];
501+
NSString *userID = self.userID;
502+
450503
// Given an ordered set of unique batchIDs perform a skipping scan over the main table to find
451504
// the mutation batches.
452505
auto mutationIterator = _db.currentTransaction->NewIterator();
453-
454-
for (FSTBatchID batchID : uniqueBatchIds) {
506+
for (FSTBatchID batchID : batchIDs) {
455507
std::string mutationKey = [FSTLevelDBMutationKey keyWithUserID:userID batchID:batchID];
456508
mutationIterator->Seek(mutationKey);
457509
if (!mutationIterator->Valid() || mutationIterator->key() != mutationKey) {

Firestore/Source/Local/FSTMemoryMutationQueue.mm

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import "Firestore/Source/Local/FSTMemoryMutationQueue.h"
1818

19+
#include <set>
20+
1921
#import "Firestore/Source/Core/FSTQuery.h"
2022
#import "Firestore/Source/Local/FSTDocumentReference.h"
2123
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
@@ -28,6 +30,7 @@
2830
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
2931

3032
using firebase::firestore::model::DocumentKey;
33+
using firebase::firestore::model::DocumentKeySet;
3134
using firebase::firestore::model::ResourcePath;
3235

3336
NS_ASSUME_NONNULL_BEGIN
@@ -242,6 +245,28 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
242245
return result;
243246
}
244247

248+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKeys:
249+
(const DocumentKeySet &)documentKeys {
250+
// First find the set of affected batch IDs.
251+
__block std::set<FSTBatchID> batchIDs;
252+
for (const DocumentKey &key : documentKeys) {
253+
FSTDocumentReference *start = [[FSTDocumentReference alloc] initWithKey:key ID:0];
254+
255+
FSTDocumentReferenceBlock block = ^(FSTDocumentReference *reference, BOOL *stop) {
256+
if (![key isEqualToKey:reference.key]) {
257+
*stop = YES;
258+
return;
259+
}
260+
261+
batchIDs.insert(reference.ID);
262+
};
263+
264+
[self.batchesByDocumentKey enumerateObjectsFrom:start to:nil usingBlock:block];
265+
}
266+
267+
return [self allMutationBatchesWithBatchIDs:batchIDs];
268+
}
269+
245270
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingQuery:(FSTQuery *)query {
246271
// Use the query path as a prefix for testing if a document matches the query.
247272
const ResourcePath &prefix = query.path;
@@ -258,8 +283,7 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
258283
[[FSTDocumentReference alloc] initWithKey:DocumentKey{startPath} ID:0];
259284

260285
// Find unique batchIDs referenced by all documents potentially matching the query.
261-
__block FSTImmutableSortedSet<NSNumber *> *uniqueBatchIDs =
262-
[FSTImmutableSortedSet setWithComparator:NumberComparator];
286+
__block std::set<FSTBatchID> uniqueBatchIDs;
263287
FSTDocumentReferenceBlock block = ^(FSTDocumentReference *reference, BOOL *stop) {
264288
const ResourcePath &rowKeyPath = reference.key.path();
265289
if (!prefix.IsPrefixOf(rowKeyPath)) {
@@ -274,19 +298,26 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
274298
return;
275299
}
276300

277-
uniqueBatchIDs = [uniqueBatchIDs setByAddingObject:@(reference.ID)];
301+
uniqueBatchIDs.insert(reference.ID);
278302
};
279303
[self.batchesByDocumentKey enumerateObjectsFrom:start to:nil usingBlock:block];
280304

281-
// Construct an array of matching batches, sorted by batchID to ensure that multiple mutations
282-
// affecting the same document key are applied in order.
305+
return [self allMutationBatchesWithBatchIDs:uniqueBatchIDs];
306+
}
307+
308+
/**
309+
* Constructs an array of matching batches, sorted by batchID to ensure that multiple mutations
310+
* affecting the same document key are applied in order.
311+
*/
312+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesWithBatchIDs:
313+
(const std::set<FSTBatchID> &)batchIDs {
283314
NSMutableArray<FSTMutationBatch *> *result = [NSMutableArray array];
284-
[uniqueBatchIDs enumerateObjectsUsingBlock:^(NSNumber *batchID, BOOL *stop) {
285-
FSTMutationBatch *batch = [self lookupMutationBatch:[batchID intValue]];
315+
for (FSTBatchID batchID : batchIDs) {
316+
FSTMutationBatch *batch = [self lookupMutationBatch:batchID];
286317
if (batch) {
287318
[result addObject:batch];
288319
}
289-
}];
320+
};
290321

291322
return result;
292323
}

Firestore/Source/Local/FSTMutationQueue.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#import "Firestore/Source/Local/FSTGarbageCollector.h"
2121

2222
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
23+
#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
2324

2425
@class FSTMutation;
2526
@class FSTMutationBatch;
@@ -115,10 +116,21 @@ NS_ASSUME_NONNULL_BEGIN
115116
* don't contain the document key at all if it's convenient.
116117
*/
117118
// TODO(mcg): This should really return an NSEnumerator
118-
// also for b/32992024, all backing stores should really index by document key
119119
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKey:
120120
(const firebase::firestore::model::DocumentKey &)documentKey;
121121

122+
/**
123+
* Finds all mutation batches that could @em possibly affect the given document keys. Not all
124+
* mutations in a batch will necessarily affect each key, so when looping through the batches you'll
125+
* need to check that the mutation itself matches the key.
126+
*
127+
* Note that because of this requirement implementations are free to return mutation batches that
128+
* don't contain any of the given document keys at all if it's convenient.
129+
*/
130+
// TODO(mcg): This should really return an NSEnumerator
131+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKeys:
132+
(const firebase::firestore::model::DocumentKeySet &)documentKeys;
133+
122134
/**
123135
* Finds all mutation batches that could affect the results for the given query. Not all
124136
* mutations in a batch will necessarily affect the query, so when looping through the batch

0 commit comments

Comments
 (0)