|
15 | 15 | package com.google.firebase.firestore.local; |
16 | 16 |
|
17 | 17 | import static com.google.common.truth.Truth.assertThat; |
| 18 | +import static com.google.common.truth.Truth.assertWithMessage; |
18 | 19 | import static com.google.firebase.firestore.testutil.TestUtil.addedRemoteEvent; |
19 | 20 | import static com.google.firebase.firestore.testutil.TestUtil.deleteMutation; |
20 | 21 | import static com.google.firebase.firestore.testutil.TestUtil.deletedDoc; |
|
27 | 28 | import static com.google.firebase.firestore.testutil.TestUtil.orFilters; |
28 | 29 | import static com.google.firebase.firestore.testutil.TestUtil.orderBy; |
29 | 30 | import static com.google.firebase.firestore.testutil.TestUtil.query; |
| 31 | +import static com.google.firebase.firestore.testutil.TestUtil.removedRemoteEvent; |
30 | 32 | import static com.google.firebase.firestore.testutil.TestUtil.setMutation; |
31 | 33 | import static com.google.firebase.firestore.testutil.TestUtil.updateRemoteEvent; |
32 | 34 | import static com.google.firebase.firestore.testutil.TestUtil.version; |
|
39 | 41 | import com.google.firebase.firestore.model.DocumentKey; |
40 | 42 | import com.google.firebase.firestore.model.FieldIndex; |
41 | 43 | import com.google.firebase.firestore.model.FieldPath; |
| 44 | +import com.google.firebase.firestore.model.MutableDocument; |
42 | 45 | import com.google.firebase.firestore.model.ObjectValue; |
| 46 | +import com.google.firebase.firestore.model.ResourcePath; |
43 | 47 | import com.google.firebase.firestore.model.ServerTimestamps; |
44 | 48 | import com.google.firebase.firestore.model.mutation.FieldMask; |
45 | 49 | import com.google.firebase.firestore.model.mutation.FieldTransform; |
|
64 | 68 | @RunWith(RobolectricTestRunner.class) |
65 | 69 | @Config(manifest = Config.NONE) |
66 | 70 | public class SQLiteLocalStoreTest extends LocalStoreTestCase { |
| 71 | + |
| 72 | + private final AtomicReference<SQLitePersistence> sqlitePersistence = new AtomicReference<>(); |
| 73 | + |
67 | 74 | @Override |
68 | 75 | Persistence getPersistence() { |
69 | | - return PersistenceTestHelpers.createSQLitePersistence(); |
| 76 | + SQLitePersistence sqlitePersistence = PersistenceTestHelpers.createSQLitePersistence(); |
| 77 | + if (!this.sqlitePersistence.compareAndSet(null, sqlitePersistence)) { |
| 78 | + throw new RuntimeException("getPersistence() has already been called [error code qvw3g9kns]"); |
| 79 | + } |
| 80 | + return sqlitePersistence; |
70 | 81 | } |
71 | 82 |
|
72 | 83 | @Override |
@@ -816,4 +827,78 @@ public void testIndexAutoCreationDoesnotWorkWithMultipleInequality() { |
816 | 827 | assertRemoteDocumentsRead(/* byKey= */ 0, /* byCollection= */ 2); |
817 | 828 | assertQueryReturned("coll/a", "coll/e"); |
818 | 829 | } |
| 830 | + |
| 831 | + @Test |
| 832 | + public void testDocumentTypeIsSetWhenDocumentedAdded() { |
| 833 | + Query query = query("coll"); |
| 834 | + int targetId = allocateQuery(query); |
| 835 | + MutableDocument doc = doc("coll/a", 10, map("foo", 42)); |
| 836 | + |
| 837 | + applyRemoteEvent(addedRemoteEvent(doc, targetId)); |
| 838 | + |
| 839 | + Map<ResourcePath, Integer> expected = new HashMap<>(); |
| 840 | + expected.put(doc.getKey().getPath(), 2); // 2 is a "found" document |
| 841 | + assertThat(getDocumentTypeByPathFromRemoteDocumentsTable()).containsExactlyEntriesIn(expected); |
| 842 | + } |
| 843 | + |
| 844 | + @Test |
| 845 | + public void testDocumentTypeIsUpdatedWhenDocumentedDeleted() { |
| 846 | + Query query = query("coll"); |
| 847 | + int targetId = allocateQuery(query); |
| 848 | + MutableDocument doc = doc("coll/a", 10, map("foo", 42)); |
| 849 | + applyRemoteEvent(addedRemoteEvent(doc, targetId)); |
| 850 | + |
| 851 | + applyRemoteEvent(removedRemoteEvent(doc.getKey(), 11, targetId)); |
| 852 | + |
| 853 | + Map<ResourcePath, Integer> expected = new HashMap<>(); |
| 854 | + expected.put(doc.getKey().getPath(), 1); // 1 is a "no" document |
| 855 | + assertThat(getDocumentTypeByPathFromRemoteDocumentsTable()).containsExactlyEntriesIn(expected); |
| 856 | + } |
| 857 | + |
| 858 | + @Test |
| 859 | + public void testDocumentTypeColumnIsBackfilledByQuery() { |
| 860 | + Query query = query("coll"); |
| 861 | + int targetId = allocateQuery(query); |
| 862 | + MutableDocument existingDoc = doc("coll/a", 10, map("foo", 42)); |
| 863 | + applyRemoteEvent(addedRemoteEvent(existingDoc, targetId)); |
| 864 | + MutableDocument deletedDoc = doc("coll/b", 10, map("foo", 42)); |
| 865 | + applyRemoteEvent(addedRemoteEvent(deletedDoc, targetId)); |
| 866 | + applyRemoteEvent(removedRemoteEvent(deletedDoc.getKey(), 11, targetId)); |
| 867 | + SQLitePersistence persistence = this.sqlitePersistence.get(); |
| 868 | + persistence.execute("UPDATE remote_documents SET document_type = NULL"); |
| 869 | + assertWithMessage("precondition check: all rows have null document type") |
| 870 | + .that(getDocumentTypeByPathFromRemoteDocumentsTable().values()) |
| 871 | + .containsExactly(null, null); |
| 872 | + |
| 873 | + executeQuery(query); |
| 874 | + |
| 875 | + Map<ResourcePath, Integer> expected = new HashMap<>(); |
| 876 | + expected.put(existingDoc.getKey().getPath(), 2); // 2 is a "found" document |
| 877 | + expected.put(deletedDoc.getKey().getPath(), 1); // 1 is a "no" document |
| 878 | + assertThat(getDocumentTypeByPathFromRemoteDocumentsTable()).containsExactlyEntriesIn(expected); |
| 879 | + } |
| 880 | + |
| 881 | + /** |
| 882 | + * Scans the entire "remote documents" sqlite table and returns the value of the "document type" |
| 883 | + * column for each row, keyed by the value of the "path" column of the corresponding row. Note |
| 884 | + * that the values of the returned map may be `null`, if the column value for that row is null. |
| 885 | + */ |
| 886 | + private Map<ResourcePath, Integer> getDocumentTypeByPathFromRemoteDocumentsTable() { |
| 887 | + HashMap<ResourcePath, Integer> documentTypeByPath = new HashMap<>(); |
| 888 | + sqlitePersistence |
| 889 | + .get() |
| 890 | + .query("SELECT path, document_type FROM remote_documents") |
| 891 | + .forEach( |
| 892 | + row -> { |
| 893 | + String encodedPath = row.getString(0); |
| 894 | + ResourcePath path = EncodedPath.decodeResourcePath(encodedPath); |
| 895 | + Integer documentType = row.isNull(1) ? null : row.getInt(1); |
| 896 | + synchronized (documentTypeByPath) { |
| 897 | + documentTypeByPath.put(path, documentType); |
| 898 | + } |
| 899 | + }); |
| 900 | + synchronized (documentTypeByPath) { |
| 901 | + return documentTypeByPath; |
| 902 | + } |
| 903 | + } |
819 | 904 | } |
0 commit comments