Skip to content

Commit 723defc

Browse files
Use PathLength
1 parent 0f93b23 commit 723defc

File tree

3 files changed

+74
-72
lines changed

3 files changed

+74
-72
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteRemoteDocumentCache.java

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ public void add(MutableDocument document, SnapshotVersion readTime) {
6363

6464
db.execute(
6565
"INSERT OR REPLACE INTO remote_documents "
66-
+ "(path, parent_path, read_time_seconds, read_time_nanos, contents) "
66+
+ "(path, path_length, read_time_seconds, read_time_nanos, contents) "
6767
+ "VALUES (?, ?, ?, ?, ?)",
6868
EncodedPath.encode(documentKey.getPath()),
69-
EncodedPath.encode(documentKey.getCollectionPath()),
69+
documentKey.getPath().length(),
7070
timestamp.getSeconds(),
7171
timestamp.getNanoseconds(),
7272
message.toByteArray());
@@ -135,69 +135,73 @@ public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQ
135135
!query.isCollectionGroupQuery(),
136136
"CollectionGroup queries should be handled in LocalDocumentsView");
137137

138-
String parentPath = EncodedPath.encode(query.getPath());
139-
BackgroundQueue backgroundQueue = new BackgroundQueue();
138+
StringBuilder sql =
139+
new StringBuilder(
140+
"SELECT contents, read_time_seconds, read_time_nanos "
141+
+ "FROM remote_documents WHERE path >= ? AND path < ? AND path_length = ?");
140142

141-
ImmutableSortedMap<DocumentKey, MutableDocument>[] matchingDocuments =
142-
(ImmutableSortedMap<DocumentKey, MutableDocument>[])
143-
new ImmutableSortedMap[] {DocumentCollections.emptyMutableDocumentMap()};
143+
Object[] bindVars = new Object[3 + (FieldIndex.IndexOffset.NONE.equals(offset) ? 0 : 6)];
144144

145-
SQLitePersistence.Query sqlQuery;
146-
if (FieldIndex.IndexOffset.NONE.equals(offset)) {
147-
sqlQuery =
148-
db.query(
149-
"SELECT contents, read_time_seconds, read_time_nanos "
150-
+ "FROM remote_documents WHERE parent_path = ?")
151-
.binding(parentPath);
152-
} else {
145+
String prefix = EncodedPath.encode(query.getPath());
146+
147+
int i = 0;
148+
bindVars[i++] = prefix;
149+
bindVars[i++] = EncodedPath.prefixSuccessor(prefix);
150+
bindVars[i++] = query.getPath().length() + 1;
151+
152+
if (!FieldIndex.IndexOffset.NONE.equals(offset)) {
153153
Timestamp readTime = offset.getReadTime().getTimestamp();
154154
DocumentKey documentKey = offset.getDocumentKey();
155155

156-
sqlQuery =
157-
db.query(
158-
"SELECT contents, read_time_seconds, read_time_nanos "
159-
+ "FROM remote_documents WHERE parent_path = ? AND ("
160-
+ "read_time_seconds > ? OR ("
161-
+ "read_time_seconds = ? AND read_time_nanos > ?) OR ("
162-
+ "read_time_seconds = ? AND read_time_nanos = ? and path > ?))")
163-
.binding(
164-
parentPath,
165-
readTime.getSeconds(),
166-
readTime.getSeconds(),
167-
readTime.getNanoseconds(),
168-
readTime.getSeconds(),
169-
readTime.getNanoseconds(),
170-
EncodedPath.encode(documentKey.getPath()));
156+
sql.append(
157+
" AND (read_time_seconds > ? OR ("
158+
+ "read_time_seconds = ? AND read_time_nanos > ?) OR ("
159+
+ "read_time_seconds = ? AND read_time_nanos = ? and path > ?))");
160+
bindVars[i++] = readTime.getSeconds();
161+
bindVars[i++] = readTime.getSeconds();
162+
bindVars[i++] = readTime.getNanoseconds();
163+
bindVars[i++] = readTime.getSeconds();
164+
bindVars[i++] = readTime.getNanoseconds();
165+
bindVars[i] = EncodedPath.encode(documentKey.getPath());
171166
}
172-
sqlQuery.forEach(
173-
row -> {
174-
// Store row values in array entries to provide the correct context inside the executor.
175-
final byte[] rawDocument = row.getBlob(0);
176-
final int[] readTimeSeconds = {row.getInt(1)};
177-
final int[] readTimeNanos = {row.getInt(2)};
178-
179-
// Since scheduling background tasks incurs overhead, we only dispatch to a
180-
// background thread if there are still some documents remaining.
181-
Executor executor = row.isLast() ? Executors.DIRECT_EXECUTOR : backgroundQueue;
182-
executor.execute(
183-
() -> {
184-
MutableDocument document =
185-
decodeMaybeDocument(rawDocument, readTimeSeconds[0], readTimeNanos[0]);
186-
if (document.isFoundDocument() && query.matches(document)) {
187-
synchronized (SQLiteRemoteDocumentCache.this) {
188-
matchingDocuments[0] = matchingDocuments[0].insert(document.getKey(), document);
189-
}
190-
}
191-
});
192-
});
167+
168+
ImmutableSortedMap<DocumentKey, MutableDocument>[] results =
169+
(ImmutableSortedMap<DocumentKey, MutableDocument>[])
170+
new ImmutableSortedMap[] {DocumentCollections.emptyMutableDocumentMap()};
171+
BackgroundQueue backgroundQueue = new BackgroundQueue();
172+
173+
db.query(sql.toString())
174+
.binding(bindVars)
175+
.forEach(
176+
row -> {
177+
// Store row values in array entries to provide the correct context inside the
178+
// executor.
179+
final byte[] rawDocument = row.getBlob(0);
180+
final int[] readTimeSeconds = {row.getInt(1)};
181+
final int[] readTimeNanos = {row.getInt(2)};
182+
183+
// Since scheduling background tasks incurs overhead, we only dispatch to a
184+
// background thread if there are still some documents remaining.
185+
Executor executor = row.isLast() ? Executors.DIRECT_EXECUTOR : backgroundQueue;
186+
executor.execute(
187+
() -> {
188+
MutableDocument document =
189+
decodeMaybeDocument(rawDocument, readTimeSeconds[0], readTimeNanos[0]);
190+
if (document.isFoundDocument() && query.matches(document)) {
191+
synchronized (SQLiteRemoteDocumentCache.this) {
192+
results[0] = results[0].insert(document.getKey(), document);
193+
}
194+
}
195+
});
196+
});
193197

194198
try {
195199
backgroundQueue.drain();
196200
} catch (InterruptedException e) {
197201
fail("Interrupted while deserializing documents", e);
198202
}
199203

200-
return matchingDocuments[0];
204+
return results[0];
201205
}
202206

203207
@Override

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteSchema.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ void runSchemaUpgrades(int fromVersion, int toVersion) {
174174
}
175175

176176
if (fromVersion < 13 && toVersion >= 13) {
177-
addParentPath();
178-
ensureParentPath();
177+
addPathLength();
178+
ensurePathLength();
179179
}
180180

