Skip to content

Commit a692f63

Browse files
authored
DocumentOverlays schema and scaffolding (#3007)
1 parent 6ac9d7f commit a692f63

File tree

14 files changed

+407
-5
lines changed

14 files changed

+407
-5
lines changed

firebase-firestore/firebase-firestore.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,19 @@ android.libraryVariants.all { variant ->
106106
} catch (FileNotFoundException e) {
107107
}
108108

109+
// TODO(Indexing): Delete below once indexing is shipped.
109110
if (localProps['firestoreEnableIndexing']) {
110111
variant.buildConfigField("boolean", "ENABLE_INDEXING", "true")
111112
} else {
112113
variant.buildConfigField("boolean", "ENABLE_INDEXING", "false")
113114
}
115+
116+
// TODO(Overlay): Delete below once overlay is shipped.
117+
if (localProps['firestoreEnableOverlay']) {
118+
variant.buildConfigField("boolean", "ENABLE_OVERLAY", "true")
119+
} else {
120+
variant.buildConfigField("boolean", "ENABLE_OVERLAY", "false")
121+
}
114122
}
115123

116124
configurations.all {

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/testutil/IntegrationTestUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ public static FirebaseFirestore testFirestore(
255255
// This unfortunately is a global setting that affects existing Firestore clients.
256256
Logger.setLogLevel(logLevel);
257257

258-
// TODO: Remove this once this is ready to ship.
258+
// TODO(Overlay): Remove below once this is ready to ship.
259+
Persistence.OVERLAY_SUPPORT_ENABLED = true;
259260
Persistence.INDEXING_SUPPORT_ENABLED = true;
260261

261262
Context context = ApplicationProvider.getApplicationContext();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.local;
16+
17+
import androidx.annotation.Nullable;
18+
import com.google.firebase.firestore.model.DocumentKey;
19+
import com.google.firebase.firestore.model.mutation.Mutation;
20+
21+
/**
22+
* Provides methods to read and write document overlays.
23+
*
24+
* <p>An overlay is a saved {@link Mutation}, that gives a local view of a document when applied to
25+
* the remote version of the document.
26+
*/
27+
public interface DocumentOverlay {
28+
/**
29+
* Gets the saved overlay mutation for the given document key. Returns null if there is no overlay
30+
* for that key.
31+
*/
32+
@Nullable
33+
Mutation getOverlay(DocumentKey key);
34+
35+
/** Saves the given mutation as overlay for the given document key. */
36+
void saveOverlay(DocumentKey key, Mutation mutation);
37+
38+
/** Removes the overlay associated for the given document key. */
39+
void removeOverlay(DocumentKey key);
40+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,12 @@ public FieldIndex decodeFieldIndex(
327327
fieldIndex.withVersion(new SnapshotVersion(new Timestamp(updateSeconds, updateNanos)));
328328
return fieldIndex;
329329
}
330+
331+
public Mutation decodeMutation(Write mutation) {
332+
return rpcSerializer.decodeMutation(mutation);
333+
}
334+
335+
public Write encodeMutation(Mutation mutation) {
336+
return rpcSerializer.encodeMutation(mutation);
337+
}
330338
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.local;
16+
17+
import androidx.annotation.Nullable;
18+
import com.google.firebase.firestore.model.DocumentKey;
19+
import com.google.firebase.firestore.model.mutation.Mutation;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
public class MemoryDocumentOverlay implements DocumentOverlay {
24+
private Map<DocumentKey, Mutation> overlays = new HashMap<>();
25+
26+
@Nullable
27+
@Override
28+
public Mutation getOverlay(DocumentKey key) {
29+
return overlays.get(key);
30+
}
31+
32+
@Override
33+
public void saveOverlay(DocumentKey key, Mutation mutation) {
34+
overlays.put(key, mutation);
35+
}
36+
37+
@Override
38+
public void removeOverlay(DocumentKey key) {
39+
overlays.remove(key);
40+
}
41+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public final class MemoryPersistence extends Persistence {
3232
// LocalStore wrapping this Persistence instance and this will make the in-memory persistence
3333
// layer behave as if it were actually persisting values.
3434
private final Map<User, MemoryMutationQueue> mutationQueues;
35+
private final Map<User, MemoryDocumentOverlay> overlays;
3536
private final MemoryIndexManager indexManager;
3637
private final MemoryTargetCache targetCache;
3738
private final MemoryBundleCache bundleCache;
@@ -61,6 +62,7 @@ private MemoryPersistence() {
6162
targetCache = new MemoryTargetCache(this);
6263
bundleCache = new MemoryBundleCache();
6364
remoteDocumentCache = new MemoryRemoteDocumentCache(this);
65+
overlays = new HashMap<>();
6466
}
6567

6668
@Override
@@ -125,6 +127,16 @@ BundleCache getBundleCache() {
125127
return bundleCache;
126128
}
127129

130+
@Override
131+
DocumentOverlay getDocumentOverlay(User user) {
132+
MemoryDocumentOverlay overlay = overlays.get(user);
133+
if (overlay == null) {
134+
overlay = new MemoryDocumentOverlay();
135+
overlays.put(user, overlay);
136+
}
137+
return overlay;
138+
}
139+
128140
@Override
129141
void runTransaction(String action, Runnable operation) {
130142
referenceDelegate.onTransactionStarted();

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@
5050
public abstract class Persistence {
5151
static final String TAG = Persistence.class.getSimpleName();
5252

53+
/** Temporary setting for enabling document overlays. */
54+
// TODO(Overlay): Remove this.
55+
public static boolean OVERLAY_SUPPORT_ENABLED = BuildConfig.ENABLE_OVERLAY;
56+
5357
/** Temporary setting for enabling indexing-specific code paths while in development. */
54-
// TODO: Remove this.
58+
// TODO(Indexing): Remove this.
5559
public static boolean INDEXING_SUPPORT_ENABLED = BuildConfig.ENABLE_INDEXING;
5660

5761
// Local subclasses only, please.
@@ -93,6 +97,9 @@ public abstract class Persistence {
9397
/** Returns a BundleCache representing the persisted cache of loaded bundles. */
9498
abstract BundleCache getBundleCache();
9599

100+
/** Returns a DocumentOverlay representing the documents that are mutated locally. */
101+
abstract DocumentOverlay getDocumentOverlay(User user);
102+
96103
/**
97104
* Performs an operation inside a persistence transaction. Any reads or writes against persistence
98105
* must be performed within a transaction. Writes will be committed atomically once the
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.local;
16+
17+
import static com.google.firebase.firestore.util.Assert.fail;
18+
19+
import androidx.annotation.Nullable;
20+
import com.google.firebase.firestore.auth.User;
21+
import com.google.firebase.firestore.model.DocumentKey;
22+
import com.google.firebase.firestore.model.mutation.Mutation;
23+
import com.google.firestore.v1.Write;
24+
import com.google.protobuf.InvalidProtocolBufferException;
25+
26+
public class SQLiteDocumentOverlay implements DocumentOverlay {
27+
private final SQLitePersistence db;
28+
private final LocalSerializer serializer;
29+
private final String uid;
30+
31+
public SQLiteDocumentOverlay(SQLitePersistence db, LocalSerializer serializer, User user) {
32+
this.db = db;
33+
this.serializer = serializer;
34+
this.uid = user.isAuthenticated() ? user.getUid() : "";
35+
}
36+
37+
@Nullable
38+
@Override
39+
public Mutation getOverlay(DocumentKey key) {
40+
String path = EncodedPath.encode(key.getPath());
41+
return db.query("SELECT overlay_mutation FROM document_overlays WHERE uid = ? AND path = ?")
42+
.binding(uid, path)
43+
.firstValue(
44+
row -> {
45+
if (row != null) {
46+
try {
47+
Write mutation = Write.parseFrom(row.getBlob(0));
48+
return serializer.decodeMutation(mutation);
49+
} catch (InvalidProtocolBufferException e) {
50+
throw fail("Overlay failed to parse: %s", e);
51+
}
52+
}
53+
54+
return null;
55+
});
56+
}
57+
58+
@Override
59+
public void saveOverlay(DocumentKey key, Mutation mutation) {
60+
db.execute(
61+
"INSERT OR REPLACE INTO document_overlays "
62+
+ "(uid, path, overlay_mutation) VALUES (?, ?, ?)",
63+
uid,
64+
EncodedPath.encode(key.getPath()),
65+
serializer.encodeMutation(mutation).toByteArray());
66+
}
67+
68+
@Override
69+
public void removeOverlay(DocumentKey key) {
70+
db.execute(
71+
"DELETE FROM document_overlays WHERE uid = ? AND path = ?",
72+
uid,
73+
EncodedPath.encode(key.getPath()));
74+
}
75+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ BundleCache getBundleCache() {
192192
return bundleCache;
193193
}
194194

195+
@Override
196+
DocumentOverlay getDocumentOverlay(User user) {
197+
return new SQLiteDocumentOverlay(this, this.serializer, user);
198+
}
199+
195200
@Override
196201
RemoteDocumentCache getRemoteDocumentCache() {
197202
return remoteDocumentCache;

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ class SQLiteSchema {
5151
*/
5252
static final int VERSION = 12;
5353

54+
static final int OVERLAY_SUPPORT_VERSION = VERSION + 1;
55+
5456
// TODO(indexing): Remove this constant and increment VERSION to enable indexing support
55-
static final int INDEXING_SUPPORT_VERSION = VERSION + 1;
57+
static final int INDEXING_SUPPORT_VERSION = OVERLAY_SUPPORT_VERSION + 1;
5658

5759
/**
5860
* The batch size for the sequence number migration in `ensureSequenceNumbers()`.
@@ -77,8 +79,14 @@ void runMigrations() {
7779
}
7880

7981
void runMigrations(int fromVersion) {
80-
runMigrations(
81-
fromVersion, Persistence.INDEXING_SUPPORT_ENABLED ? INDEXING_SUPPORT_VERSION : VERSION);
82+
int toVersion = VERSION;
83+
if (Persistence.OVERLAY_SUPPORT_ENABLED) {
84+
toVersion = OVERLAY_SUPPORT_VERSION;
85+
}
86+
if (Persistence.INDEXING_SUPPORT_ENABLED) {
87+
toVersion = INDEXING_SUPPORT_VERSION;
88+
}
89+
runMigrations(fromVersion, toVersion);
8290
}
8391

8492
/**
@@ -174,6 +182,11 @@ void runMigrations(int fromVersion, int toVersion) {
174182
* maintained invariants from later versions, so migrations that update values cannot assume
175183
* that existing values have been properly maintained. Calculate them again, if applicable.
176184
*/
185+
if (fromVersion < OVERLAY_SUPPORT_VERSION && toVersion >= OVERLAY_SUPPORT_VERSION) {
186+
Preconditions.checkState(
187+
Persistence.OVERLAY_SUPPORT_ENABLED || Persistence.INDEXING_SUPPORT_ENABLED);
188+
createOverlays();
189+
}
177190

178191
if (fromVersion < INDEXING_SUPPORT_VERSION && toVersion >= INDEXING_SUPPORT_VERSION) {
179192
Preconditions.checkState(Persistence.INDEXING_SUPPORT_ENABLED);
@@ -600,6 +613,19 @@ private void createBundleCache() {
600613
});
601614
}
602615

616+
private void createOverlays() {
617+
ifTablesDontExist(
618+
new String[] {"document_overlays"},
619+
() -> {
620+
db.execSQL(
621+
"CREATE TABLE document_overlays ("
622+
+ "uid TEXT, "
623+
+ "path TEXT, "
624+
+ "overlay_mutation BLOB, "
625+
+ "PRIMARY KEY (uid, path))");
626+
});
627+
}
628+
603629
private boolean tableExists(String table) {
604630
return !new SQLitePersistence.Query(db, "SELECT 1=1 FROM sqlite_master WHERE tbl_name = ?")
605631
.binding(table)

0 commit comments

Comments
 (0)