in Firestore/core/src/core/view.cc [95:233]
ViewDocumentChanges View::ComputeDocumentChanges(
const DocumentMap& doc_changes,
const absl::optional<ViewDocumentChanges>& previous_changes) const {
DocumentViewChangeSet change_set;
if (previous_changes) {
change_set = previous_changes->change_set();
}
DocumentSet old_document_set =
previous_changes ? previous_changes->document_set() : document_set_;
DocumentKeySet new_mutated_keys =
previous_changes ? previous_changes->mutated_keys() : mutated_keys_;
DocumentKeySet old_mutated_keys = mutated_keys_;
DocumentSet new_document_set = old_document_set;
bool needs_refill = false;
// Track the last doc in a (full) limit. This is necessary, because some
// update (a delete, or an update moving a doc past the old limit) might mean
// there is some other document in the local cache that either should come (1)
// between the old last limit doc and the new last document, in the case of
// updates, or (2) after the new last document, in the case of deletes. So we
// keep this doc at the old limit to compare the updates to.
//
// Note that this should never get used in a refill (when previous_changes is
// set), because there will only be adds -- no deletes or updates.
absl::optional<Document> last_doc_in_limit;
if (query_.has_limit_to_first() &&
old_document_set.size() == static_cast<size_t>(query_.limit())) {
last_doc_in_limit = old_document_set.GetLastDocument();
}
absl::optional<Document> first_doc_in_limit;
if (query_.has_limit_to_last() &&
old_document_set.size() == static_cast<size_t>(query_.limit())) {
first_doc_in_limit = old_document_set.GetFirstDocument();
}
for (const auto& kv : doc_changes) {
const DocumentKey& key = kv.first;
absl::optional<Document> old_doc = old_document_set.GetDocument(key);
absl::optional<Document> new_doc = query_.Matches(kv.second)
? absl::optional<Document>{kv.second}
: absl::nullopt;
bool old_doc_had_pending_mutations =
old_doc && old_mutated_keys.contains(key);
// We only consider committed mutations for documents that were mutated
// during the lifetime of the view.
bool new_doc_has_pending_mutations =
new_doc && ((*new_doc)->has_local_mutations() ||
(old_mutated_keys.contains(key) &&
(*new_doc)->has_committed_mutations()));
bool change_applied = false;
// Calculate change
if (old_doc && new_doc) {
bool docs_equal = (*old_doc)->value() == (*new_doc)->value();
if (!docs_equal) {
if (!ShouldWaitForSyncedDocument(*new_doc, *old_doc)) {
change_set.AddChange(
DocumentViewChange{*new_doc, DocumentViewChange::Type::Modified});
change_applied = true;
bool outside_limit =
last_doc_in_limit &&
util::Descending(Compare(*new_doc, *last_doc_in_limit));
bool outside_limit_to_last =
first_doc_in_limit &&
util::Ascending(Compare(*new_doc, *first_doc_in_limit));
if (outside_limit || outside_limit_to_last) {
// This doc moved from inside the limit to after the limit. That
// means there may be some doc in the local cache that's actually
// less than this one.
needs_refill = true;
}
}
} else if (old_doc_had_pending_mutations !=
new_doc_has_pending_mutations) {
change_set.AddChange(
DocumentViewChange{*new_doc, DocumentViewChange::Type::Metadata});
change_applied = true;
}
} else if (!old_doc && new_doc) {
change_set.AddChange(
DocumentViewChange{*new_doc, DocumentViewChange::Type::Added});
change_applied = true;
} else if (old_doc && !new_doc) {
change_set.AddChange(
DocumentViewChange{*old_doc, DocumentViewChange::Type::Removed});
change_applied = true;
if (last_doc_in_limit || first_doc_in_limit) {
// A doc was removed from a full limit query. We'll need to re-query
// from the local cache to see if we know about some other doc that
// should be in the results.
needs_refill = true;
}
}
if (change_applied) {
if (new_doc) {
new_document_set = new_document_set.insert(new_doc);
if ((*new_doc)->has_local_mutations()) {
new_mutated_keys = new_mutated_keys.insert(key);
} else {
new_mutated_keys = new_mutated_keys.erase(key);
}
} else {
new_document_set = new_document_set.erase(key);
new_mutated_keys = new_mutated_keys.erase(key);
}
}
}
// Drop documents out to meet limitToFirst/limitToLast requirement.
if (query_.limit_type() != LimitType::None) {
auto limit = static_cast<size_t>(query_.limit());
if (limit < new_document_set.size()) {
for (size_t i = new_document_set.size() - limit; i > 0; --i) {
absl::optional<Document> found =
query_.has_limit_to_first() ? new_document_set.GetLastDocument()
: new_document_set.GetFirstDocument();
const Document& old_doc = *found;
new_document_set = new_document_set.erase(old_doc->key());
new_mutated_keys = new_mutated_keys.erase(old_doc->key());
change_set.AddChange(
DocumentViewChange{old_doc, DocumentViewChange::Type::Removed});
}
}
}
HARD_ASSERT(!needs_refill || !previous_changes,
"View was refilled using docs that themselves needed refilling.");
return ViewDocumentChanges(std::move(new_document_set), std::move(change_set),
new_mutated_keys, needs_refill);
}