Files
firebase-ios-sdk/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
T

612 lines
23 KiB
Plaintext
Raw Normal View History

2017-10-03 08:55:22 -07:00
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
2017-10-03 08:55:22 -07:00
#include <memory>
2017-10-03 08:55:22 -07:00
#include <set>
#include <string>
#include <utility>
#include <vector>
2017-10-03 08:55:22 -07:00
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
2017-10-03 08:55:22 -07:00
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_util.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
2018-01-25 13:03:07 -05:00
#include "Firestore/core/src/firebase/firestore/util/string_util.h"
#include "absl/strings/match.h"
#include "leveldb/db.h"
#include "leveldb/write_batch.h"
2017-10-03 08:55:22 -07:00
NS_ASSUME_NONNULL_BEGIN
namespace util = firebase::firestore::util;
using firebase::firestore::auth::User;
using firebase::firestore::local::DescribeKey;
2018-08-08 12:34:03 -07:00
using firebase::firestore::local::LevelDbDocumentMutationKey;
using firebase::firestore::local::LevelDbMutationKey;
using firebase::firestore::local::LevelDbMutationQueueKey;
using firebase::firestore::local::LevelDbTransaction;
using firebase::firestore::local::MakeStringView;
2018-03-23 17:52:01 -04:00
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::DocumentKeySet;
using firebase::firestore::model::ResourcePath;
2017-10-03 08:55:22 -07:00
using leveldb::DB;
using leveldb::Iterator;
using leveldb::ReadOptions;
using leveldb::Slice;
using leveldb::Status;
using leveldb::WriteBatch;
using leveldb::WriteOptions;
@interface FSTLevelDBMutationQueue ()
- (instancetype)initWithUserID:(std::string)userID
db:(FSTLevelDB *)db
2017-10-03 08:55:22 -07:00
serializer:(FSTLocalSerializer *)serializer NS_DESIGNATED_INITIALIZER;
/**
* Next value to use when assigning sequential IDs to each mutation batch.
*
* NOTE: There can only be one FSTLevelDBMutationQueue for a given db at a time, hence it is safe
* to track nextBatchID as an instance-level property. Should we ever relax this constraint we'll
* need to revisit this.
*/
@property(nonatomic, assign) FSTBatchID nextBatchID;
/** A write-through cache copy of the metadata describing the current queue. */
@property(nonatomic, strong, nullable) FSTPBMutationQueue *metadata;
@property(nonatomic, strong, readonly) FSTLocalSerializer *serializer;
@end
@implementation FSTLevelDBMutationQueue {
FSTLevelDB *_db;
/** The normalized userID (e.g. nil UID => @"" userID) used in our LevelDB keys. */
std::string _userID;
2017-10-03 08:55:22 -07:00
}
+ (instancetype)mutationQueueWithUser:(const User &)user
db:(FSTLevelDB *)db
2017-10-03 08:55:22 -07:00
serializer:(FSTLocalSerializer *)serializer {
std::string userID = user.is_authenticated() ? user.uid() : "";
2017-10-03 08:55:22 -07:00
return [[FSTLevelDBMutationQueue alloc] initWithUserID:std::move(userID)
db:db
serializer:serializer];
2017-10-03 08:55:22 -07:00
}
- (instancetype)initWithUserID:(std::string)userID
db:(FSTLevelDB *)db
2017-10-03 08:55:22 -07:00
serializer:(FSTLocalSerializer *)serializer {
if (self = [super init]) {
_userID = std::move(userID);
2017-10-03 08:55:22 -07:00
_db = db;
_serializer = serializer;
}
return self;
}
2018-03-29 08:55:10 -07:00
- (void)start {
FSTBatchID nextBatchID = [FSTLevelDBMutationQueue loadNextBatchIDFromDB:_db.ptr];
2017-10-03 08:55:22 -07:00
// On restart, nextBatchId may end up lower than lastAcknowledgedBatchId since it's computed from
// the queue contents, and there may be no mutations in the queue. In this case, we need to reset
// lastAcknowledgedBatchId (which is safe since the queue must be empty).
std::string key = [self keyForCurrentMutationQueue];
FSTPBMutationQueue *metadata = [self metadataForKey:key];
if (!metadata) {
metadata = [FSTPBMutationQueue message];
// proto3's default value for lastAcknowledgedBatchId is zero, but that would consider the first
// entry in the queue to be acknowledged without that acknowledgement actually happening.
metadata.lastAcknowledgedBatchId = kFSTBatchIDUnknown;
} else {
FSTBatchID lastAcked = metadata.lastAcknowledgedBatchId;
if (lastAcked >= nextBatchID) {
HARD_ASSERT([self isEmpty], "Reset nextBatchID is only possible when the queue is empty");
2017-10-03 08:55:22 -07:00
lastAcked = kFSTBatchIDUnknown;
metadata.lastAcknowledgedBatchId = lastAcked;
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
2017-10-03 08:55:22 -07:00
}
}
self.nextBatchID = nextBatchID;
self.metadata = metadata;
}
2018-06-13 08:12:09 -07:00
+ (FSTBatchID)loadNextBatchIDFromDB:(DB *)db {
// TODO(gsoltis): implement Prev() and SeekToLast() on LevelDbTransaction::Iterator, then port
// this to a transaction.
std::unique_ptr<Iterator> it(db->NewIterator(LevelDbTransaction::DefaultReadOptions()));
2017-10-03 08:55:22 -07:00
2018-08-08 12:34:03 -07:00
auto tableKey = LevelDbMutationKey::KeyPrefix();
2017-10-03 08:55:22 -07:00
2018-08-08 12:34:03 -07:00
LevelDbMutationKey rowKey;
2017-10-03 08:55:22 -07:00
FSTBatchID maxBatchID = kFSTBatchIDUnknown;
BOOL moreUserIDs = NO;
std::string nextUserID;
it->Seek(tableKey);
2018-08-08 12:34:03 -07:00
if (it->Valid() && rowKey.Decode(MakeStringView(it->key()))) {
2017-10-03 08:55:22 -07:00
moreUserIDs = YES;
2018-08-08 12:34:03 -07:00
nextUserID = rowKey.user_id();
2017-10-03 08:55:22 -07:00
}
// This loop assumes that nextUserId contains the next username at the start of the iteration.
while (moreUserIDs) {
// Compute the first key after the last mutation for nextUserID.
2018-08-08 12:34:03 -07:00
auto userEnd = LevelDbMutationKey::KeyPrefix(nextUserID);
userEnd = util::PrefixSuccessor(userEnd);
2017-10-03 08:55:22 -07:00
// Seek to that key with the intent of finding the boundary between nextUserID's mutations
// and the one after that (if any).
it->Seek(userEnd);
// At this point there are three possible cases to handle differently. Each case must prepare
// the next iteration (by assigning to nextUserID or setting moreUserIDs = NO) and seek the
// iterator to the last row in the current user's mutation sequence.
if (!it->Valid()) {
// The iterator is past the last row altogether (there are no additional userIDs and now
// rows in any table after mutations). The last row will have the highest batchID.
moreUserIDs = NO;
it->SeekToLast();
2018-08-08 12:34:03 -07:00
} else if (rowKey.Decode(MakeStringView(it->key()))) {
2017-10-03 08:55:22 -07:00
// The iterator is valid and the key decoded successfully so the next user was just decoded.
2018-08-08 12:34:03 -07:00
nextUserID = rowKey.user_id();
2017-10-03 08:55:22 -07:00
it->Prev();
} else {
// The iterator is past the end of the mutations table but there are other rows.
moreUserIDs = NO;
it->Prev();
}
// In all the cases above there was at least one row for the current user and each case has
// set things up such that iterator points to it.
2018-08-08 12:34:03 -07:00
if (!rowKey.Decode(MakeStringView(it->key()))) {
HARD_FAIL("There should have been a key previous to %s", userEnd);
2017-10-03 08:55:22 -07:00
}
2018-08-08 12:34:03 -07:00
if (rowKey.batch_id() > maxBatchID) {
maxBatchID = rowKey.batch_id();
2017-10-03 08:55:22 -07:00
}
}
return maxBatchID + 1;
}
- (BOOL)isEmpty {
2018-08-08 12:34:03 -07:00
std::string userKey = LevelDbMutationKey::KeyPrefix(_userID);
2017-10-03 08:55:22 -07:00
auto it = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
it->Seek(userKey);
BOOL empty = YES;
if (it->Valid() && absl::StartsWith(it->key(), userKey)) {
2017-10-03 08:55:22 -07:00
empty = NO;
}
return empty;
}
- (FSTBatchID)highestAcknowledgedBatchID {
return self.metadata.lastAcknowledgedBatchId;
}
2018-03-29 08:55:10 -07:00
- (void)acknowledgeBatch:(FSTMutationBatch *)batch streamToken:(nullable NSData *)streamToken {
2017-10-03 08:55:22 -07:00
FSTBatchID batchID = batch.batchID;
HARD_ASSERT(batchID > self.highestAcknowledgedBatchID,
"Mutation batchIDs must be acknowledged in order");
2017-10-03 08:55:22 -07:00
FSTPBMutationQueue *metadata = self.metadata;
metadata.lastAcknowledgedBatchId = batchID;
metadata.lastStreamToken = streamToken;
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
2017-10-03 08:55:22 -07:00
}
- (nullable NSData *)lastStreamToken {
return self.metadata.lastStreamToken;
}
2018-03-29 08:55:10 -07:00
- (void)setLastStreamToken:(nullable NSData *)streamToken {
2017-10-03 08:55:22 -07:00
FSTPBMutationQueue *metadata = self.metadata;
metadata.lastStreamToken = streamToken;
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
2017-10-03 08:55:22 -07:00
}
- (std::string)keyForCurrentMutationQueue {
2018-08-08 12:34:03 -07:00
return LevelDbMutationQueueKey::Key(_userID);
2017-10-03 08:55:22 -07:00
}
- (nullable FSTPBMutationQueue *)metadataForKey:(const std::string &)key {
std::string value;
Status status = _db.currentTransaction->Get(key, &value);
2017-10-03 08:55:22 -07:00
if (status.ok()) {
return [self parsedMetadata:value];
} else if (status.IsNotFound()) {
return nil;
} else {
HARD_FAIL("metadataForKey: failed loading key %s with status: %s", key, status.ToString());
2017-10-03 08:55:22 -07:00
}
}
- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FIRTimestamp *)localWriteTime
2018-03-29 08:55:10 -07:00
mutations:(NSArray<FSTMutation *> *)mutations {
2017-10-03 08:55:22 -07:00
FSTBatchID batchID = self.nextBatchID;
self.nextBatchID += 1;
FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:batchID
localWriteTime:localWriteTime
mutations:mutations];
std::string key = [self mutationKeyForBatch:batch];
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Put(key, [self.serializer encodedMutationBatch:batch]);
2017-10-03 08:55:22 -07:00
// Store an empty value in the index which is equivalent to serializing a GPBEmpty message. In the
// future if we wanted to store some other kind of value here, we can parse these empty values as
// with some other protocol buffer (and the parser will see all default values).
std::string emptyBuffer;
for (FSTMutation *mutation in mutations) {
2018-08-08 12:34:03 -07:00
key = LevelDbDocumentMutationKey::Key(_userID, mutation.key, batchID);
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Put(key, emptyBuffer);
2017-10-03 08:55:22 -07:00
}
return batch;
}
- (nullable FSTMutationBatch *)lookupMutationBatch:(FSTBatchID)batchID {
std::string key = [self mutationKeyForBatchID:batchID];
std::string value;
Status status = _db.currentTransaction->Get(key, &value);
2017-10-03 08:55:22 -07:00
if (!status.ok()) {
if (status.IsNotFound()) {
return nil;
}
HARD_FAIL("Lookup mutation batch (%s, %s) failed with status: %s", _userID, batchID,
status.ToString());
2017-10-03 08:55:22 -07:00
}
return [self decodedMutationBatch:value];
}
- (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID {
// All batches with batchID <= self.metadata.lastAcknowledgedBatchId have been acknowledged so
// the first unacknowledged batch after batchID will have a batchID larger than both of these
// values.
FSTBatchID nextBatchID = MAX(batchID, self.metadata.lastAcknowledgedBatchId) + 1;
std::string key = [self mutationKeyForBatchID:nextBatchID];
auto it = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
it->Seek(key);
2018-08-08 12:34:03 -07:00
LevelDbMutationKey rowKey;
if (!it->Valid() || !rowKey.Decode(it->key())) {
2017-10-03 08:55:22 -07:00
// Past the last row in the DB or out of the mutations table
return nil;
}
2018-08-08 12:34:03 -07:00
if (rowKey.user_id() != _userID) {
2017-10-03 08:55:22 -07:00
// Jumped past the last mutation for this user
return nil;
}
2018-08-08 12:34:03 -07:00
HARD_ASSERT(rowKey.batch_id() >= nextBatchID, "Should have found mutation after %s", nextBatchID);
2017-10-03 08:55:22 -07:00
return [self decodedMutationBatch:it->value()];
}
- (NSArray<FSTMutationBatch *> *)allMutationBatchesThroughBatchID:(FSTBatchID)batchID {
2018-08-08 12:34:03 -07:00
std::string userKey = LevelDbMutationKey::KeyPrefix(_userID);
2017-10-03 08:55:22 -07:00
auto it = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
it->Seek(userKey);
NSMutableArray *result = [NSMutableArray array];
2018-08-08 12:34:03 -07:00
LevelDbMutationKey rowKey;
for (; it->Valid() && rowKey.Decode(it->key()); it->Next()) {
if (rowKey.user_id() != _userID) {
2017-10-03 08:55:22 -07:00
// End of this user's mutations
break;
2018-08-08 12:34:03 -07:00
} else if (rowKey.batch_id() > batchID) {
2017-10-03 08:55:22 -07:00
// This mutation is past what we're looking for
break;
}
[result addObject:[self decodedMutationBatch:it->value()]];
}
return result;
}
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKey:
2018-03-23 17:52:01 -04:00
(const DocumentKey &)documentKey {
2017-10-03 08:55:22 -07:00
// Scan the document-mutation index starting with a prefix starting with the given documentKey.
2018-08-08 12:34:03 -07:00
std::string indexPrefix = LevelDbDocumentMutationKey::KeyPrefix(_userID, documentKey.path());
auto indexIterator = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
indexIterator->Seek(indexPrefix);
// Simultaneously scan the mutation queue. This works because each (key, batchID) pair is unique
// and ordered, so when scanning a table prefixed by exactly key, all the batchIDs encountered
// will be unique and in order.
2018-08-08 12:34:03 -07:00
std::string mutationsPrefix = LevelDbMutationKey::KeyPrefix(_userID);
auto mutationIterator = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
NSMutableArray *result = [NSMutableArray array];
2018-08-08 12:34:03 -07:00
LevelDbDocumentMutationKey rowKey;
2017-10-03 08:55:22 -07:00
for (; indexIterator->Valid(); indexIterator->Next()) {
// Only consider rows matching exactly the specific key of interest. Index rows have this
// form (with markers in brackets):
//
// <User>user <Path>collection <Path>doc <BatchId>2 <Terminator>
// <User>user <Path>collection <Path>doc <BatchId>3 <Terminator>
// <User>user <Path>collection <Path>doc <Path>sub <Path>doc <BatchId>3 <Terminator>
//
// Note that Path markers sort after BatchId markers so this means that when searching for
// collection/doc, all the entries for it will be contiguous in the table, allowing a break
// after any mismatch.
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
2018-08-08 12:34:03 -07:00
!rowKey.Decode(indexIterator->key()) || rowKey.document_key() != documentKey) {
2017-10-03 08:55:22 -07:00
break;
}
// Each row is a unique combination of key and batchID, so this foreign key reference can
// only occur once.
2018-08-08 12:34:03 -07:00
std::string mutationKey = LevelDbMutationKey::Key(_userID, rowKey.batch_id());
2017-10-03 08:55:22 -07:00
mutationIterator->Seek(mutationKey);
if (!mutationIterator->Valid() || mutationIterator->key() != mutationKey) {
HARD_FAIL(
"Dangling document-mutation reference found: "
"%s points to %s; seeking there found %s",
DescribeKey(indexIterator), DescribeKey(mutationKey), DescribeKey(mutationIterator));
2017-10-03 08:55:22 -07:00
}
[result addObject:[self decodedMutationBatch:mutationIterator->value()]];
}
return result;
}
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKeys:
(const DocumentKeySet &)documentKeys {
// Take a pass through the document keys and collect the set of unique mutation batchIDs that
// affect them all. Some batches can affect more than one key.
std::set<FSTBatchID> batchIDs;
auto indexIterator = _db.currentTransaction->NewIterator();
2018-08-08 12:34:03 -07:00
LevelDbDocumentMutationKey rowKey;
for (const DocumentKey &documentKey : documentKeys) {
2018-08-08 12:34:03 -07:00
std::string indexPrefix = LevelDbDocumentMutationKey::KeyPrefix(_userID, documentKey.path());
for (indexIterator->Seek(indexPrefix); indexIterator->Valid(); indexIterator->Next()) {
// Only consider rows matching exactly the specific key of interest. Index rows have this
// form (with markers in brackets):
//
// <User>user <Path>collection <Path>doc <BatchId>2 <Terminator>
// <User>user <Path>collection <Path>doc <BatchId>3 <Terminator>
// <User>user <Path>collection <Path>doc <Path>sub <Path>doc <BatchId>3 <Terminator>
//
// Note that Path markers sort after BatchId markers so this means that when searching for
// collection/doc, all the entries for it will be contiguous in the table, allowing a break
// after any mismatch.
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
2018-08-08 12:34:03 -07:00
!rowKey.Decode(indexIterator->key()) || rowKey.document_key() != documentKey) {
break;
}
2018-08-08 12:34:03 -07:00
batchIDs.insert(rowKey.batch_id());
}
}
return [self allMutationBatchesWithBatchIDs:batchIDs];
}
2017-10-03 08:55:22 -07:00
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingQuery:(FSTQuery *)query {
HARD_ASSERT(![query isDocumentQuery], "Document queries shouldn't go down this path");
2017-10-03 08:55:22 -07:00
const ResourcePath &queryPath = query.path;
size_t immediateChildrenPathLength = queryPath.size() + 1;
2017-10-03 08:55:22 -07:00
// TODO(mcg): Actually implement a single-collection query
//
// This is actually executing an ancestor query, traversing the whole subtree below the
// collection which can be horrifically inefficient for some structures. The right way to
// solve this is to implement the full value index, but that's not in the cards in the near
// future so this is the best we can do for the moment.
//
// Since we don't yet index the actual properties in the mutations, our current approach is to
// just return all mutation batches that affect documents in the collection being queried.
//
// Unlike allMutationBatchesAffectingDocumentKey, this iteration will scan the document-mutation
// index for more than a single document so the associated batchIDs will be neither necessarily
// unique nor in order. This means an efficient simultaneous scan isn't possible.
2018-08-08 12:34:03 -07:00
std::string indexPrefix = LevelDbDocumentMutationKey::KeyPrefix(_userID, queryPath);
auto indexIterator = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
indexIterator->Seek(indexPrefix);
2018-08-08 12:34:03 -07:00
LevelDbDocumentMutationKey rowKey;
2017-10-03 08:55:22 -07:00
// Collect up unique batchIDs encountered during a scan of the index. Use a set<FSTBatchID> to
// accumulate batch IDs so they can be traversed in order in a scan of the main table.
//
// This method is faster than performing lookups of the keys with _db->Get and keeping a hash of
// batchIDs that have already been looked up. The performance difference is minor for small
// numbers of keys but > 30% faster for larger numbers of keys.
std::set<FSTBatchID> uniqueBatchIDs;
2017-10-03 08:55:22 -07:00
for (; indexIterator->Valid(); indexIterator->Next()) {
if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
2018-08-08 12:34:03 -07:00
!rowKey.Decode(indexIterator->key())) {
2017-10-03 08:55:22 -07:00
break;
}
// Rows with document keys more than one segment longer than the query path can't be matches.
// For example, a query on 'rooms' can't match the document /rooms/abc/messages/xyx.
// TODO(mcg): we'll need a different scanner when we implement ancestor queries.
2018-08-08 12:34:03 -07:00
if (rowKey.document_key().path().size() != immediateChildrenPathLength) {
2017-10-03 08:55:22 -07:00
continue;
}
2018-08-08 12:34:03 -07:00
uniqueBatchIDs.insert(rowKey.batch_id());
2017-10-03 08:55:22 -07:00
}
return [self allMutationBatchesWithBatchIDs:uniqueBatchIDs];
}
/**
* Constructs an array of matching batches, sorted by batchID to ensure that multiple mutations
* affecting the same document key are applied in order.
*/
- (NSArray<FSTMutationBatch *> *)allMutationBatchesWithBatchIDs:
(const std::set<FSTBatchID> &)batchIDs {
NSMutableArray *result = [NSMutableArray array];
2017-10-03 08:55:22 -07:00
// Given an ordered set of unique batchIDs perform a skipping scan over the main table to find
// the mutation batches.
auto mutationIterator = _db.currentTransaction->NewIterator();
for (FSTBatchID batchID : batchIDs) {
2018-08-08 12:34:03 -07:00
std::string mutationKey = LevelDbMutationKey::Key(_userID, batchID);
2017-10-03 08:55:22 -07:00
mutationIterator->Seek(mutationKey);
if (!mutationIterator->Valid() || mutationIterator->key() != mutationKey) {
HARD_FAIL(
"Dangling document-mutation reference found: "
"Missing batch %s; seeking there found %s",
DescribeKey(mutationKey), DescribeKey(mutationIterator));
2017-10-03 08:55:22 -07:00
}
[result addObject:[self decodedMutationBatch:mutationIterator->value()]];
}
return result;
}
- (NSArray<FSTMutationBatch *> *)allMutationBatches {
2018-08-08 12:34:03 -07:00
std::string userKey = LevelDbMutationKey::KeyPrefix(_userID);
2017-10-03 08:55:22 -07:00
auto it = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
it->Seek(userKey);
NSMutableArray *result = [NSMutableArray array];
for (; it->Valid() && absl::StartsWith(it->key(), userKey); it->Next()) {
2017-10-03 08:55:22 -07:00
[result addObject:[self decodedMutationBatch:it->value()]];
}
return result;
}
2018-03-29 08:55:10 -07:00
- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
auto checkIterator = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
for (FSTMutationBatch *batch in batches) {
FSTBatchID batchID = batch.batchID;
2018-08-08 12:34:03 -07:00
std::string key = LevelDbMutationKey::Key(_userID, batchID);
2017-10-03 08:55:22 -07:00
// As a sanity check, verify that the mutation batch exists before deleting it.
checkIterator->Seek(key);
HARD_ASSERT(checkIterator->Valid(), "Mutation batch %s did not exist", DescribeKey(key));
2017-10-03 08:55:22 -07:00
HARD_ASSERT(key == checkIterator->key(), "Mutation batch %s not found; found %s",
DescribeKey(key), DescribeKey(checkIterator));
2017-10-03 08:55:22 -07:00
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Delete(key);
2017-10-03 08:55:22 -07:00
for (FSTMutation *mutation in batch.mutations) {
2018-08-08 12:34:03 -07:00
key = LevelDbDocumentMutationKey::Key(_userID, mutation.key, batchID);
2018-03-29 08:55:10 -07:00
_db.currentTransaction->Delete(key);
2018-05-08 10:49:54 -07:00
[_db.referenceDelegate removeMutationReference:mutation.key];
2017-10-03 08:55:22 -07:00
}
}
}
- (void)performConsistencyCheck {
if (![self isEmpty]) {
return;
}
// Verify that there are no entries in the document-mutation index if the queue is empty.
2018-08-08 12:34:03 -07:00
std::string indexPrefix = LevelDbDocumentMutationKey::KeyPrefix(_userID);
auto indexIterator = _db.currentTransaction->NewIterator();
2017-10-03 08:55:22 -07:00
indexIterator->Seek(indexPrefix);
std::vector<std::string> danglingMutationReferences;
2017-10-03 08:55:22 -07:00
for (; indexIterator->Valid(); indexIterator->Next()) {
// Only consider rows matching this index prefix for the current user.
if (!absl::StartsWith(indexIterator->key(), indexPrefix)) {
2017-10-03 08:55:22 -07:00
break;
}
danglingMutationReferences.push_back(DescribeKey(indexIterator));
2017-10-03 08:55:22 -07:00
}
HARD_ASSERT(danglingMutationReferences.empty(),
"Document leak -- detected dangling mutation references when queue "
"is empty. Dangling keys: %s",
util::ToString(danglingMutationReferences));
2017-10-03 08:55:22 -07:00
}
- (std::string)mutationKeyForBatch:(FSTMutationBatch *)batch {
2018-08-08 12:34:03 -07:00
return LevelDbMutationKey::Key(_userID, batch.batchID);
2017-10-03 08:55:22 -07:00
}
- (std::string)mutationKeyForBatchID:(FSTBatchID)batchID {
2018-08-08 12:34:03 -07:00
return LevelDbMutationKey::Key(_userID, batchID);
2017-10-03 08:55:22 -07:00
}
/** Parses the MutationQueue metadata from the given LevelDB row contents. */
- (FSTPBMutationQueue *)parsedMetadata:(Slice)slice {
NSData *data =
[[NSData alloc] initWithBytesNoCopy:(void *)slice.data() length:slice.size() freeWhenDone:NO];
NSError *error;
FSTPBMutationQueue *proto = [FSTPBMutationQueue parseFromData:data error:&error];
if (!proto) {
HARD_FAIL("FSTPBMutationQueue failed to parse: %s", error);
2017-10-03 08:55:22 -07:00
}
return proto;
}
- (FSTMutationBatch *)decodedMutationBatch:(absl::string_view)encoded {
NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data()
length:encoded.size()
freeWhenDone:NO];
2017-10-03 08:55:22 -07:00
NSError *error;
FSTPBWriteBatch *proto = [FSTPBWriteBatch parseFromData:data error:&error];
if (!proto) {
HARD_FAIL("FSTPBMutationBatch failed to parse: %s", error);
2017-10-03 08:55:22 -07:00
}
return [self.serializer decodedMutationBatch:proto];
}
@end
NS_ASSUME_NONNULL_END