use of com.google.firebase.firestore.model.ResourcePath in project firebase-android-sdk by firebase.
the class SQLiteMutationQueue method performConsistencyCheck.
@Override
public void performConsistencyCheck() {
if (!isEmpty()) {
return;
}
// Verify that there are no entries in the document_mutations index if the queue is empty.
List<ResourcePath> danglingMutationReferences = new ArrayList<>();
db.query("SELECT path FROM document_mutations WHERE uid = ?").binding(uid).forEach(row -> {
ResourcePath path = EncodedPath.decodeResourcePath(row.getString(0));
danglingMutationReferences.add(path);
});
hardAssert(danglingMutationReferences.isEmpty(), "Document leak -- detected dangling mutation references when queue is empty. " + "Dangling keys: %s", danglingMutationReferences);
}
use of com.google.firebase.firestore.model.ResourcePath in project firebase-android-sdk by firebase.
the class SQLiteMutationQueue method getAllMutationBatchesAffectingQuery.
@Override
public List<MutationBatch> getAllMutationBatchesAffectingQuery(Query query) {
hardAssert(!query.isCollectionGroupQuery(), "CollectionGroup queries should be handled in LocalDocumentsView");
// Use the query path as a prefix for testing if a document matches the query.
ResourcePath prefix = query.getPath();
int immediateChildrenPathLength = prefix.length() + 1;
// Scan the document_mutations table looking for documents whose path has a prefix that matches
// the query path.
//
// The most obvious way to do this would be with a LIKE query with a trailing wildcard (e.g.
// path LIKE 'foo/%'). Unfortunately SQLite does not convert a trailing wildcard like that into
// the equivalent range scan so a LIKE query ends up being a table scan. The query below is
// equivalent but hits the index on both uid and path, so it's much faster.
// TODO: Actually implement a single-collection query
//
// This is actually executing an ancestor query, traversing the whole subtree below the
// collection which can be horrifically inefficient for some structures. The right way to
// solve this is to implement the full value index, but that's not in the cards in the near
// future so this is the best we can do for the moment.
String prefixPath = EncodedPath.encode(prefix);
String prefixSuccessorPath = EncodedPath.prefixSuccessor(prefixPath);
List<MutationBatch> result = new ArrayList<>();
db.query("SELECT dm.batch_id, dm.path, SUBSTR(m.mutations, 1, ?) " + "FROM document_mutations dm, mutations m " + "WHERE dm.uid = ? " + "AND dm.path >= ? " + "AND dm.path < ? " + "AND dm.uid = m.uid " + "AND dm.batch_id = m.batch_id " + "ORDER BY dm.batch_id").binding(BLOB_MAX_INLINE_LENGTH, uid, prefixPath, prefixSuccessorPath).forEach(row -> {
// Ensure unique batches only. This works because the batches come out in order so
// we only need to ensure that the batchId of this row is different from the
// preceding one.
int batchId = row.getInt(0);
int size = result.size();
if (size > 0 && batchId == result.get(size - 1).getBatchId()) {
return;
}
// The query is actually returning any path that starts with the query path prefix
// which may include documents in subcollections. For example, a query on 'rooms'
// will return rooms/abc/messages/xyx but we shouldn't match it. Fix this by
// discarding rows with document keys more than one segment longer than the query
// path.
ResourcePath path = EncodedPath.decodeResourcePath(row.getString(1));
if (path.length() != immediateChildrenPathLength) {
return;
}
result.add(decodeInlineMutationBatch(batchId, row.getBlob(2)));
});
return result;
}
use of com.google.firebase.firestore.model.ResourcePath in project firebase-android-sdk by firebase.
the class SQLiteDocumentOverlayCache method getOverlays.
@Override
public Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys) {
hardAssert(keys.comparator() == null, "getOverlays() requires natural order");
Map<DocumentKey, Overlay> result = new HashMap<>();
BackgroundQueue backgroundQueue = new BackgroundQueue();
ResourcePath currentCollection = ResourcePath.EMPTY;
List<Object> accumulatedDocumentIds = new ArrayList<>();
for (DocumentKey key : keys) {
if (!currentCollection.equals(key.getCollectionPath())) {
processSingleCollection(result, backgroundQueue, currentCollection, accumulatedDocumentIds);
currentCollection = key.getCollectionPath();
accumulatedDocumentIds.clear();
}
accumulatedDocumentIds.add(key.getDocumentId());
}
processSingleCollection(result, backgroundQueue, currentCollection, accumulatedDocumentIds);
backgroundQueue.drain();
return result;
}
use of com.google.firebase.firestore.model.ResourcePath in project firebase-android-sdk by firebase.
the class SQLiteSchema method ensurePathLength.
/**
* Populates the remote_document's path_length column.
*/
private void ensurePathLength() {
SQLitePersistence.Query documentsToMigrate = new SQLitePersistence.Query(db, "SELECT path FROM remote_documents WHERE path_length IS NULL LIMIT ?").binding(MIGRATION_BATCH_SIZE);
SQLiteStatement insertKey = db.compileStatement("UPDATE remote_documents SET path_length = ? WHERE path = ?");
boolean[] resultsRemaining = new boolean[1];
do {
resultsRemaining[0] = false;
documentsToMigrate.forEach(row -> {
resultsRemaining[0] = true;
String encodedPath = row.getString(0);
ResourcePath decodedPath = EncodedPath.decodeResourcePath(encodedPath);
insertKey.clearBindings();
insertKey.bindLong(1, decodedPath.length());
insertKey.bindString(2, encodedPath);
hardAssert(insertKey.executeUpdateDelete() != -1, "Failed to update document path");
});
} while (resultsRemaining[0]);
}
use of com.google.firebase.firestore.model.ResourcePath in project firebase-android-sdk by firebase.
the class SQLiteSchema method createV8CollectionParentsIndex.
private void createV8CollectionParentsIndex() {
ifTablesDontExist(new String[] { "collection_parents" }, () -> {
// A table storing associations between a Collection ID (e.g. 'messages') to a parent path
// (e.g. '/chats/123') that contains it as a (sub)collection. This is used to efficiently
// find all collections to query when performing a Collection Group query. Note that the
// parent path will be an empty path in the case of root-level collections.
db.execSQL("CREATE TABLE collection_parents (" + "collection_id TEXT, " + "parent TEXT, " + "PRIMARY KEY(collection_id, parent))");
});
// Helper to add an index entry iff we haven't already written it.
MemoryIndexManager.MemoryCollectionParentIndex cache = new MemoryIndexManager.MemoryCollectionParentIndex();
SQLiteStatement addIndexEntry = db.compileStatement("INSERT OR REPLACE INTO collection_parents (collection_id, parent) VALUES (?, ?)");
Consumer<ResourcePath> addEntry = collectionPath -> {
if (cache.add(collectionPath)) {
String collectionId = collectionPath.getLastSegment();
ResourcePath parentPath = collectionPath.popLast();
addIndexEntry.clearBindings();
addIndexEntry.bindString(1, collectionId);
addIndexEntry.bindString(2, EncodedPath.encode(parentPath));
addIndexEntry.execute();
}
};
// Index existing remote documents.
SQLitePersistence.Query remoteDocumentsQuery = new SQLitePersistence.Query(db, "SELECT path FROM remote_documents");
remoteDocumentsQuery.forEach(row -> {
ResourcePath path = EncodedPath.decodeResourcePath(row.getString(0));
addEntry.accept(path.popLast());
});
// Index existing mutations.
SQLitePersistence.Query documentMutationsQuery = new SQLitePersistence.Query(db, "SELECT path FROM document_mutations");
documentMutationsQuery.forEach(row -> {
ResourcePath path = EncodedPath.decodeResourcePath(row.getString(0));
addEntry.accept(path.popLast());
});
}
Aggregations