-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix RTDB query getData
caching behavior
#9707
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,5 +70,9 @@ | |
- (BOOL)viewExistsForQuery:(FQuerySpec *)query; | ||
- (BOOL)hasCompleteView; | ||
- (FView *)completeView; | ||
- (void)registerQuery:(FQuerySpec *)query | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add comments for any new functions? |
||
writesCache:(FWriteTreeRef *)writesCache | ||
serverCache:(FCacheNode *)serverCache; | ||
- (BOOL)unregisterQuery:(FQuerySpec *)query; | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,14 +161,9 @@ - (FView *)getView:(FQuerySpec *)query | |
return [[FView alloc] initWithQuery:query initialViewCache:viewCache]; | ||
} | ||
|
||
/** | ||
* Add an event callback for the specified query | ||
* Returns an array of events to raise. | ||
*/ | ||
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration | ||
forNonExistingViewForQuery:(FQuerySpec *)query | ||
writesCache:(FWriteTreeRef *)writesCache | ||
serverCache:(FCacheNode *)serverCache { | ||
- (void)registerQuery:(FQuerySpec *)query | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method does everything that |
||
writesCache:(FWriteTreeRef *)writesCache | ||
serverCache:(FCacheNode *)serverCache { | ||
NSAssert(self.views[query.params] == nil, @"Found view for query: %@", | ||
query.params); | ||
// TODO: make writesCache take flag for complete server node | ||
|
@@ -187,6 +182,17 @@ - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration | |
[self.persistenceManager setTrackedQueryKeys:allKeys forQuery:query]; | ||
} | ||
self.views[query.params] = view; | ||
} | ||
|
||
/** | ||
* Add an event callback for the specified query | ||
* Returns an array of events to raise. | ||
*/ | ||
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration | ||
forNonExistingViewForQuery:(FQuerySpec *)query | ||
writesCache:(FWriteTreeRef *)writesCache | ||
serverCache:(FCacheNode *)serverCache { | ||
[self registerQuery:query writesCache:writesCache serverCache:serverCache]; | ||
return [self addEventRegistration:eventRegistration | ||
forExistingViewForQuery:query]; | ||
} | ||
|
@@ -199,6 +205,15 @@ - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration | |
return [view initialEvents:eventRegistration]; | ||
} | ||
|
||
- (BOOL)unregisterQuery:(FQuerySpec *)query { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method just removes the query from the view map if no other query added an event registration to it. There is quite a bit of logic that isn't brought over from
I'm least sure about this function. |
||
FView *view = self.views[query.params]; | ||
NSAssert(view != nil, @"no view for query %@", query); | ||
if ([view isEmpty]) { | ||
[self.views removeObjectForKey:query.params]; | ||
} | ||
return [view isEmpty]; | ||
} | ||
|
||
/** | ||
* Remove event callback(s). Return cancelEvents if a cancelError is specified. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,6 +164,15 @@ - (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager | |
return self; | ||
} | ||
|
||
- (NSNumber *)tagQuery:(FQuerySpec *)query { | ||
NSAssert(self.queryToTagMap[query] == nil, | ||
@"View does not exist, but we have a tag"); | ||
NSNumber *tagId = [self.queryTagCounter getAndIncrement]; | ||
self.queryToTagMap[query] = tagId; | ||
self.tagToQueryMap[tagId] = query; | ||
return tagId; | ||
} | ||
|
||
#pragma mark - | ||
#pragma mark Apply Operations | ||
|
||
|
@@ -448,6 +457,42 @@ - (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path | |
} | ||
} | ||
|
||
- (NSNumber *)registerQuery:(FQuerySpec *)query { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Query is used a lot, including for listeners. Should we call this something like "registerGet"? |
||
FPath *path = query.path; | ||
|
||
__block BOOL foundAncestorDefaultView = NO; | ||
[self.syncPointTree | ||
forEachOnPath:query.path | ||
whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) { | ||
foundAncestorDefaultView = | ||
foundAncestorDefaultView || [syncPoint hasCompleteView]; | ||
return !foundAncestorDefaultView; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't actually think we're using this block, since we aren't checking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, will remove this. |
||
}]; | ||
|
||
[self.persistenceManager setQueryActive:query]; | ||
|
||
FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path]; | ||
if (syncPoint == nil) { | ||
syncPoint = [[FSyncPoint alloc] | ||
initWithPersistenceManager:self.persistenceManager]; | ||
self.syncPointTree = [self.syncPointTree setValue:syncPoint | ||
atPath:path]; | ||
} | ||
NSNumber *tag = nil; | ||
BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like the code up until here is reused from |
||
if (!viewAlreadyExists) { | ||
FWriteTreeRef *writesCache = | ||
[self.pendingWriteTree childWritesForPath:path]; | ||
FCacheNode *serverCache = [self serverCacheForQuery:query]; | ||
[syncPoint registerQuery:query | ||
writesCache:writesCache | ||
serverCache:serverCache]; | ||
} else if (![query loadsAllData]) { | ||
tag = [self tagQuery:query]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be moved into the
bock above. |
||
} | ||
return tag; | ||
} | ||
|
||
/** | ||
* Add an event callback for the specified query | ||
* @return NSArray of FEvent to raise. | ||
|
@@ -575,6 +620,23 @@ - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query { | |
return serverCache; | ||
} | ||
|
||
- (void)unregisterQuery:(FQuerySpec *)query { | ||
FPath *path = query.path; | ||
FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path]; | ||
|
||
if (maybeSyncPoint && | ||
([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) { | ||
BOOL removed = [maybeSyncPoint unregisterQuery:query]; | ||
if ([maybeSyncPoint isEmpty]) { | ||
self.syncPointTree = [self.syncPointTree removeValueAtPath:path]; | ||
} | ||
if (removed) { | ||
[self removeTags:@[ query ]]; | ||
[self.persistenceManager setQueryInactive:query]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: only call setQueryInactive when we just removed the last query in the view. This is consistent with how |
||
} | ||
} | ||
} | ||
|
||
/** | ||
* Remove event callback(s). | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* 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 "FirebaseDatabase/Tests/Helpers/FTestBase.h" | ||
|
||
@interface FIRDatabaseGetTests : FTestBase | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* 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 "FirebaseDatabase/Tests/Integration/FIRDatabaseGetTests.h" | ||
#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" | ||
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h" | ||
#import "FirebaseDatabase/Sources/Constants/FConstants.h" | ||
#import "FirebaseDatabase/Sources/Core/FQuerySpec.h" | ||
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h" | ||
#import "FirebaseDatabase/Tests/Helpers/FEventTester.h" | ||
#import "FirebaseDatabase/Tests/Helpers/FIRFakeApp.h" | ||
#import "FirebaseDatabase/Tests/Helpers/FTestExpectations.h" | ||
#import "FirebaseDatabase/Tests/Helpers/FTupleEventTypeString.h" | ||
|
||
@implementation FIRDatabaseGetTests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test where we perform a get, then perform a listen before the get is finished? |
||
|
||
- (void)testGetDoesntTriggerExtraListens { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we also add a test to ensure that a subsequent listen for the same query returns the cached data immediately? |
||
FIRDatabaseReference* ref = [FTestHelpers getRandomNode]; | ||
FIRDatabaseReference* root = [ref root]; | ||
FIRDatabaseReference* list = [root child:@"list"]; | ||
|
||
__block BOOL removeDone = NO; | ||
[root removeValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference* ref) { | ||
removeDone = YES; | ||
}]; | ||
WAIT_FOR(removeDone); | ||
|
||
[self waitForCompletionOf:[list childByAutoId] setValue:@{@"name" : @"child1"}]; | ||
[self waitForCompletionOf:[list childByAutoId] setValue:@{@"name" : @"child2"}]; | ||
|
||
// The original report of this issue makes a listen call first, and then | ||
// performs a getData. However, in the testing environment, if the listen | ||
// is made first, it will round trip and cache results before get has a | ||
// chance to run, at which point it reads from cache. | ||
// https://github.com/firebase/firebase-ios-sdk/issues/8286 | ||
__block BOOL getDone = NO; | ||
[[[list queryOrderedByChild:@"name"] queryEqualToValue:@"child2"] | ||
getDataWithCompletionBlock:^(NSError* error, FIRDataSnapshot* snapshot) { | ||
XCTAssertNil(error); | ||
XCTAssertEqual(snapshot.childrenCount, 1L); | ||
getDone = YES; | ||
}]; | ||
__block NSInteger numListenEvents = 0L; | ||
FIRDatabaseHandle handle = [list observeEventType:FIRDataEventTypeValue | ||
withBlock:^(FIRDataSnapshot* snapshot) { | ||
XCTAssertEqual(snapshot.childrenCount, 2L); | ||
numListenEvents += 1; | ||
}]; | ||
WAIT_FOR(getDone); | ||
[NSThread sleepForTimeInterval:1]; | ||
XCTAssertEqual(numListenEvents, 1); | ||
[list removeObserverWithHandle:handle]; | ||
} | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think get queries are tagged on the backend, but it looks like the tag is only used retrieve the query spec in
applyTaggedQueryOverwriteAtPath
.