Files
firebase-ios-sdk/FirebaseDatabase/Sources/Persistence/FTrackedQueryManager.m
T

375 lines
14 KiB
Objective-C
Raw Normal View History

2017-05-15 12:27:07 -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.
*/
2020-06-22 07:58:52 -07:00
#import "FirebaseDatabase/Sources/Persistence/FTrackedQueryManager.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
2020-06-22 07:58:52 -07:00
#import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
#import "FirebaseDatabase/Sources/FClock.h"
#import "FirebaseDatabase/Sources/Persistence/FCachePolicy.h"
#import "FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.h"
#import "FirebaseDatabase/Sources/Persistence/FPruneForest.h"
#import "FirebaseDatabase/Sources/Persistence/FTrackedQuery.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
2017-05-15 12:27:07 -07:00
@interface FTrackedQueryManager ()
2019-08-05 13:27:47 -07:00
@property(nonatomic, strong) FImmutableTree *trackedQueryTree;
@property(nonatomic, strong) id<FStorageEngine> storageEngine;
@property(nonatomic, strong) id<FClock> clock;
@property(nonatomic) NSUInteger currentQueryId;
2017-05-15 12:27:07 -07:00
@end
@implementation FTrackedQueryManager
2019-08-05 13:27:47 -07:00
- (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine
clock:(id<FClock>)clock {
2017-05-15 12:27:07 -07:00
self = [super init];
if (self != nil) {
self->_storageEngine = storageEngine;
self->_clock = clock;
self->_trackedQueryTree = [FImmutableTree empty];
NSTimeInterval lastUse = [clock currentTime];
NSArray *trackedQueries = [self.storageEngine loadTrackedQueries];
2019-08-05 13:27:47 -07:00
[trackedQueries enumerateObjectsUsingBlock:^(
FTrackedQuery *trackedQuery, NSUInteger idx,
BOOL *stop) {
self.currentQueryId =
MAX(trackedQuery.queryId + 1, self.currentQueryId);
if (trackedQuery.isActive) {
trackedQuery =
[[trackedQuery setActiveState:NO] updateLastUse:lastUse];
FFDebug(
@"I-RDB081001",
@"Setting active query %lu from previous app start inactive",
(unsigned long)trackedQuery.queryId);
[self.storageEngine saveTrackedQuery:trackedQuery];
}
[self cacheTrackedQuery:trackedQuery];
2017-05-15 12:27:07 -07:00
}];
}
return self;
}
+ (void)assertValidTrackedQuery:(FQuerySpec *)query {
2019-08-05 13:27:47 -07:00
NSAssert(!query.loadsAllData || query.isDefault,
@"Can't have tracked non-default query that loads all data");
2017-05-15 12:27:07 -07:00
}
+ (FQuerySpec *)normalizeQuery:(FQuerySpec *)query {
2019-08-05 13:27:47 -07:00
return query.loadsAllData ? [FQuerySpec defaultQueryAtPath:query.path]
: query;
2017-05-15 12:27:07 -07:00
}
- (FTrackedQuery *)findTrackedQuery:(FQuerySpec *)query {
query = [FTrackedQueryManager normalizeQuery:query];
NSDictionary *set = [self.trackedQueryTree valueAtPath:query.path];
return set[query.params];
}
- (void)removeTrackedQuery:(FQuerySpec *)query {
query = [FTrackedQueryManager normalizeQuery:query];
FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
NSAssert(trackedQuery, @"Tracked query must exist to be removed!");
[self.storageEngine removeTrackedQuery:trackedQuery.queryId];
2019-08-05 13:27:47 -07:00
NSMutableDictionary *trackedQueries =
[self.trackedQueryTree valueAtPath:query.path];
2017-05-15 12:27:07 -07:00
[trackedQueries removeObjectForKey:query.params];
}
- (void)setQueryActive:(FQuerySpec *)query {
[self setQueryActive:YES forQuery:query];
}
- (void)setQueryInactive:(FQuerySpec *)query {
[self setQueryActive:NO forQuery:query];
}
- (void)setQueryActive:(BOOL)isActive forQuery:(FQuerySpec *)query {
query = [FTrackedQueryManager normalizeQuery:query];
FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
2019-08-05 13:27:47 -07:00
// Regardless of whether it's now active or no langer active, we update the
// lastUse time
2017-05-15 12:27:07 -07:00
NSTimeInterval lastUse = [self.clock currentTime];
if (trackedQuery != nil) {
2019-08-05 13:27:47 -07:00
trackedQuery =
[[trackedQuery updateLastUse:lastUse] setActiveState:isActive];
2017-05-15 12:27:07 -07:00
[self.storageEngine saveTrackedQuery:trackedQuery];
} else {
2019-08-05 13:27:47 -07:00
NSAssert(isActive, @"If we're setting the query to inactive, we should "
@"already be tracking it!");
2017-05-15 12:27:07 -07:00
trackedQuery = [[FTrackedQuery alloc] initWithId:self.currentQueryId++
query:query
lastUse:lastUse
isActive:isActive];
[self.storageEngine saveTrackedQuery:trackedQuery];
}
[self cacheTrackedQuery:trackedQuery];
}
- (void)setQueryComplete:(FQuerySpec *)query {
query = [FTrackedQueryManager normalizeQuery:query];
FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
if (!trackedQuery) {
2019-08-05 13:27:47 -07:00
// We might have removed a query and pruned it before we got the
// complete message from the server...
FFWarn(@"I-RDB081002",
@"Trying to set a query complete that is not tracked!");
2017-05-15 12:27:07 -07:00
} else if (!trackedQuery.isComplete) {
trackedQuery = [trackedQuery setComplete];
[self.storageEngine saveTrackedQuery:trackedQuery];
[self cacheTrackedQuery:trackedQuery];
} else {
// Nothing to do, already marked complete
}
}
- (void)setQueriesCompleteAtPath:(FPath *)path {
2019-08-05 13:27:47 -07:00
[[self.trackedQueryTree subtreeAtPath:path]
forEach:^(FPath *childPath, NSDictionary *trackedQueries) {
[trackedQueries enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *parms, FTrackedQuery *trackedQuery,
BOOL *stop) {
2017-05-15 12:27:07 -07:00
if (!trackedQuery.isComplete) {
FTrackedQuery *newTrackedQuery = [trackedQuery setComplete];
[self.storageEngine saveTrackedQuery:newTrackedQuery];
[self cacheTrackedQuery:newTrackedQuery];
}
2019-08-05 13:27:47 -07:00
}];
2017-05-15 12:27:07 -07:00
}];
}
- (BOOL)isQueryComplete:(FQuerySpec *)query {
if ([self isIncludedInDefaultCompleteQuery:query]) {
return YES;
} else if (query.loadsAllData) {
// We didn't find a default complete query, so must not be complete.
return NO;
} else {
2019-08-05 13:27:47 -07:00
NSDictionary *trackedQueries =
[self.trackedQueryTree valueAtPath:query.path];
2017-05-15 12:27:07 -07:00
return [trackedQueries[query.params] isComplete];
}
}
- (BOOL)hasActiveDefaultQueryAtPath:(FPath *)path {
2019-08-05 13:27:47 -07:00
return [self.trackedQueryTree
rootMostValueOnPath:path
matching:^BOOL(NSDictionary *trackedQueries) {
return
[trackedQueries[[FQueryParams defaultInstance]]
isActive];
}] != nil;
2017-05-15 12:27:07 -07:00
}
- (void)ensureCompleteTrackedQueryAtPath:(FPath *)path {
FQuerySpec *query = [FQuerySpec defaultQueryAtPath:path];
if (![self isIncludedInDefaultCompleteQuery:query]) {
FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
if (trackedQuery == nil) {
2019-08-05 13:27:47 -07:00
trackedQuery =
[[FTrackedQuery alloc] initWithId:self.currentQueryId++
query:query
lastUse:[self.clock currentTime]
isActive:NO
isComplete:YES];
2017-05-15 12:27:07 -07:00
} else {
2019-08-05 13:27:47 -07:00
NSAssert(!trackedQuery.isComplete,
@"This should have been handled above!");
2017-05-15 12:27:07 -07:00
trackedQuery = [trackedQuery setComplete];
}
[self.storageEngine saveTrackedQuery:trackedQuery];
[self cacheTrackedQuery:trackedQuery];
}
}
- (BOOL)isIncludedInDefaultCompleteQuery:(FQuerySpec *)query {
2019-08-05 13:27:47 -07:00
return
[self.trackedQueryTree
findRootMostMatchingPath:query.path
predicate:^BOOL(NSDictionary *trackedQueries) {
return
[trackedQueries[[FQueryParams defaultInstance]]
isComplete];
}] != nil;
2017-05-15 12:27:07 -07:00
}
- (void)cacheTrackedQuery:(FTrackedQuery *)query {
[FTrackedQueryManager assertValidTrackedQuery:query.query];
2019-08-05 13:27:47 -07:00
NSMutableDictionary *trackedDict =
[self.trackedQueryTree valueAtPath:query.query.path];
2017-05-15 12:27:07 -07:00
if (trackedDict == nil) {
trackedDict = [NSMutableDictionary dictionary];
2019-08-05 13:27:47 -07:00
self.trackedQueryTree =
[self.trackedQueryTree setValue:trackedDict
atPath:query.query.path];
2017-05-15 12:27:07 -07:00
}
trackedDict[query.query.params] = query;
}
2019-08-05 13:27:47 -07:00
- (NSUInteger)numberOfQueriesToPrune:(id<FCachePolicy>)cachePolicy
prunableCount:(NSUInteger)numPrunable {
NSUInteger numPercent = (NSUInteger)ceilf(
numPrunable * [cachePolicy percentOfQueriesToPruneAtOnce]);
2017-05-15 12:27:07 -07:00
NSUInteger maxToKeep = [cachePolicy maxNumberOfQueriesToKeep];
NSUInteger numMax = (numPrunable > maxToKeep) ? numPrunable - maxToKeep : 0;
// Make sure we get below number of max queries to prune
return MAX(numMax, numPercent);
}
- (FPruneForest *)pruneOldQueries:(id<FCachePolicy>)cachePolicy {
NSMutableArray *pruneableQueries = [NSMutableArray array];
NSMutableArray *unpruneableQueries = [NSMutableArray array];
2019-08-05 13:27:47 -07:00
[self.trackedQueryTree
forEach:^(FPath *path, NSDictionary *trackedQueries) {
[trackedQueries enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *params, FTrackedQuery *trackedQuery,
BOOL *stop) {
2017-05-15 12:27:07 -07:00
if (!trackedQuery.isActive) {
[pruneableQueries addObject:trackedQuery];
} else {
[unpruneableQueries addObject:trackedQuery];
}
2019-08-05 13:27:47 -07:00
}];
2017-05-15 12:27:07 -07:00
}];
2019-08-05 13:27:47 -07:00
[pruneableQueries sortUsingComparator:^NSComparisonResult(
FTrackedQuery *q1, FTrackedQuery *q2) {
if (q1.lastUse < q2.lastUse) {
return NSOrderedAscending;
} else if (q1.lastUse > q2.lastUse) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
2017-05-15 12:27:07 -07:00
}];
__block FPruneForest *pruneForest = [FPruneForest empty];
2019-08-05 13:27:47 -07:00
NSUInteger numToPrune =
[self numberOfQueriesToPrune:cachePolicy
prunableCount:pruneableQueries.count];
2017-05-15 12:27:07 -07:00
// TODO: do in transaction
for (NSUInteger i = 0; i < numToPrune; i++) {
FTrackedQuery *toPrune = pruneableQueries[i];
pruneForest = [pruneForest prunePath:toPrune.query.path];
[self removeTrackedQuery:toPrune.query];
}
// Keep the rest of the prunable queries
for (NSUInteger i = numToPrune; i < pruneableQueries.count; i++) {
FTrackedQuery *toKeep = pruneableQueries[i];
pruneForest = [pruneForest keepPath:toKeep.query.path];
}
// Also keep unprunable queries
2019-08-05 13:27:47 -07:00
[unpruneableQueries enumerateObjectsUsingBlock:^(
FTrackedQuery *toKeep, NSUInteger idx, BOOL *stop) {
pruneForest = [pruneForest keepPath:toKeep.query.path];
2017-05-15 12:27:07 -07:00
}];
return pruneForest;
}
- (NSUInteger)numberOfPrunableQueries {
__block NSUInteger count = 0;
2019-08-05 13:27:47 -07:00
[self.trackedQueryTree
forEach:^(FPath *path, NSDictionary *trackedQueries) {
[trackedQueries enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *params, FTrackedQuery *trackedQuery,
BOOL *stop) {
2017-05-15 12:27:07 -07:00
if (!trackedQuery.isActive) {
count++;
}
2019-08-05 13:27:47 -07:00
}];
2017-05-15 12:27:07 -07:00
}];
return count;
}
- (NSSet *)filteredQueryIdsAtPath:(FPath *)path {
NSDictionary *queries = [self.trackedQueryTree valueAtPath:path];
if (queries) {
NSMutableSet *ids = [NSMutableSet set];
2019-08-05 13:27:47 -07:00
[queries enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *params, FTrackedQuery *query, BOOL *stop) {
if (!query.query.loadsAllData) {
[ids addObject:@(query.queryId)];
}
2017-05-15 12:27:07 -07:00
}];
return ids;
} else {
return [NSSet set];
}
}
- (NSSet *)knownCompleteChildrenAtPath:(FPath *)path {
2019-08-05 13:27:47 -07:00
NSAssert(![self isQueryComplete:[FQuerySpec defaultQueryAtPath:path]],
@"Path is fully complete");
2017-05-15 12:27:07 -07:00
NSMutableSet *completeChildren = [NSMutableSet set];
// First, get complete children from any queries at this location.
NSSet *queryIds = [self filteredQueryIdsAtPath:path];
[queryIds enumerateObjectsUsingBlock:^(NSNumber *queryId, BOOL *stop) {
2019-08-05 13:27:47 -07:00
NSSet *keys = [self.storageEngine
trackedQueryKeysForQuery:[queryId unsignedIntegerValue]];
[completeChildren unionSet:keys];
2017-05-15 12:27:07 -07:00
}];
// Second, get any complete default queries immediately below us.
2019-08-05 13:27:47 -07:00
[[[self.trackedQueryTree subtreeAtPath:path] children]
enumerateKeysAndObjectsUsingBlock:^(
NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
if ([childTree.value[[FQueryParams defaultInstance]] isComplete]) {
[completeChildren addObject:childKey];
}
}];
2017-05-15 12:27:07 -07:00
return completeChildren;
}
- (void)verifyCache {
NSArray *storedTrackedQueries = [self.storageEngine loadTrackedQueries];
NSMutableArray *trackedQueries = [NSMutableArray array];
[self.trackedQueryTree forEach:^(FPath *path, NSDictionary *queryDict) {
2019-08-05 13:27:47 -07:00
[trackedQueries addObjectsFromArray:queryDict.allValues];
2017-05-15 12:27:07 -07:00
}];
2019-08-05 13:27:47 -07:00
NSComparator comparator =
^NSComparisonResult(FTrackedQuery *q1, FTrackedQuery *q2) {
if (q1.queryId < q2.queryId) {
return NSOrderedAscending;
} else if (q1.queryId > q2.queryId) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
};
2017-05-15 12:27:07 -07:00
[trackedQueries sortUsingComparator:comparator];
2019-08-05 13:27:47 -07:00
storedTrackedQueries =
[storedTrackedQueries sortedArrayUsingComparator:comparator];
2017-05-15 12:27:07 -07:00
if (![trackedQueries isEqualToArray:storedTrackedQueries]) {
2019-08-05 13:27:47 -07:00
[NSException
raise:NSInternalInconsistencyException
format:@"Tracked queries and queries stored on disk don't match"];
2017-05-15 12:27:07 -07:00
}
}
@end