FTrackedQueryManager.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseDatabase/Sources/Persistence/FTrackedQueryManager.h"
  17. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  18. #import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
  19. #import "FirebaseDatabase/Sources/FClock.h"
  20. #import "FirebaseDatabase/Sources/Persistence/FCachePolicy.h"
  21. #import "FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.h"
  22. #import "FirebaseDatabase/Sources/Persistence/FPruneForest.h"
  23. #import "FirebaseDatabase/Sources/Persistence/FTrackedQuery.h"
  24. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  25. @interface FTrackedQueryManager ()
  26. @property(nonatomic, strong) FImmutableTree *trackedQueryTree;
  27. @property(nonatomic, strong) id<FStorageEngine> storageEngine;
  28. @property(nonatomic, strong) id<FClock> clock;
  29. @property(nonatomic) NSUInteger currentQueryId;
  30. @end
  31. @implementation FTrackedQueryManager
  32. - (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine
  33. clock:(id<FClock>)clock {
  34. self = [super init];
  35. if (self != nil) {
  36. self->_storageEngine = storageEngine;
  37. self->_clock = clock;
  38. self->_trackedQueryTree = [FImmutableTree empty];
  39. NSTimeInterval lastUse = [clock currentTime];
  40. NSArray *trackedQueries = [self.storageEngine loadTrackedQueries];
  41. [trackedQueries enumerateObjectsUsingBlock:^(
  42. FTrackedQuery *trackedQuery, NSUInteger idx,
  43. BOOL *stop) {
  44. self.currentQueryId =
  45. MAX(trackedQuery.queryId + 1, self.currentQueryId);
  46. if (trackedQuery.isActive) {
  47. trackedQuery =
  48. [[trackedQuery setActiveState:NO] updateLastUse:lastUse];
  49. FFDebug(
  50. @"I-RDB081001",
  51. @"Setting active query %lu from previous app start inactive",
  52. (unsigned long)trackedQuery.queryId);
  53. [self.storageEngine saveTrackedQuery:trackedQuery];
  54. }
  55. [self cacheTrackedQuery:trackedQuery];
  56. }];
  57. }
  58. return self;
  59. }
  60. + (void)assertValidTrackedQuery:(FQuerySpec *)query {
  61. NSAssert(!query.loadsAllData || query.isDefault,
  62. @"Can't have tracked non-default query that loads all data");
  63. }
  64. + (FQuerySpec *)normalizeQuery:(FQuerySpec *)query {
  65. return query.loadsAllData ? [FQuerySpec defaultQueryAtPath:query.path]
  66. : query;
  67. }
  68. - (FTrackedQuery *)findTrackedQuery:(FQuerySpec *)query {
  69. query = [FTrackedQueryManager normalizeQuery:query];
  70. NSDictionary *set = [self.trackedQueryTree valueAtPath:query.path];
  71. return set[query.params];
  72. }
  73. - (void)removeTrackedQuery:(FQuerySpec *)query {
  74. query = [FTrackedQueryManager normalizeQuery:query];
  75. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  76. NSAssert(trackedQuery, @"Tracked query must exist to be removed!");
  77. [self.storageEngine removeTrackedQuery:trackedQuery.queryId];
  78. NSMutableDictionary *trackedQueries =
  79. [self.trackedQueryTree valueAtPath:query.path];
  80. [trackedQueries removeObjectForKey:query.params];
  81. }
  82. - (void)setQueryActive:(FQuerySpec *)query {
  83. [self setQueryActive:YES forQuery:query];
  84. }
  85. - (void)setQueryInactive:(FQuerySpec *)query {
  86. [self setQueryActive:NO forQuery:query];
  87. }
  88. - (void)setQueryActive:(BOOL)isActive forQuery:(FQuerySpec *)query {
  89. query = [FTrackedQueryManager normalizeQuery:query];
  90. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  91. // Regardless of whether it's now active or no langer active, we update the
  92. // lastUse time
  93. NSTimeInterval lastUse = [self.clock currentTime];
  94. if (trackedQuery != nil) {
  95. trackedQuery =
  96. [[trackedQuery updateLastUse:lastUse] setActiveState:isActive];
  97. [self.storageEngine saveTrackedQuery:trackedQuery];
  98. } else {
  99. NSAssert(isActive, @"If we're setting the query to inactive, we should "
  100. @"already be tracking it!");
  101. trackedQuery = [[FTrackedQuery alloc] initWithId:self.currentQueryId++
  102. query:query
  103. lastUse:lastUse
  104. isActive:isActive];
  105. [self.storageEngine saveTrackedQuery:trackedQuery];
  106. }
  107. [self cacheTrackedQuery:trackedQuery];
  108. }
  109. - (void)setQueryComplete:(FQuerySpec *)query {
  110. query = [FTrackedQueryManager normalizeQuery:query];
  111. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  112. if (!trackedQuery) {
  113. // We might have removed a query and pruned it before we got the
  114. // complete message from the server...
  115. FFWarn(@"I-RDB081002",
  116. @"Trying to set a query complete that is not tracked!");
  117. } else if (!trackedQuery.isComplete) {
  118. trackedQuery = [trackedQuery setComplete];
  119. [self.storageEngine saveTrackedQuery:trackedQuery];
  120. [self cacheTrackedQuery:trackedQuery];
  121. } else {
  122. // Nothing to do, already marked complete
  123. }
  124. }
  125. - (void)setQueriesCompleteAtPath:(FPath *)path {
  126. [[self.trackedQueryTree subtreeAtPath:path]
  127. forEach:^(FPath *childPath, NSDictionary *trackedQueries) {
  128. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(
  129. FQueryParams *parms, FTrackedQuery *trackedQuery,
  130. BOOL *stop) {
  131. if (!trackedQuery.isComplete) {
  132. FTrackedQuery *newTrackedQuery = [trackedQuery setComplete];
  133. [self.storageEngine saveTrackedQuery:newTrackedQuery];
  134. [self cacheTrackedQuery:newTrackedQuery];
  135. }
  136. }];
  137. }];
  138. }
  139. - (BOOL)isQueryComplete:(FQuerySpec *)query {
  140. if ([self isIncludedInDefaultCompleteQuery:query]) {
  141. return YES;
  142. } else if (query.loadsAllData) {
  143. // We didn't find a default complete query, so must not be complete.
  144. return NO;
  145. } else {
  146. NSDictionary *trackedQueries =
  147. [self.trackedQueryTree valueAtPath:query.path];
  148. return [trackedQueries[query.params] isComplete];
  149. }
  150. }
  151. - (BOOL)hasActiveDefaultQueryAtPath:(FPath *)path {
  152. return [self.trackedQueryTree
  153. rootMostValueOnPath:path
  154. matching:^BOOL(NSDictionary *trackedQueries) {
  155. return
  156. [trackedQueries[[FQueryParams defaultInstance]]
  157. isActive];
  158. }] != nil;
  159. }
  160. - (void)ensureCompleteTrackedQueryAtPath:(FPath *)path {
  161. FQuerySpec *query = [FQuerySpec defaultQueryAtPath:path];
  162. if (![self isIncludedInDefaultCompleteQuery:query]) {
  163. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  164. if (trackedQuery == nil) {
  165. trackedQuery =
  166. [[FTrackedQuery alloc] initWithId:self.currentQueryId++
  167. query:query
  168. lastUse:[self.clock currentTime]
  169. isActive:NO
  170. isComplete:YES];
  171. } else {
  172. NSAssert(!trackedQuery.isComplete,
  173. @"This should have been handled above!");
  174. trackedQuery = [trackedQuery setComplete];
  175. }
  176. [self.storageEngine saveTrackedQuery:trackedQuery];
  177. [self cacheTrackedQuery:trackedQuery];
  178. }
  179. }
  180. - (BOOL)isIncludedInDefaultCompleteQuery:(FQuerySpec *)query {
  181. return
  182. [self.trackedQueryTree
  183. findRootMostMatchingPath:query.path
  184. predicate:^BOOL(NSDictionary *trackedQueries) {
  185. return
  186. [trackedQueries[[FQueryParams defaultInstance]]
  187. isComplete];
  188. }] != nil;
  189. }
  190. - (void)cacheTrackedQuery:(FTrackedQuery *)query {
  191. [FTrackedQueryManager assertValidTrackedQuery:query.query];
  192. NSMutableDictionary *trackedDict =
  193. [self.trackedQueryTree valueAtPath:query.query.path];
  194. if (trackedDict == nil) {
  195. trackedDict = [NSMutableDictionary dictionary];
  196. self.trackedQueryTree =
  197. [self.trackedQueryTree setValue:trackedDict
  198. atPath:query.query.path];
  199. }
  200. trackedDict[query.query.params] = query;
  201. }
  202. - (NSUInteger)numberOfQueriesToPrune:(id<FCachePolicy>)cachePolicy
  203. prunableCount:(NSUInteger)numPrunable {
  204. NSUInteger numPercent = (NSUInteger)ceilf(
  205. numPrunable * [cachePolicy percentOfQueriesToPruneAtOnce]);
  206. NSUInteger maxToKeep = [cachePolicy maxNumberOfQueriesToKeep];
  207. NSUInteger numMax = (numPrunable > maxToKeep) ? numPrunable - maxToKeep : 0;
  208. // Make sure we get below number of max queries to prune
  209. return MAX(numMax, numPercent);
  210. }
  211. - (FPruneForest *)pruneOldQueries:(id<FCachePolicy>)cachePolicy {
  212. NSMutableArray *pruneableQueries = [NSMutableArray array];
  213. NSMutableArray *unpruneableQueries = [NSMutableArray array];
  214. [self.trackedQueryTree
  215. forEach:^(FPath *path, NSDictionary *trackedQueries) {
  216. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(
  217. FQueryParams *params, FTrackedQuery *trackedQuery,
  218. BOOL *stop) {
  219. if (!trackedQuery.isActive) {
  220. [pruneableQueries addObject:trackedQuery];
  221. } else {
  222. [unpruneableQueries addObject:trackedQuery];
  223. }
  224. }];
  225. }];
  226. [pruneableQueries sortUsingComparator:^NSComparisonResult(
  227. FTrackedQuery *q1, FTrackedQuery *q2) {
  228. if (q1.lastUse < q2.lastUse) {
  229. return NSOrderedAscending;
  230. } else if (q1.lastUse > q2.lastUse) {
  231. return NSOrderedDescending;
  232. } else {
  233. return NSOrderedSame;
  234. }
  235. }];
  236. __block FPruneForest *pruneForest = [FPruneForest empty];
  237. NSUInteger numToPrune =
  238. [self numberOfQueriesToPrune:cachePolicy
  239. prunableCount:pruneableQueries.count];
  240. // TODO: do in transaction
  241. for (NSUInteger i = 0; i < numToPrune; i++) {
  242. FTrackedQuery *toPrune = pruneableQueries[i];
  243. pruneForest = [pruneForest prunePath:toPrune.query.path];
  244. [self removeTrackedQuery:toPrune.query];
  245. }
  246. // Keep the rest of the prunable queries
  247. for (NSUInteger i = numToPrune; i < pruneableQueries.count; i++) {
  248. FTrackedQuery *toKeep = pruneableQueries[i];
  249. pruneForest = [pruneForest keepPath:toKeep.query.path];
  250. }
  251. // Also keep unprunable queries
  252. [unpruneableQueries enumerateObjectsUsingBlock:^(
  253. FTrackedQuery *toKeep, NSUInteger idx, BOOL *stop) {
  254. pruneForest = [pruneForest keepPath:toKeep.query.path];
  255. }];
  256. return pruneForest;
  257. }
  258. - (NSUInteger)numberOfPrunableQueries {
  259. __block NSUInteger count = 0;
  260. [self.trackedQueryTree
  261. forEach:^(FPath *path, NSDictionary *trackedQueries) {
  262. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(
  263. FQueryParams *params, FTrackedQuery *trackedQuery,
  264. BOOL *stop) {
  265. if (!trackedQuery.isActive) {
  266. count++;
  267. }
  268. }];
  269. }];
  270. return count;
  271. }
  272. - (NSSet *)filteredQueryIdsAtPath:(FPath *)path {
  273. NSDictionary *queries = [self.trackedQueryTree valueAtPath:path];
  274. if (queries) {
  275. NSMutableSet *ids = [NSMutableSet set];
  276. [queries enumerateKeysAndObjectsUsingBlock:^(
  277. FQueryParams *params, FTrackedQuery *query, BOOL *stop) {
  278. if (!query.query.loadsAllData) {
  279. [ids addObject:@(query.queryId)];
  280. }
  281. }];
  282. return ids;
  283. } else {
  284. return [NSSet set];
  285. }
  286. }
  287. - (NSSet *)knownCompleteChildrenAtPath:(FPath *)path {
  288. NSAssert(![self isQueryComplete:[FQuerySpec defaultQueryAtPath:path]],
  289. @"Path is fully complete");
  290. NSMutableSet *completeChildren = [NSMutableSet set];
  291. // First, get complete children from any queries at this location.
  292. NSSet *queryIds = [self filteredQueryIdsAtPath:path];
  293. [queryIds enumerateObjectsUsingBlock:^(NSNumber *queryId, BOOL *stop) {
  294. NSSet *keys = [self.storageEngine
  295. trackedQueryKeysForQuery:[queryId unsignedIntegerValue]];
  296. [completeChildren unionSet:keys];
  297. }];
  298. // Second, get any complete default queries immediately below us.
  299. [[[self.trackedQueryTree subtreeAtPath:path] children]
  300. enumerateKeysAndObjectsUsingBlock:^(
  301. NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
  302. if ([childTree.value[[FQueryParams defaultInstance]] isComplete]) {
  303. [completeChildren addObject:childKey];
  304. }
  305. }];
  306. return completeChildren;
  307. }
  308. - (void)verifyCache {
  309. NSArray *storedTrackedQueries = [self.storageEngine loadTrackedQueries];
  310. NSMutableArray *trackedQueries = [NSMutableArray array];
  311. [self.trackedQueryTree forEach:^(FPath *path, NSDictionary *queryDict) {
  312. [trackedQueries addObjectsFromArray:queryDict.allValues];
  313. }];
  314. NSComparator comparator =
  315. ^NSComparisonResult(FTrackedQuery *q1, FTrackedQuery *q2) {
  316. if (q1.queryId < q2.queryId) {
  317. return NSOrderedAscending;
  318. } else if (q1.queryId > q2.queryId) {
  319. return NSOrderedDescending;
  320. } else {
  321. return NSOrderedSame;
  322. }
  323. };
  324. [trackedQueries sortUsingComparator:comparator];
  325. storedTrackedQueries =
  326. [storedTrackedQueries sortedArrayUsingComparator:comparator];
  327. if (![trackedQueries isEqualToArray:storedTrackedQueries]) {
  328. [NSException
  329. raise:NSInternalInconsistencyException
  330. format:@"Tracked queries and queries stored on disk don't match"];
  331. }
  332. }
  333. @end