Skip to content

Add GetOptions for controlling offline get behaviour #655

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

Merged
merged 12 commits into from
Jan 19, 2018

Conversation

rsgowman
Copy link
Member

Add option to allow the user to control where DocumentReference.getDocument() and Query.getDocuments() fetches from. By default, they fetch from the server (if possible) and fall back to the local cache. It's now possible to alternatively fetch from the local cache only, or to
fetch from the server only (though in the server only case, latency compensation is still enabled).

Add option to allow the user to control where
DocumentReference.getDocument() fetches from. By default, it fetches
from the server (if possible) and falls back to the local cache. It's
now possible to alternatively fetch from the local cache only, or to
fetch from the server only (though in the server only case, latency
compensation is still enabled).

Still TODO: Also enable this for CollectionReferenece.getDocuments().
Enable GetOptions for CollectionReference / Query .getDocuments().
@rsgowman
Copy link
Member Author

@wilhuff Pay particular attention to Firestore/Source/Core/FSTFirestoreClient.m since I'm not sure what I'm doing there is at all valid.

Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mostly looks good except for the manual computation of views. These views are highly unlikely to conform to all the invariants we expect and will make the Cache-only snapshot substantially different form the first snapshot with default options.

FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
includeDocumentMetadataChanges:NO
waitForSyncWhenOnline:NO];
FSTListenOptions *options =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the changes in this file look like whitespace-only changes. Are you on clang 6?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ clang --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

I think I see what happened though. I (mistakenly) changed this file in 80a40bd and then backed out those changes in 58ccdba. However, the run of style.sh was in between.

If you like, I can revert this file completely. OTOH, style.sh thinks this is better, and since it's already fixed it...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. thinking about this further, I'll just back this out.


@import FirebaseFirestore;

