2121import static com .google .firebase .firestore .util .Util .repeatSequence ;
2222
2323import android .database .Cursor ;
24+ import android .util .Log ;
2425import androidx .annotation .NonNull ;
2526import androidx .annotation .VisibleForTesting ;
2627import com .google .firebase .Timestamp ;
4142import java .util .Collection ;
4243import java .util .Collections ;
4344import java .util .HashMap ;
45+ import java .util .Iterator ;
4446import java .util .List ;
4547import java .util .Map ;
4648import java .util .Set ;
49+ import java .util .concurrent .ConcurrentHashMap ;
4750import java .util .concurrent .Executor ;
4851import javax .annotation .Nonnull ;
4952import 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