aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Database/Core/View
diff options
context:
space:
mode:
Diffstat (limited to 'Firebase/Database/Core/View')
-rw-r--r--Firebase/Database/Core/View/FCacheNode.h44
-rw-r--r--Firebase/Database/Core/View/FCacheNode.m60
-rw-r--r--Firebase/Database/Core/View/FCancelEvent.h30
-rw-r--r--Firebase/Database/Core/View/FCancelEvent.m55
-rw-r--r--Firebase/Database/Core/View/FChange.h38
-rw-r--r--Firebase/Database/Core/View/FChange.m65
-rw-r--r--Firebase/Database/Core/View/FChildEventRegistration.h37
-rw-r--r--Firebase/Database/Core/View/FChildEventRegistration.m92
-rw-r--r--Firebase/Database/Core/View/FDataEvent.h39
-rw-r--r--Firebase/Database/Core/View/FDataEvent.m74
-rw-r--r--Firebase/Database/Core/View/FEvent.h27
-rw-r--r--Firebase/Database/Core/View/FEventRaiser.h35
-rw-r--r--Firebase/Database/Core/View/FEventRaiser.m72
-rw-r--r--Firebase/Database/Core/View/FEventRegistration.h36
-rw-r--r--Firebase/Database/Core/View/FKeepSyncedEventRegistration.h28
-rw-r--r--Firebase/Database/Core/View/FKeepSyncedEventRegistration.m64
-rw-r--r--Firebase/Database/Core/View/FValueEventRegistration.h34
-rw-r--r--Firebase/Database/Core/View/FValueEventRegistration.m89
-rw-r--r--Firebase/Database/Core/View/FView.h53
-rw-r--r--Firebase/Database/Core/View/FView.m223
-rw-r--r--Firebase/Database/Core/View/FViewCache.h35
-rw-r--r--Firebase/Database/Core/View/FViewCache.m61
-rw-r--r--Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h28
-rw-r--r--Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m80
-rw-r--r--Firebase/Database/Core/View/Filter/FCompleteChildSource.h28
-rw-r--r--Firebase/Database/Core/View/Filter/FIndexedFilter.h27
-rw-r--r--Firebase/Database/Core/View/Filter/FIndexedFilter.m147
-rw-r--r--Firebase/Database/Core/View/Filter/FLimitedFilter.h26
-rw-r--r--Firebase/Database/Core/View/Filter/FLimitedFilter.m243
-rw-r--r--Firebase/Database/Core/View/Filter/FNodeFilter.h71
30 files changed, 1941 insertions, 0 deletions
diff --git a/Firebase/Database/Core/View/FCacheNode.h b/Firebase/Database/Core/View/FCacheNode.h
new file mode 100644
index 0000000..b23869c
--- /dev/null
+++ b/Firebase/Database/Core/View/FCacheNode.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+@protocol FNode;
+@class FIndexedNode;
+@class FPath;
+
+/**
+* A cache node only stores complete children. Additionally it holds a flag whether the node can be considered fully
+* initialized in the sense that we know at one point in time, this represented a valid state of the world, e.g.
+* initialized with data from the server, or a complete overwrite by the client. It is not necessarily complete because
+* it may have been from a tagged query. The filtered flag also tracks whether a node potentially had children removed
+* due to a filter.
+*/
+@interface FCacheNode : NSObject
+
+- (id) initWithIndexedNode:(FIndexedNode *)indexedNode
+ isFullyInitialized:(BOOL)fullyInitialized
+ isFiltered:(BOOL)filtered;
+
+- (BOOL) isCompleteForPath:(FPath *)path;
+- (BOOL) isCompleteForChild:(NSString *)childKey;
+
+@property (nonatomic, readonly) BOOL isFullyInitialized;
+@property (nonatomic, readonly) BOOL isFiltered;
+@property (nonatomic, strong, readonly) FIndexedNode *indexedNode;
+@property (nonatomic, strong, readonly) id<FNode> node;
+
+@end
diff --git a/Firebase/Database/Core/View/FCacheNode.m b/Firebase/Database/Core/View/FCacheNode.m
new file mode 100644
index 0000000..4767a25
--- /dev/null
+++ b/Firebase/Database/Core/View/FCacheNode.m
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#import "FCacheNode.h"
+#import "FNode.h"
+#import "FPath.h"
+#import "FEmptyNode.h"
+#import "FIndexedNode.h"
+
+@interface FCacheNode ()
+@property (nonatomic, readwrite) BOOL isFullyInitialized;
+@property (nonatomic, readwrite) BOOL isFiltered;
+@property (nonatomic, strong, readwrite) FIndexedNode *indexedNode;
+@end
+
+@implementation FCacheNode
+- (id) initWithIndexedNode:(FIndexedNode *)indexedNode
+ isFullyInitialized:(BOOL)fullyInitialized
+ isFiltered:(BOOL)filtered
+{
+ self = [super init];
+ if (self) {
+ self.indexedNode = indexedNode;
+ self.isFullyInitialized = fullyInitialized;
+ self.isFiltered = filtered;
+ }
+ return self;
+}
+
+- (BOOL)isCompleteForPath:(FPath *)path {
+ if (path.isEmpty) {
+ return self.isFullyInitialized && !self.isFiltered;
+ } else {
+ NSString *childKey = [path getFront];
+ return [self isCompleteForChild:childKey];
+ }
+}
+
+- (BOOL)isCompleteForChild:(NSString *)childKey {
+ return (self.isFullyInitialized && !self.isFiltered) || [self.node hasChild:childKey];
+}
+
+- (id<FNode>)node {
+ return self.indexedNode.node;
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FCancelEvent.h b/Firebase/Database/Core/View/FCancelEvent.h
new file mode 100644
index 0000000..38277f7
--- /dev/null
+++ b/Firebase/Database/Core/View/FCancelEvent.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FEvent.h"
+
+@protocol FEventRegistration;
+
+
+@interface FCancelEvent : NSObject<FEvent>
+
+- initWithEventRegistration:(id<FEventRegistration>)eventRegistration error:(NSError *)error path:(FPath *)path;
+
+@property (nonatomic, strong, readonly) NSError *error;
+@property (nonatomic, strong, readonly) FPath *path;
+
+@end
diff --git a/Firebase/Database/Core/View/FCancelEvent.m b/Firebase/Database/Core/View/FCancelEvent.m
new file mode 100644
index 0000000..fb73f17
--- /dev/null
+++ b/Firebase/Database/Core/View/FCancelEvent.m
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#import "FCancelEvent.h"
+#import "FPath.h"
+#import "FEventRegistration.h"
+
+@interface FCancelEvent ()
+@property (nonatomic, strong) id<FEventRegistration> eventRegistration;
+@property (nonatomic, strong, readwrite) NSError *error;
+@property (nonatomic, strong, readwrite) FPath *path;
+@end
+
+@implementation FCancelEvent
+
+@synthesize eventRegistration;
+@synthesize error;
+@synthesize path;
+
+- (id)initWithEventRegistration:(id <FEventRegistration>)registration error:(NSError *)anError path:(FPath *)aPath {
+ self = [super init];
+ if (self) {
+ self.eventRegistration = registration;
+ self.error = anError;
+ self.path = aPath;
+ }
+ return self;
+}
+
+- (void) fireEventOnQueue:(dispatch_queue_t)queue {
+ [self.eventRegistration fireEvent:self queue:queue];
+}
+
+- (BOOL) isCancelEvent {
+ return YES;
+}
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"%@: cancel", self.path];
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FChange.h b/Firebase/Database/Core/View/FChange.h
new file mode 100644
index 0000000..d728fe0
--- /dev/null
+++ b/Firebase/Database/Core/View/FChange.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FIRDatabaseReference.h"
+#import "FNode.h"
+#import "FIndexedNode.h"
+
+@interface FChange : NSObject
+
+@property (nonatomic, readonly) FIRDataEventType type;
+@property (nonatomic, strong, readonly) FIndexedNode *indexedNode;
+@property (nonatomic, strong, readonly) NSString *childKey;
+@property (nonatomic, strong, readonly) NSString *prevKey;
+@property (nonatomic, strong, readonly) FIndexedNode *oldIndexedNode;
+
+- (id)initWithType:(FIRDataEventType)type indexedNode:(FIndexedNode *)indexedNode;
+- (id)initWithType:(FIRDataEventType)type indexedNode:(FIndexedNode *)indexedNode childKey:(NSString *)childKey;
+- (id)initWithType:(FIRDataEventType)type
+ indexedNode:(FIndexedNode *)indexedNode
+ childKey:(NSString *)childKey
+ oldIndexedNode:(FIndexedNode *)oldIndexedNode;
+
+- (FChange *) changeWithPrevKey:(NSString *)prevKey;
+@end
diff --git a/Firebase/Database/Core/View/FChange.m b/Firebase/Database/Core/View/FChange.m
new file mode 100644
index 0000000..893fce4
--- /dev/null
+++ b/Firebase/Database/Core/View/FChange.m
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#import "FChange.h"
+
+@interface FChange ()
+
+@property (nonatomic, strong, readwrite) NSString *prevKey;
+
+@end
+
+@implementation FChange
+
+- (id)initWithType:(FIRDataEventType)type indexedNode:(FIndexedNode *)indexedNode
+{
+ return [self initWithType:type indexedNode:indexedNode childKey:nil oldIndexedNode:nil];
+}
+
+- (id)initWithType:(FIRDataEventType)type indexedNode:(FIndexedNode *)indexedNode childKey:(NSString *)childKey
+{
+ return [self initWithType:type indexedNode:indexedNode childKey:childKey oldIndexedNode:nil];
+}
+
+- (id)initWithType:(FIRDataEventType)type
+ indexedNode:(FIndexedNode *)indexedNode
+ childKey:(NSString *)childKey
+ oldIndexedNode:(FIndexedNode *)oldIndexedNode
+{
+ self = [super init];
+ if (self != nil) {
+ self->_type = type;
+ self->_indexedNode = indexedNode;
+ self->_childKey = childKey;
+ self->_oldIndexedNode = oldIndexedNode;
+ }
+ return self;
+}
+
+- (FChange *) changeWithPrevKey:(NSString *)prevKey {
+ FChange *newChange = [[FChange alloc] initWithType:self.type
+ indexedNode:self.indexedNode
+ childKey:self.childKey
+ oldIndexedNode:self.oldIndexedNode];
+ newChange.prevKey = prevKey;
+ return newChange;
+}
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"event: %d, data: %@", (int)self.type, [self.indexedNode.node val]];
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FChildEventRegistration.h b/Firebase/Database/Core/View/FChildEventRegistration.h
new file mode 100644
index 0000000..8da0b8f
--- /dev/null
+++ b/Firebase/Database/Core/View/FChildEventRegistration.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FEventRegistration.h"
+#import "FTypedefs.h"
+
+@class FRepo;
+
+@interface FChildEventRegistration : NSObject <FEventRegistration>
+
+- (id) initWithRepo:(FRepo *)repo
+ handle:(FIRDatabaseHandle)fHandle
+ callbacks:(NSDictionary *)callbackBlocks
+ cancelCallback:(fbt_void_nserror)cancelCallbackBlock;
+
+/**
+* Maps FIRDataEventType (as NSNumber) to fbt_void_datasnapshot_nsstring
+*/
+@property (nonatomic, copy, readonly) NSDictionary *callbacks;
+@property (nonatomic, copy, readonly) fbt_void_nserror cancelCallback;
+@property (nonatomic, readonly) FIRDatabaseHandle handle;
+
+@end
diff --git a/Firebase/Database/Core/View/FChildEventRegistration.m b/Firebase/Database/Core/View/FChildEventRegistration.m
new file mode 100644
index 0000000..6308a90
--- /dev/null
+++ b/Firebase/Database/Core/View/FChildEventRegistration.m
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#import "FChildEventRegistration.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FQueryParams.h"
+#import "FQuerySpec.h"
+#import "FIRDataSnapshot_Private.h"
+#import "FDataEvent.h"
+#import "FCancelEvent.h"
+
+@interface FChildEventRegistration ()
+@property (nonatomic, strong) FRepo *repo;
+@property (nonatomic, copy, readwrite) NSDictionary *callbacks;
+@property (nonatomic, copy, readwrite) fbt_void_nserror cancelCallback;
+@property (nonatomic, readwrite) FIRDatabaseHandle handle;
+@end
+
+@implementation FChildEventRegistration
+
+- (id)initWithRepo:(id)repo handle:(FIRDatabaseHandle)fHandle callbacks:(NSDictionary *)callbackBlocks cancelCallback:(fbt_void_nserror)cancelCallbackBlock {
+ self = [super init];
+ if (self) {
+ self.repo = repo;
+ self.handle = fHandle;
+ self.callbacks = callbackBlocks;
+ self.cancelCallback = cancelCallbackBlock;
+ }
+ return self;
+}
+
+- (BOOL) responseTo:(FIRDataEventType)eventType {
+ return self.callbacks != nil && [self.callbacks objectForKey:[NSNumber numberWithInteger:eventType]] != nil;
+}
+
+- (FDataEvent *) createEventFrom:(FChange *)change query:(FQuerySpec *)query {
+ FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] initWithRepo:self.repo path:[query.path childFromString:change.childKey]];
+ FIRDataSnapshot *snapshot = [[FIRDataSnapshot alloc] initWithRef:ref indexedNode:change.indexedNode];
+
+ FDataEvent *eventData = [[FDataEvent alloc] initWithEventType:change.type eventRegistration:self
+ dataSnapshot:snapshot prevName:change.prevKey];
+ return eventData;
+}
+
+- (void) fireEvent:(id <FEvent>)event queue:(dispatch_queue_t)queue {
+ if ([event isCancelEvent]) {
+ FCancelEvent *cancelEvent = event;
+ FFLog(@"I-RDB061001", @"Raising cancel value event on %@", event.path);
+ NSAssert(self.cancelCallback != nil, @"Raising a cancel event on a listener with no cancel callback");
+ dispatch_async(queue, ^{
+ self.cancelCallback(cancelEvent.error);
+ });
+ } else if (self.callbacks != nil) {
+ FDataEvent *dataEvent = event;
+ FFLog(@"I-RDB061002", @"Raising event callback (%ld) on %@", (long)dataEvent.eventType, dataEvent.path);
+ fbt_void_datasnapshot_nsstring callback = [self.callbacks objectForKey:[NSNumber numberWithInteger:dataEvent.eventType]];
+
+ if (callback != nil) {
+ dispatch_async(queue, ^{
+ callback(dataEvent.snapshot, dataEvent.prevName);
+ });
+ }
+ }
+}
+
+- (FCancelEvent *) createCancelEventFromError:(NSError *)error path:(FPath *)path {
+ if (self.cancelCallback != nil) {
+ return [[FCancelEvent alloc] initWithEventRegistration:self error:error path:path];
+ } else {
+ return nil;
+ }
+}
+
+- (BOOL) matches:(id<FEventRegistration>)other {
+ return self.handle == NSNotFound || other.handle == NSNotFound || self.handle == other.handle;
+}
+
+
+@end
diff --git a/Firebase/Database/Core/View/FDataEvent.h b/Firebase/Database/Core/View/FDataEvent.h
new file mode 100644
index 0000000..da90b03
--- /dev/null
+++ b/Firebase/Database/Core/View/FDataEvent.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FIRDataSnapshot.h"
+#import "FIRDatabaseReference.h"
+#import "FTupleUserCallback.h"
+#import "FEvent.h"
+
+@protocol FEventRegistration;
+@protocol FIndex;
+
+@interface FDataEvent : NSObject<FEvent>
+
+- initWithEventType:(FIRDataEventType)type eventRegistration:(id<FEventRegistration>)eventRegistration
+ dataSnapshot:(FIRDataSnapshot *)dataSnapshot;
+- initWithEventType:(FIRDataEventType)type eventRegistration:(id<FEventRegistration>)eventRegistration
+ dataSnapshot:(FIRDataSnapshot *)snapshot prevName:(NSString *)prevName;
+
+
+@property (nonatomic, strong, readonly) id<FEventRegistration> eventRegistration;
+@property (nonatomic, strong, readonly) FIRDataSnapshot * snapshot;
+@property (nonatomic, strong, readonly) NSString* prevName;
+@property (nonatomic, readonly) FIRDataEventType eventType;
+
+@end
diff --git a/Firebase/Database/Core/View/FDataEvent.m b/Firebase/Database/Core/View/FDataEvent.m
new file mode 100644
index 0000000..6c97faf
--- /dev/null
+++ b/Firebase/Database/Core/View/FDataEvent.m
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#import "FDataEvent.h"
+#import "FEventRegistration.h"
+#import "FIndex.h"
+#import "FIRDatabaseQuery_Private.h"
+
+@interface FDataEvent ()
+@property (nonatomic, strong, readwrite) id<FEventRegistration> eventRegistration;
+@property (nonatomic, strong, readwrite) FIRDataSnapshot *snapshot;
+@property (nonatomic, strong, readwrite) NSString *prevName;
+@property (nonatomic, readwrite) FIRDataEventType eventType;
+@end
+
+@implementation FDataEvent
+
+@synthesize eventRegistration;
+@synthesize snapshot;
+@synthesize prevName;
+@synthesize eventType;
+
+- (id)initWithEventType:(FIRDataEventType)type eventRegistration:(id <FEventRegistration>)registration dataSnapshot:(FIRDataSnapshot *)dataSnapshot {
+ return [self initWithEventType:type eventRegistration:registration dataSnapshot:dataSnapshot prevName:nil];
+}
+
+- (id)initWithEventType:(FIRDataEventType)type eventRegistration:(id <FEventRegistration>)registration dataSnapshot:(FIRDataSnapshot *)dataSnapshot prevName:(NSString *)previousName {
+ self = [super init];
+ if (self) {
+ self.eventRegistration = registration;
+ self.snapshot = dataSnapshot;
+ self.prevName = previousName;
+ self.eventType = type;
+ }
+ return self;
+}
+
+- (FPath *) path {
+ // Used for logging, so delay calculation
+ FIRDatabaseReference *ref = self.snapshot.ref;
+ if (self.eventType == FIRDataEventTypeValue) {
+ return ref.path;
+ } else {
+ return ref.parent.path;
+ }
+}
+
+- (void) fireEventOnQueue:(dispatch_queue_t)queue {
+ [self.eventRegistration fireEvent:self queue:queue];
+}
+
+- (BOOL) isCancelEvent {
+ return NO;
+}
+
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"event %d, data: %@", (int) eventType, [snapshot value]];
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FEvent.h b/Firebase/Database/Core/View/FEvent.h
new file mode 100644
index 0000000..6b9e31a
--- /dev/null
+++ b/Firebase/Database/Core/View/FEvent.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FIRDataEventType.h"
+
+@class FPath;
+
+@protocol FEvent <NSObject>
+- (FPath *) path;
+- (void) fireEventOnQueue:(dispatch_queue_t)queue;
+- (BOOL) isCancelEvent;
+- (NSString *) description;
+@end
diff --git a/Firebase/Database/Core/View/FEventRaiser.h b/Firebase/Database/Core/View/FEventRaiser.h
new file mode 100644
index 0000000..01a0130
--- /dev/null
+++ b/Firebase/Database/Core/View/FEventRaiser.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#import "FTypedefs.h"
+
+@class FPath;
+@class FRepo;
+@class FIRDatabaseConfig;
+
+/**
+* Left as instance methods rather than class methods so that we could potentially callback on different queues for different repos.
+* This is semi-parallel to JS's FEventQueue
+*/
+@interface FEventRaiser : NSObject
+
+- (id)initWithQueue:(dispatch_queue_t)queue;
+
+- (void) raiseEvents:(NSArray *)eventDataList;
+- (void) raiseCallback:(fbt_void_void)callback;
+- (void) raiseCallbacks:(NSArray *)callbackList;
+
+@end
diff --git a/Firebase/Database/Core/View/FEventRaiser.m b/Firebase/Database/Core/View/FEventRaiser.m
new file mode 100644
index 0000000..94a0907
--- /dev/null
+++ b/Firebase/Database/Core/View/FEventRaiser.m
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#import "FEventRaiser.h"
+#import "FDataEvent.h"
+#import "FTypedefs.h"
+#import "FUtilities.h"
+#import "FTupleUserCallback.h"
+#import "FRepo.h"
+#import "FRepoManager.h"
+
+@interface FEventRaiser ()
+
+@property (nonatomic, strong) dispatch_queue_t queue;
+
+@end
+
+/**
+* This class exists for symmetry with other clients, but since events are async, we don't need to do the complicated
+* stuff the JS client does to preserve event order.
+*/
+@implementation FEventRaiser
+
+- (id)init {
+ [NSException raise:NSInternalInconsistencyException format:@"Can't use default constructor"];
+ return nil;
+}
+
+- (id)initWithQueue:(dispatch_queue_t)queue {
+ self = [super init];
+ if (self != nil) {
+ self->_queue = queue;
+ }
+ return self;
+}
+
+- (void) raiseEvents:(NSArray *)eventDataList {
+ for (id<FEvent> event in eventDataList) {
+ [event fireEventOnQueue:self.queue];
+ }
+}
+
+- (void) raiseCallback:(fbt_void_void)callback {
+ dispatch_async(self.queue, callback);
+}
+
+- (void) raiseCallbacks:(NSArray *)callbackList {
+ for (fbt_void_void callback in callbackList) {
+ dispatch_async(self.queue, callback);
+ }
+}
+
++ (void) raiseCallbacks:(NSArray *)callbackList queue:(dispatch_queue_t)queue {
+ for (fbt_void_void callback in callbackList) {
+ dispatch_async(queue, callback);
+ }
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FEventRegistration.h b/Firebase/Database/Core/View/FEventRegistration.h
new file mode 100644
index 0000000..5b845ac
--- /dev/null
+++ b/Firebase/Database/Core/View/FEventRegistration.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FChange.h"
+#import "FIRDataEventType.h"
+
+@protocol FEvent;
+@class FDataEvent;
+@class FCancelEvent;
+@class FQuerySpec;
+
+@protocol FEventRegistration <NSObject>
+- (BOOL) responseTo:(FIRDataEventType)eventType;
+- (FDataEvent *) createEventFrom:(FChange *)change query:(FQuerySpec *)query;
+- (void) fireEvent:(id<FEvent>)event queue:(dispatch_queue_t)queue;
+- (FCancelEvent *) createCancelEventFromError:(NSError *)error path:(FPath *)path;
+/**
+* Used to figure out what event registration match the event registration that needs to be removed.
+*/
+- (BOOL) matches:(id<FEventRegistration>)other;
+@property (nonatomic, readonly) FIRDatabaseHandle handle;
+@end
diff --git a/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h b/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h
new file mode 100644
index 0000000..669e012
--- /dev/null
+++ b/Firebase/Database/Core/View/FKeepSyncedEventRegistration.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FEventRegistration.h"
+
+/**
+ * A singleton event registration to mark a query as keep synced
+ */
+@interface FKeepSyncedEventRegistration : NSObject<FEventRegistration>
+
++ (FKeepSyncedEventRegistration *)instance;
+
+@end
diff --git a/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m b/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m
new file mode 100644
index 0000000..806d54f
--- /dev/null
+++ b/Firebase/Database/Core/View/FKeepSyncedEventRegistration.m
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#import "FKeepSyncedEventRegistration.h"
+
+@interface FKeepSyncedEventRegistration ()
+
+@end
+
+@implementation FKeepSyncedEventRegistration
+
++ (FKeepSyncedEventRegistration *)instance {
+ static dispatch_once_t onceToken;
+ static FKeepSyncedEventRegistration *keepSynced;
+ dispatch_once(&onceToken, ^{
+ keepSynced = [[FKeepSyncedEventRegistration alloc] init];
+ });
+ return keepSynced;
+}
+
+- (BOOL) responseTo:(FIRDataEventType)eventType {
+ return NO;
+}
+
+- (FDataEvent *) createEventFrom:(FChange *)change query:(FQuerySpec *)query {
+ [NSException raise:NSInternalInconsistencyException format:@"Should never create event for FKeepSyncedEventRegistration"];
+ return nil;
+}
+
+- (void) fireEvent:(id<FEvent>)event queue:(dispatch_queue_t)queue {
+ [NSException raise:NSInternalInconsistencyException format:@"Should never raise event for FKeepSyncedEventRegistration"];
+}
+
+- (FCancelEvent *) createCancelEventFromError:(NSError *)error path:(FPath *)path {
+ // Don't create cancel events....
+ return nil;
+}
+
+- (FIRDatabaseHandle) handle {
+ // TODO[offline]: returning arbitray, can't return NSNotFound since that is used to match other event registrations
+ // We should really redo this to match on different kind of events (single observer, all observers, cancelled)
+ // rather than on a NSNotFound handle...
+ return NSNotFound - 1;
+}
+
+- (BOOL) matches:(id<FEventRegistration>)other {
+ // Only matches singleton instance
+ return self == other;
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FValueEventRegistration.h b/Firebase/Database/Core/View/FValueEventRegistration.h
new file mode 100644
index 0000000..1220c60
--- /dev/null
+++ b/Firebase/Database/Core/View/FValueEventRegistration.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FEventRegistration.h"
+#import "FTypedefs.h"
+
+@class FRepo;
+
+@interface FValueEventRegistration : NSObject<FEventRegistration>
+
+- (id) initWithRepo:(FRepo *)repo
+ handle:(FIRDatabaseHandle)fHandle
+ callback:(fbt_void_datasnapshot)callbackBlock
+ cancelCallback:(fbt_void_nserror)cancelCallbackBlock;
+
+@property (nonatomic, copy, readonly) fbt_void_datasnapshot callback;
+@property (nonatomic, copy, readonly) fbt_void_nserror cancelCallback;
+@property (nonatomic, readonly) FIRDatabaseHandle handle;
+
+@end
diff --git a/Firebase/Database/Core/View/FValueEventRegistration.m b/Firebase/Database/Core/View/FValueEventRegistration.m
new file mode 100644
index 0000000..d351a4b
--- /dev/null
+++ b/Firebase/Database/Core/View/FValueEventRegistration.m
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#import "FValueEventRegistration.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FQueryParams.h"
+#import "FQuerySpec.h"
+#import "FIRDataSnapshot_Private.h"
+#import "FCancelEvent.h"
+#import "FDataEvent.h"
+
+@interface FValueEventRegistration ()
+@property (nonatomic, strong) FRepo* repo;
+@property (nonatomic, copy, readwrite) fbt_void_datasnapshot callback;
+@property (nonatomic, copy, readwrite) fbt_void_nserror cancelCallback;
+@property (nonatomic, readwrite) FIRDatabaseHandle handle;
+@end
+
+@implementation FValueEventRegistration
+
+- (id) initWithRepo:(FRepo *)repo
+ handle:(FIRDatabaseHandle)fHandle
+ callback:(fbt_void_datasnapshot)callbackBlock
+ cancelCallback:(fbt_void_nserror)cancelCallbackBlock {
+ self = [super init];
+ if (self) {
+ self.repo = repo;
+ self.handle = fHandle;
+ self.callback = callbackBlock;
+ self.cancelCallback = cancelCallbackBlock;
+ }
+ return self;
+}
+
+- (BOOL) responseTo:(FIRDataEventType)eventType {
+ return eventType == FIRDataEventTypeValue;
+}
+
+- (FDataEvent *) createEventFrom:(FChange *)change query:(FQuerySpec *)query {
+ FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] initWithRepo:self.repo path:query.path];
+ FIRDataSnapshot *snapshot = [[FIRDataSnapshot alloc] initWithRef:ref indexedNode:change.indexedNode];
+ FDataEvent *eventData = [[FDataEvent alloc] initWithEventType:FIRDataEventTypeValue eventRegistration:self
+ dataSnapshot:snapshot];
+ return eventData;
+}
+
+- (void) fireEvent:(id <FEvent>)event queue:(dispatch_queue_t)queue {
+ if ([event isCancelEvent]) {
+ FCancelEvent *cancelEvent = event;
+ FFLog(@"I-RDB065001", @"Raising cancel value event on %@", event.path);
+ NSAssert(self.cancelCallback != nil, @"Raising a cancel event on a listener with no cancel callback");
+ dispatch_async(queue, ^{
+ self.cancelCallback(cancelEvent.error);
+ });
+ } else if (self.callback != nil) {
+ FDataEvent *dataEvent = event;
+ FFLog(@"I-RDB065002", @"Raising value event on %@", dataEvent.snapshot.key);
+ dispatch_async(queue, ^{
+ self.callback(dataEvent.snapshot);
+ });
+ }
+}
+
+- (FCancelEvent *) createCancelEventFromError:(NSError *)error path:(FPath *)path {
+ if (self.cancelCallback != nil) {
+ return [[FCancelEvent alloc] initWithEventRegistration:self error:error path:path];
+ } else {
+ return nil;
+ }
+}
+
+- (BOOL) matches:(id<FEventRegistration>)other {
+ return self.handle == NSNotFound || other.handle == NSNotFound || self.handle == other.handle;
+}
+
+@end
diff --git a/Firebase/Database/Core/View/FView.h b/Firebase/Database/Core/View/FView.h
new file mode 100644
index 0000000..2d0761a
--- /dev/null
+++ b/Firebase/Database/Core/View/FView.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+@protocol FNode;
+@protocol FOperation;
+@protocol FEventRegistration;
+@class FWriteTreeRef;
+@class FQuerySpec;
+@class FChange;
+@class FPath;
+@class FViewCache;
+
+@interface FViewOperationResult : NSObject
+
+@property (nonatomic, strong, readonly) NSArray* changes;
+@property (nonatomic, strong, readonly) NSArray* events;
+
+@end
+
+
+@interface FView : NSObject
+
+@property (nonatomic, strong, readonly) FQuerySpec *query;
+
+- (id) initWithQuery:(FQuerySpec *)query initialViewCache:(FViewCache *)initialViewCache;
+
+- (id<FNode>) eventCache;
+- (id<FNode>) serverCache;
+- (id<FNode>) completeServerCacheFor:(FPath*)path;
+- (BOOL) isEmpty;
+
+- (void) addEventRegistration:(id<FEventRegistration>)eventRegistration;
+- (NSArray *) removeEventRegistration:(id<FEventRegistration>)eventRegistration cancelError:(NSError *)cancelError;
+
+- (FViewOperationResult *) applyOperation:(id <FOperation>)operation writesCache:(FWriteTreeRef *)writesCache serverCache:(id <FNode>)optCompleteServerCache;
+- (NSArray *) initialEvents:(id<FEventRegistration>)registration;
+
+@end
diff --git a/Firebase/Database/Core/View/FView.m b/Firebase/Database/Core/View/FView.m
new file mode 100644
index 0000000..1aea4d7
--- /dev/null
+++ b/Firebase/Database/Core/View/FView.m
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ */
+
+#import "FView.h"
+#import "FNode.h"
+#import "FWriteTreeRef.h"
+#import "FOperation.h"
+#import "FIRDatabaseQuery.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FEventRegistration.h"
+#import "FQueryParams.h"
+#import "FQuerySpec.h"
+#import "FViewCache.h"
+#import "FPath.h"
+#import "FEventGenerator.h"
+#import "FOperationSource.h"
+#import "FCancelEvent.h"
+#import "FIndexedFilter.h"
+#import "FCacheNode.h"
+#import "FEmptyNode.h"
+#import "FViewProcessor.h"
+#import "FViewProcessorResult.h"
+#import "FIndexedNode.h"
+
+@interface FViewOperationResult ()
+
+@property (nonatomic, strong, readwrite) NSArray *changes;
+@property (nonatomic, strong, readwrite) NSArray *events;
+
+@end
+
+@implementation FViewOperationResult
+
+- (id)initWithChanges:(NSArray *)changes events:(NSArray *)events {
+ self = [super init];
+ if (self != nil) {
+ self->_changes = changes;
+ self->_events = events;
+ }
+ return self;
+}
+
+@end
+
+/**
+* A view represents a specific location and query that has 1 or more event registrations.
+*
+* It does several things:
+* - Maintains the list of event registration for this location/query.
+* - Maintains a cache of the data visible for this location/query.
+* - Applies new operations (via applyOperation), updates the cache, and based on the event
+* registrations returns the set of events to be raised.
+*/
+@interface FView ()
+
+@property (nonatomic, strong, readwrite) FQuerySpec *query;
+@property (nonatomic, strong) FViewProcessor *processor;
+@property (nonatomic, strong) FViewCache *viewCache;
+@property (nonatomic, strong) NSMutableArray *eventRegistrations;
+@property (nonatomic, strong) FEventGenerator *eventGenerator;
+
+@end
+
+@implementation FView
+- (id) initWithQuery:(FQuerySpec *)query initialViewCache:(FViewCache *)initialViewCache {
+ self = [super init];
+ if (self) {
+ self.query = query;
+
+ FIndexedFilter *indexFilter = [[FIndexedFilter alloc] initWithIndex:query.index];
+ id<FNodeFilter> filter = query.params.nodeFilter;
+ self.processor = [[FViewProcessor alloc] initWithFilter:filter];
+ FCacheNode *initialServerCache = initialViewCache.cachedServerSnap;
+ FCacheNode *initialEventCache = initialViewCache.cachedEventSnap;
+
+ // Don't filter server node with other filter than index, wait for tagged listen
+ FIndexedNode *emptyIndexedNode = [FIndexedNode indexedNodeWithNode:[FEmptyNode emptyNode] index:query.index];
+ FIndexedNode *serverSnap = [indexFilter updateFullNode:emptyIndexedNode
+ withNewNode:initialServerCache.indexedNode
+ accumulator:nil];
+ FIndexedNode *eventSnap = [filter updateFullNode:emptyIndexedNode
+ withNewNode:initialEventCache.indexedNode
+ accumulator:nil];
+ FCacheNode *newServerCache = [[FCacheNode alloc] initWithIndexedNode:serverSnap
+ isFullyInitialized:initialServerCache.isFullyInitialized
+ isFiltered:indexFilter.filtersNodes];
+ FCacheNode *newEventCache = [[FCacheNode alloc] initWithIndexedNode:eventSnap
+ isFullyInitialized:initialEventCache.isFullyInitialized
+ isFiltered:filter.filtersNodes];
+
+ self.viewCache = [[FViewCache alloc] initWithEventCache:newEventCache serverCache:newServerCache];
+
+ self.eventRegistrations = [[NSMutableArray alloc] init];
+
+ self.eventGenerator = [[FEventGenerator alloc] initWithQuery:query];
+ }
+
+ return self;
+}
+
+- (id <FNode>) serverCache {
+ return self.viewCache.cachedServerSnap.node;
+}
+
+- (id <FNode>) eventCache {
+ return self.viewCache.cachedEventSnap.node;
+}
+
+- (id <FNode>) completeServerCacheFor:(FPath*)path {
+ id<FNode> cache = self.viewCache.completeServerSnap;
+ if (cache) {
+ // If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
+ // we need to see if it contains the child we're interested in.
+ if ([self.query loadsAllData] ||
+ (!path.isEmpty && ![cache getImmediateChild:path.getFront].isEmpty)) {
+ return [cache getChild:path];
+ }
+ }
+ return nil;
+}
+
+- (BOOL) isEmpty {
+ return self.eventRegistrations.count == 0;
+}
+
+- (void) addEventRegistration:(id <FEventRegistration>)eventRegistration {
+ [self.eventRegistrations addObject:eventRegistration];
+}
+
+/**
+* @param eventRegistration If null, remove all callbacks.
+* @param cancelError If a cancelError is provided, appropriate cancel events will be returned.
+* @return Cancel events, if cancelError was provided.
+*/
+- (NSArray *) removeEventRegistration:(id <FEventRegistration>)eventRegistration cancelError:(NSError *)cancelError {
+ NSMutableArray *cancelEvents = [[NSMutableArray alloc] init];
+ if (cancelError != nil) {
+ NSAssert(eventRegistration == nil, @"A cancel should cancel all event registrations.");
+ FPath *path = self.query.path;
+ for (id <FEventRegistration> registration in self.eventRegistrations) {
+ FCancelEvent *maybeEvent = [registration createCancelEventFromError:cancelError path:path];
+ if (maybeEvent) {
+ [cancelEvents addObject:maybeEvent];
+ }
+ }
+ }
+
+ if (eventRegistration) {
+ NSUInteger i = 0;
+ while (i < self.eventRegistrations.count) {
+ id<FEventRegistration> existing = self.eventRegistrations[i];
+ if ([existing matches:eventRegistration]) {
+ [self.eventRegistrations removeObjectAtIndex:i];
+ } else {
+ i++;
+ }
+ }
+ } else {
+ [self.eventRegistrations removeAllObjects];
+ }
+ return cancelEvents;
+}
+
+/**
+ * Applies the given Operation, updates our cache, and returns the appropriate events and changes
+ */
+- (FViewOperationResult *) applyOperation:(id <FOperation>)operation writesCache:(FWriteTreeRef *)writesCache serverCache:(id <FNode>)optCompleteServerCache {
+ if (operation.type == FOperationTypeMerge && operation.source.queryParams != nil) {
+ NSAssert(self.viewCache.completeServerSnap != nil, @"We should always have a full cache before handling merges");
+ NSAssert(self.viewCache.completeEventSnap != nil, @"Missing event cache, even though we have a server cache");
+ }
+ FViewCache *oldViewCache = self.viewCache;
+ FViewProcessorResult *result = [self.processor applyOperationOn:oldViewCache operation:operation writesCache:writesCache completeCache:optCompleteServerCache];
+
+ NSAssert(result.viewCache.cachedServerSnap.isFullyInitialized || !oldViewCache.cachedServerSnap.isFullyInitialized, @"Once a server snap is complete, it should never go back.");
+
+ self.viewCache = result.viewCache;
+ NSArray *events = [self generateEventsForChanges:result.changes eventCache:result.viewCache.cachedEventSnap.indexedNode registration:nil];
+ return [[FViewOperationResult alloc] initWithChanges:result.changes events:events];
+}
+
+- (NSArray *) initialEvents:(id<FEventRegistration>)registration {
+ FCacheNode *eventSnap = self.viewCache.cachedEventSnap;
+ NSMutableArray *initialChanges = [[NSMutableArray alloc] init];
+ [eventSnap.indexedNode.node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
+ FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:node];
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded indexedNode:indexed childKey:key];
+ [initialChanges addObject:change];
+ }];
+ if (eventSnap.isFullyInitialized) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeValue indexedNode:eventSnap.indexedNode];
+ [initialChanges addObject:change];
+ }
+ return [self generateEventsForChanges:initialChanges eventCache:eventSnap.indexedNode registration:registration];
+}
+
+- (NSArray *) generateEventsForChanges:(NSArray *)changes eventCache:(FIndexedNode *)eventCache registration:(id<FEventRegistration>)registration {
+ NSArray *registrations;
+ if (registration == nil) {
+ registrations = [[NSArray alloc] initWithArray:self.eventRegistrations];
+ } else {
+ registrations = [[NSArray alloc] initWithObjects:registration, nil];
+ }
+ return [self.eventGenerator generateEventsForChanges:changes eventCache:eventCache eventRegistrations:registrations];
+}
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"FView (%@)", self.query];
+}
+@end
diff --git a/Firebase/Database/Core/View/FViewCache.h b/Firebase/Database/Core/View/FViewCache.h
new file mode 100644
index 0000000..4d01877
--- /dev/null
+++ b/Firebase/Database/Core/View/FViewCache.h
@@ -0,0 +1,35 @@
+#/*
+* 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.
+*/
+
+#import <Foundation/Foundation.h>
+
+@protocol FNode;
+@class FCacheNode;
+@class FIndexedNode;
+
+@interface FViewCache : NSObject
+
+- (id) initWithEventCache:(FCacheNode *)eventCache serverCache:(FCacheNode *)serverCache;
+
+- (FViewCache *) updateEventSnap:(FIndexedNode *)eventSnap isComplete:(BOOL)complete isFiltered:(BOOL)filtered;
+- (FViewCache *) updateServerSnap:(FIndexedNode *)serverSnap isComplete:(BOOL)complete isFiltered:(BOOL)filtered;
+
+@property (nonatomic, strong, readonly) FCacheNode *cachedEventSnap;
+@property (nonatomic, strong, readonly) id<FNode> completeEventSnap;
+@property (nonatomic, strong, readonly) FCacheNode *cachedServerSnap;
+@property (nonatomic, strong, readonly) id<FNode> completeServerSnap;
+
+@end
diff --git a/Firebase/Database/Core/View/FViewCache.m b/Firebase/Database/Core/View/FViewCache.m
new file mode 100644
index 0000000..c6ec8b1
--- /dev/null
+++ b/Firebase/Database/Core/View/FViewCache.m
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#import "FViewCache.h"
+#import "FCacheNode.h"
+#import "FNode.h"
+#import "FEmptyNode.h"
+
+@interface FViewCache ()
+@property (nonatomic, strong, readwrite) FCacheNode *cachedEventSnap;
+@property (nonatomic, strong, readwrite) FCacheNode *cachedServerSnap;
+@end
+
+@implementation FViewCache
+
+- (id) initWithEventCache:(FCacheNode *)eventCache serverCache:(FCacheNode *)serverCache {
+ self = [super init];
+ if (self) {
+ self.cachedEventSnap = eventCache;
+ self.cachedServerSnap = serverCache;
+ }
+ return self;
+}
+
+- (FViewCache *) updateEventSnap:(FIndexedNode *)eventSnap isComplete:(BOOL)complete isFiltered:(BOOL)filtered {
+ FCacheNode *updatedEventCache = [[FCacheNode alloc] initWithIndexedNode:eventSnap
+ isFullyInitialized:complete
+ isFiltered:filtered];
+ return [[FViewCache alloc] initWithEventCache:updatedEventCache serverCache:self.cachedServerSnap];
+}
+
+- (FViewCache *) updateServerSnap:(FIndexedNode *)serverSnap isComplete:(BOOL)complete isFiltered:(BOOL)filtered {
+ FCacheNode *updatedServerCache = [[FCacheNode alloc] initWithIndexedNode:serverSnap
+ isFullyInitialized:complete
+ isFiltered:filtered];
+ return [[FViewCache alloc] initWithEventCache:self.cachedEventSnap serverCache:updatedServerCache];
+}
+
+- (id<FNode>) completeEventSnap {
+ return (self.cachedEventSnap.isFullyInitialized) ? self.cachedEventSnap.node : nil;
+}
+
+- (id<FNode>) completeServerSnap {
+ return (self.cachedServerSnap.isFullyInitialized) ? self.cachedServerSnap.node : nil;
+}
+
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h b/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h
new file mode 100644
index 0000000..59b0a85
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class FChange;
+
+
+@interface FChildChangeAccumulator : NSObject
+
+- (id) init;
+- (void) trackChildChange:(FChange *)change;
+- (NSArray *) changes;
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m b/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m
new file mode 100644
index 0000000..e43fd7c
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FChildChangeAccumulator.m
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#import "FChildChangeAccumulator.h"
+#import "FChange.h"
+#import "FIndex.h"
+
+@interface FChildChangeAccumulator ()
+@property (nonatomic, strong) NSMutableDictionary *changeMap;
+@end
+
+@implementation FChildChangeAccumulator
+
+- (id) init {
+ self = [super init];
+ if (self) {
+ self.changeMap = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (void) trackChildChange:(FChange *)change {
+ FIRDataEventType type = change.type;
+ NSString *childKey = change.childKey;
+ NSAssert(type == FIRDataEventTypeChildAdded || type == FIRDataEventTypeChildChanged || type == FIRDataEventTypeChildRemoved, @"Only child changes supported for tracking.");
+ NSAssert(![change.childKey isEqualToString:@".priority"], @"Changes not tracked on priority");
+ if (self.changeMap[childKey] != nil) {
+ FChange *oldChange = [self.changeMap objectForKey:childKey];
+ FIRDataEventType oldType = oldChange.type;
+ if (type == FIRDataEventTypeChildAdded && oldType == FIRDataEventTypeChildRemoved) {
+ FChange *newChange = [[FChange alloc] initWithType:FIRDataEventTypeChildChanged
+ indexedNode:change.indexedNode
+ childKey:childKey
+ oldIndexedNode:oldChange.indexedNode];
+ [self.changeMap setObject:newChange forKey:childKey];
+ } else if (type == FIRDataEventTypeChildRemoved && oldType == FIRDataEventTypeChildAdded) {
+ [self.changeMap removeObjectForKey:childKey];
+ } else if (type == FIRDataEventTypeChildRemoved && oldType == FIRDataEventTypeChildChanged) {
+ FChange *newChange = [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved
+ indexedNode:oldChange.oldIndexedNode
+ childKey:childKey];
+ [self.changeMap setObject:newChange forKey:childKey];
+ } else if (type == FIRDataEventTypeChildChanged && oldType == FIRDataEventTypeChildAdded) {
+ FChange *newChange = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded
+ indexedNode:change.indexedNode
+ childKey:childKey];
+ [self.changeMap setObject:newChange forKey:childKey];
+ } else if (type == FIRDataEventTypeChildChanged && oldType == FIRDataEventTypeChildChanged) {
+ FChange *newChange = [[FChange alloc] initWithType:FIRDataEventTypeChildChanged
+ indexedNode:change.indexedNode
+ childKey:childKey
+ oldIndexedNode:oldChange.oldIndexedNode];
+ [self.changeMap setObject:newChange forKey:childKey];
+ } else {
+ NSString *reason = [NSString stringWithFormat:@"Illegal combination of changes: %@ occurred after %@", change, oldChange];
+ @throw [[NSException alloc] initWithName:@"FirebaseDatabaseInternalError" reason:reason userInfo:nil];
+ }
+ } else {
+ [self.changeMap setObject:change forKey:childKey];
+ }
+}
+
+- (NSArray *) changes {
+ return [self.changeMap allValues];
+}
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FCompleteChildSource.h b/Firebase/Database/Core/View/Filter/FCompleteChildSource.h
new file mode 100644
index 0000000..4e99045
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FCompleteChildSource.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+@protocol FNode;
+@class FNamedNode;
+@protocol FIndex;
+
+@protocol FCompleteChildSource<NSObject>
+
+- (id<FNode>) completeChild:(NSString *)childKey;
+- (FNamedNode *) childByIndex:(id<FIndex>)index afterChild:(FNamedNode *)child isReverse:(BOOL)reverse;
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FIndexedFilter.h b/Firebase/Database/Core/View/Filter/FIndexedFilter.h
new file mode 100644
index 0000000..5081a77
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FIndexedFilter.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FNodeFilter.h"
+
+@protocol FIndex;
+
+
+@interface FIndexedFilter : NSObject<FNodeFilter>
+
+- (id) initWithIndex:(id<FIndex>)theIndex;
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FIndexedFilter.m b/Firebase/Database/Core/View/Filter/FIndexedFilter.m
new file mode 100644
index 0000000..44c411c
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FIndexedFilter.m
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#import "FNode.h"
+#import "FIndexedFilter.h"
+#import "FChildChangeAccumulator.h"
+#import "FIndex.h"
+#import "FChange.h"
+#import "FChildrenNode.h"
+#import "FKeyIndex.h"
+#import "FEmptyNode.h"
+#import "FIndexedNode.h"
+
+@interface FIndexedFilter ()
+@property (nonatomic, strong, readwrite) id<FIndex> index;
+@end
+
+@implementation FIndexedFilter
+- (id) initWithIndex:(id<FIndex>)theIndex {
+ self = [super init];
+ if (self) {
+ self.index = theIndex;
+ }
+ return self;
+}
+
+- (FIndexedNode *)updateChildIn:(FIndexedNode *)indexedNode
+ forChildKey:(NSString *)childKey
+ newChild:(id<FNode>)newChildSnap
+ affectedPath:(FPath *)affectedPath
+ fromSource:(id<FCompleteChildSource>)source
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator
+{
+ NSAssert([indexedNode hasIndex:self.index], @"The index in FIndexedNode must match the index of the filter");
+ id<FNode> node = indexedNode.node;
+ id<FNode> oldChildSnap = [node getImmediateChild:childKey];
+
+ // Check if anything actually changed.
+ if ([[oldChildSnap getChild:affectedPath] isEqual:[newChildSnap getChild:affectedPath]]) {
+ // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
+ // In this case, affectedPath will appear null in both the old and new snapshots. So we need
+ // to avoid treating these cases as "nothing changed."
+ if (oldChildSnap.isEmpty == newChildSnap.isEmpty) {
+ // Nothing changed.
+ #ifdef DEBUG
+ NSAssert([oldChildSnap isEqual:newChildSnap], @"Old and new snapshots should be equal.");
+ #endif
+
+ return indexedNode;
+ }
+ }
+ if (optChangeAccumulator) {
+ if (newChildSnap.isEmpty) {
+ if ([node hasChild:childKey]) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved
+ indexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:change];
+ } else {
+ NSAssert(node.isLeafNode, @"A child remove without an old child only makes sense on a leaf node.");
+ }
+ } else if (oldChildSnap.isEmpty) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded
+ indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:change];
+ } else {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildChanged
+ indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap]
+ childKey:childKey
+ oldIndexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ }
+ if (node.isLeafNode && newChildSnap.isEmpty) {
+ return indexedNode;
+ } else {
+ return [indexedNode updateChild:childKey withNewChild:newChildSnap];
+ }
+}
+
+- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap
+ withNewNode:(FIndexedNode *)newSnap
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator
+{
+ if (optChangeAccumulator) {
+ [oldSnap.node enumerateChildrenUsingBlock:^(NSString *childKey, id<FNode> childNode, BOOL *stop) {
+ if (![newSnap.node hasChild:childKey]) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved
+ indexedNode:[FIndexedNode indexedNodeWithNode:childNode]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ }];
+
+ [newSnap.node enumerateChildrenUsingBlock:^(NSString *childKey, id<FNode> childNode, BOOL *stop) {
+ if ([oldSnap.node hasChild:childKey]) {
+ id<FNode> oldChildSnap = [oldSnap.node getImmediateChild:childKey];
+ if (![oldChildSnap isEqual:childNode]) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildChanged
+ indexedNode:[FIndexedNode indexedNodeWithNode:childNode]
+ childKey:childKey
+ oldIndexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ } else {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded
+ indexedNode:[FIndexedNode indexedNodeWithNode:childNode]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ }];
+ }
+ return newSnap;
+}
+
+- (FIndexedNode *)updatePriority:(id<FNode>)priority forNode:(FIndexedNode *)oldSnap
+{
+ if ([oldSnap.node isEmpty]) {
+ return oldSnap;
+ } else {
+ return [oldSnap updatePriority:priority];
+ }
+}
+
+- (BOOL) filtersNodes {
+ return NO;
+}
+
+- (id<FNodeFilter>) indexedFilter {
+ return self;
+}
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FLimitedFilter.h b/Firebase/Database/Core/View/Filter/FLimitedFilter.h
new file mode 100644
index 0000000..1690980
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FLimitedFilter.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "FNodeFilter.h"
+
+@class FQueryParams;
+
+
+@interface FLimitedFilter : NSObject<FNodeFilter>
+
+- (id) initWithQueryParams:(FQueryParams *)params;
+@end
diff --git a/Firebase/Database/Core/View/Filter/FLimitedFilter.m b/Firebase/Database/Core/View/Filter/FLimitedFilter.m
new file mode 100644
index 0000000..8bc6e87
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FLimitedFilter.m
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+#import "FLimitedFilter.h"
+#import "FChildChangeAccumulator.h"
+#import "FIndex.h"
+#import "FRangedFilter.h"
+#import "FQueryParams.h"
+#import "FQueryParams.h"
+#import "FNamedNode.h"
+#import "FEmptyNode.h"
+#import "FChildrenNode.h"
+#import "FCompleteChildSource.h"
+#import "FChange.h"
+#import "FTreeSortedDictionary.h"
+
+@interface FLimitedFilter ()
+@property (nonatomic, strong) FRangedFilter *rangedFilter;
+@property (nonatomic, strong, readwrite) id<FIndex> index;
+@property (nonatomic) NSInteger limit;
+@property (nonatomic) BOOL reverse;
+
+@end
+
+@implementation FLimitedFilter
+- (id) initWithQueryParams:(FQueryParams *)params {
+ self = [super init];
+ if (self) {
+ self.rangedFilter = [[FRangedFilter alloc] initWithQueryParams:params];
+ self.index = params.index;
+ self.limit = params.limit;
+ self.reverse = !params.isViewFromLeft;
+ }
+ return self;
+}
+
+
+- (FIndexedNode *)updateChildIn:(FIndexedNode *)oldSnap
+ forChildKey:(NSString *)childKey
+ newChild:(id<FNode>)newChildSnap
+ affectedPath:(FPath *)affectedPath
+ fromSource:(id<FCompleteChildSource>)source
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator
+{
+ if (![self.rangedFilter matchesKey:childKey andNode:newChildSnap]) {
+ newChildSnap = [FEmptyNode emptyNode];
+ }
+ if ([[oldSnap.node getImmediateChild:childKey] isEqual:newChildSnap]) {
+ // No change
+ return oldSnap;
+ } else if (oldSnap.node.numChildren < self.limit) {
+ return [[self.rangedFilter indexedFilter] updateChildIn:oldSnap
+ forChildKey:childKey
+ newChild:newChildSnap
+ affectedPath:affectedPath
+ fromSource:source
+ accumulator:optChangeAccumulator];
+ } else {
+ return [self fullLimitUpdateNode:oldSnap
+ forChildKey:childKey
+ newChild:newChildSnap
+ fromSource:source
+ accumulator:optChangeAccumulator];
+ }
+}
+
+- (FIndexedNode *)fullLimitUpdateNode:(FIndexedNode *)oldIndexed
+ forChildKey:(NSString *)childKey
+ newChild:(id<FNode>)newChildSnap
+ fromSource:(id<FCompleteChildSource>)source
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator
+{
+ NSAssert(oldIndexed.node.numChildren == self.limit, @"Should have number of children equal to limit.");
+
+ FNamedNode *windowBoundary = self.reverse ? oldIndexed.firstChild : oldIndexed.lastChild;
+
+ BOOL inRange = [self.rangedFilter matchesKey:childKey andNode:newChildSnap];
+ if ([oldIndexed.node hasChild:childKey]) {
+ // `childKey` was already in `oldSnap`. Figure out if it remains in the window or needs to be replaced.
+ id<FNode> oldChildSnap = [oldIndexed.node getImmediateChild:childKey];
+
+ // In case the `newChildSnap` falls outside the window, get the `nextChild` that might replace it.
+ FNamedNode *nextChild = [source childByIndex:self.index afterChild:windowBoundary isReverse:(BOOL)self.reverse];
+ if (nextChild != nil && ([nextChild.name isEqualToString:childKey] ||
+ [oldIndexed.node hasChild:nextChild.name])) {
+ // There is a weird edge case where a node is updated as part of a merge in the write tree, but hasn't
+ // been applied to the limited filter yet. Ignore this next child which will be updated later in
+ // the limited filter...
+ nextChild = [source childByIndex:self.index afterChild:nextChild isReverse:self.reverse];
+ }
+
+
+
+ // Figure out if `newChildSnap` is in range and ordered before `nextChild`
+ BOOL remainsInWindow = inRange && !newChildSnap.isEmpty;
+ remainsInWindow = remainsInWindow && (!nextChild || [self.index compareKey:nextChild.name
+ andNode:nextChild.node
+ toOtherKey:childKey
+ andNode:newChildSnap
+ reverse:self.reverse] >= NSOrderedSame);
+ if (remainsInWindow) {
+ // `newChildSnap` is ordered before `nextChild`, so it's a child changed event
+ if (optChangeAccumulator != nil) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildChanged
+ indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap]
+ childKey:childKey
+ oldIndexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ return [oldIndexed updateChild:childKey withNewChild:newChildSnap];
+ } else {
+ // `newChildSnap` is ordered after `nextChild`, so it's a child removed event
+ if (optChangeAccumulator != nil) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved
+ indexedNode:[FIndexedNode indexedNodeWithNode:oldChildSnap]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ FIndexedNode *newIndexed = [oldIndexed updateChild:childKey withNewChild:[FEmptyNode emptyNode]];
+
+ // We need to check if the `nextChild` is actually in range before adding it
+ BOOL nextChildInRange = (nextChild != nil) && [self.rangedFilter matchesKey:nextChild.name
+ andNode:nextChild.node];
+ if (nextChildInRange) {
+ if (optChangeAccumulator != nil) {
+ FChange *change = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded
+ indexedNode:[FIndexedNode indexedNodeWithNode:nextChild.node]
+ childKey:nextChild.name];
+ [optChangeAccumulator trackChildChange:change];
+ }
+ return [newIndexed updateChild:nextChild.name withNewChild:nextChild.node];
+ } else {
+ return newIndexed;
+ }
+ }
+ } else if (newChildSnap.isEmpty) {
+ // We're deleting a node, but it was not in the window, so ignore it.
+ return oldIndexed;
+ } else if (inRange) {
+ // `newChildSnap` is in range, but was ordered after `windowBoundary`. If this has changed, we bump out the
+ // `windowBoundary` and add the `newChildSnap`
+ if ([self.index compareKey:windowBoundary.name
+ andNode:windowBoundary.node
+ toOtherKey:childKey
+ andNode:newChildSnap
+ reverse:self.reverse] >= NSOrderedSame) {
+ if (optChangeAccumulator != nil) {
+ FChange *removedChange = [[FChange alloc] initWithType:FIRDataEventTypeChildRemoved
+ indexedNode:[FIndexedNode indexedNodeWithNode:windowBoundary.node]
+ childKey:windowBoundary.name];
+ FChange *addedChange = [[FChange alloc] initWithType:FIRDataEventTypeChildAdded
+ indexedNode:[FIndexedNode indexedNodeWithNode:newChildSnap]
+ childKey:childKey];
+ [optChangeAccumulator trackChildChange:removedChange];
+ [optChangeAccumulator trackChildChange:addedChange];
+ }
+ return [[oldIndexed updateChild:childKey withNewChild:newChildSnap] updateChild:windowBoundary.name
+ withNewChild:[FEmptyNode emptyNode]];
+ } else {
+ return oldIndexed;
+ }
+ } else {
+ // `newChildSnap` was not in range and remains not in range, so ignore it.
+ return oldIndexed;
+ }
+}
+
+- (FIndexedNode *)updateFullNode:(FIndexedNode *)oldSnap
+ withNewNode:(FIndexedNode *)newSnap
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator
+{
+ __block FIndexedNode *filtered;
+ if (newSnap.node.isLeafNode || newSnap.node.isEmpty) {
+ // Make sure we have a children node with the correct index, not a leaf node
+ filtered = [FIndexedNode indexedNodeWithNode:[FEmptyNode emptyNode] index:self.index];
+ } else {
+ filtered = newSnap;
+ // Don't support priorities on queries.
+ filtered = [filtered updatePriority:[FEmptyNode emptyNode]];
+ FNamedNode *startPost = nil;
+ FNamedNode *endPost = nil;
+ if (self.reverse) {
+ startPost = self.rangedFilter.endPost;
+ endPost = self.rangedFilter.startPost;
+ } else {
+ startPost = self.rangedFilter.startPost;
+ endPost = self.rangedFilter.endPost;
+ }
+ __block BOOL foundStartPost = NO;
+ __block NSUInteger count = 0;
+ [newSnap enumerateChildrenReverse:self.reverse usingBlock:^(NSString *childKey, id<FNode> childNode, BOOL *stop) {
+ if (!foundStartPost && [self.index compareKey:startPost.name
+ andNode:startPost.node
+ toOtherKey:childKey
+ andNode:childNode
+ reverse:self.reverse] <= NSOrderedSame) {
+ // Start adding
+ foundStartPost = YES;
+ }
+ BOOL inRange = foundStartPost && count < self.limit;
+ inRange = inRange && [self.index compareKey:childKey
+ andNode:childNode
+ toOtherKey:endPost.name
+ andNode:endPost.node
+ reverse:self.reverse] <= NSOrderedSame;
+ if (inRange) {
+ count++;
+ } else {
+ filtered = [filtered updateChild:childKey withNewChild:[FEmptyNode emptyNode]];
+ }
+ }];
+ }
+ return [self.indexedFilter updateFullNode:oldSnap withNewNode:filtered accumulator:optChangeAccumulator];
+}
+
+- (FIndexedNode *)updatePriority:(id<FNode>)priority forNode:(FIndexedNode *)oldSnap
+{
+ // Don't support priorities on queries.
+ return oldSnap;
+}
+
+- (BOOL) filtersNodes {
+ return YES;
+}
+
+- (id<FNodeFilter>) indexedFilter {
+ return self.rangedFilter.indexedFilter;
+}
+
+@end
diff --git a/Firebase/Database/Core/View/Filter/FNodeFilter.h b/Firebase/Database/Core/View/Filter/FNodeFilter.h
new file mode 100644
index 0000000..f29a85a
--- /dev/null
+++ b/Firebase/Database/Core/View/Filter/FNodeFilter.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+@protocol FNode;
+@class FIndexedNode;
+@protocol FCompleteChildSource;
+@class FChildChangeAccumulator;
+@protocol FIndex;
+@class FPath;
+
+/**
+* FNodeFilter is used to update nodes and complete children of nodes while applying queries on the fly and keeping
+* track of any child changes. This class does not track value changes as value changes depend on more than just the
+* node itself. Different kind of queries require different kind of implementations of this interface.
+*/
+@protocol FNodeFilter<NSObject>
+
+/**
+* Update a single complete child in the snap. If the child equals the old child in the snap, this is a no-op.
+* The method expects an indexed snap.
+*/
+- (FIndexedNode *) updateChildIn:(FIndexedNode *)oldSnap
+ forChildKey:(NSString *)childKey
+ newChild:(id<FNode>)newChildSnap
+ affectedPath:(FPath *)affectedPath
+ fromSource:(id<FCompleteChildSource>)source
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator;
+
+/**
+* Update a node in full and output any resulting change from this complete update.
+*/
+- (FIndexedNode *) updateFullNode:(FIndexedNode *)oldSnap
+ withNewNode:(FIndexedNode *)newSnap
+ accumulator:(FChildChangeAccumulator *)optChangeAccumulator;
+
+/**
+* Update the priority of the root node
+*/
+- (FIndexedNode *) updatePriority:(id<FNode>)priority forNode:(FIndexedNode *)oldSnap;
+
+/**
+* Returns true if children might be filtered due to query critiera
+*/
+- (BOOL) filtersNodes;
+
+/**
+* Returns the index filter that this filter uses to get a NodeFilter that doesn't filter any children.
+*/
+@property (nonatomic, strong, readonly) id<FNodeFilter> indexedFilter;
+
+/**
+* Returns the index that this filter uses
+*/
+@property (nonatomic, strong, readonly) id<FIndex> index;
+
+@end