aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Source/Local/FSTLevelDBQueryCache.mm
diff options
context:
space:
mode:
authorGravatar Gil <mcg@google.com>2017-10-03 08:55:22 -0700
committerGravatar GitHub <noreply@github.com>2017-10-03 08:55:22 -0700
commitbde743ed25166a0b320ae157bfb1d68064f531c9 (patch)
tree4dd7525d9df32fa5dbdb721d4b0d4f9b87f5e884 /Firestore/Source/Local/FSTLevelDBQueryCache.mm
parentbf550507ffa8beee149383a5bf1e2363bccefbb4 (diff)
Release 4.3.0 (#327)
Initial release of Firestore at 0.8.0 Bump FirebaseCommunity to 0.1.3
Diffstat (limited to 'Firestore/Source/Local/FSTLevelDBQueryCache.mm')
-rw-r--r--Firestore/Source/Local/FSTLevelDBQueryCache.mm340
1 files changed, 340 insertions, 0 deletions
diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
new file mode 100644
index 0000000..c1ba654
--- /dev/null
+++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
@@ -0,0 +1,340 @@
+/*
+ * 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 "FSTLevelDBQueryCache.h"
+
+#include <leveldb/db.h>
+#include <leveldb/write_batch.h>
+#include <string>
+
+#import "Target.pbobjc.h"
+#import "FSTQuery.h"
+#import "FSTLevelDBKey.h"
+#import "FSTLocalSerializer.h"
+#import "FSTQueryData.h"
+#import "FSTWriteGroup.h"
+#import "FSTDocumentKey.h"
+#import "FSTAssert.h"
+
+#include "ordered_code.h"
+#include "string_util.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+using Firestore::OrderedCode;
+using Firestore::StringView;
+using leveldb::DB;
+using leveldb::Iterator;
+using leveldb::ReadOptions;
+using leveldb::Slice;
+using leveldb::Status;
+using leveldb::WriteOptions;
+
+/**
+ * Returns a standard set of read options.
+ *
+ * For now this is paranoid, but perhaps disable that in production builds.
+ */
+static ReadOptions GetStandardReadOptions() {
+ ReadOptions options;
+ options.verify_checksums = true;
+ return options;
+}
+
+@interface FSTLevelDBQueryCache ()
+
+/** A write-through cached copy of the metadata for the query cache. */
+@property(nonatomic, strong, nullable) FSTPBTargetGlobal *metadata;
+
+@property(nonatomic, strong, readonly) FSTLocalSerializer *serializer;
+
+@end
+
+@implementation FSTLevelDBQueryCache {
+ // The DB pointer is shared with all cooperating LevelDB-related objects.
+ std::shared_ptr<DB> _db;
+
+ /**
+ * The last received snapshot version. This is part of `metadata` but we store it separately to
+ * avoid extra conversion to/from GPBTimestamp.
+ */
+ FSTSnapshotVersion *_lastRemoteSnapshotVersion;
+}
+
+- (instancetype)initWithDB:(std::shared_ptr<DB>)db serializer:(FSTLocalSerializer *)serializer {
+ if (self = [super init]) {
+ FSTAssert(db, @"db must not be NULL");
+ _db = db;
+ _serializer = serializer;
+ }
+ return self;
+}
+
+- (void)start {
+ std::string key = [FSTLevelDBTargetGlobalKey key];
+ FSTPBTargetGlobal *metadata = [self metadataForKey:key];
+ if (!metadata) {
+ metadata = [FSTPBTargetGlobal message];
+ }
+ _lastRemoteSnapshotVersion = [self.serializer decodedVersion:metadata.lastRemoteSnapshotVersion];
+
+ self.metadata = metadata;
+}
+
+#pragma mark - FSTQueryCache implementation
+
+- (FSTTargetID)highestTargetID {
+ return self.metadata.highestTargetId;
+}
+
+- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+ return _lastRemoteSnapshotVersion;
+}
+
+- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ group:(FSTWriteGroup *)group {
+ _lastRemoteSnapshotVersion = snapshotVersion;
+ self.metadata.lastRemoteSnapshotVersion = [self.serializer encodedVersion:snapshotVersion];
+ [group setMessage:self.metadata forKey:[FSTLevelDBTargetGlobalKey key]];
+}
+
+- (void)shutdown {
+ _db.reset();
+}
+
+- (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group {
+ // TODO(mcg): actually populate listen sequence number
+ FSTTargetID targetID = queryData.targetID;
+ std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
+ [group setMessage:[self.serializer encodedQueryData:queryData] forKey:key];
+
+ NSString *canonicalID = queryData.query.canonicalID;
+ std::string indexKey =
+ [FSTLevelDBQueryTargetKey keyWithCanonicalID:canonicalID targetID:targetID];
+ std::string emptyBuffer;
+ [group setData:emptyBuffer forKey:indexKey];
+
+ FSTPBTargetGlobal *metadata = self.metadata;
+ if (targetID > metadata.highestTargetId) {
+ metadata.highestTargetId = targetID;
+ [group setMessage:metadata forKey:[FSTLevelDBTargetGlobalKey key]];
+ }
+}
+
+- (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group {
+ FSTTargetID targetID = queryData.targetID;
+
+ [self removeMatchingKeysForTargetID:targetID group:group];
+
+ std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
+ [group removeMessageForKey:key];
+
+ std::string indexKey =
+ [FSTLevelDBQueryTargetKey keyWithCanonicalID:queryData.query.canonicalID targetID:targetID];
+ [group removeMessageForKey:indexKey];
+}
+
+/**
+ * Looks up the query global metadata associated with the given key.
+ *
+ * @return the parsed protocol buffer message or nil if the row referenced by the given key does
+ * not exist.
+ */
+- (nullable FSTPBTargetGlobal *)metadataForKey:(const std::string &)key {
+ std::string value;
+ Status status = _db->Get(GetStandardReadOptions(), key, &value);
+ if (status.IsNotFound()) {
+ return nil;
+ } else if (!status.ok()) {
+ FSTFail(@"metadataForKey: failed loading key %s with status: %s", key.c_str(),
+ status.ToString().c_str());
+ }
+
+ NSData *data =
+ [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
+
+ NSError *error;
+ FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
+ if (!proto) {
+ FSTFail(@"FSTPBTargetGlobal failed to parse: %@", error);
+ }
+
+ return proto;
+}
+
+/**
+ * Parses the given bytes as an FSTPBTarget protocol buffer and then converts to the equivalent
+ * query data.
+ */
+- (FSTQueryData *)decodedTargetWithSlice:(Slice)slice {
+ NSData *data =
+ [[NSData alloc] initWithBytesNoCopy:(void *)slice.data() length:slice.size() freeWhenDone:NO];
+
+ NSError *error;
+ FSTPBTarget *proto = [FSTPBTarget parseFromData:data error:&error];
+ if (!proto) {
+ FSTFail(@"FSTPBTarget failed to parse: %@", error);
+ }
+
+ return [self.serializer decodedQueryData:proto];
+}
+
+- (nullable FSTQueryData *)queryDataForQuery:(FSTQuery *)query {
+ // Scan the query-target index starting with a prefix starting with the given query's canonicalID.
+ // Note that this is a scan rather than a get because canonicalIDs are not required to be unique
+ // per target.
+ Slice canonicalID = StringView(query.canonicalID);
+ std::unique_ptr<Iterator> indexItererator(_db->NewIterator(GetStandardReadOptions()));
+ std::string indexPrefix = [FSTLevelDBQueryTargetKey keyPrefixWithCanonicalID:canonicalID];
+ indexItererator->Seek(indexPrefix);
+
+ // Simultaneously scan the targets table. This works because each (canonicalID, targetID) pair is
+ // unique and ordered, so when scanning a table prefixed by exactly one canonicalID, all the
+ // targetIDs will be unique and in order.
+ std::string targetPrefix = [FSTLevelDBTargetKey keyPrefix];
+ std::unique_ptr<Iterator> targetIterator(_db->NewIterator(GetStandardReadOptions()));
+
+ FSTLevelDBQueryTargetKey *rowKey = [[FSTLevelDBQueryTargetKey alloc] init];
+ for (; indexItererator->Valid(); indexItererator->Next()) {
+ Slice indexKey = indexItererator->key();
+
+ // Only consider rows matching exactly the specific canonicalID of interest.
+ if (!indexKey.starts_with(indexPrefix) || ![rowKey decodeKey:indexKey] ||
+ canonicalID != rowKey.canonicalID) {
+ // End of this canonicalID's possible targets.
+ break;
+ }
+
+ // Each row is a unique combination of canonicalID and targetID, so this foreign key reference
+ // can only occur once.
+ std::string targetKey = [FSTLevelDBTargetKey keyWithTargetID:rowKey.targetID];
+ targetIterator->Seek(targetKey);
+ if (!targetIterator->Valid() || targetIterator->key() != targetKey) {
+ NSString *foundKeyDescription = @"the end of the table";
+ if (targetIterator->Valid()) {
+ foundKeyDescription = [FSTLevelDBKey descriptionForKey:targetIterator->key()];
+ }
+ FSTFail(@"Dangling query-target reference found: "
+ @"%@ points to %@; seeking there found %@",
+ [FSTLevelDBKey descriptionForKey:indexKey],
+ [FSTLevelDBKey descriptionForKey:targetKey], foundKeyDescription);
+ }
+
+ // Finally after finding a potential match, check that the query is actually equal to the
+ // requested query.
+ FSTQueryData *target = [self decodedTargetWithSlice:targetIterator->value()];
+ if ([target.query isEqual:query]) {
+ return target;
+ }
+ }
+
+ return nil;
+}
+
+#pragma mark Matching Key tracking
+
+- (void)addMatchingKeys:(FSTDocumentKeySet *)keys
+ forTargetID:(FSTTargetID)targetID
+ group:(FSTWriteGroup *)group {
+ // 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;
+
+ [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *documentKey, BOOL *stop) {
+ [group setData:emptyBuffer
+ forKey:[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:documentKey]];
+ [group setData:emptyBuffer
+ forKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID]];
+ }];
+}
+
+- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys
+ forTargetID:(FSTTargetID)targetID
+ group:(FSTWriteGroup *)group {
+ [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
+ [group
+ removeMessageForKey:[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]];
+ [group
+ removeMessageForKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]];
+ [self.garbageCollector addPotentialGarbageKey:key];
+ }];
+}
+
+- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID group:(FSTWriteGroup *)group {
+ std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
+ std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+ indexIterator->Seek(indexPrefix);
+
+ FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
+ for (; indexIterator->Valid(); indexIterator->Next()) {
+ Slice indexKey = indexIterator->key();
+
+ // Only consider rows matching this specific targetID.
+ if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
+ break;
+ }
+ FSTDocumentKey *documentKey = rowKey.documentKey;
+
+ // Delete both index rows
+ [group removeMessageForKey:indexKey];
+ [group removeMessageForKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey
+ targetID:targetID]];
+ [self.garbageCollector addPotentialGarbageKey:documentKey];
+ }
+}
+
+- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID {
+ std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
+ std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+ indexIterator->Seek(indexPrefix);
+
+ FSTDocumentKeySet *result = [FSTDocumentKeySet keySet];
+ FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
+ for (; indexIterator->Valid(); indexIterator->Next()) {
+ Slice indexKey = indexIterator->key();
+
+ // Only consider rows matching this specific targetID.
+ if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
+ break;
+ }
+
+ result = [result setByAddingObject:rowKey.documentKey];
+ }
+
+ return result;
+}
+
+#pragma mark - FSTGarbageSource implementation
+
+- (BOOL)containsKey:(FSTDocumentKey *)key {
+ std::string indexPrefix = [FSTLevelDBDocumentTargetKey keyPrefixWithResourcePath:key.path];
+ std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+ indexIterator->Seek(indexPrefix);
+
+ if (indexIterator->Valid()) {
+ FSTLevelDBDocumentTargetKey *rowKey = [[FSTLevelDBDocumentTargetKey alloc] init];
+ if ([rowKey decodeKey:indexIterator->key()] && [rowKey.documentKey isEqualToKey:key]) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END