#import <FirebaseFirestore/FIRFirestore.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be covered by importing the module above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* acknowledge the writes. (This is expected to be called primarily when
* offline, so waiting for the server doesn't make sense.)
*/
- (void)updateData {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps name this in a way that indicates it doesn't wait? Perhaps updateDataNoWait?

However, see above: it's not clear after calling this when a test makes an assertion about _testDoc but not _testCol why not. These tests would be clearer if you updated the things you cared about in the test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant.

- (void)setUp {
[super setUp];

_emptyDoc = [self documentRef];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factoring out these common parts is a good thing from a DRY perspective but it has the effect of making these tests tightly coupled, brittle, hard to read, and ultimately do more work than they need to do.

For example, in testGetDocumentWhileOnlineWithDefaultGetOptions, it's not obvious why [self readDocumentForRef:_testDoc] would necessarily satisfy the requirement that _testDoc "exists, is not from the cache, and matches the initial data".

This was the subject TotT 258 (from 2012), available externally here: https://testing.googleblog.com/2017/01/testing-on-toilet-keep-cause-and-effect.html

Please set up what documents you need per test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undone the refactor commit.

XCTAssertEqualObjects(result.data, _testDocInitialData);
}

- (void)testGetDocumentsWhileOnlineWithDefaultGetOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name differs from the above by a single 's'. Something like testGetCollection... would make the distinction clearer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does the api. :) (getDocumentWithCompletion vs getDocumentsWithCompletion. Though admittedly, they're at least on different objects.)

Done (throughout.)

code:FIRFirestoreErrorCodeUnavailable
userInfo:@{
NSLocalizedDescriptionKey :
@"Failed to get documents from server."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More error text to explain that this is a user-requested error condition (as above).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Failed to get documents from server. (However, these documents may exist in the local cache. Run again without setting FIRServer in the GetOptions to retrieve the cached documents.)"

nil, [NSError errorWithDomain:FIRFirestoreErrorDomain
code:FIRFirestoreErrorCodeUnavailable
userInfo:@{
NSLocalizedDescriptionKey : @"Failed to get document from server.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More error text to explain that this is a user-requested error condition (as above).

Also in this case, this is a cache-only request so the server has nothing to do with it, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re cache vs server: Oops.

Updated text:

"Failed to get document from cache. (However, this document may exist on the server. Run again without setting FIRCache in the GetOptions to attempt to retrieve the document from the server.)"

oldDocuments:oldDocuments
documentChanges:@[]
fromCache:YES
hasPendingWrites:NO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the wrong value. The local store will apply local mutations if present.

Also the documentChanges array being empty seems suspicious: the view processing code will normally make all the documents in the initial snapshot adds.

These are both examples of reasons why you shouldn't compose the view snapshots yourself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done; ptal.

@@ -195,6 +196,11 @@ NS_SWIFT_NAME(DocumentReference)
- (void)getDocumentWithCompletion:(FIRDocumentSnapshotBlock)completion
NS_SWIFT_NAME(getDocument(completion:));

// clang-format off
- (void)getDocumentWithOptions:(FIRGetOptions *)options completion:(FIRDocumentSnapshotBlock)completion
NS_SWIFT_NAME(getDocument(options:completion:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a swift-language test that verifies this looks correct from swift.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* stale with respect to the value on the server.) For a single document, the
* get will fail if the document doesn't exist.
*/
typedef NS_ENUM(NSUInteger, FIRSource) { FIRDefault, FIRServer, FIRCache } NS_SWIFT_NAME(Source);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order for swift bridging to work correctly the enumeration names need to all be suffixes of the type.

That means these need to be FIRSourceDefault, FIRSourceServer, etc.

I'm a little worried that "FIRSource" is too generic a term: we share the FIR namespace with the rest of Firebase, unfortunately. We should flag this as a potential issue in the API proposal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. (Also flagged in the api review doc.)

Still have a few (more in depth) comments to address.
Artifacts of an earlier attempt at this. :(
@rsgowman rsgowman force-pushed the rsgowman/get_options_2 branch from 58ccdba to 2a32091 Compare January 16, 2018 20:20
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updates LGTM after nitpicking.

Still need to use FSTViewChange to actually compose the snapshot, but I know you're still working on that.

* try to give a consistent (server-retrieved) snapshot, or else revert to the
* cache to provide a value.
*
* FIRServer causes Firestore to avoid the cache (generating an error if a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments should match the names: s/FIRServer/FIRSourceServer/, here and throughout.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

NSLocalizedDescriptionKey :
@"Failed to get document from server. (However, this "
@"document does exist in the local cache. Run again "
@"without setting FIRServer in the GetOptions to "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line mixes FIR-prefixed Server (which should be FIRSourceServer, incidentally) with non-prefixed GetOptions. These should be consistent (and probably prefixed).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@wilhuff wilhuff assigned rsgowman and unassigned wilhuff Jan 17, 2018
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the view computation you've added is sound. A few final nitpicks and we're good to go.

extern "C" NSArray<NSArray<id> *> *FIRQuerySnapshotGetDocChangesData(FIRQuerySnapshot *docs) {
NSMutableArray<NSMutableArray<id> *> *result = [NSMutableArray array];
for (FIRDocumentChange *docChange in docs.documentChanges) {
NSMutableArray<id> *docChangeData = [NSMutableArray array];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two bits of feedback here:

  1. You can create an array of known contents with far less typing by using an array literal, like so:
NSArray<id> *docChangeData = @[
  @(docChange.type), docChange.document.documentID, docChange.document.data
];
  1. I'm not wild about making this untyped and unstructured. Historically the way we've done this is to make a test helper for concisely creating the expected result, usually something like what's in FSTHelpers.h and then defining isEqual/describe on it such that we can XCTAssertEqualObjects and have the right thing happen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs];
FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
FSTAssert(viewChange.limboChanges.count == 0,
@"View returned limbo docs before target ack from the server.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion text makes no sense in this context (there won't be a target ack from the server since we're not starting a listen with the server).

Rather this should be @"View returned limbo documents during local-only query execution".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -228,11 +228,11 @@ func readDocument(at docRef: DocumentReference) {
func readDocumentWithOptions(at docRef: DocumentReference) {
docRef.getDocument(options:GetOptions.defaultOptions()) { document, error in
}
docRef.getDocument(options:GetOptions.init(source:Source.default)) { document, error in
docRef.getDocument(options:GetOptions.init(source:GetSource.default)) { document, error in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try making one of these use the unqualified form:

docRef.getDocument(options:GetOptions(source:.default)) { // ...

Also, explicitly calling init is not required. Using a constructor-style invocation calls the init function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Swift api
limbo error message
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That last commit is a cure worse than the disease. I can see now that there's no easy way to construct a FIRDocumentChange in a test that would compare equal to the generated values so let's just drop the final commit.

@wilhuff
Copy link
Contributor

wilhuff commented Jan 19, 2018

Also, this API hasn't been formally approved yet, so let's squash and merge into the firestore-api-changes branch.

@rsgowman rsgowman force-pushed the rsgowman/get_options_2 branch from 36fc2da to 893e56a Compare January 19, 2018 18:33
@rsgowman rsgowman changed the base branch from master to firestore-api-changes January 19, 2018 18:37
@rsgowman rsgowman merged commit 3440fef into firestore-api-changes Jan 19, 2018
rsgowman added a commit that referenced this pull request Feb 1, 2018
* Convert FIRGetOptions.initWithSource to factory method.

* source -> optionsWithSource

* Swift: GetOptions.source(source:) -> source(_:)
@rsgowman rsgowman deleted the rsgowman/get_options_2 branch February 21, 2018 20:57
rsgowman added a commit that referenced this pull request Apr 18, 2018
Add option to allow the user to control where DocumentReference.getDocument() and CollectionReference.getDocuments() fetches from. By default, it fetches from the server (if possible) and falls back to the local cache. It's now possible to alternatively fetch from the local cache only, or to fetch from the server only (though in the server only case, latency compensation is still enabled).
minafarid pushed a commit to minafarid/firebase-ios-sdk that referenced this pull request Jun 6, 2018
Add option to allow the user to control where DocumentReference.getDocument() and CollectionReference.getDocuments() fetches from. By default, it fetches from the server (if possible) and falls back to the local cache. It's now possible to alternatively fetch from the local cache only, or to fetch from the server only (though in the server only case, latency compensation is still enabled).
@firebase firebase locked and limited conversation to collaborators Nov 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants