Skip to content

Commit 76bf96a

Browse files
committed
Add allMutationsAffectingDocumentKeys
1 parent c6c2a9c commit 76bf96a

File tree

4 files changed

+186
-16
lines changed

4 files changed

+186
-16
lines changed

Firestore/Example/Tests/Local/FSTMutationQueueTests.mm

Lines changed: 84 additions & 0 deletions
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

@@ -315,6 +318,87 @@ - (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+
NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
367+
368+
NSArray<FSTMutation *> *group1 = @[
369+
FSTTestSetMutation(@"foo/bar",
370+
@{ @"a" : @1 }),
371+
FSTTestSetMutation(@"foo/baz",
372+
@{ @"a" : @1 }),
373+
];
374+
FSTMutationBatch *batch1 =
375+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
376+
mutations:group1];
377+
378+
NSArray<FSTMutation *> *group2 = @[ FSTTestSetMutation(@"food/bar", @{ @"a" : @1 }) ];
379+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp] mutations:group2];
380+
381+
NSArray<FSTMutation *> *group3 = @[
382+
FSTTestSetMutation(@"foo/bar",
383+
@{ @"b" : @1 }),
384+
];
385+
FSTMutationBatch *batch3 =
386+
[self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
387+
mutations:group3];
388+
389+
DocumentKeySet keys{
390+
Key("foo/bar"),
391+
Key("foo/baz"),
392+
};
393+
394+
NSArray<FSTMutationBatch *> *expected = @[ batch1, batch3 ];
395+
NSArray<FSTMutationBatch *> *matches =
396+
[self.mutationQueue allMutationBatchesAffectingDocumentKeys:keys];
397+
398+
XCTAssertEqualObjects(matches, expected);
399+
});
400+
}
401+
318402
- (void)testAllMutationBatchesAffectingQuery {
319403
if ([self isTestBaseClass]) return;
320404

Firestore/Source/Local/FSTLevelDBMutationQueue.mm

Lines changed: 50 additions & 7 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;
@@ -396,6 +396,39 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
396396
return result;
397397
}
398398

399+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKeys:
400+
(const DocumentKeySet &)documentKeys {
401+
NSString *userID = self.userID;
402+
403+
// Take a pass through the document keys and collect the set of unique mutation batchIDs that
404+
// affect them all. Some batches can affect more than one key.
405+
std::set<FSTBatchID> batchIDs;
406+
407+
auto indexIterator = _db.currentTransaction->NewIterator();
408+
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
409+
for (const DocumentKey &documentKey : documentKeys) {
410+
std::string indexPrefix =
411+
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:userID resourcePath:documentKey.path()];
412+
indexIterator->Seek(indexPrefix);
413+
for (; indexIterator->Valid(); indexIterator->Next()) {
414+
// Only consider rows matching exactly the specific key of interest. Note that because we
415+
// order by path first, and we order terminators before path separators, we'll encounter all
416+
// the index rows for documentKey contiguously. In particular, all the rows for documentKey
417+
// will occur before any rows for documents nested in a subcollection beneath documentKey so
418+
// we can stop as soon as we hit any such row.
419+
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
420+
![rowKey decodeKey:indexIterator->key()] ||
421+
DocumentKey{rowKey.documentKey} != documentKey) {
422+
break;
423+
}
424+
425+
batchIDs.insert(rowKey.batchID);
426+
}
427+
}
428+
429+
return [self allMutationBatchesWithBatchIDs:batchIDs];
430+
}
431+
399432
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingQuery:(FSTQuery *)query {
400433
HARD_ASSERT(![query isDocumentQuery], "Document queries shouldn't go down this path");
401434
NSString *userID = self.userID;
@@ -417,11 +450,10 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
417450
// index for more than a single document so the associated batchIDs will be neither necessarily
418451
// unique nor in order. This means an efficient simultaneous scan isn't possible.
419452
std::string indexPrefix =
420-
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID resourcePath:queryPath];
453+
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:userID resourcePath:queryPath];
421454
auto indexIterator = _db.currentTransaction->NewIterator();
422455
indexIterator->Seek(indexPrefix);
423456

424-
NSMutableArray *result = [NSMutableArray array];
425457
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
426458

427459
// Collect up unique batchIDs encountered during a scan of the index. Use a set<FSTBatchID> to
@@ -430,7 +462,7 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
430462
// This method is faster than performing lookups of the keys with _db->Get and keeping a hash of
431463
// batchIDs that have already been looked up. The performance difference is minor for small
432464
// numbers of keys but > 30% faster for larger numbers of keys.
433-
std::set<FSTBatchID> uniqueBatchIds;
465+
std::set<FSTBatchID> uniqueBatchIDs;
434466
for (; indexIterator->Valid(); indexIterator->Next()) {
435467
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
436468
![rowKey decodeKey:indexIterator->key()]) {
@@ -444,14 +476,25 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID
444476
continue;
445477
}
446478

447-
uniqueBatchIds.insert(rowKey.batchID);
479+
uniqueBatchIDs.insert(rowKey.batchID);
448480
}
449481

482+
return [self allMutationBatchesWithBatchIDs:uniqueBatchIDs];
483+
}
484+
485+
/**
486+
* Constructs an array of matching batches, sorted by batchID to ensure that multiple mutations
487+
* affecting the same document key are applied in order.
488+
*/
489+
- (NSArray<FSTMutationBatch *> *)allMutationBatchesWithBatchIDs:
490+
(const std::set<FSTBatchID> &)batchIDs {
491+
NSMutableArray *result = [NSMutableArray array];
492+
NSString *userID = self.userID;
493+
450494
// Given an ordered set of unique batchIDs perform a skipping scan over the main table to find
451495
// the mutation batches.
452496
auto mutationIterator = _db.currentTransaction->NewIterator();
453-
454-
for (FSTBatchID batchID : uniqueBatchIds) {
497+
for (FSTBatchID batchID : batchIDs) {
455498
std::string mutationKey = [FSTLevelDBMutationKey keyWithUserID:userID batchID:batchID];
456499
mutationIterator->Seek(mutationKey);
457500
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)