ViewDocumentChanges View::ComputeDocumentChanges()

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);
}