diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteSchema.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteSchema.java index 0093132e09f..b2270ce8155 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteSchema.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteSchema.java @@ -38,7 +38,9 @@ class SQLiteSchema { * The version of the schema. Increase this by one for each migration added to runMigrations * below. */ - static final int VERSION = (Persistence.INDEXING_SUPPORT_ENABLED) ? 7 : 6; + static final int VERSION = 7; + // Remove this constant and increment VERSION to enable indexing support + static final int INDEXING_SUPPORT_VERSION = VERSION + 1; private final SQLiteDatabase db; @@ -99,6 +101,10 @@ void runMigrations(int fromVersion, int toVersion) { } if (fromVersion < 7 && toVersion >= 7) { + ensureSequenceNumbers(); + } + + if (fromVersion < INDEXING_SUPPORT_VERSION && toVersion >= INDEXING_SUPPORT_VERSION) { Preconditions.checkState(Persistence.INDEXING_SUPPORT_ENABLED); createLocalDocumentsCollectionIndex(); } @@ -245,4 +251,34 @@ private void addTargetCount() { private void addSequenceNumber() { db.execSQL("ALTER TABLE target_documents ADD COLUMN sequence_number INTEGER"); } + + /** + * Ensures that each entry in the remote document cache has a corresponding sentinel row. Any + * entries that lack a sentinel row are given one with the sequence number set to the highest + * recorded sequence number from the target metadata. + */ + private void ensureSequenceNumbers() { + // Get the current highest sequence number + SQLitePersistence.Query sequenceNumberQuery = + new SQLitePersistence.Query( + db, "SELECT highest_listen_sequence_number FROM target_globals LIMIT 1"); + Long boxedSequenceNumber = sequenceNumberQuery.firstValue(c -> c.getLong(0)); + hardAssert(boxedSequenceNumber != null, "Missing highest sequence number"); + + long sequenceNumber = boxedSequenceNumber; + SQLiteStatement tagDocument = + db.compileStatement( + "INSERT INTO target_documents (target_id, path, sequence_number) VALUES (0, ?, ?)"); + SQLitePersistence.Query untaggedDocumentsQuery = + new SQLitePersistence.Query( + db, + "SELECT RD.path FROM remote_documents AS RD WHERE NOT EXISTS (SELECT TD.path FROM target_documents AS TD WHERE RD.path = TD.path AND TD.target_id = 0)"); + untaggedDocumentsQuery.forEach( + row -> { + tagDocument.clearBindings(); + tagDocument.bindString(1, row.getString(0)); + tagDocument.bindLong(2, sequenceNumber); + hardAssert(tagDocument.executeInsert() != -1, "Failed to insert a sentinel row"); + }); + } } diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteSchemaTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteSchemaTest.java index 1d592e5720a..ec03c6160ed 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteSchemaTest.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteSchemaTest.java @@ -189,6 +189,49 @@ private void addMutationBatch(SQLiteDatabase db, int batchId, String uid, String new Object[] {uid, batchId, write.build().toByteArray()}); } + @Test + public void addsSentinelRows() { + schema.runMigrations(0, 6); + + long oldSequenceNumber = 1; + // Set the highest sequence number to this value so that untagged documents + // will pick up this value. + long newSequenceNumber = 2; + db.execSQL( + "UPDATE target_globals SET highest_listen_sequence_number = ?", + new Object[] {newSequenceNumber}); + + // Set up some documents (we only need the keys) + // For the odd ones, add sentinel rows. + for (int i = 0; i < 10; i++) { + String path = "docs/doc_" + i; + db.execSQL("INSERT INTO remote_documents (path) VALUES (?)", new String[] {path}); + if (i % 2 == 1) { + db.execSQL( + "INSERT INTO target_documents (target_id, path, sequence_number) VALUES (0, ?, ?)", + new Object[] {path, oldSequenceNumber}); + } + } + + schema.runMigrations(6, 7); + + // Verify. + new SQLitePersistence.Query( + db, "SELECT path, sequence_number FROM target_documents WHERE target_id = 0") + .forEach( + row -> { + String path = row.getString(0); + long sequenceNumber = row.getLong(1); + + int docNum = Integer.parseInt(path.split("_")[1]); + // The even documents were missing sequence numbers, they should now be filled in + // to have the new sequence number. The odd documents should have their + // sequence number unchanged, and so be the old value. + long expected = docNum % 2 == 1 ? oldSequenceNumber : newSequenceNumber; + assertEquals(expected, sequenceNumber); + }); + } + private void assertNoResultsForQuery(String query, String[] args) { Cursor cursor = null; try {