diff --git a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm index 66f223e75f2..f4a4a3bdbf7 100644 --- a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm +++ b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm @@ -26,14 +26,17 @@ #import "Firestore/Source/Model/FSTDocumentSet.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "absl/memory/memory.h" #include "leveldb/db.h" #include "leveldb/write_batch.h" NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::local::LevelDbRemoteDocumentCache; using firebase::firestore::local::LevelDbRemoteDocumentKey; using firebase::firestore::local::LevelDbTransaction; using firebase::firestore::model::DocumentKey; @@ -43,110 +46,35 @@ using leveldb::DB; using leveldb::Status; -@interface FSTLevelDBRemoteDocumentCache () - -@property(nonatomic, strong, readonly) FSTLocalSerializer *serializer; - -@end - @implementation FSTLevelDBRemoteDocumentCache { - FSTLevelDB *_db; + std::unique_ptr _cache; } - (instancetype)initWithDB:(FSTLevelDB *)db serializer:(FSTLocalSerializer *)serializer { if (self = [super init]) { - _db = db; - _serializer = serializer; + _cache = absl::make_unique(db, serializer); } return self; } - (void)addEntry:(FSTMaybeDocument *)document { - std::string key = [self remoteDocumentKey:document.key]; - _db.currentTransaction->Put(key, [self.serializer encodedMaybeDocument:document]); + _cache->AddEntry(document); } - (void)removeEntryForKey:(const DocumentKey &)documentKey { - std::string key = [self remoteDocumentKey:documentKey]; - _db.currentTransaction->Delete(key); + _cache->RemoveEntry(documentKey); } - (nullable FSTMaybeDocument *)entryForKey:(const DocumentKey &)documentKey { - std::string key = LevelDbRemoteDocumentKey::Key(documentKey); - std::string value; - Status status = _db.currentTransaction->Get(key, &value); - if (status.IsNotFound()) { - return nil; - } else if (status.ok()) { - return [self decodeMaybeDocument:value withKey:documentKey]; - } else { - HARD_FAIL("Fetch document for key (%s) failed with status: %s", documentKey.ToString(), - status.ToString()); - } + return _cache->Get(documentKey); } - (MaybeDocumentMap)entriesForKeys:(const DocumentKeySet &)keys { - MaybeDocumentMap results; - - LevelDbRemoteDocumentKey currentKey; - auto it = _db.currentTransaction->NewIterator(); - - for (const DocumentKey &key : keys) { - it->Seek([self remoteDocumentKey:key]); - if (!it->Valid() || !currentKey.Decode(it->key()) || currentKey.document_key() != key) { - results = results.insert(key, nil); - } else { - results = results.insert(key, [self decodeMaybeDocument:it->value() withKey:key]); - } - } - - return results; + return _cache->GetAll(keys); } - (DocumentMap)documentsMatchingQuery:(FSTQuery *)query { - DocumentMap results; - - // Documents are ordered by key, so we can use a prefix scan to narrow down - // the documents we need to match the query against. - std::string startKey = LevelDbRemoteDocumentKey::KeyPrefix(query.path); - auto it = _db.currentTransaction->NewIterator(); - it->Seek(startKey); - - LevelDbRemoteDocumentKey currentKey; - for (; it->Valid() && currentKey.Decode(it->key()); it->Next()) { - FSTMaybeDocument *maybeDoc = - [self decodeMaybeDocument:it->value() withKey:currentKey.document_key()]; - if (!query.path.IsPrefixOf(maybeDoc.key.path())) { - break; - } else if ([maybeDoc isKindOfClass:[FSTDocument class]]) { - results = results.insert(maybeDoc.key, static_cast(maybeDoc)); - } - } - - return results; -} - -- (std::string)remoteDocumentKey:(const DocumentKey &)key { - return LevelDbRemoteDocumentKey::Key(key); -} - -- (FSTMaybeDocument *)decodeMaybeDocument:(absl::string_view)encoded - withKey:(const DocumentKey &)documentKey { - NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data() - length:encoded.size() - freeWhenDone:NO]; - - NSError *error; - FSTPBMaybeDocument *proto = [FSTPBMaybeDocument parseFromData:data error:&error]; - if (!proto) { - HARD_FAIL("FSTPBMaybeDocument failed to parse: %s", error); - } - - FSTMaybeDocument *maybeDocument = [self.serializer decodedMaybeDocument:proto]; - HARD_ASSERT(maybeDocument.key == documentKey, - "Read document has key (%s) instead of expected key (%s).", - maybeDocument.key.ToString(), documentKey.ToString()); - return maybeDocument; + return _cache->GetMatchingDocuments(query); } @end diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h new file mode 100644 index 00000000000..b4ba9e3d17a --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_REMOTE_DOCUMENT_CACHE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_REMOTE_DOCUMENT_CACHE_H_ + +#if !defined(__OBJC__) +#error "For now, this file must only be included by ObjC source files." +#endif // !defined(__OBJC__) + +#include + +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/model/types.h" +#include "absl/strings/string_view.h" + +@class FSTLevelDB; +@class FSTLocalSerializer; +@class FSTMaybeDocument; +@class FSTQuery; + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +/** Cached Remote Documents backed by leveldb. */ +class LevelDbRemoteDocumentCache { + public: + LevelDbRemoteDocumentCache(FSTLevelDB* db, FSTLocalSerializer* serializer); + + void AddEntry(FSTMaybeDocument* document); + void RemoveEntry(const model::DocumentKey& key); + + FSTMaybeDocument* _Nullable Get(const model::DocumentKey& key); + model::MaybeDocumentMap GetAll(const model::DocumentKeySet& keys); + model::DocumentMap GetMatchingDocuments(FSTQuery* query); + + private: + FSTMaybeDocument* DecodeMaybeDocument(absl::string_view encoded, + const model::DocumentKey& key); + + FSTLevelDB* db_; + FSTLocalSerializer* serializer_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_REMOTE_DOCUMENT_CACHE_H_ diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm new file mode 100644 index 00000000000..341ce12fb09 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm @@ -0,0 +1,138 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h" + +#import + +#include + +#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h" +#import "Firestore/Source/Core/FSTQuery.h" +#import "Firestore/Source/Local/FSTLevelDB.h" +#import "Firestore/Source/Local/FSTLocalSerializer.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "leveldb/db.h" + +using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentMap; +using firebase::firestore::model::MaybeDocumentMap; +using leveldb::Status; + +namespace firebase { +namespace firestore { +namespace local { + +LevelDbRemoteDocumentCache::LevelDbRemoteDocumentCache( + FSTLevelDB* db, FSTLocalSerializer* serializer) + : db_(db), serializer_(serializer) { +} + +void LevelDbRemoteDocumentCache::AddEntry(FSTMaybeDocument* document) { + std::string ldb_key = LevelDbRemoteDocumentKey::Key(document.key); + db_.currentTransaction->Put(ldb_key, + [serializer_ encodedMaybeDocument:document]); +} + +void LevelDbRemoteDocumentCache::RemoveEntry(const DocumentKey& key) { + std::string ldb_key = LevelDbRemoteDocumentKey::Key(key); + db_.currentTransaction->Delete(ldb_key); +} + +FSTMaybeDocument* _Nullable LevelDbRemoteDocumentCache::Get( + const DocumentKey& key) { + std::string ldb_key = LevelDbRemoteDocumentKey::Key(key); + std::string value; + Status status = db_.currentTransaction->Get(ldb_key, &value); + if (status.IsNotFound()) { + return nil; + } else if (status.ok()) { + return DecodeMaybeDocument(value, key); + } else { + HARD_FAIL("Fetch document for key (%s) failed with status: %s", + key.ToString(), status.ToString()); + } +} + +MaybeDocumentMap LevelDbRemoteDocumentCache::GetAll( + const DocumentKeySet& keys) { + MaybeDocumentMap results; + + LevelDbRemoteDocumentKey currentKey; + auto it = db_.currentTransaction->NewIterator(); + + for (const DocumentKey& key : keys) { + it->Seek(LevelDbRemoteDocumentKey::Key(key)); + if (!it->Valid() || !currentKey.Decode(it->key()) || + currentKey.document_key() != key) { + results = results.insert(key, nil); + } else { + results = results.insert(key, DecodeMaybeDocument(it->value(), key)); + } + } + + return results; +} + +DocumentMap LevelDbRemoteDocumentCache::GetMatchingDocuments(FSTQuery* query) { + DocumentMap results; + + // Documents are ordered by key, so we can use a prefix scan to narrow down + // the documents we need to match the query against. + std::string startKey = LevelDbRemoteDocumentKey::KeyPrefix(query.path); + auto it = db_.currentTransaction->NewIterator(); + it->Seek(startKey); + + LevelDbRemoteDocumentKey currentKey; + for (; it->Valid() && currentKey.Decode(it->key()); it->Next()) { + FSTMaybeDocument* maybeDoc = + DecodeMaybeDocument(it->value(), currentKey.document_key()); + if (!query.path.IsPrefixOf(maybeDoc.key.path())) { + break; + } else if ([maybeDoc isKindOfClass:[FSTDocument class]]) { + results = + results.insert(maybeDoc.key, static_cast(maybeDoc)); + } + } + + return results; +} + +FSTMaybeDocument* LevelDbRemoteDocumentCache::DecodeMaybeDocument( + absl::string_view encoded, const DocumentKey& key) { + NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)encoded.data() + length:encoded.size() + freeWhenDone:NO]; + + NSError* error; + FSTPBMaybeDocument* proto = + [FSTPBMaybeDocument parseFromData:data error:&error]; + if (!proto) { + HARD_FAIL("FSTPBMaybeDocument failed to parse: %s", error); + } + + FSTMaybeDocument* maybeDocument = [serializer_ decodedMaybeDocument:proto]; + HARD_ASSERT(maybeDocument.key == key, + "Read document has key (%s) instead of expected key (%s).", + maybeDocument.key.ToString(), key.ToString()); + return maybeDocument; +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h index 4c23ef54103..09538a0f4fa 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h +++ b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h @@ -21,8 +21,6 @@ #error "For now, this file must only be included by ObjC source files." #endif // !defined(__OBJC__) -#import - #include #include "Firestore/core/src/firebase/firestore/model/document_key.h"