Skip to content

Fix missing query results when it is a match after a local patch mutation. #1957

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
6 commits merged into from
Jul 12, 2019

Conversation

ghost
Copy link

@ghost ghost commented Jul 10, 2019

@ghost ghost requested a review from var-const July 10, 2019 16:36
@ghost ghost assigned var-const Jul 10, 2019
);
})
.next(baseDocsForPatching => {
baseDocsForPatching.forEach((key, doc) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: if you decide to follow the suggestion to create a helper function, perhaps you can move this filtering step there as well.

Copy link
Author

Choose a reason for hiding this comment

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

As i wrote in other reply, i'd like to keep the block as-is.

missingBaseDocEntriesForPatching
);
})
.next(baseDocsForPatching => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this variable name is perhaps slightly misleading; without context, it sounds like it contains all base documents, but in fact it's only a subset of those documents (those that have pending writes making them potentially match). Consider renaming (docsWithPendingWrites? docsWithLocalPatches?).

Copy link
Author

Choose a reason for hiding this comment

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

changed to docsWithPendingPatches.

@var-const var-const assigned ghost and unassigned var-const Jul 10, 2019
@ghost ghost assigned var-const and unassigned ghost Jul 10, 2019
@var-const
Copy link
Contributor

@schmidt-sebastian Sebastian, since you previously volunteered, could you please check this implementation doesn't leave out any important steps? It looks fine to me, but I'm concerned I might be missing some of the context here.

for (const batch of matchingMutationBatches) {
for (const mutation of batch.mutations) {
// Only process documents belonging to the collection.
if (!query.path.isImmediateParentOf(mutation.key.path)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this needed? I would think that these mutations would already be filtered out by getAllMutationBatchesAffectingQuery.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, getAllMutationBatchesAffectingQuery seems to be filtering that already. I keep it because the block below also has it..I removed that as well.

missingBaseDocEntriesForPatching
);
})
.next(docsWithPendingPatches => {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could improve the readability here if you chained this off the invocation ending in 252. That would move the code closer to its surrounding logic.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@@ -98,4 +98,41 @@ describeSpec('Queries:', [], () => {
hasPendingWrites: true
});
});

specTest(
'Ensure correct query results for latency-compensated updates',
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe 'Latency-compensated updates are included in query results'?

Copy link
Author

Choose a reason for hiding this comment

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

Done.

'Ensure correct query results for latency-compensated updates',
[],
() => {
const fullQuery = Query.atPath(ResourcePath.fromString('collection'));
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be Query.atPath(path('collection'))

Copy link
Author

Choose a reason for hiding this comment

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

Done.

.userListens(fullQuery)
.watchAcksFull(fullQuery, 1000, docA)
.expectEvents(fullQuery, { added: [docA] })

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that if you do userUnlisten(fullQuery) here then you can remove the event right below and further narrow down your test case.

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure i understand..can you explain more?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am myself no longer fully sold on my idea. But let me explain what I had in mind:

    spec()
          .withGcEnabled(false)
          .userListens(fullQuery)
          .watchAcksFull(fullQuery, 1000, docA)
          .expectEvents(fullQuery, { added: [docA] })
          .userUnlistens(fullQuery)
          .userPatches('collection/a', { match: true })
          .userListens(filteredQuery)
          .expectEvents(filteredQuery, {
            added: [docAv2Local],
            fromCache: true,
            hasPendingWrites: true
          })

I believe this would work and it would remove the event in line 124 that is not directly related to your change. The reason I am no longer fully sold on this is that you would have to run the test with GC disabled to make this work. Otherwise, the SDK would delete docA from the RemoteDocumentCache as the user unlistens.

Copy link
Author

Choose a reason for hiding this comment

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

Yep, can confirm what you described. Without GC disabled this is not working.

I am keep it as-is as that is slightly more likely what happens in real world.

@schmidt-sebastian schmidt-sebastian assigned ghost and unassigned schmidt-sebastian Jul 10, 2019
results = results.remove(key);
mutationBatches = matchingMutationBatches;
// It is possible that a PatchMutation can make a document a match to the query,
// but the version in `remoteDocumentCache` is not a match yet (waiting for server
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: s/remoteDocumentCache/the RemoteDocumentCache/ (since it is a widely used concept in Firestore)

Copy link
Author

Choose a reason for hiding this comment

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

Done.

mutationBatches = matchingMutationBatches;
// It is possible that a PatchMutation can make a document a match to the query,
// but the version in `remoteDocumentCache` is not a match yet (waiting for server
// to ack). To handle this, we find all document keys affected by some`PatchMutation`s
Copy link
Contributor

Choose a reason for hiding this comment

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

s/somePatchMutations/the PatchMutations/

Copy link
Author

Choose a reason for hiding this comment

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

Done.

// that are not in `result` yet, and back fill them via `remoteDocumentCache.getEntries`,
// otherwise those `PatchMutations` will be ignored because no base document can be found,
// and lead to missing result for the query.
return this.getDocumentWithPendingPatches(transaction, mutationBatches, results)
Copy link
Contributor

Choose a reason for hiding this comment

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

getDocumentWithPendingPatches only conveys half of the story. Maybe `getMissingBaseDocuments()?

Copy link
Author

Choose a reason for hiding this comment

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

Done.

transaction: PersistenceTransaction,
matchingMutationBatches: MutationBatch[],
results: DocumentMap
): PersistencePromise<NullableMaybeDocumentMap> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense for this to return a DocumentMap? It would simplify the code above, but you would need to post-filter in here. Alternatively, you could also assemble the final result map here and make this a populateDocumentMapWithMissingBaseDocuments() or something similar. Optional.

Copy link
Author

Choose a reason for hiding this comment

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

My understanding is DocumentMap being an immutable structure makes it awkward to update from the helper function. I have not tried it..but don't expect it to work without extra tricks.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can get return the combined set of existingDocuments and the result of this function. The goal would be to simplify the getDocumentsMatchingCollectionQuery function.

Optional.

Copy link
Author

Choose a reason for hiding this comment

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

Decide not to do it because it is not a significant improvement..

private getDocumentWithPendingPatches(
transaction: PersistenceTransaction,
matchingMutationBatches: MutationBatch[],
results: DocumentMap
Copy link
Contributor

Choose a reason for hiding this comment

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

results is not very clear. Maybe something like existingDocuments?

Copy link
Author

Choose a reason for hiding this comment

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

Done.

if (doc !== null && doc instanceof Document) {
results = results.insert(key, doc);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Add empty line to convey that the loop below does something very different (we move from one stage to another).

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@ghost ghost assigned schmidt-sebastian and unassigned ghost Jul 11, 2019
@ghost ghost merged commit 0d2e108 into master Jul 12, 2019
@firebase firebase locked and limited conversation to collaborators Oct 9, 2019
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants