Skip to content

Commit 21580d1

Browse files
committed
run document type backfills on query
1 parent 46b6943 commit 21580d1

File tree

1 file changed

+110
-41
lines changed

1 file changed

+110
-41
lines changed

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

Lines changed: 110 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.firebase.firestore.util.Util.repeatSequence;
2222

2323
import android.database.Cursor;
24+
import android.util.Log;
2425
import androidx.annotation.NonNull;
2526
import androidx.annotation.VisibleForTesting;
2627
import com.google.firebase.Timestamp;
@@ -41,9 +42,11 @@
4142
import java.util.Collection;
4243
import java.util.Collections;
4344
import java.util.HashMap;
45+
import java.util.Iterator;
4446
import java.util.List;
4547
import java.util.Map;
4648
import java.util.Set;
49+
import java.util.concurrent.ConcurrentHashMap;
4750
import java.util.concurrent.Executor;
4851
import javax.annotation.Nonnull;
4952
import javax.annotation.Nullable;
@@ -56,6 +59,8 @@ final class SQLiteRemoteDocumentCache implements RemoteDocumentCache {
5659
private final LocalSerializer serializer;
5760
private IndexManager indexManager;
5861

62+
private final DocumentTypeBackfills documentTypeBackfills = new DocumentTypeBackfills();
63+
5964
SQLiteRemoteDocumentCache(SQLitePersistence persistence, LocalSerializer serializer) {
6065
this.db = persistence;
6166
this.serializer = serializer;
@@ -168,17 +173,15 @@ public Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> documentKe
168173
") ORDER BY path");
169174

170175
BackgroundQueue backgroundQueue = new BackgroundQueue();
171-
ArrayList<DocumentTypeUpdateInfo> documentTypeBackfills = new ArrayList<>();
172176
while (longQuery.hasMoreSubqueries()) {
173177
longQuery
174178
.performNextSubquery()
175-
.forEach(
176-
row ->
177-
processRowInBackground(
178-
backgroundQueue, results, row, documentTypeBackfills, /*filter*/ null));
179+
.forEach(row -> processRowInBackground(backgroundQueue, results, row, /*filter*/ null));
179180
}
180181
backgroundQueue.drain();
181182

183+
documentTypeBackfills.backfill(db);
184+
182185
synchronized (results) {
183186
return results;
184187
}
@@ -265,18 +268,19 @@ private Map<DocumentKey, MutableDocument> getAll(
265268

266269
BackgroundQueue backgroundQueue = new BackgroundQueue();
267270
Map<DocumentKey, MutableDocument> results = new HashMap<>();
268-
ArrayList<DocumentTypeUpdateInfo> documentTypeBackfills = new ArrayList<>();
269271
db.query(sql.toString())
270272
.binding(bindVars)
271273
.forEach(
272274
row -> {
273-
processRowInBackground(backgroundQueue, results, row, documentTypeBackfills, filter);
275+
processRowInBackground(backgroundQueue, results, row, filter);
274276
if (context != null) {
275277
context.incrementDocumentReadCount();
276278
}
277279
});
278280
backgroundQueue.drain();
279281

282+
documentTypeBackfills.backfill(db);
283+
280284
synchronized (results) {
281285
return results;
282286
}
@@ -295,7 +299,6 @@ private void processRowInBackground(
295299
BackgroundQueue backgroundQueue,
296300
Map<DocumentKey, MutableDocument> results,
297301
Cursor row,
298-
List<DocumentTypeUpdateInfo> documentTypeBackfills,
299302
@Nullable Function<MutableDocument, Boolean> filter) {
300303
byte[] rawDocument = row.getBlob(0);
301304
int readTimeSeconds = row.getInt(1);
@@ -311,15 +314,7 @@ private void processRowInBackground(
311314
MutableDocument document =
312315
decodeMaybeDocument(rawDocument, readTimeSeconds, readTimeNanos);
313316
if (documentTypeIsNull) {
314-
DocumentTypeUpdateInfo updateInfo =
315-
new DocumentTypeUpdateInfo(
316-
path,
317-
readTimeSeconds,
318-
readTimeNanos,
319-
DocumentType.forMutableDocument(document));
320-
synchronized (documentTypeBackfills) {
321-
documentTypeBackfills.add(updateInfo);
322-
}
317+
documentTypeBackfills.add(path, readTimeSeconds, readTimeNanos, document);
323318
}
324319
if (filter == null || filter.apply(document)) {
325320
synchronized (results) {
@@ -364,32 +359,106 @@ private MutableDocument decodeMaybeDocument(
364359
}
365360
}
366361

367-
private static class DocumentTypeUpdateInfo {
368-
final String path;
369-
final int readTimeSeconds;
370-
final int readTimeNanos;
371-
final DocumentType documentType;
372-
373-
DocumentTypeUpdateInfo(
374-
String path, int readTimeSeconds, int readTimeNanos, DocumentType documentType) {
375-
this.path = path;
376-
this.readTimeSeconds = readTimeSeconds;
377-
this.readTimeNanos = readTimeNanos;
378-
this.documentType = documentType;
362+
// This class is thread safe and all public methods may be safely called concurrently from
363+
// multiple threads. This makes it safe to use instances of this class from BackgroundQueue.
364+
private static class DocumentTypeBackfills {
365+
366+
private final ConcurrentHashMap<BackfillKey, DocumentType> documentTypeByBackfillKey =
367+
new ConcurrentHashMap<>();
368+
369+
enum BackfillResult {
370+
NO_PENDING_BACKFILLS,
371+
HAS_PENDING_BACKFILLS,
372+
}
373+
374+
void add(String path, int readTimeSeconds, int readTimeNanos, MutableDocument document) {
375+
BackfillKey backfillKey = new BackfillKey(path, readTimeSeconds, readTimeNanos);
376+
DocumentType documentType = DocumentType.forMutableDocument(document);
377+
documentTypeByBackfillKey.putIfAbsent(backfillKey, documentType);
378+
}
379+
380+
BackfillResult backfill(SQLitePersistence db) {
381+
ArrayList<String> caseClauses = new ArrayList<>();
382+
ArrayList<Object> caseClauseBindings = new ArrayList<>();
383+
ArrayList<String> whereClauses = new ArrayList<>();
384+
ArrayList<Object> whereClauseBindings = new ArrayList<>();
385+
386+
Iterator<BackfillKey> backfillKeys = documentTypeByBackfillKey.keySet().iterator();
387+
while (backfillKeys.hasNext()
388+
&& caseClauseBindings.size() + whereClauseBindings.size() < 900) {
389+
BackfillKey backfillKey = backfillKeys.next();
390+
DocumentType documentType = documentTypeByBackfillKey.remove(backfillKey);
391+
if (documentType == null) {
392+
continue;
393+
}
394+
395+
caseClauses.add("WHEN path=? AND read_time_seconds=? AND read_time_nanos=? THEN ?");
396+
caseClauseBindings.add(backfillKey.path);
397+
caseClauseBindings.add(backfillKey.readTimeSeconds);
398+
caseClauseBindings.add(backfillKey.readTimeNanos);
399+
caseClauseBindings.add(documentType.dbValue);
400+
401+
whereClauses.add("(path=? AND read_time_seconds=? AND read_time_nanos=?)");
402+
whereClauseBindings.add(backfillKey.path);
403+
whereClauseBindings.add(backfillKey.readTimeSeconds);
404+
whereClauseBindings.add(backfillKey.readTimeNanos);
405+
}
406+
407+
if (!caseClauseBindings.isEmpty()) {
408+
String sql;
409+
{
410+
StringBuilder sb = new StringBuilder("UPDATE remote_documents SET document_type = CASE");
411+
for (String caseClause : caseClauses) {
412+
sb.append(' ').append(caseClause);
413+
}
414+
sb.append(" ELSE NULL END WHERE ");
415+
boolean isFirstWhereClause = true;
416+
for (String whereClause : whereClauses) {
417+
if (isFirstWhereClause) {
418+
isFirstWhereClause = false;
419+
} else {
420+
sb.append(" OR ");
421+
}
422+
sb.append(whereClause);
423+
}
424+
sql = sb.toString();
425+
}
426+
427+
caseClauseBindings.addAll(whereClauseBindings);
428+
Object[] bindings = caseClauseBindings.toArray();
429+
430+
Log.i("zzyzx", "sql=sql");
431+
432+
db.execute(sql, bindings);
433+
}
434+
435+
return documentTypeByBackfillKey.isEmpty()
436+
? BackfillResult.NO_PENDING_BACKFILLS
437+
: BackfillResult.HAS_PENDING_BACKFILLS;
379438
}
380439

381-
@NonNull
382-
@Override
383-
public String toString() {
384-
return "DocumentTypeUpdateInfo(path="
385-
+ path
386-
+ ", readTimeSeconds="
387-
+ readTimeSeconds
388-
+ ", readTimeNanos="
389-
+ readTimeNanos
390-
+ ", documentType="
391-
+ documentType
392-
+ ")";
440+
private static class BackfillKey {
441+
final String path;
442+
final int readTimeSeconds;
443+
final int readTimeNanos;
444+
445+
BackfillKey(String path, int readTimeSeconds, int readTimeNanos) {
446+
this.path = path;
447+
this.readTimeSeconds = readTimeSeconds;
448+
this.readTimeNanos = readTimeNanos;
449+
}
450+
451+
@NonNull
452+
@Override
453+
public String toString() {
454+
return "DocumentTypeBackfills.BackfillKey(path="
455+
+ path
456+
+ ", readTimeSeconds="
457+
+ readTimeSeconds
458+
+ ", readTimeNanos="
459+
+ readTimeNanos
460+
+ ")";
461+
}
393462
}
394463
}
395464
}

0 commit comments

Comments
 (0)