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.
|
|
|
|
|
*/
|
|
|
|
|
|
2017-11-16 20:52:01 -08:00
|
|
|
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
|
2017-10-03 08:55:22 -07:00
|
|
|
|
2018-04-09 11:54:39 -07:00
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
2018-04-25 14:05:36 -04:00
|
|
|
#include <utility>
|
2018-04-09 11:54:39 -07:00
|
|
|
|
2017-11-16 20:52:01 -08:00
|
|
|
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
|
|
|
|
|
#import "Firestore/Source/Core/FSTQuery.h"
|
2018-01-30 14:36:22 -08:00
|
|
|
#import "Firestore/Source/Local/FSTLevelDB.h"
|
2017-11-16 20:52:01 -08:00
|
|
|
#import "Firestore/Source/Local/FSTLevelDBKey.h"
|
|
|
|
|
#import "Firestore/Source/Local/FSTLocalSerializer.h"
|
|
|
|
|
#import "Firestore/Source/Local/FSTQueryData.h"
|
2017-10-03 08:55:22 -07:00
|
|
|
|
2018-03-23 17:52:01 -04:00
|
|
|
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
|
2018-04-25 14:05:36 -04:00
|
|
|
#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
|
2018-05-24 11:00:23 -07:00
|
|
|
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
|
2018-07-27 13:58:11 -07:00
|
|
|
#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
|
2018-04-09 11:54:39 -07:00
|
|
|
#include "absl/strings/match.h"
|
2018-03-23 17:52:01 -04:00
|
|
|
|
2017-10-03 08:55:22 -07:00
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
2018-03-22 15:45:05 -07:00
|
|
|
using firebase::firestore::local::LevelDbTransaction;
|
2018-07-27 13:58:11 -07:00
|
|
|
using firebase::firestore::util::OrderedCode;
|
2017-10-03 08:55:22 -07:00
|
|
|
using Firestore::StringView;
|
2018-03-23 17:52:01 -04:00
|
|
|
using firebase::firestore::model::DocumentKey;
|
2018-04-25 14:05:36 -04:00
|
|
|
using firebase::firestore::model::SnapshotVersion;
|
2017-10-03 08:55:22 -07:00
|
|
|
using leveldb::DB;
|
|
|
|
|
using leveldb::Slice;
|
|
|
|
|
using leveldb::Status;
|
2018-05-05 08:10:51 -07:00
|
|
|
using firebase::firestore::model::DocumentKeySet;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
2018-07-27 13:58:11 -07:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
FSTListenSequenceNumber ReadSequenceNumber(const absl::string_view &slice) {
|
|
|
|
|
FSTListenSequenceNumber decoded;
|
|
|
|
|
absl::string_view tmp(slice.data(), slice.size());
|
|
|
|
|
if (!OrderedCode::ReadSignedNumIncreasing(&tmp, &decoded)) {
|
|
|
|
|
HARD_FAIL("Failed to read sequence number from a sentinel row");
|
|
|
|
|
}
|
|
|
|
|
return decoded;
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2017-10-03 08:55:22 -07:00
|
|
|
@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 {
|
2018-03-22 17:31:16 -07:00
|
|
|
FSTLevelDB *_db;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The last received snapshot version. This is part of `metadata` but we store it separately to
|
|
|
|
|
* avoid extra conversion to/from GPBTimestamp.
|
|
|
|
|
*/
|
2018-04-25 14:05:36 -04:00
|
|
|
SnapshotVersion _lastRemoteSnapshotVersion;
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
2018-03-22 15:45:05 -07:00
|
|
|
+ (nullable FSTPBTargetGlobal *)readTargetMetadataWithTransaction:
|
|
|
|
|
(firebase::firestore::local::LevelDbTransaction *)transaction {
|
|
|
|
|
std::string key = [FSTLevelDBTargetGlobalKey key];
|
|
|
|
|
std::string value;
|
|
|
|
|
Status status = transaction->Get(key, &value);
|
|
|
|
|
if (status.IsNotFound()) {
|
|
|
|
|
return nil;
|
|
|
|
|
} else if (!status.ok()) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL("metadataForKey: failed loading key %s with status: %s", key, status.ToString());
|
2018-03-22 15:45:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSData *data =
|
|
|
|
|
[[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
|
|
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
|
FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
|
|
|
|
|
if (!proto) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL("FSTPBTargetGlobal failed to parse: %s", error);
|
2018-03-22 15:45:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return proto;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-13 08:12:09 -07:00
|
|
|
+ (nullable FSTPBTargetGlobal *)readTargetMetadataFromDB:(DB *)db {
|
2018-01-30 14:36:22 -08:00
|
|
|
std::string key = [FSTLevelDBTargetGlobalKey key];
|
|
|
|
|
std::string value;
|
|
|
|
|
Status status = db->Get([FSTLevelDB standardReadOptions], key, &value);
|
|
|
|
|
if (status.IsNotFound()) {
|
|
|
|
|
return nil;
|
|
|
|
|
} else if (!status.ok()) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL("metadataForKey: failed loading key %s with status: %s", key, status.ToString());
|
2018-01-30 14:36:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSData *data =
|
|
|
|
|
[[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
|
|
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
|
FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
|
|
|
|
|
if (!proto) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL("FSTPBTargetGlobal failed to parse: %s", error);
|
2018-01-30 14:36:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return proto;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 17:31:16 -07:00
|
|
|
- (instancetype)initWithDB:(FSTLevelDB *)db serializer:(FSTLocalSerializer *)serializer {
|
2017-10-03 08:55:22 -07:00
|
|
|
if (self = [super init]) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_ASSERT(db, "db must not be NULL");
|
2017-10-03 08:55:22 -07:00
|
|
|
_db = db;
|
|
|
|
|
_serializer = serializer;
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)start {
|
2018-03-22 17:31:16 -07:00
|
|
|
// TODO(gsoltis): switch this usage of ptr to currentTransaction
|
|
|
|
|
FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.ptr];
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_ASSERT(
|
2018-01-30 14:36:22 -08:00
|
|
|
metadata != nil,
|
2018-05-24 11:00:23 -07:00
|
|
|
"Found nil metadata, expected schema to be at version 0 which ensures metadata existence");
|
2017-10-03 08:55:22 -07:00
|
|
|
_lastRemoteSnapshotVersion = [self.serializer decodedVersion:metadata.lastRemoteSnapshotVersion];
|
|
|
|
|
|
|
|
|
|
self.metadata = metadata;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - FSTQueryCache implementation
|
|
|
|
|
|
|
|
|
|
- (FSTTargetID)highestTargetID {
|
|
|
|
|
return self.metadata.highestTargetId;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 12:23:45 -08:00
|
|
|
- (FSTListenSequenceNumber)highestListenSequenceNumber {
|
|
|
|
|
return self.metadata.highestListenSequenceNumber;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-25 14:05:36 -04:00
|
|
|
- (const SnapshotVersion &)lastRemoteSnapshotVersion {
|
2017-10-03 08:55:22 -07:00
|
|
|
return _lastRemoteSnapshotVersion;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-25 14:05:36 -04:00
|
|
|
- (void)setLastRemoteSnapshotVersion:(SnapshotVersion)snapshotVersion {
|
|
|
|
|
_lastRemoteSnapshotVersion = std::move(snapshotVersion);
|
|
|
|
|
self.metadata.lastRemoteSnapshotVersion =
|
|
|
|
|
[self.serializer encodedVersion:_lastRemoteSnapshotVersion];
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-27 13:58:11 -07:00
|
|
|
- (void)enumerateTargetsUsingBlock:(void (^)(FSTQueryData *queryData, BOOL *stop))block {
|
|
|
|
|
// Enumerate all targets, give their sequence numbers.
|
|
|
|
|
std::string targetPrefix = [FSTLevelDBTargetKey keyPrefix];
|
|
|
|
|
auto it = _db.currentTransaction->NewIterator();
|
|
|
|
|
it->Seek(targetPrefix);
|
|
|
|
|
BOOL stop = NO;
|
|
|
|
|
for (; !stop && it->Valid() && absl::StartsWith(it->key(), targetPrefix); it->Next()) {
|
|
|
|
|
FSTQueryData *target = [self decodedTarget:it->value()];
|
|
|
|
|
block(target, &stop);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)enumerateOrphanedDocumentsUsingBlock:
|
|
|
|
|
(void (^)(const DocumentKey &docKey, FSTListenSequenceNumber sequenceNumber, BOOL *stop))block {
|
|
|
|
|
std::string documentTargetPrefix = [FSTLevelDBDocumentTargetKey keyPrefix];
|
|
|
|
|
auto it = _db.currentTransaction->NewIterator();
|
|
|
|
|
it->Seek(documentTargetPrefix);
|
|
|
|
|
FSTListenSequenceNumber nextToReport = 0;
|
|
|
|
|
DocumentKey keyToReport;
|
|
|
|
|
FSTLevelDBDocumentTargetKey *key = [[FSTLevelDBDocumentTargetKey alloc] init];
|
|
|
|
|
BOOL stop = NO;
|
|
|
|
|
for (; !stop && it->Valid() && absl::StartsWith(it->key(), documentTargetPrefix); it->Next()) {
|
|
|
|
|
[key decodeKey:it->key()];
|
|
|
|
|
if (FSTTargetIDIsSentinel(key.targetID)) {
|
|
|
|
|
// if nextToReport is non-zero, report it, this is a new key so the last one
|
|
|
|
|
// must be not be a member of any targets.
|
|
|
|
|
if (nextToReport != 0) {
|
|
|
|
|
block(keyToReport, nextToReport, &stop);
|
|
|
|
|
}
|
|
|
|
|
// set nextToReport to be this sequence number. It's the next one we might
|
|
|
|
|
// report, if we don't find any targets for this document.
|
|
|
|
|
nextToReport = ReadSequenceNumber(it->value());
|
|
|
|
|
keyToReport = key.documentKey;
|
|
|
|
|
} else {
|
|
|
|
|
// set nextToReport to be 0, we know we don't need to report this one since
|
|
|
|
|
// we found a target for it.
|
|
|
|
|
nextToReport = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// if not stop and nextToReport is non-zero, report it. We didn't find any targets for
|
|
|
|
|
// that document, and we weren't asked to stop.
|
|
|
|
|
if (!stop && nextToReport != 0) {
|
|
|
|
|
block(keyToReport, nextToReport, &stop);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
- (void)saveQueryData:(FSTQueryData *)queryData {
|
2017-10-03 08:55:22 -07:00
|
|
|
FSTTargetID targetID = queryData.targetID;
|
|
|
|
|
std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put(key, [self.serializer encodedQueryData:queryData]);
|
2018-02-13 11:12:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)updateMetadataForQueryData:(FSTQueryData *)queryData {
|
|
|
|
|
BOOL updatedMetadata = NO;
|
|
|
|
|
|
|
|
|
|
if (queryData.targetID > self.metadata.highestTargetId) {
|
|
|
|
|
self.metadata.highestTargetId = queryData.targetID;
|
|
|
|
|
updatedMetadata = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (queryData.sequenceNumber > self.metadata.highestListenSequenceNumber) {
|
|
|
|
|
self.metadata.highestListenSequenceNumber = queryData.sequenceNumber;
|
|
|
|
|
updatedMetadata = YES;
|
|
|
|
|
}
|
|
|
|
|
return updatedMetadata;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
- (void)addQueryData:(FSTQueryData *)queryData {
|
|
|
|
|
[self saveQueryData:queryData];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
NSString *canonicalID = queryData.query.canonicalID;
|
|
|
|
|
std::string indexKey =
|
2018-02-13 11:12:17 -08:00
|
|
|
[FSTLevelDBQueryTargetKey keyWithCanonicalID:canonicalID targetID:queryData.targetID];
|
2017-10-03 08:55:22 -07:00
|
|
|
std::string emptyBuffer;
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put(indexKey, emptyBuffer);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
2018-02-13 11:12:17 -08:00
|
|
|
self.metadata.targetCount += 1;
|
|
|
|
|
[self updateMetadataForQueryData:queryData];
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
|
2018-02-13 11:12:17 -08:00
|
|
|
}
|
2018-01-17 12:23:45 -08:00
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
- (void)updateQueryData:(FSTQueryData *)queryData {
|
|
|
|
|
[self saveQueryData:queryData];
|
2018-01-17 12:23:45 -08:00
|
|
|
|
2018-02-13 11:12:17 -08:00
|
|
|
if ([self updateMetadataForQueryData:queryData]) {
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
- (void)removeQueryData:(FSTQueryData *)queryData {
|
2017-10-03 08:55:22 -07:00
|
|
|
FSTTargetID targetID = queryData.targetID;
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
[self removeMatchingKeysForTargetID:targetID];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Delete(key);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
std::string indexKey =
|
|
|
|
|
[FSTLevelDBQueryTargetKey keyWithCanonicalID:queryData.query.canonicalID targetID:targetID];
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Delete(indexKey);
|
2018-02-13 11:12:17 -08:00
|
|
|
self.metadata.targetCount -= 1;
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
|
2018-02-13 11:12:17 -08:00
|
|
|
}
|
|
|
|
|
|
2018-07-27 13:58:11 -07:00
|
|
|
- (int)removeQueriesThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
|
|
|
|
|
liveQueries:(NSDictionary<NSNumber *, FSTQueryData *> *)liveQueries {
|
|
|
|
|
int count = 0;
|
|
|
|
|
std::string targetPrefix = [FSTLevelDBTargetKey keyPrefix];
|
|
|
|
|
auto it = _db.currentTransaction->NewIterator();
|
|
|
|
|
it->Seek(targetPrefix);
|
|
|
|
|
for (; it->Valid() && absl::StartsWith(it->key(), targetPrefix); it->Next()) {
|
|
|
|
|
FSTQueryData *queryData = [self decodedTarget:it->value()];
|
|
|
|
|
if (queryData.sequenceNumber <= sequenceNumber && !liveQueries[@(queryData.targetID)]) {
|
|
|
|
|
[self removeQueryData:queryData];
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-13 11:12:17 -08:00
|
|
|
- (int32_t)count {
|
|
|
|
|
return self.metadata.targetCount;
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the given bytes as an FSTPBTarget protocol buffer and then converts to the equivalent
|
|
|
|
|
* query data.
|
|
|
|
|
*/
|
2018-07-27 13:58:11 -07:00
|
|
|
- (FSTQueryData *)decodedTarget:(absl::string_view)encoded {
|
2018-03-22 17:31:16 -07:00
|
|
|
NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data()
|
|
|
|
|
length:encoded.size()
|
|
|
|
|
freeWhenDone:NO];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
|
FSTPBTarget *proto = [FSTPBTarget parseFromData:data error:&error];
|
|
|
|
|
if (!proto) {
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL("FSTPBTarget failed to parse: %s", error);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2018-03-22 17:31:16 -07:00
|
|
|
auto indexItererator = _db.currentTransaction->NewIterator();
|
2017-10-03 08:55:22 -07:00
|
|
|
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];
|
2018-03-22 17:31:16 -07:00
|
|
|
auto targetIterator = _db.currentTransaction->NewIterator();
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
FSTLevelDBQueryTargetKey *rowKey = [[FSTLevelDBQueryTargetKey alloc] init];
|
|
|
|
|
for (; indexItererator->Valid(); indexItererator->Next()) {
|
|
|
|
|
// Only consider rows matching exactly the specific canonicalID of interest.
|
2018-03-22 17:31:16 -07:00
|
|
|
if (!absl::StartsWith(indexItererator->key(), indexPrefix) ||
|
|
|
|
|
![rowKey decodeKey:indexItererator->key()] || canonicalID != rowKey.canonicalID) {
|
2017-10-03 08:55:22 -07:00
|
|
|
// 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()];
|
|
|
|
|
}
|
2018-05-24 11:00:23 -07:00
|
|
|
HARD_FAIL(
|
|
|
|
|
"Dangling query-target reference found: "
|
|
|
|
|
"%s points to %s; seeking there found %s",
|
2018-03-22 17:31:16 -07:00
|
|
|
[FSTLevelDBKey descriptionForKey:indexItererator->key()],
|
|
|
|
|
[FSTLevelDBKey descriptionForKey:targetKey], foundKeyDescription);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finally after finding a potential match, check that the query is actually equal to the
|
|
|
|
|
// requested query.
|
2018-07-27 13:58:11 -07:00
|
|
|
FSTQueryData *target = [self decodedTarget:targetIterator->value()];
|
2017-10-03 08:55:22 -07:00
|
|
|
if ([target.query isEqual:query]) {
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark Matching Key tracking
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
- (void)addMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
|
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;
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
for (const DocumentKey &key : keys) {
|
2018-03-29 08:55:10 -07:00
|
|
|
self->_db.currentTransaction->Put(
|
2018-05-05 08:10:51 -07:00
|
|
|
[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key], emptyBuffer);
|
2018-03-29 08:55:10 -07:00
|
|
|
self->_db.currentTransaction->Put(
|
2018-05-05 08:10:51 -07:00
|
|
|
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID], emptyBuffer);
|
2018-07-27 13:58:11 -07:00
|
|
|
[self->_db.referenceDelegate addReference:key];
|
2018-05-08 10:49:54 -07:00
|
|
|
};
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
- (void)removeMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
|
|
|
|
|
for (const DocumentKey &key : keys) {
|
2018-03-29 08:55:10 -07:00
|
|
|
self->_db.currentTransaction->Delete(
|
|
|
|
|
[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]);
|
|
|
|
|
self->_db.currentTransaction->Delete(
|
|
|
|
|
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]);
|
2018-07-27 13:58:11 -07:00
|
|
|
[self->_db.referenceDelegate removeReference:key];
|
2018-05-05 08:10:51 -07:00
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
|
2017-10-03 08:55:22 -07:00
|
|
|
std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
|
2018-03-22 17:31:16 -07:00
|
|
|
auto indexIterator = _db.currentTransaction->NewIterator();
|
2017-10-03 08:55:22 -07:00
|
|
|
indexIterator->Seek(indexPrefix);
|
|
|
|
|
|
|
|
|
|
FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
|
|
|
|
|
for (; indexIterator->Valid(); indexIterator->Next()) {
|
2018-03-22 17:31:16 -07:00
|
|
|
absl::string_view indexKey = indexIterator->key();
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
// Only consider rows matching this specific targetID.
|
|
|
|
|
if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-03-23 17:52:01 -04:00
|
|
|
const DocumentKey &documentKey = rowKey.documentKey;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
// Delete both index rows
|
2018-03-29 08:55:10 -07:00
|
|
|
_db.currentTransaction->Delete(indexKey);
|
|
|
|
|
_db.currentTransaction->Delete(
|
|
|
|
|
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID]);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
- (DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID {
|
2017-10-03 08:55:22 -07:00
|
|
|
std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
|
2018-03-22 17:31:16 -07:00
|
|
|
auto indexIterator = _db.currentTransaction->NewIterator();
|
2017-10-03 08:55:22 -07:00
|
|
|
indexIterator->Seek(indexPrefix);
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
DocumentKeySet result;
|
2017-10-03 08:55:22 -07:00
|
|
|
FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
|
|
|
|
|
for (; indexIterator->Valid(); indexIterator->Next()) {
|
2018-03-22 17:31:16 -07:00
|
|
|
absl::string_view indexKey = indexIterator->key();
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
// Only consider rows matching this specific targetID.
|
|
|
|
|
if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
result = result.insert(rowKey.documentKey);
|
2017-10-03 08:55:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-23 17:52:01 -04:00
|
|
|
- (BOOL)containsKey:(const DocumentKey &)key {
|
2018-07-27 13:58:11 -07:00
|
|
|
// ignore sentinel rows when determining if a key belongs to a target. Sentinel row just says the
|
|
|
|
|
// document exists, not that it's a member of any particular target.
|
2018-03-23 17:52:01 -04:00
|
|
|
std::string indexPrefix = [FSTLevelDBDocumentTargetKey keyPrefixWithResourcePath:key.path()];
|
2018-03-22 17:31:16 -07:00
|
|
|
auto indexIterator = _db.currentTransaction->NewIterator();
|
2017-10-03 08:55:22 -07:00
|
|
|
indexIterator->Seek(indexPrefix);
|
|
|
|
|
|
2018-07-27 13:58:11 -07:00
|
|
|
for (; indexIterator->Valid() && absl::StartsWith(indexIterator->key(), indexPrefix);
|
|
|
|
|
indexIterator->Next()) {
|
2017-10-03 08:55:22 -07:00
|
|
|
FSTLevelDBDocumentTargetKey *rowKey = [[FSTLevelDBDocumentTargetKey alloc] init];
|
2018-07-27 13:58:11 -07:00
|
|
|
if ([rowKey decodeKey:indexIterator->key()] && !FSTTargetIDIsSentinel(rowKey.targetID) &&
|
|
|
|
|
DocumentKey{rowKey.documentKey} == key) {
|
2017-10-03 08:55:22 -07:00
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|