181181
/*
@@ -447,10 +447,10 @@ private void addSequenceNumber() {
447447
}
448448
}
449449

450-
private void addParentPath() {
451-
if (!tableContainsColumn("remote_documents", "parent_path")) {
452-
db.execSQL("ALTER TABLE remote_documents ADD COLUMN parent_path TEXT");
453-
db.execSQL("CREATE INDEX parent_path_index ON remote_documents(parent_path)");
450+
private void addPathLength() {
451+
if (!tableContainsColumn("remote_documents", "path_length")) {
452+
// The "path_length" column store the number of segments in the path.
453+
db.execSQL("ALTER TABLE remote_documents ADD COLUMN path_length INTEGER");
454454
}
455455
}
456456

@@ -629,14 +629,14 @@ private void rewriteCanonicalIds() {
629629
});
630630
}
631631

632-
/** Fill the remote_document's parent path column. */
633-
private void ensureParentPath() {
632+
/** Fill the remote_document's path_length column. */
633+
private void ensurePathLength() {
634634
SQLitePersistence.Query documentsToMigrate =
635635
new SQLitePersistence.Query(
636-
db, "SELECT path FROM remote_documents WHERE parent_path IS NULL LIMIT ?")
636+
db, "SELECT path FROM remote_documents WHERE path_length IS NULL LIMIT ?")
637637
.binding(MIGRATION_BATCH_SIZE);
638638
SQLiteStatement insertKey =
639-
db.compileStatement("UPDATE remote_documents SET parent_path = ? WHERE path = ?");
639+
db.compileStatement("UPDATE remote_documents SET path_length = ? WHERE path = ?");
640640

641641
boolean[] resultsRemaining = new boolean[1];
642642

@@ -651,7 +651,7 @@ private void ensureParentPath() {
651651
ResourcePath decodedPath = EncodedPath.decodeResourcePath(encodedPath);
652652

653653
insertKey.clearBindings();
654-
insertKey.bindString(1, EncodedPath.encode(decodedPath.popLast()));
654+
insertKey.bindLong(1, decodedPath.length());
655655
insertKey.bindString(2, encodedPath);
656656
hardAssert(insertKey.executeUpdateDelete() != -1, "Failed to update document path");
657657
});

firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteSchemaTest.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -578,10 +578,10 @@ public void rewritesCanonicalIds() {
578578
}
579579

580580
@Test
581-
public void addParentPaths() {
581+
public void addPathLengths() {
582582
schema.runSchemaUpgrades(0, 12);
583583

584-
ResourcePath paths[] = new ResourcePath[] {path("collA/doc"), path("collB/doc")};
584+
ResourcePath paths[] = new ResourcePath[] {path("collA/doc"), path("collA/doc/collB/doc")};
585585

586586
for (ResourcePath path : paths) {
587587
db.execSQL(
@@ -592,18 +592,17 @@ public void addParentPaths() {
592592
schema.runSchemaUpgrades(12, 13);
593593

594594
int[] current = new int[] {0};
595-
new SQLitePersistence.Query(db, "SELECT parent_path FROM remote_documents ORDER BY path")
595+
new SQLitePersistence.Query(db, "SELECT path_length FROM remote_documents ORDER BY path")
596596
.forEach(
597597
cursor -> {
598-
ResourcePath parentPath = EncodedPath.decodeResourcePath(cursor.getString(0));
599-
assertEquals(paths[current[0]].popLast(), parentPath);
598+
assertEquals(paths[current[0]].length(), cursor.getInt(0));
600599
++current[0];
601600
});
602601
assertEquals(2, current[0]);
603602
}
604603

605604
@Test
606-
public void usesMultipleBatchesToAddParentPaths() {
605+
public void usesMultipleBatchesToAddPathLengths() {
607606
schema.runSchemaUpgrades(0, 12);
608607

609608
for (int i = 0; i < SQLiteSchema.MIGRATION_BATCH_SIZE + 1; ++i) {
@@ -616,11 +615,10 @@ public void usesMultipleBatchesToAddParentPaths() {
616615
schema.runSchemaUpgrades(12, 13);
617616

618617
int[] current = new int[] {0};
619-
new SQLitePersistence.Query(db, "SELECT parent_path FROM remote_documents ORDER by path")
618+
new SQLitePersistence.Query(db, "SELECT path_length FROM remote_documents ORDER by path")
620619
.forEach(
621620
cursor -> {
622-
ResourcePath parentPath = EncodedPath.decodeResourcePath(cursor.getString(0));
623-
assertEquals(path("coll"), parentPath);
621+
assertEquals(2, cursor.getInt(0));
624622
++current[0];
625623
});
626624
assertEquals(SQLiteSchema.MIGRATION_BATCH_SIZE + 1, current[0]);

0 commit comments

Comments
 (0)