use of com.google.firebase.firestore.model.MutableDocument in project firebase-android-sdk by firebase.
the class LocalStore method writeLocally.
/**
* Accepts locally generated Mutations and commits them to storage.
*/
public LocalDocumentsResult writeLocally(List<Mutation> mutations) {
Timestamp localWriteTime = Timestamp.now();
// TODO: Call queryEngine.handleDocumentChange() appropriately.
Set<DocumentKey> keys = new HashSet<>();
for (Mutation mutation : mutations) {
keys.add(mutation.getKey());
}
return persistence.runTransaction("Locally write mutations", () -> {
// Figure out which keys do not have a remote version in the cache, this is needed to
// create the right overlay mutation: if no remote version presents, we do not need to
// create overlays as patch mutations.
// TODO(Overlay): Is there a better way to determine this? Document version does not work
// because local mutations set them back to 0.
Map<DocumentKey, MutableDocument> remoteDocs = remoteDocuments.getAll(keys);
Set<DocumentKey> docsWithoutRemoteVersion = new HashSet<>();
for (Map.Entry<DocumentKey, MutableDocument> entry : remoteDocs.entrySet()) {
if (!entry.getValue().isValidDocument()) {
docsWithoutRemoteVersion.add(entry.getKey());
}
}
// Load and apply all existing mutations. This lets us compute the current base state for
// all non-idempotent transforms before applying any additional user-provided writes.
Map<DocumentKey, OverlayedDocument> overlayedDocuments = localDocuments.getOverlayedDocuments(remoteDocs);
// For non-idempotent mutations (such as `FieldValue.increment()`), we record the base
// state in a separate patch mutation. This is later used to guarantee consistent values
// and prevents flicker even if the backend sends us an update that already includes our
// transform.
List<Mutation> baseMutations = new ArrayList<>();
for (Mutation mutation : mutations) {
ObjectValue baseValue = mutation.extractTransformBaseValue(overlayedDocuments.get(mutation.getKey()).getDocument());
if (baseValue != null) {
// NOTE: The base state should only be applied if there's some existing
// document to override, so use a Precondition of exists=true
baseMutations.add(new PatchMutation(mutation.getKey(), baseValue, baseValue.getFieldMask(), Precondition.exists(true)));
}
}
MutationBatch batch = mutationQueue.addMutationBatch(localWriteTime, baseMutations, mutations);
Map<DocumentKey, Mutation> overlays = batch.applyToLocalDocumentSet(overlayedDocuments, docsWithoutRemoteVersion);
documentOverlayCache.saveOverlays(batch.getBatchId(), overlays);
return LocalDocumentsResult.fromOverlayedDocuments(batch.getBatchId(), overlayedDocuments);
});
}
use of com.google.firebase.firestore.model.MutableDocument in project firebase-android-sdk by firebase.
the class LocalStore method populateDocumentChanges.
/**
* Populates the remote document cache with documents from backend or a bundle. Returns the
* document changes resulting from applying those documents, and also a set of documents whose
* existence state are changed as a result.
*
* <p>Note: this function will use `documentVersions` if it is defined. When it is not defined, it
* resorts to `globalVersion`.
*
* @param documents Documents to be applied.
*/
private DocumentChangeResult populateDocumentChanges(Map<DocumentKey, MutableDocument> documents) {
Map<DocumentKey, MutableDocument> changedDocs = new HashMap<>();
List<DocumentKey> removedDocs = new ArrayList<>();
Set<DocumentKey> conditionChanged = new HashSet<>();
// Each loop iteration only affects its "own" doc, so it's safe to get all the remote
// documents in advance in a single call.
Map<DocumentKey, MutableDocument> existingDocs = remoteDocuments.getAll(documents.keySet());
for (Entry<DocumentKey, MutableDocument> entry : documents.entrySet()) {
DocumentKey key = entry.getKey();
MutableDocument doc = entry.getValue();
MutableDocument existingDoc = existingDocs.get(key);
// Check if see if there is a existence state change for this document.
if (doc.isFoundDocument() != existingDoc.isFoundDocument()) {
conditionChanged.add(key);
}
// never add documents to cache.
if (doc.isNoDocument() && doc.getVersion().equals(SnapshotVersion.NONE)) {
// NoDocuments with SnapshotVersion.NONE are used in manufactured events. We remove
// these documents from cache since we lost access.
removedDocs.add(doc.getKey());
changedDocs.put(key, doc);
} else if (!existingDoc.isValidDocument() || doc.getVersion().compareTo(existingDoc.getVersion()) > 0 || (doc.getVersion().compareTo(existingDoc.getVersion()) == 0 && existingDoc.hasPendingWrites())) {
hardAssert(!SnapshotVersion.NONE.equals(doc.getReadTime()), "Cannot add a document when the remote version is zero");
remoteDocuments.add(doc, doc.getReadTime());
changedDocs.put(key, doc);
} else {
Logger.debug("LocalStore", "Ignoring outdated watch update for %s." + "Current version: %s Watch version: %s", key, existingDoc.getVersion(), doc.getVersion());
}
}
remoteDocuments.removeAll(removedDocs);
return new DocumentChangeResult(changedDocs, conditionChanged);
}
use of com.google.firebase.firestore.model.MutableDocument in project firebase-android-sdk by firebase.
the class LocalStore method applyBundledDocuments.
@Override
public ImmutableSortedMap<DocumentKey, Document> applyBundledDocuments(ImmutableSortedMap<DocumentKey, MutableDocument> documents, String bundleId) {
// Allocates a target to hold all document keys from the bundle, such that
// they will not get garbage collected right away.
TargetData umbrellaTargetData = allocateTarget(newUmbrellaTarget(bundleId));
return persistence.runTransaction("Apply bundle documents", () -> {
ImmutableSortedSet<DocumentKey> documentKeys = DocumentKey.emptyKeySet();
Map<DocumentKey, MutableDocument> documentMap = new HashMap<>();
for (Entry<DocumentKey, MutableDocument> entry : documents) {
DocumentKey documentKey = entry.getKey();
MutableDocument document = entry.getValue();
if (document.isFoundDocument()) {
documentKeys = documentKeys.insert(documentKey);
}
documentMap.put(documentKey, document);
}
targetCache.removeMatchingKeysForTargetId(umbrellaTargetData.getTargetId());
targetCache.addMatchingKeys(documentKeys, umbrellaTargetData.getTargetId());
DocumentChangeResult result = populateDocumentChanges(documentMap);
Map<DocumentKey, MutableDocument> changedDocs = result.changedDocuments;
return localDocuments.getLocalViewOfDocuments(changedDocs, result.existenceChangedKeys);
});
}
use of com.google.firebase.firestore.model.MutableDocument in project firebase-android-sdk by firebase.
the class LocalStore method applyRemoteEvent.
/**
* Updates the "ground-state" (remote) documents. We assume that the remote event reflects any
* write batches that have been acknowledged or rejected (i.e. we do not re-apply local mutations
* to updates from this event).
*
* <p>LocalDocuments are re-calculated if there are remaining mutations in the queue.
*/
public ImmutableSortedMap<DocumentKey, Document> applyRemoteEvent(RemoteEvent remoteEvent) {
SnapshotVersion remoteVersion = remoteEvent.getSnapshotVersion();
// TODO: Call queryEngine.handleDocumentChange() appropriately.
return persistence.runTransaction("Apply remote event", () -> {
Map<Integer, TargetChange> targetChanges = remoteEvent.getTargetChanges();
long sequenceNumber = persistence.getReferenceDelegate().getCurrentSequenceNumber();
for (Map.Entry<Integer, TargetChange> entry : targetChanges.entrySet()) {
Integer boxedTargetId = entry.getKey();
int targetId = boxedTargetId;
TargetChange change = entry.getValue();
TargetData oldTargetData = queryDataByTarget.get(targetId);
if (oldTargetData == null) {
// we persist the updated query data along with the updated assignment.
continue;
}
targetCache.removeMatchingKeys(change.getRemovedDocuments(), targetId);
targetCache.addMatchingKeys(change.getAddedDocuments(), targetId);
TargetData newTargetData = oldTargetData.withSequenceNumber(sequenceNumber);
if (remoteEvent.getTargetMismatches().contains(targetId)) {
newTargetData = newTargetData.withResumeToken(ByteString.EMPTY, SnapshotVersion.NONE).withLastLimboFreeSnapshotVersion(SnapshotVersion.NONE);
} else if (!change.getResumeToken().isEmpty()) {
newTargetData = newTargetData.withResumeToken(change.getResumeToken(), remoteEvent.getSnapshotVersion());
}
queryDataByTarget.put(targetId, newTargetData);
// since the last update).
if (shouldPersistTargetData(oldTargetData, newTargetData, change)) {
targetCache.updateTargetData(newTargetData);
}
}
Map<DocumentKey, MutableDocument> documentUpdates = remoteEvent.getDocumentUpdates();
Set<DocumentKey> limboDocuments = remoteEvent.getResolvedLimboDocuments();
for (DocumentKey key : documentUpdates.keySet()) {
if (limboDocuments.contains(key)) {
persistence.getReferenceDelegate().updateLimboDocument(key);
}
}
DocumentChangeResult result = populateDocumentChanges(documentUpdates);
Map<DocumentKey, MutableDocument> changedDocs = result.changedDocuments;
// HACK: The only reason we allow snapshot version NONE is so that we can synthesize
// remote events when we get permission denied errors while trying to resolve the
// state of a locally cached document that is in limbo.
SnapshotVersion lastRemoteVersion = targetCache.getLastRemoteSnapshotVersion();
if (!remoteVersion.equals(SnapshotVersion.NONE)) {
hardAssert(remoteVersion.compareTo(lastRemoteVersion) >= 0, "Watch stream reverted to previous snapshot?? (%s < %s)", remoteVersion, lastRemoteVersion);
targetCache.setLastRemoteSnapshotVersion(remoteVersion);
}
return localDocuments.getLocalViewOfDocuments(changedDocs, result.existenceChangedKeys);
});
}
use of com.google.firebase.firestore.model.MutableDocument in project firebase-android-sdk by firebase.
the class SQLiteRemoteDocumentCache method processRowInBackground.
private void processRowInBackground(BackgroundQueue backgroundQueue, Map<DocumentKey, MutableDocument> results, Cursor row) {
byte[] rawDocument = row.getBlob(0);
int readTimeSeconds = row.getInt(1);
int readTimeNanos = row.getInt(2);
// Since scheduling background tasks incurs overhead, we only dispatch to a
// background thread if there are still some documents remaining.
Executor executor = row.isLast() ? Executors.DIRECT_EXECUTOR : backgroundQueue;
executor.execute(() -> {
MutableDocument document = decodeMaybeDocument(rawDocument, readTimeSeconds, readTimeNanos);
synchronized (results) {
results.put(document.getKey(), document);
}
});
}
Aggregations