FMockStorageEngine.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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/Tests/Helpers/FMockStorageEngine.h"
  17. #import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
  18. #import "FirebaseDatabase/Sources/Persistence/FPruneForest.h"
  19. #import "FirebaseDatabase/Sources/Persistence/FTrackedQuery.h"
  20. #import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
  21. #import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
  22. #import "FirebaseDatabase/Sources/Snapshot/FNode.h"
  23. @interface FMockStorageEngine ()
  24. @property(nonatomic) BOOL closed;
  25. @property(nonatomic, strong) NSMutableDictionary *userWritesDict;
  26. @property(nonatomic, strong) FCompoundWrite *serverCache;
  27. @property(nonatomic, strong) NSMutableDictionary *trackedQueries;
  28. @property(nonatomic, strong) NSMutableDictionary *trackedQueryKeys;
  29. @end
  30. @implementation FMockStorageEngine
  31. - (id)init {
  32. self = [super init];
  33. if (self != nil) {
  34. self->_userWritesDict = [NSMutableDictionary dictionary];
  35. self->_serverCache = [FCompoundWrite emptyWrite];
  36. self->_trackedQueries = [NSMutableDictionary dictionary];
  37. self->_trackedQueryKeys = [NSMutableDictionary dictionary];
  38. }
  39. return self;
  40. }
  41. - (void)close {
  42. self.closed = YES;
  43. }
  44. - (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId {
  45. FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path
  46. overwrite:node
  47. writeId:writeId
  48. visible:YES];
  49. self.userWritesDict[@(writeId)] = writeRecord;
  50. }
  51. - (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId {
  52. FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path merge:merge writeId:writeId];
  53. self.userWritesDict[@(writeId)] = writeRecord;
  54. }
  55. - (void)removeUserWrite:(NSUInteger)writeId {
  56. [self.userWritesDict removeObjectForKey:@(writeId)];
  57. }
  58. - (void)removeAllUserWrites {
  59. [self.userWritesDict removeAllObjects];
  60. }
  61. - (NSArray *)userWrites {
  62. return [[self.userWritesDict allValues]
  63. sortedArrayUsingComparator:^NSComparisonResult(FWriteRecord *obj1, FWriteRecord *obj2) {
  64. if (obj1.writeId < obj2.writeId) {
  65. return NSOrderedAscending;
  66. } else if (obj1.writeId > obj2.writeId) {
  67. return NSOrderedDescending;
  68. } else {
  69. return NSOrderedSame;
  70. }
  71. }];
  72. }
  73. - (id<FNode>)serverCacheAtPath:(FPath *)path {
  74. return [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
  75. }
  76. - (id<FNode>)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path {
  77. __block id<FNode> children = [FEmptyNode emptyNode];
  78. id<FNode> fullNode =
  79. [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
  80. [keys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
  81. children = [children updateImmediateChild:key withNewChild:[fullNode getImmediateChild:key]];
  82. }];
  83. return children;
  84. }
  85. - (void)updateServerCache:(id<FNode>)node atPath:(FPath *)path merge:(BOOL)merge {
  86. if (merge) {
  87. [node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> childNode, BOOL *stop) {
  88. self.serverCache = [self.serverCache addWrite:childNode atPath:[path childFromString:key]];
  89. }];
  90. } else {
  91. self.serverCache = [self.serverCache addWrite:node atPath:path];
  92. }
  93. }
  94. - (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path {
  95. self.serverCache = [self.serverCache addCompoundWrite:merge atPath:path];
  96. }
  97. - (NSUInteger)serverCacheEstimatedSizeInBytes {
  98. id data = [[self.serverCache applyToNode:[FEmptyNode emptyNode]] valForExport:YES];
  99. return [NSJSONSerialization dataWithJSONObject:data options:0 error:nil].length;
  100. }
  101. - (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)prunePath {
  102. [self.serverCache enumerateWrites:^(FPath *absolutePath, id<FNode> node, BOOL *stop) {
  103. NSAssert([prunePath isEqual:absolutePath] || ![absolutePath contains:prunePath],
  104. @"Pruning at %@ but we found data higher up!", prunePath);
  105. if ([prunePath contains:absolutePath]) {
  106. FPath *relativePath = [FPath relativePathFrom:prunePath to:absolutePath];
  107. if ([pruneForest shouldPruneUnkeptDescendantsAtPath:relativePath]) {
  108. __block FCompoundWrite *newCache = [FCompoundWrite emptyWrite];
  109. [[pruneForest childAtPath:relativePath] enumarateKeptNodesUsingBlock:^(FPath *keepPath) {
  110. newCache = [newCache addWrite:[node getChild:keepPath] atPath:keepPath];
  111. }];
  112. self.serverCache =
  113. [[self.serverCache removeWriteAtPath:absolutePath] addCompoundWrite:newCache
  114. atPath:absolutePath];
  115. } else {
  116. // NOTE: This is technically a valid scenario (e.g. you ask to prune at / but only want to
  117. // prune 'foo' and 'bar' and ignore everything else). But currently our pruning will
  118. // explicitly prune or keep everything we know about, so if we hit this it means our tracked
  119. // queries and the server cache are out of sync.
  120. NSAssert([pruneForest shouldKeepPath:relativePath],
  121. @"We have data at %@ that is neither pruned nor kept.", relativePath);
  122. }
  123. }
  124. }];
  125. }
  126. - (NSArray *)loadTrackedQueries {
  127. return self.trackedQueries.allValues;
  128. }
  129. - (void)removeTrackedQuery:(NSUInteger)queryId {
  130. [self.trackedQueries removeObjectForKey:@(queryId)];
  131. [self.trackedQueryKeys removeObjectForKey:@(queryId)];
  132. }
  133. - (void)saveTrackedQuery:(FTrackedQuery *)query {
  134. self.trackedQueries[@(query.queryId)] = query;
  135. }
  136. - (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId {
  137. self.trackedQueryKeys[@(queryId)] = keys;
  138. }
  139. - (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added
  140. removedKeys:(NSSet *)removed
  141. forQueryId:(NSUInteger)queryId {
  142. NSSet *oldKeys = [self trackedQueryKeysForQuery:queryId];
  143. NSMutableSet *newKeys = [NSMutableSet setWithSet:oldKeys];
  144. [newKeys minusSet:removed];
  145. [newKeys unionSet:added];
  146. self.trackedQueryKeys[@(queryId)] = newKeys;
  147. }
  148. - (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId {
  149. NSSet *keys = self.trackedQueryKeys[@(queryId)];
  150. return keys != nil ? keys : [NSSet set];
  151. }
  152. @end