FSyncTree.m 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  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/Core/FSyncTree.h"
  17. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  18. #import "FirebaseDatabase/Sources/Core/FCompoundHash.h"
  19. #import "FirebaseDatabase/Sources/Core/FListenProvider.h"
  20. #import "FirebaseDatabase/Sources/Core/FQueryParams.h"
  21. #import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
  22. #import "FirebaseDatabase/Sources/Core/FRangeMerge.h"
  23. #import "FirebaseDatabase/Sources/Core/FServerValues.h"
  24. #import "FirebaseDatabase/Sources/Core/FSnapshotHolder.h"
  25. #import "FirebaseDatabase/Sources/Core/FSyncPoint.h"
  26. #import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
  27. #import "FirebaseDatabase/Sources/Core/FWriteTree.h"
  28. #import "FirebaseDatabase/Sources/Core/FWriteTreeRef.h"
  29. #import "FirebaseDatabase/Sources/Core/Operation/FAckUserWrite.h"
  30. #import "FirebaseDatabase/Sources/Core/Operation/FMerge.h"
  31. #import "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
  32. #import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
  33. #import "FirebaseDatabase/Sources/Core/Operation/FOverwrite.h"
  34. #import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
  35. #import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
  36. #import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
  37. #import "FirebaseDatabase/Sources/Core/View/FEventRaiser.h"
  38. #import "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
  39. #import "FirebaseDatabase/Sources/Core/View/FKeepSyncedEventRegistration.h"
  40. #import "FirebaseDatabase/Sources/Core/View/FView.h"
  41. #import "FirebaseDatabase/Sources/FListenComplete.h"
  42. #import "FirebaseDatabase/Sources/Persistence/FPersistenceManager.h"
  43. #import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
  44. #import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
  45. #import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
  46. #import "FirebaseDatabase/Sources/Snapshot/FNode.h"
  47. #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
  48. #import "FirebaseDatabase/Sources/Utilities/FAtomicNumber.h"
  49. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  50. #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleRemovedQueriesEvents.h"
  51. // Size after which we start including the compound hash
  52. static const NSUInteger kFSizeThresholdForCompoundHash = 1024;
  53. @interface FListenContainer : NSObject <FSyncTreeHash>
  54. @property(nonatomic, strong) FView *view;
  55. @property(nonatomic, copy) fbt_nsarray_nsstring onComplete;
  56. @end
  57. @implementation FListenContainer
  58. - (instancetype)initWithView:(FView *)view
  59. onComplete:(fbt_nsarray_nsstring)onComplete {
  60. self = [super init];
  61. if (self != nil) {
  62. self->_view = view;
  63. self->_onComplete = onComplete;
  64. }
  65. return self;
  66. }
  67. - (id<FNode>)serverCache {
  68. return self.view.serverCache;
  69. }
  70. - (FCompoundHash *)compoundHash {
  71. return [FCompoundHash fromNode:[self serverCache]];
  72. }
  73. - (NSString *)simpleHash {
  74. return [[self serverCache] dataHash];
  75. }
  76. - (BOOL)includeCompoundHash {
  77. return [FSnapshotUtilities estimateSerializedNodeSize:[self serverCache]] >
  78. kFSizeThresholdForCompoundHash;
  79. }
  80. @end
  81. @interface FSyncTree ()
  82. /**
  83. * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more
  84. * views.
  85. */
  86. @property(nonatomic, strong) FImmutableTree *syncPointTree;
  87. /**
  88. * A tree of all pending user writes (user-initiated set, transactions, updates,
  89. * etc)
  90. */
  91. @property(nonatomic, strong) FWriteTree *pendingWriteTree;
  92. /**
  93. * Maps tagId -> FTuplePathQueryParams
  94. */
  95. @property(nonatomic, strong) NSMutableDictionary *tagToQueryMap;
  96. @property(nonatomic, strong) NSMutableDictionary *queryToTagMap;
  97. @property(nonatomic, strong) FListenProvider *listenProvider;
  98. @property(nonatomic, strong) FPersistenceManager *persistenceManager;
  99. @property(nonatomic, strong) FAtomicNumber *queryTagCounter;
  100. @property(nonatomic, strong) NSMutableSet *keepSyncedQueries;
  101. @end
  102. /**
  103. * SyncTree is the central class for managing event callback registration, data
  104. * caching, views (query processing), and event generation. There are typically
  105. * two SyncTree instances for each Repo, one for the normal Firebase data, and
  106. * one for the .info data.
  107. *
  108. * It has a number of responsibilities, including:
  109. * - Tracking all user event callbacks (registered via addEventRegistration:
  110. * and removeEventRegistration:).
  111. * - Applying and caching data changes for user setValue:,
  112. * runTransactionBlock:, and updateChildValues: calls
  113. * (applyUserOverwriteAtPath:, applyUserMergeAtPath:).
  114. * - Applying and caching data changes for server data changes
  115. * (applyServerOverwriteAtPath:, applyServerMergeAtPath:).
  116. * - Generating user-facing events for server and user changes (all of the
  117. * apply* methods return the set of events that need to be raised as a result).
  118. * - Maintaining the appropriate set of server listens to ensure we are always
  119. * subscribed to the correct set of paths and queries to satisfy the current set
  120. * of user event callbacks (listens are started/stopped using the provided
  121. * listenProvider).
  122. *
  123. * NOTE: Although SyncTree tracks event callbacks and calculates events to
  124. * raise, the actual events are returned to the caller rather than raised
  125. * synchronously.
  126. */
  127. @implementation FSyncTree
  128. - (id)initWithListenProvider:(FListenProvider *)provider {
  129. return [self initWithPersistenceManager:nil listenProvider:provider];
  130. }
  131. - (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager
  132. listenProvider:(FListenProvider *)provider {
  133. self = [super init];
  134. if (self) {
  135. self.syncPointTree = [FImmutableTree empty];
  136. self.pendingWriteTree = [[FWriteTree alloc] init];
  137. self.tagToQueryMap = [[NSMutableDictionary alloc] init];
  138. self.queryToTagMap = [[NSMutableDictionary alloc] init];
  139. self.listenProvider = provider;
  140. self.persistenceManager = persistenceManager;
  141. self.queryTagCounter = [[FAtomicNumber alloc] init];
  142. self.keepSyncedQueries = [NSMutableSet set];
  143. }
  144. return self;
  145. }
  146. #pragma mark -
  147. #pragma mark Apply Operations
  148. /**
  149. * Apply data changes for a user-generated setValue: runTransactionBlock:
  150. * updateChildValues:, etc.
  151. * @return NSArray of FEvent to raise.
  152. */
  153. - (NSArray *)applyUserOverwriteAtPath:(FPath *)path
  154. newData:(id<FNode>)newData
  155. writeId:(NSInteger)writeId
  156. isVisible:(BOOL)visible {
  157. // Record pending write
  158. [self.pendingWriteTree addOverwriteAtPath:path
  159. newData:newData
  160. writeId:writeId
  161. isVisible:visible];
  162. if (!visible) {
  163. return @[];
  164. } else {
  165. FOverwrite *operation =
  166. [[FOverwrite alloc] initWithSource:[FOperationSource userInstance]
  167. path:path
  168. snap:newData];
  169. return [self applyOperationToSyncPoints:operation];
  170. }
  171. }
  172. /**
  173. * Apply the data from a user-generated updateChildValues: call
  174. * @return NSArray of FEvent to raise.
  175. */
  176. - (NSArray *)applyUserMergeAtPath:(FPath *)path
  177. changedChildren:(FCompoundWrite *)changedChildren
  178. writeId:(NSInteger)writeId {
  179. // Record pending merge
  180. [self.pendingWriteTree addMergeAtPath:path
  181. changedChildren:changedChildren
  182. writeId:writeId];
  183. FMerge *operation =
  184. [[FMerge alloc] initWithSource:[FOperationSource userInstance]
  185. path:path
  186. children:changedChildren];
  187. return [self applyOperationToSyncPoints:operation];
  188. }
  189. /**
  190. * Acknowledge a pending user write that was previously registered with
  191. * applyUserOverwriteAtPath: or applyUserMergeAtPath:
  192. * TODO[offline]: Taking a serverClock here is awkward, but server values are
  193. * awkward. :-(
  194. * @return NSArray of FEvent to raise.
  195. */
  196. - (NSArray *)ackUserWriteWithWriteId:(NSInteger)writeId
  197. revert:(BOOL)revert
  198. persist:(BOOL)persist
  199. clock:(id<FClock>)clock {
  200. FWriteRecord *write = [self.pendingWriteTree writeForId:writeId];
  201. BOOL needToReevaluate = [self.pendingWriteTree removeWriteId:writeId];
  202. if (write.visible) {
  203. if (persist) {
  204. [self.persistenceManager removeUserWrite:writeId];
  205. }
  206. if (!revert) {
  207. NSDictionary *serverValues =
  208. [FServerValues generateServerValues:clock];
  209. if ([write isOverwrite]) {
  210. id<FNode> resolvedNode =
  211. [FServerValues resolveDeferredValueSnapshot:write.overwrite
  212. withSyncTree:self
  213. atPath:write.path
  214. serverValues:serverValues];
  215. [self.persistenceManager applyUserWrite:resolvedNode
  216. toServerCacheAtPath:write.path];
  217. } else {
  218. FCompoundWrite *resolvedMerge = [FServerValues
  219. resolveDeferredValueCompoundWrite:write.merge
  220. withSyncTree:self
  221. atPath:write.path
  222. serverValues:serverValues];
  223. [self.persistenceManager applyUserMerge:resolvedMerge
  224. toServerCacheAtPath:write.path];
  225. }
  226. }
  227. }
  228. if (!needToReevaluate) {
  229. return @[];
  230. } else {
  231. __block FImmutableTree *affectedTree = [FImmutableTree empty];
  232. if (write.isOverwrite) {
  233. affectedTree = [affectedTree setValue:@YES atPath:[FPath empty]];
  234. } else {
  235. [write.merge
  236. enumerateWrites:^(FPath *path, id<FNode> node, BOOL *stop) {
  237. affectedTree = [affectedTree setValue:@YES atPath:path];
  238. }];
  239. }
  240. FAckUserWrite *operation =
  241. [[FAckUserWrite alloc] initWithPath:write.path
  242. affectedTree:affectedTree
  243. revert:revert];
  244. return [self applyOperationToSyncPoints:operation];
  245. }
  246. }
  247. /**
  248. * Apply new server data for the specified path
  249. * @return NSArray of FEvent to raise.
  250. */
  251. - (NSArray *)applyServerOverwriteAtPath:(FPath *)path
  252. newData:(id<FNode>)newData {
  253. [self.persistenceManager
  254. updateServerCacheWithNode:newData
  255. forQuery:[FQuerySpec defaultQueryAtPath:path]];
  256. FOverwrite *operation =
  257. [[FOverwrite alloc] initWithSource:[FOperationSource serverInstance]
  258. path:path
  259. snap:newData];
  260. return [self applyOperationToSyncPoints:operation];
  261. }
  262. /**
  263. * Applied new server data to be merged in at the specified path
  264. * @return NSArray of FEvent to raise.
  265. */
  266. - (NSArray *)applyServerMergeAtPath:(FPath *)path
  267. changedChildren:(FCompoundWrite *)changedChildren {
  268. [self.persistenceManager updateServerCacheWithMerge:changedChildren
  269. atPath:path];
  270. FMerge *operation =
  271. [[FMerge alloc] initWithSource:[FOperationSource serverInstance]
  272. path:path
  273. children:changedChildren];
  274. return [self applyOperationToSyncPoints:operation];
  275. }
  276. - (NSArray *)applyServerRangeMergeAtPath:(FPath *)path
  277. updates:(NSArray *)ranges {
  278. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  279. if (syncPoint == nil) {
  280. // Removed view, so it's safe to just ignore this update
  281. return @[];
  282. } else {
  283. // This could be for any "complete" (unfiltered) view, and if there is
  284. // more than one complete view, they should each have the same cache so
  285. // it doesn't matter which one we use.
  286. FView *view = [syncPoint completeView];
  287. if (view != nil) {
  288. id<FNode> serverNode = [view serverCache];
  289. for (FRangeMerge *merge in ranges) {
  290. serverNode = [merge applyToNode:serverNode];
  291. }
  292. return [self applyServerOverwriteAtPath:path newData:serverNode];
  293. } else {
  294. // There doesn't exist a view for this update, so it was removed and
  295. // it's safe to just ignore this range merge
  296. return @[];
  297. }
  298. }
  299. }
  300. /**
  301. * Apply a listen complete to a path
  302. * @return NSArray of FEvent to raise.
  303. */
  304. - (NSArray *)applyListenCompleteAtPath:(FPath *)path {
  305. [self.persistenceManager
  306. setQueryComplete:[FQuerySpec defaultQueryAtPath:path]];
  307. id<FOperation> operation = [[FListenComplete alloc]
  308. initWithSource:[FOperationSource serverInstance]
  309. path:path];
  310. return [self applyOperationToSyncPoints:operation];
  311. }
  312. /**
  313. * Apply a listen complete to a path
  314. * @return NSArray of FEvent to raise.
  315. */
  316. - (NSArray *)applyTaggedListenCompleteAtPath:(FPath *)path
  317. tagId:(NSNumber *)tagId {
  318. FQuerySpec *query = [self queryForTag:tagId];
  319. if (query != nil) {
  320. [self.persistenceManager setQueryComplete:query];
  321. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  322. id<FOperation> op = [[FListenComplete alloc]
  323. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  324. path:relativePath];
  325. return [self applyTaggedOperation:op atPath:query.path];
  326. } else {
  327. // We've already removed the query. No big deal, ignore the update.
  328. return @[];
  329. }
  330. }
  331. /**
  332. * Internal helper method to apply tagged operation
  333. */
  334. - (NSArray *)applyTaggedOperation:(id<FOperation>)operation
  335. atPath:(FPath *)path {
  336. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  337. NSAssert(syncPoint != nil,
  338. @"Missing sync point for query tag that we're tracking.");
  339. FWriteTreeRef *writesCache =
  340. [self.pendingWriteTree childWritesForPath:path];
  341. return [syncPoint applyOperation:operation
  342. writesCache:writesCache
  343. serverCache:nil];
  344. }
  345. /**
  346. * Apply new server data for the specified tagged query
  347. * @return NSArray of FEvent to raise.
  348. */
  349. - (NSArray *)applyTaggedQueryOverwriteAtPath:(FPath *)path
  350. newData:(id<FNode>)newData
  351. tagId:(NSNumber *)tagId {
  352. FQuerySpec *query = [self queryForTag:tagId];
  353. if (query != nil) {
  354. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  355. FQuerySpec *queryToOverwrite =
  356. relativePath.isEmpty ? query : [FQuerySpec defaultQueryAtPath:path];
  357. [self.persistenceManager updateServerCacheWithNode:newData
  358. forQuery:queryToOverwrite];
  359. FOverwrite *operation = [[FOverwrite alloc]
  360. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  361. path:relativePath
  362. snap:newData];
  363. return [self applyTaggedOperation:operation atPath:query.path];
  364. } else {
  365. // Query must have been removed already
  366. return @[];
  367. }
  368. }
  369. /**
  370. * Apply server data to be merged in for the specified tagged query
  371. * @return NSArray of FEvent to raise.
  372. */
  373. - (NSArray *)applyTaggedQueryMergeAtPath:(FPath *)path
  374. changedChildren:(FCompoundWrite *)changedChildren
  375. tagId:(NSNumber *)tagId {
  376. FQuerySpec *query = [self queryForTag:tagId];
  377. if (query != nil) {
  378. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  379. [self.persistenceManager updateServerCacheWithMerge:changedChildren
  380. atPath:path];
  381. FMerge *operation = [[FMerge alloc]
  382. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  383. path:relativePath
  384. children:changedChildren];
  385. return [self applyTaggedOperation:operation atPath:query.path];
  386. } else {
  387. // We've already removed the query. No big deal, ignore the update.
  388. return @[];
  389. }
  390. }
  391. - (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path
  392. updates:(NSArray *)ranges
  393. tagId:(NSNumber *)tagId {
  394. FQuerySpec *query = [self queryForTag:tagId];
  395. if (query != nil) {
  396. NSAssert([path isEqual:query.path],
  397. @"Tagged update path and query path must match");
  398. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  399. NSAssert(syncPoint != nil,
  400. @"Missing sync point for query tag that we're tracking.");
  401. FView *view = [syncPoint viewForQuery:query];
  402. NSAssert(view != nil,
  403. @"Missing view for query tag that we're tracking");
  404. id<FNode> serverNode = [view serverCache];
  405. for (FRangeMerge *merge in ranges) {
  406. serverNode = [merge applyToNode:serverNode];
  407. }
  408. return [self applyTaggedQueryOverwriteAtPath:path
  409. newData:serverNode
  410. tagId:tagId];
  411. } else {
  412. // We've already removed the query. No big deal, ignore the update.
  413. return @[];
  414. }
  415. }
  416. - (NSNumber *)trackTagForQuery:(FQuerySpec *)query {
  417. NSAssert(self.queryToTagMap[query] == nil,
  418. @"View does not exist, but we have a tag");
  419. NSNumber *tagId = [self.queryTagCounter getAndIncrement];
  420. self.queryToTagMap[query] = tagId;
  421. self.tagToQueryMap[tagId] = query;
  422. return tagId;
  423. }
  424. - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
  425. forQuery:(FQuerySpec *)query
  426. skipListenSetup:(BOOL)skipListenSetup {
  427. FPath *path = query.path;
  428. __block BOOL foundAncestorDefaultView = NO;
  429. [self.syncPointTree
  430. forEachOnPath:query.path
  431. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  432. foundAncestorDefaultView =
  433. foundAncestorDefaultView || [syncPoint hasCompleteView];
  434. return !foundAncestorDefaultView;
  435. }];
  436. [self.persistenceManager setQueryActive:query];
  437. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  438. if (syncPoint == nil) {
  439. syncPoint = [[FSyncPoint alloc]
  440. initWithPersistenceManager:self.persistenceManager];
  441. self.syncPointTree = [self.syncPointTree setValue:syncPoint
  442. atPath:path];
  443. }
  444. BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query];
  445. NSArray *events;
  446. if (viewAlreadyExists) {
  447. events = [syncPoint addEventRegistration:eventRegistration
  448. forExistingViewForQuery:query];
  449. } else {
  450. if (![query loadsAllData] && !skipListenSetup) {
  451. // We need to track a tag for this query. If this is an
  452. // FRepo#getData, then we've already tracked a tag at
  453. // a higher level.
  454. [self trackTagForQuery:query];
  455. }
  456. FWriteTreeRef *writesCache =
  457. [self.pendingWriteTree childWritesForPath:path];
  458. FCacheNode *serverCache = [self serverCacheForQuery:query];
  459. events = [syncPoint addEventRegistration:eventRegistration
  460. forNonExistingViewForQuery:query
  461. writesCache:writesCache
  462. serverCache:serverCache];
  463. // There was no view and no default listen
  464. if (!foundAncestorDefaultView && !skipListenSetup) {
  465. FView *view = [syncPoint viewForQuery:query];
  466. NSMutableArray *mutableEvents = [events mutableCopy];
  467. [mutableEvents
  468. addObjectsFromArray:[self setupListenerOnQuery:query
  469. view:view]];
  470. events = mutableEvents;
  471. }
  472. }
  473. return events;
  474. }
  475. /**
  476. * Add an event callback for the specified query
  477. * @return NSArray of FEvent to raise.
  478. */
  479. - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
  480. forQuery:(FQuerySpec *)query {
  481. return [self addEventRegistration:eventRegistration
  482. forQuery:query
  483. skipListenSetup:NO];
  484. }
  485. - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
  486. __block id<FNode> serverCacheNode = nil;
  487. [self.syncPointTree
  488. forEachOnPath:query.path
  489. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  490. FPath *relativePath = [FPath relativePathFrom:pathToSyncPoint
  491. to:query.path];
  492. serverCacheNode =
  493. [syncPoint completeServerCacheAtPath:relativePath];
  494. return serverCacheNode == nil;
  495. }];
  496. FCacheNode *serverCache;
  497. if (serverCacheNode != nil) {
  498. FIndexedNode *indexed =
  499. [FIndexedNode indexedNodeWithNode:serverCacheNode
  500. index:query.index];
  501. serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed
  502. isFullyInitialized:YES
  503. isFiltered:NO];
  504. } else {
  505. FCacheNode *persistenceServerCache =
  506. [self.persistenceManager serverCacheForQuery:query];
  507. if (persistenceServerCache.isFullyInitialized) {
  508. serverCache = persistenceServerCache;
  509. } else {
  510. serverCacheNode = [FEmptyNode emptyNode];
  511. FImmutableTree *subtree =
  512. [self.syncPointTree subtreeAtPath:query.path];
  513. [subtree
  514. forEachChild:^(NSString *childKey, FSyncPoint *childSyncPoint) {
  515. id<FNode> completeCache =
  516. [childSyncPoint completeServerCacheAtPath:[FPath empty]];
  517. if (completeCache) {
  518. serverCacheNode =
  519. [serverCacheNode updateImmediateChild:childKey
  520. withNewChild:completeCache];
  521. }
  522. }];
  523. // Fill the node with any available children we have
  524. [persistenceServerCache.node
  525. enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node,
  526. BOOL *stop) {
  527. if (![serverCacheNode hasChild:key]) {
  528. serverCacheNode =
  529. [serverCacheNode updateImmediateChild:key
  530. withNewChild:node];
  531. }
  532. }];
  533. FIndexedNode *indexed =
  534. [FIndexedNode indexedNodeWithNode:serverCacheNode
  535. index:query.index];
  536. serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed
  537. isFullyInitialized:NO
  538. isFiltered:NO];
  539. }
  540. }
  541. return serverCache;
  542. }
  543. /**
  544. * Remove event callback(s).
  545. *
  546. * If query is the default query, we'll check all queries for the specified
  547. * eventRegistration. If eventRegistration is null, we'll remove all callbacks
  548. * for the specified query/queries.
  549. *
  550. * @param eventRegistration if nil, all callbacks are removed
  551. * @param cancelError If provided, appropriate cancel events will be returned
  552. * @param skipListenDedup If provided, don't try to deduplicate listens, see
  553. * FRepo#getData.
  554. * @return NSArray of FEvent to raise.
  555. */
  556. - (NSArray *)removeEventRegistration:(id<FEventRegistration>)eventRegistration
  557. forQuery:(FQuerySpec *)query
  558. cancelError:(NSError *)cancelError
  559. skipListenDedup:(BOOL)skipListenDedup {
  560. // Find the syncPoint first. Then deal with whether or not it has matching
  561. // listeners
  562. FPath *path = query.path;
  563. FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path];
  564. NSArray *cancelEvents = @[];
  565. // A removal on a default query affects all queries at that location. A
  566. // removal on an indexed query, even one without other query constraints,
  567. // does *not* affect all queries at that location. So this check must be for
  568. // 'default', and not loadsAllData:
  569. if (maybeSyncPoint &&
  570. ([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) {
  571. FTupleRemovedQueriesEvents *removedAndEvents =
  572. [maybeSyncPoint removeEventRegistration:eventRegistration
  573. forQuery:query
  574. cancelError:cancelError];
  575. if ([maybeSyncPoint isEmpty]) {
  576. self.syncPointTree = [self.syncPointTree removeValueAtPath:path];
  577. }
  578. NSArray *removed = removedAndEvents.removedQueries;
  579. cancelEvents = removedAndEvents.cancelEvents;
  580. // We may have just removed one of many listeners and can short-circuit
  581. // this whole process We may also not have removed a default listener,
  582. // in which case all of the descendant listeners should already be
  583. // properly set up.
  584. //
  585. // Since indexed queries can shadow if they don't have other query
  586. // constraints, check for loadsAllData: instead of isDefault:
  587. NSUInteger defaultQueryIndex = [removed
  588. indexOfObjectPassingTest:^BOOL(FQuerySpec *q, NSUInteger idx,
  589. BOOL *stop) {
  590. return [q loadsAllData];
  591. }];
  592. BOOL removingDefault = defaultQueryIndex != NSNotFound;
  593. [removed enumerateObjectsUsingBlock:^(FQuerySpec *query, NSUInteger idx,
  594. BOOL *stop) {
  595. [self.persistenceManager setQueryInactive:query];
  596. }];
  597. if (skipListenDedup) {
  598. if ([cancelEvents count] > 0) {
  599. FFWarn(@"I-RDB086004",
  600. @"Non-empty cancelEvents for skipListenDedup "
  601. @"removeRegistration");
  602. }
  603. return cancelEvents;
  604. }
  605. NSNumber *covered = [self.syncPointTree
  606. findOnPath:path
  607. andApplyBlock:^id(FPath *relativePath,
  608. FSyncPoint *parentSyncPoint) {
  609. return
  610. [NSNumber numberWithBool:[parentSyncPoint hasCompleteView]];
  611. }];
  612. if (removingDefault && ![covered boolValue]) {
  613. FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
  614. // There are potentially child listeners. Determine what if any
  615. // listens we need to send before executing the removal
  616. if (![subtree isEmpty]) {
  617. // We need to fold over our subtree and collect the listeners to
  618. // send
  619. NSArray *newViews =
  620. [self collectDistinctViewsForSubTree:subtree];
  621. // Ok, we've collected all the listens we need. Set them up.
  622. [newViews enumerateObjectsUsingBlock:^(
  623. FView *view, NSUInteger idx, BOOL *stop) {
  624. FQuerySpec *newQuery = view.query;
  625. FListenContainer *listenContainer =
  626. [self createListenerForView:view];
  627. self.listenProvider.startListening(
  628. [self queryForListening:newQuery],
  629. [self tagForQuery:newQuery], listenContainer,
  630. listenContainer.onComplete);
  631. }];
  632. } else {
  633. // There's nothing below us, so nothing we need to start
  634. // listening on
  635. }
  636. }
  637. // If we removed anything and we're not covered by a higher up listen,
  638. // we need to stop listening on this query. The above block has us
  639. // covered in terms of making sure we're set up on listens lower in the
  640. // tree. Also, note that if we have a cancelError, it's already been
  641. // removed at the provider level.
  642. if (![covered boolValue] && [removed count] > 0 && cancelError == nil) {
  643. // If we removed a default, then we weren't listening on any of the
  644. // other queries here. Just cancel the one default. Otherwise, we
  645. // need to iterate through and cancel each individual query
  646. if (removingDefault) {
  647. // We don't tag default listeners
  648. self.listenProvider.stopListening(
  649. [self queryForListening:query], nil);
  650. } else {
  651. [removed
  652. enumerateObjectsUsingBlock:^(FQuerySpec *queryToRemove,
  653. NSUInteger idx, BOOL *stop) {
  654. NSNumber *tagToRemove =
  655. [self.queryToTagMap objectForKey:queryToRemove];
  656. self.listenProvider.stopListening(
  657. [self queryForListening:queryToRemove], tagToRemove);
  658. }];
  659. }
  660. }
  661. // Now, clear all the tags we're tracking for the removed listens.
  662. [self removeTags:removed];
  663. } else {
  664. // No-op, this listener must've been already removed
  665. }
  666. return cancelEvents;
  667. }
  668. - (NSArray *)removeEventRegistration:(id<FEventRegistration>)eventRegistration
  669. forQuery:(FQuerySpec *)query
  670. cancelError:(NSError *)cancelError {
  671. return [self removeEventRegistration:eventRegistration
  672. forQuery:query
  673. cancelError:cancelError
  674. skipListenDedup:NO];
  675. }
  676. - (void)keepQuery:(FQuerySpec *)query
  677. synced:(BOOL)keepSynced
  678. skipListen:(BOOL)skip {
  679. // Only do something if we actually need to add/remove an event registration
  680. if (keepSynced && ![self.keepSyncedQueries containsObject:query]) {
  681. [self addEventRegistration:[FKeepSyncedEventRegistration instance]
  682. forQuery:query
  683. skipListenSetup:skip];
  684. [self.keepSyncedQueries addObject:query];
  685. } else if (!keepSynced && [self.keepSyncedQueries containsObject:query]) {
  686. [self removeEventRegistration:[FKeepSyncedEventRegistration instance]
  687. forQuery:query
  688. cancelError:nil
  689. skipListenDedup:skip];
  690. [self.keepSyncedQueries removeObject:query];
  691. }
  692. }
  693. - (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced {
  694. [self keepQuery:query synced:keepSynced skipListen:NO];
  695. }
  696. - (NSArray *)removeAllWrites {
  697. [self.persistenceManager removeAllUserWrites];
  698. NSArray *removedWrites = [self.pendingWriteTree removeAllWrites];
  699. if (removedWrites.count > 0) {
  700. FImmutableTree *affectedTree =
  701. [[FImmutableTree empty] setValue:@YES atPath:[FPath empty]];
  702. return [self applyOperationToSyncPoints:[[FAckUserWrite alloc]
  703. initWithPath:[FPath empty]
  704. affectedTree:affectedTree
  705. revert:YES]];
  706. } else {
  707. return @[];
  708. }
  709. }
  710. /** Returns a non-empty cache node if one exists. Otherwise returns null. */
  711. - (FIndexedNode *)persistenceServerCache:(FQuerySpec *)querySpec {
  712. FCacheNode *cacheNode =
  713. [self.persistenceManager serverCacheForQuery:querySpec];
  714. if (cacheNode == nil || cacheNode.node.isEmpty) {
  715. return nil;
  716. }
  717. return cacheNode.indexedNode;
  718. }
  719. - (id<FNode>)getServerValue:(FQuerySpec *)query {
  720. __block id<FNode> serverCacheNode = nil;
  721. __block FSyncPoint *targetSyncPoint = nil;
  722. __block FPath *syncPointToQuery = nil;
  723. [self.syncPointTree
  724. forEachOnPath:query.path
  725. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  726. syncPointToQuery = [FPath relativePathFrom:pathToSyncPoint
  727. to:query.path];
  728. serverCacheNode =
  729. [syncPoint completeServerCacheAtPath:syncPointToQuery];
  730. targetSyncPoint = syncPoint;
  731. return serverCacheNode == nil;
  732. }];
  733. if (targetSyncPoint == nil) {
  734. targetSyncPoint = [[FSyncPoint alloc]
  735. initWithPersistenceManager:self.persistenceManager];
  736. // TODO(wyszynski): We probably shouldn't be inserting this node into the tree.
  737. // The only reason we're creating a sync point is to complete the event snap.
  738. // self.syncPointTree = [self.syncPointTree setValue:targetSyncPoint
  739. // atPath:[query path]];
  740. } else {
  741. serverCacheNode =
  742. serverCacheNode != nil
  743. ? serverCacheNode
  744. : [targetSyncPoint completeServerCacheAtPath:[FPath empty]];
  745. }
  746. FIndexedNode *indexed = [FIndexedNode
  747. indexedNodeWithNode:serverCacheNode != nil ? serverCacheNode
  748. : [FEmptyNode emptyNode]
  749. index:query.index];
  750. FCacheNode *serverCache =
  751. [[FCacheNode alloc] initWithIndexedNode:indexed
  752. isFullyInitialized:YES
  753. isFiltered:NO];
  754. FView *view = [targetSyncPoint
  755. getView:query
  756. writesCache:[_pendingWriteTree childWritesForPath:[query path]]
  757. serverCache:serverCache];
  758. return [view completeEventCacheFor:syncPointToQuery];
  759. }
  760. /**
  761. * Returns a complete cache, if we have one, of the data at a particular path.
  762. * The location must have a listener above it, but as this is only used by
  763. * transaction code, that should always be the case anyways.
  764. *
  765. * Note: this method will *include* hidden writes from transaction with
  766. * applyLocally set to false.
  767. * @param path The path to the data we want
  768. * @param writeIdsToExclude A specific set to be excluded
  769. */
  770. - (id<FNode>)calcCompleteEventCacheAtPath:(FPath *)path
  771. excludeWriteIds:(NSArray *)writeIdsToExclude {
  772. BOOL includeHiddenSets = YES;
  773. FWriteTree *writeTree = self.pendingWriteTree;
  774. id<FNode> serverCache = [self.syncPointTree
  775. findOnPath:path
  776. andApplyBlock:^id<FNode>(FPath *pathSoFar, FSyncPoint *syncPoint) {
  777. FPath *relativePath = [FPath relativePathFrom:pathSoFar to:path];
  778. id<FNode> serverCache =
  779. [syncPoint completeServerCacheAtPath:relativePath];
  780. if (serverCache) {
  781. return serverCache;
  782. } else {
  783. return nil;
  784. }
  785. }];
  786. return [writeTree calculateCompleteEventCacheAtPath:path
  787. completeServerCache:serverCache
  788. excludeWriteIds:writeIdsToExclude
  789. includeHiddenWrites:includeHiddenSets];
  790. }
  791. #pragma mark -
  792. #pragma mark Private Methods
  793. /**
  794. * This collapses multiple unfiltered views into a single view, since we only
  795. * need a single listener for them.
  796. * @return NSArray of FView
  797. */
  798. - (NSArray *)collectDistinctViewsForSubTree:(FImmutableTree *)subtree {
  799. return [subtree foldWithBlock:^NSArray *(FPath *relativePath,
  800. FSyncPoint *maybeChildSyncPoint,
  801. NSDictionary *childMap) {
  802. if (maybeChildSyncPoint && [maybeChildSyncPoint hasCompleteView]) {
  803. FView *completeView = [maybeChildSyncPoint completeView];
  804. return @[ completeView ];
  805. } else {
  806. // No complete view here, flatten any deeper listens into an array
  807. NSMutableArray *views = [[NSMutableArray alloc] init];
  808. if (maybeChildSyncPoint) {
  809. views = [[maybeChildSyncPoint queryViews] mutableCopy];
  810. }
  811. [childMap enumerateKeysAndObjectsUsingBlock:^(
  812. NSString *childKey, NSArray *childViews, BOOL *stop) {
  813. [views addObjectsFromArray:childViews];
  814. }];
  815. return views;
  816. }
  817. }];
  818. }
  819. /**
  820. * @param queries NSArray of FQuerySpec
  821. */
  822. - (void)removeTags:(NSArray *)queries {
  823. [queries enumerateObjectsUsingBlock:^(FQuerySpec *removedQuery,
  824. NSUInteger idx, BOOL *stop) {
  825. if (![removedQuery loadsAllData]) {
  826. // We should have a tag for this
  827. NSNumber *removedQueryTag = self.queryToTagMap[removedQuery];
  828. [self.queryToTagMap removeObjectForKey:removedQuery];
  829. [self.tagToQueryMap removeObjectForKey:removedQueryTag];
  830. }
  831. }];
  832. }
  833. - (FQuerySpec *)queryForListening:(FQuerySpec *)query {
  834. if (query.loadsAllData && !query.isDefault) {
  835. // We treat queries that load all data as default queries
  836. return [FQuerySpec defaultQueryAtPath:query.path];
  837. } else {
  838. return query;
  839. }
  840. }
  841. /**
  842. * For a given new listen, manage the de-duplication of outstanding
  843. * subscriptions.
  844. * @return NSArray of FEvent events to support synchronous data sources
  845. */
  846. - (NSArray *)setupListenerOnQuery:(FQuerySpec *)query view:(FView *)view {
  847. FPath *path = query.path;
  848. NSNumber *tagId = [self tagForQuery:query];
  849. FListenContainer *listenContainer = [self createListenerForView:view];
  850. NSArray *events = self.listenProvider.startListening(
  851. [self queryForListening:query], tagId, listenContainer,
  852. listenContainer.onComplete);
  853. FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
  854. // The root of this subtree has our query. We're here because we definitely
  855. // need to send a listen for that, but we may need to shadow other listens
  856. // as well.
  857. if (tagId != nil) {
  858. NSAssert(![subtree.value hasCompleteView],
  859. @"If we're adding a query, it shouldn't be shadowed");
  860. } else {
  861. // Shadow everything at or below this location, this is a default
  862. // listener.
  863. NSArray *queriesToStop =
  864. [subtree foldWithBlock:^id(FPath *relativePath,
  865. FSyncPoint *maybeChildSyncPoint,
  866. NSDictionary *childMap) {
  867. if (![relativePath isEmpty] && maybeChildSyncPoint != nil &&
  868. [maybeChildSyncPoint hasCompleteView]) {
  869. return @[ [maybeChildSyncPoint completeView].query ];
  870. } else {
  871. // No default listener here, flatten any deeper queries into
  872. // an array
  873. NSMutableArray *queries = [[NSMutableArray alloc] init];
  874. if (maybeChildSyncPoint != nil) {
  875. for (FView *view in [maybeChildSyncPoint queryViews]) {
  876. [queries addObject:view.query];
  877. }
  878. }
  879. [childMap
  880. enumerateKeysAndObjectsUsingBlock:^(
  881. NSString *key, NSArray *childQueries, BOOL *stop) {
  882. [queries addObjectsFromArray:childQueries];
  883. }];
  884. return queries;
  885. }
  886. }];
  887. for (FQuerySpec *queryToStop in queriesToStop) {
  888. self.listenProvider.stopListening(
  889. [self queryForListening:queryToStop],
  890. [self tagForQuery:queryToStop]);
  891. }
  892. }
  893. return events;
  894. }
  895. - (FListenContainer *)createListenerForView:(FView *)view {
  896. FQuerySpec *query = view.query;
  897. NSNumber *tagId = [self tagForQuery:query];
  898. FListenContainer *listenContainer = [[FListenContainer alloc]
  899. initWithView:view
  900. onComplete:^(NSString *status) {
  901. if ([status isEqualToString:@"ok"]) {
  902. if (tagId != nil) {
  903. return [self applyTaggedListenCompleteAtPath:query.path
  904. tagId:tagId];
  905. } else {
  906. return [self applyListenCompleteAtPath:query.path];
  907. }
  908. } else {
  909. // If a listen failed, kill all of the listeners here, not just
  910. // the one that triggered the error. Note that this may need to
  911. // be scoped to just this listener if we change permissions on
  912. // filtered children
  913. NSError *error = [FUtilities errorForStatus:status
  914. andReason:nil];
  915. FFWarn(@"I-RDB038012", @"Listener at %@ failed: %@", query.path,
  916. status);
  917. return [self removeEventRegistration:nil
  918. forQuery:query
  919. cancelError:error];
  920. }
  921. }];
  922. return listenContainer;
  923. }
  924. /**
  925. * @return The query associated with the given tag, if we have one
  926. */
  927. - (FQuerySpec *)queryForTag:(NSNumber *)tagId {
  928. return self.tagToQueryMap[tagId];
  929. }
  930. /**
  931. * @return The tag associated with the given query
  932. */
  933. - (NSNumber *)tagForQuery:(FQuerySpec *)query {
  934. return self.queryToTagMap[query];
  935. }
  936. #pragma mark -
  937. #pragma mark applyOperation Helpers
  938. /**
  939. * A helper method that visits all descendant and ancestor SyncPoints, applying
  940. the operation.
  941. *
  942. * NOTES:
  943. * - Descendant SyncPoints will be visited first (since we raise events
  944. depth-first).
  945. * - We call applyOperation: on each SyncPoint passing three things:
  946. * 1. A version of the Operation that has been made relative to the SyncPoint
  947. location.
  948. * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
  949. * 3. A snapshot Node with cached server data, if we have it.
  950. * - We concatenate all of the events returned by each SyncPoint and return the
  951. result.
  952. *
  953. * @return Array of FEvent
  954. */
  955. - (NSArray *)applyOperationToSyncPoints:(id<FOperation>)operation {
  956. return [self applyOperationHelper:operation
  957. syncPointTree:self.syncPointTree
  958. serverCache:nil
  959. writesCache:[self.pendingWriteTree
  960. childWritesForPath:[FPath empty]]];
  961. }
  962. /**
  963. * Recursive helper for applyOperationToSyncPoints_
  964. */
  965. - (NSArray *)applyOperationHelper:(id<FOperation>)operation
  966. syncPointTree:(FImmutableTree *)syncPointTree
  967. serverCache:(id<FNode>)serverCache
  968. writesCache:(FWriteTreeRef *)writesCache {
  969. if ([operation.path isEmpty]) {
  970. return [self applyOperationDescendantsHelper:operation
  971. syncPointTree:syncPointTree
  972. serverCache:serverCache
  973. writesCache:writesCache];
  974. } else {
  975. FSyncPoint *syncPoint = syncPointTree.value;
  976. // If we don't have cached server data, see if we can get it from this
  977. // SyncPoint
  978. if (serverCache == nil && syncPoint != nil) {
  979. serverCache = [syncPoint completeServerCacheAtPath:[FPath empty]];
  980. }
  981. NSMutableArray *events = [[NSMutableArray alloc] init];
  982. NSString *childKey = [operation.path getFront];
  983. id<FOperation> childOperation = [operation operationForChild:childKey];
  984. FImmutableTree *childTree = [syncPointTree.children get:childKey];
  985. if (childTree != nil && childOperation != nil) {
  986. id<FNode> childServerCache =
  987. serverCache ? [serverCache getImmediateChild:childKey] : nil;
  988. FWriteTreeRef *childWritesCache =
  989. [writesCache childWriteTreeRef:childKey];
  990. [events
  991. addObjectsFromArray:[self
  992. applyOperationHelper:childOperation
  993. syncPointTree:childTree
  994. serverCache:childServerCache
  995. writesCache:childWritesCache]];
  996. }
  997. if (syncPoint) {
  998. [events addObjectsFromArray:[syncPoint applyOperation:operation
  999. writesCache:writesCache
  1000. serverCache:serverCache]];
  1001. }
  1002. return events;
  1003. }
  1004. }
  1005. /**
  1006. * Recursive helper for applyOperationToSyncPoints:
  1007. */
  1008. - (NSArray *)applyOperationDescendantsHelper:(id<FOperation>)operation
  1009. syncPointTree:(FImmutableTree *)syncPointTree
  1010. serverCache:(id<FNode>)serverCache
  1011. writesCache:(FWriteTreeRef *)writesCache {
  1012. FSyncPoint *syncPoint = syncPointTree.value;
  1013. // If we don't have cached server data, see if we can get it from this
  1014. // SyncPoint
  1015. id<FNode> resolvedServerCache;
  1016. if (serverCache == nil & syncPoint != nil) {
  1017. resolvedServerCache =
  1018. [syncPoint completeServerCacheAtPath:[FPath empty]];
  1019. } else {
  1020. resolvedServerCache = serverCache;
  1021. }
  1022. NSMutableArray *events = [[NSMutableArray alloc] init];
  1023. [syncPointTree.children enumerateKeysAndObjectsUsingBlock:^(
  1024. NSString *childKey, FImmutableTree *childTree,
  1025. BOOL *stop) {
  1026. id<FNode> childServerCache = nil;
  1027. if (resolvedServerCache != nil) {
  1028. childServerCache = [resolvedServerCache getImmediateChild:childKey];
  1029. }
  1030. FWriteTreeRef *childWritesCache =
  1031. [writesCache childWriteTreeRef:childKey];
  1032. id<FOperation> childOperation = [operation operationForChild:childKey];
  1033. if (childOperation != nil) {
  1034. [events addObjectsFromArray:
  1035. [self applyOperationDescendantsHelper:childOperation
  1036. syncPointTree:childTree
  1037. serverCache:childServerCache
  1038. writesCache:childWritesCache]];
  1039. }
  1040. }];
  1041. if (syncPoint) {
  1042. [events
  1043. addObjectsFromArray:[syncPoint applyOperation:operation
  1044. writesCache:writesCache
  1045. serverCache:resolvedServerCache]];
  1046. }
  1047. return events;
  1048. }
  1049. @end