@firebase/firestore 3.4.5 → 3.4.6-canary.3198d58dc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/firestore/src/core/bundle.d.ts +2 -2
- package/dist/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/firestore/src/core/query.d.ts +7 -0
- package/dist/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/firestore/src/model/collections.d.ts +7 -0
- package/dist/firestore/src/model/field_index.d.ts +10 -1
- package/dist/firestore/src/model/type_order.d.ts +2 -1
- package/dist/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/index.esm2017.js +4533 -4502
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4455 -4308
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1606 -1180
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1606 -1180
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4425 -4394
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +31 -10
- package/dist/lite/firestore/src/core/bundle.d.ts +2 -2
- package/dist/lite/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/lite/firestore/src/core/query.d.ts +7 -0
- package/dist/lite/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/lite/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/lite/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/lite/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/lite/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/lite/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/lite/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/lite/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/lite/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/lite/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/lite/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/lite/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/lite/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/lite/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/lite/firestore/src/model/collections.d.ts +7 -0
- package/dist/lite/firestore/src/model/field_index.d.ts +10 -1
- package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/lite/index.browser.esm2017.js +37 -24
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +104 -113
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +24 -9
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +24 -9
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +37 -24
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/internal.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/bundle.d.ts +2 -2
- package/dist/lite/packages/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/lite/packages/firestore/src/core/query.d.ts +7 -0
- package/dist/lite/packages/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/lite/packages/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/lite/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/lite/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/lite/packages/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/lite/packages/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/lite/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/lite/packages/firestore/src/model/collections.d.ts +7 -0
- package/dist/lite/packages/firestore/src/model/field_index.d.ts +10 -1
- package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/packages/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/lite/private.d.ts +1 -0
- package/dist/packages/firestore/dist/index.esm2017.d.ts +197 -197
- package/dist/packages/firestore/src/core/bundle.d.ts +2 -2
- package/dist/packages/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/packages/firestore/src/core/query.d.ts +7 -0
- package/dist/packages/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/packages/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/packages/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/packages/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/packages/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/packages/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/packages/firestore/src/model/collections.d.ts +7 -0
- package/dist/packages/firestore/src/model/field_index.d.ts +10 -1
- package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/packages/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/private.d.ts +31 -10
- package/package.json +9 -9
package/dist/index.node.cjs.js
CHANGED
|
@@ -33,7 +33,7 @@ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
|
|
|
33
33
|
var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
|
|
34
34
|
|
|
35
35
|
const name = "@firebase/firestore";
|
|
36
|
-
const version$1 = "3.4.
|
|
36
|
+
const version$1 = "3.4.6-canary.3198d58dc";
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* @license
|
|
@@ -86,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
86
86
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
87
87
|
User.MOCK_USER = new User('mock-user');
|
|
88
88
|
|
|
89
|
-
const version = "9.6.
|
|
89
|
+
const version = "9.6.9-canary.3198d58dc";
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @license
|
|
@@ -1224,55 +1224,41 @@ function decodeResourcePath(path) {
|
|
|
1224
1224
|
}
|
|
1225
1225
|
return new ResourcePath(segments);
|
|
1226
1226
|
}
|
|
1227
|
+
|
|
1227
1228
|
/**
|
|
1228
|
-
*
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
1243
|
-
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
1244
|
-
* 11. Add bundles and named_queries for bundle support.
|
|
1245
|
-
* 12. Add document overlays.
|
|
1246
|
-
* 13. Add indexing support.
|
|
1247
|
-
*/
|
|
1248
|
-
const SCHEMA_VERSION = 12;
|
|
1249
|
-
/**
|
|
1250
|
-
* Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
|
|
1229
|
+
* @license
|
|
1230
|
+
* Copyright 2022 Google LLC
|
|
1231
|
+
*
|
|
1232
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1233
|
+
* you may not use this file except in compliance with the License.
|
|
1234
|
+
* You may obtain a copy of the License at
|
|
1235
|
+
*
|
|
1236
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1237
|
+
*
|
|
1238
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1239
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1240
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1241
|
+
* See the License for the specific language governing permissions and
|
|
1242
|
+
* limitations under the License.
|
|
1251
1243
|
*/
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
this.seconds = seconds;
|
|
1255
|
-
this.nanoseconds = nanoseconds;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1244
|
+
const DbRemoteDocumentStore$1 = 'remoteDocuments';
|
|
1245
|
+
|
|
1258
1246
|
/**
|
|
1259
|
-
*
|
|
1247
|
+
* @license
|
|
1248
|
+
* Copyright 2022 Google LLC
|
|
1260
1249
|
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1250
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1251
|
+
* you may not use this file except in compliance with the License.
|
|
1252
|
+
* You may obtain a copy of the License at
|
|
1253
|
+
*
|
|
1254
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1255
|
+
*
|
|
1256
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1257
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1258
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1259
|
+
* See the License for the specific language governing permissions and
|
|
1260
|
+
* limitations under the License.
|
|
1266
1261
|
*/
|
|
1267
|
-
class DbPrimaryClient {
|
|
1268
|
-
constructor(ownerId,
|
|
1269
|
-
/** Whether to allow shared access from multiple tabs. */
|
|
1270
|
-
allowTabSynchronization, leaseTimestampMs) {
|
|
1271
|
-
this.ownerId = ownerId;
|
|
1272
|
-
this.allowTabSynchronization = allowTabSynchronization;
|
|
1273
|
-
this.leaseTimestampMs = leaseTimestampMs;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
1262
|
/**
|
|
1277
1263
|
* Name of the IndexedDb object store.
|
|
1278
1264
|
*
|
|
@@ -1280,550 +1266,128 @@ class DbPrimaryClient {
|
|
|
1280
1266
|
* older clients that only supported single locked access to the persistence
|
|
1281
1267
|
* layer.
|
|
1282
1268
|
*/
|
|
1283
|
-
|
|
1269
|
+
const DbPrimaryClientStore = 'owner';
|
|
1284
1270
|
/**
|
|
1285
1271
|
* The key string used for the single object that exists in the
|
|
1286
1272
|
* DbPrimaryClient store.
|
|
1287
1273
|
*/
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* An object to be stored in the 'mutationQueues' store in IndexedDb.
|
|
1291
|
-
*
|
|
1292
|
-
* Each user gets a single queue of MutationBatches to apply to the server.
|
|
1293
|
-
* DbMutationQueue tracks the metadata about the queue.
|
|
1294
|
-
*/
|
|
1295
|
-
class DbMutationQueue {
|
|
1296
|
-
constructor(
|
|
1297
|
-
/**
|
|
1298
|
-
* The normalized user ID to which this queue belongs.
|
|
1299
|
-
*/
|
|
1300
|
-
userId,
|
|
1301
|
-
/**
|
|
1302
|
-
* An identifier for the highest numbered batch that has been acknowledged
|
|
1303
|
-
* by the server. All MutationBatches in this queue with batchIds less
|
|
1304
|
-
* than or equal to this value are considered to have been acknowledged by
|
|
1305
|
-
* the server.
|
|
1306
|
-
*
|
|
1307
|
-
* NOTE: this is deprecated and no longer used by the code.
|
|
1308
|
-
*/
|
|
1309
|
-
lastAcknowledgedBatchId,
|
|
1310
|
-
/**
|
|
1311
|
-
* A stream token that was previously sent by the server.
|
|
1312
|
-
*
|
|
1313
|
-
* See StreamingWriteRequest in datastore.proto for more details about
|
|
1314
|
-
* usage.
|
|
1315
|
-
*
|
|
1316
|
-
* After sending this token, earlier tokens may not be used anymore so
|
|
1317
|
-
* only a single stream token is retained.
|
|
1318
|
-
*
|
|
1319
|
-
* NOTE: this is deprecated and no longer used by the code.
|
|
1320
|
-
*/
|
|
1321
|
-
lastStreamToken) {
|
|
1322
|
-
this.userId = userId;
|
|
1323
|
-
this.lastAcknowledgedBatchId = lastAcknowledgedBatchId;
|
|
1324
|
-
this.lastStreamToken = lastStreamToken;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1274
|
+
const DbPrimaryClientKey = 'owner';
|
|
1327
1275
|
/** Name of the IndexedDb object store. */
|
|
1328
|
-
|
|
1276
|
+
const DbMutationQueueStore = 'mutationQueues';
|
|
1329
1277
|
/** Keys are automatically assigned via the userId property. */
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* An object to be stored in the 'mutations' store in IndexedDb.
|
|
1333
|
-
*
|
|
1334
|
-
* Represents a batch of user-level mutations intended to be sent to the server
|
|
1335
|
-
* in a single write. Each user-level batch gets a separate DbMutationBatch
|
|
1336
|
-
* with a new batchId.
|
|
1337
|
-
*/
|
|
1338
|
-
class DbMutationBatch {
|
|
1339
|
-
constructor(
|
|
1340
|
-
/**
|
|
1341
|
-
* The normalized user ID to which this batch belongs.
|
|
1342
|
-
*/
|
|
1343
|
-
userId,
|
|
1344
|
-
/**
|
|
1345
|
-
* An identifier for this batch, allocated using an auto-generated key.
|
|
1346
|
-
*/
|
|
1347
|
-
batchId,
|
|
1348
|
-
/**
|
|
1349
|
-
* The local write time of the batch, stored as milliseconds since the
|
|
1350
|
-
* epoch.
|
|
1351
|
-
*/
|
|
1352
|
-
localWriteTimeMs,
|
|
1353
|
-
/**
|
|
1354
|
-
* A list of "mutations" that represent a partial base state from when this
|
|
1355
|
-
* write batch was initially created. During local application of the write
|
|
1356
|
-
* batch, these baseMutations are applied prior to the real writes in order
|
|
1357
|
-
* to override certain document fields from the remote document cache. This
|
|
1358
|
-
* is necessary in the case of non-idempotent writes (e.g. `increment()`
|
|
1359
|
-
* transforms) to make sure that the local view of the modified documents
|
|
1360
|
-
* doesn't flicker if the remote document cache receives the result of the
|
|
1361
|
-
* non-idempotent write before the write is removed from the queue.
|
|
1362
|
-
*
|
|
1363
|
-
* These mutations are never sent to the backend.
|
|
1364
|
-
*/
|
|
1365
|
-
baseMutations,
|
|
1366
|
-
/**
|
|
1367
|
-
* A list of mutations to apply. All mutations will be applied atomically.
|
|
1368
|
-
*
|
|
1369
|
-
* Mutations are serialized via toMutation().
|
|
1370
|
-
*/
|
|
1371
|
-
mutations) {
|
|
1372
|
-
this.userId = userId;
|
|
1373
|
-
this.batchId = batchId;
|
|
1374
|
-
this.localWriteTimeMs = localWriteTimeMs;
|
|
1375
|
-
this.baseMutations = baseMutations;
|
|
1376
|
-
this.mutations = mutations;
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1278
|
+
const DbMutationQueueKeyPath = 'userId';
|
|
1379
1279
|
/** Name of the IndexedDb object store. */
|
|
1380
|
-
|
|
1280
|
+
const DbMutationBatchStore = 'mutations';
|
|
1381
1281
|
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
1382
|
-
|
|
1282
|
+
const DbMutationBatchKeyPath = 'batchId';
|
|
1383
1283
|
/** The index name for lookup of mutations by user. */
|
|
1384
|
-
|
|
1284
|
+
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
1385
1285
|
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
1386
|
-
|
|
1286
|
+
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
1387
1287
|
/**
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
* A manually maintained index of all the mutation batches that affect a given
|
|
1391
|
-
* document key. The rows in this table are references based on the contents of
|
|
1392
|
-
* DbMutationBatch.mutations.
|
|
1288
|
+
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1289
|
+
* over all of a user's document mutations.
|
|
1393
1290
|
*/
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
/**
|
|
1397
|
-
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1398
|
-
* over all of a user's document mutations.
|
|
1399
|
-
*/
|
|
1400
|
-
static prefixForUser(userId) {
|
|
1401
|
-
return [userId];
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1405
|
-
* index to iterate over all at document mutations for a given path or lower.
|
|
1406
|
-
*/
|
|
1407
|
-
static prefixForPath(userId, path) {
|
|
1408
|
-
return [userId, encodeResourcePath(path)];
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1412
|
-
* and deleting into the DbDocumentMutations index.
|
|
1413
|
-
*/
|
|
1414
|
-
static key(userId, path, batchId) {
|
|
1415
|
-
return [userId, encodeResourcePath(path), batchId];
|
|
1416
|
-
}
|
|
1291
|
+
function newDbDocumentMutationPrefixForUser(userId) {
|
|
1292
|
+
return [userId];
|
|
1417
1293
|
}
|
|
1418
|
-
DbDocumentMutation.store = 'documentMutations';
|
|
1419
|
-
/**
|
|
1420
|
-
* Because we store all the useful information for this store in the key,
|
|
1421
|
-
* there is no useful information to store as the value. The raw (unencoded)
|
|
1422
|
-
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1423
|
-
* information.
|
|
1424
|
-
*/
|
|
1425
|
-
DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
|
|
1426
1294
|
/**
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1295
|
+
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1296
|
+
* index to iterate over all at document mutations for a given path or lower.
|
|
1429
1297
|
*/
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
this.path = path;
|
|
1433
|
-
this.readTime = readTime;
|
|
1434
|
-
}
|
|
1298
|
+
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
1299
|
+
return [userId, encodeResourcePath(path)];
|
|
1435
1300
|
}
|
|
1436
1301
|
/**
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1302
|
+
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1303
|
+
* and deleting into the DbDocumentMutations index.
|
|
1439
1304
|
*/
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
this.path = path;
|
|
1443
|
-
this.version = version;
|
|
1444
|
-
}
|
|
1305
|
+
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
1306
|
+
return [userId, encodeResourcePath(path), batchId];
|
|
1445
1307
|
}
|
|
1446
1308
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
* - A "no document" representing a document that is known not to exist (at
|
|
1452
|
-
* some version).
|
|
1453
|
-
* - An "unknown document" representing a document that is known to exist (at
|
|
1454
|
-
* some version) but whose contents are unknown.
|
|
1455
|
-
*
|
|
1456
|
-
* Note: This is the persisted equivalent of a MaybeDocument and could perhaps
|
|
1457
|
-
* be made more general if necessary.
|
|
1309
|
+
* Because we store all the useful information for this store in the key,
|
|
1310
|
+
* there is no useful information to store as the value. The raw (unencoded)
|
|
1311
|
+
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1312
|
+
* information.
|
|
1458
1313
|
*/
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
// inside the encoded documents). During our next migration, we should
|
|
1463
|
-
// rewrite the primary key as parentPath + document ID which would allow us
|
|
1464
|
-
// to drop one value.
|
|
1465
|
-
constructor(
|
|
1466
|
-
/**
|
|
1467
|
-
* Set to an instance of DbUnknownDocument if the data for a document is
|
|
1468
|
-
* not known, but it is known that a document exists at the specified
|
|
1469
|
-
* version (e.g. it had a successful update applied to it)
|
|
1470
|
-
*/
|
|
1471
|
-
unknownDocument,
|
|
1472
|
-
/**
|
|
1473
|
-
* Set to an instance of a DbNoDocument if it is known that no document
|
|
1474
|
-
* exists.
|
|
1475
|
-
*/
|
|
1476
|
-
noDocument,
|
|
1477
|
-
/**
|
|
1478
|
-
* Set to an instance of a Document if there's a cached version of the
|
|
1479
|
-
* document.
|
|
1480
|
-
*/
|
|
1481
|
-
document,
|
|
1482
|
-
/**
|
|
1483
|
-
* Documents that were written to the remote document store based on
|
|
1484
|
-
* a write acknowledgment are marked with `hasCommittedMutations`. These
|
|
1485
|
-
* documents are potentially inconsistent with the backend's copy and use
|
|
1486
|
-
* the write's commit version as their document version.
|
|
1487
|
-
*/
|
|
1488
|
-
hasCommittedMutations,
|
|
1489
|
-
/**
|
|
1490
|
-
* When the document was read from the backend. Undefined for data written
|
|
1491
|
-
* prior to schema version 9.
|
|
1492
|
-
*/
|
|
1493
|
-
readTime,
|
|
1494
|
-
/**
|
|
1495
|
-
* The path of the collection this document is part of. Undefined for data
|
|
1496
|
-
* written prior to schema version 9.
|
|
1497
|
-
*/
|
|
1498
|
-
parentPath) {
|
|
1499
|
-
this.unknownDocument = unknownDocument;
|
|
1500
|
-
this.noDocument = noDocument;
|
|
1501
|
-
this.document = document;
|
|
1502
|
-
this.hasCommittedMutations = hasCommittedMutations;
|
|
1503
|
-
this.readTime = readTime;
|
|
1504
|
-
this.parentPath = parentPath;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
DbRemoteDocument.store = 'remoteDocuments';
|
|
1314
|
+
const DbDocumentMutationPlaceholder = {};
|
|
1315
|
+
const DbDocumentMutationStore = 'documentMutations';
|
|
1316
|
+
const DbRemoteDocumentStore = 'remoteDocumentsV14';
|
|
1508
1317
|
/**
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
* This index is used to provide a changelog for Multi-Tab.
|
|
1318
|
+
* The primary key of the remote documents store, which allows for efficient
|
|
1319
|
+
* access by collection path and read time.
|
|
1513
1320
|
*/
|
|
1514
|
-
|
|
1515
|
-
|
|
1321
|
+
const DbRemoteDocumentKeyPath = [
|
|
1322
|
+
'prefixPath',
|
|
1323
|
+
'collectionGroup',
|
|
1324
|
+
'readTime',
|
|
1325
|
+
'documentId'
|
|
1326
|
+
];
|
|
1327
|
+
/** An index that provides access to documents by key. */
|
|
1328
|
+
const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
|
|
1329
|
+
const DbRemoteDocumentDocumentKeyIndexPath = [
|
|
1330
|
+
'prefixPath',
|
|
1331
|
+
'collectionGroup',
|
|
1332
|
+
'documentId'
|
|
1333
|
+
];
|
|
1516
1334
|
/**
|
|
1517
|
-
* An index that provides access to documents
|
|
1335
|
+
* An index that provides access to documents by collection group and read
|
|
1518
1336
|
* time.
|
|
1519
1337
|
*
|
|
1520
|
-
* This index is used
|
|
1521
|
-
* documents in a collection.
|
|
1522
|
-
*/
|
|
1523
|
-
DbRemoteDocument.collectionReadTimeIndex = 'collectionReadTimeIndex';
|
|
1524
|
-
DbRemoteDocument.collectionReadTimeIndexPath = ['parentPath', 'readTime'];
|
|
1525
|
-
/**
|
|
1526
|
-
* Contains a single entry that has metadata about the remote document cache.
|
|
1338
|
+
* This index is used by the index backfiller.
|
|
1527
1339
|
*/
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
DbRemoteDocumentGlobal.key = 'remoteDocumentGlobalKey';
|
|
1539
|
-
/**
|
|
1540
|
-
* An object to be stored in the 'targets' store in IndexedDb.
|
|
1541
|
-
*
|
|
1542
|
-
* This is based on and should be kept in sync with the proto used in the iOS
|
|
1543
|
-
* client.
|
|
1544
|
-
*
|
|
1545
|
-
* Each query the client listens to against the server is tracked on disk so
|
|
1546
|
-
* that the query can be efficiently resumed on restart.
|
|
1547
|
-
*/
|
|
1548
|
-
class DbTarget {
|
|
1549
|
-
constructor(
|
|
1550
|
-
/**
|
|
1551
|
-
* An auto-generated sequential numeric identifier for the query.
|
|
1552
|
-
*
|
|
1553
|
-
* Queries are stored using their canonicalId as the key, but these
|
|
1554
|
-
* canonicalIds can be quite long so we additionally assign a unique
|
|
1555
|
-
* queryId which can be used by referenced data structures (e.g.
|
|
1556
|
-
* indexes) to minimize the on-disk cost.
|
|
1557
|
-
*/
|
|
1558
|
-
targetId,
|
|
1559
|
-
/**
|
|
1560
|
-
* The canonical string representing this query. This is not unique.
|
|
1561
|
-
*/
|
|
1562
|
-
canonicalId,
|
|
1563
|
-
/**
|
|
1564
|
-
* The last readTime received from the Watch Service for this query.
|
|
1565
|
-
*
|
|
1566
|
-
* This is the same value as TargetChange.read_time in the protos.
|
|
1567
|
-
*/
|
|
1568
|
-
readTime,
|
|
1569
|
-
/**
|
|
1570
|
-
* An opaque, server-assigned token that allows watching a query to be
|
|
1571
|
-
* resumed after disconnecting without retransmitting all the data
|
|
1572
|
-
* that matches the query. The resume token essentially identifies a
|
|
1573
|
-
* point in time from which the server should resume sending results.
|
|
1574
|
-
*
|
|
1575
|
-
* This is related to the snapshotVersion in that the resumeToken
|
|
1576
|
-
* effectively also encodes that value, but the resumeToken is opaque
|
|
1577
|
-
* and sometimes encodes additional information.
|
|
1578
|
-
*
|
|
1579
|
-
* A consequence of this is that the resumeToken should be used when
|
|
1580
|
-
* asking the server to reason about where this client is in the watch
|
|
1581
|
-
* stream, but the client should use the snapshotVersion for its own
|
|
1582
|
-
* purposes.
|
|
1583
|
-
*
|
|
1584
|
-
* This is the same value as TargetChange.resume_token in the protos.
|
|
1585
|
-
*/
|
|
1586
|
-
resumeToken,
|
|
1587
|
-
/**
|
|
1588
|
-
* A sequence number representing the last time this query was
|
|
1589
|
-
* listened to, used for garbage collection purposes.
|
|
1590
|
-
*
|
|
1591
|
-
* Conventionally this would be a timestamp value, but device-local
|
|
1592
|
-
* clocks are unreliable and they must be able to create new listens
|
|
1593
|
-
* even while disconnected. Instead this should be a monotonically
|
|
1594
|
-
* increasing number that's incremented on each listen call.
|
|
1595
|
-
*
|
|
1596
|
-
* This is different from the queryId since the queryId is an
|
|
1597
|
-
* immutable identifier assigned to the Query on first use while
|
|
1598
|
-
* lastListenSequenceNumber is updated every time the query is
|
|
1599
|
-
* listened to.
|
|
1600
|
-
*/
|
|
1601
|
-
lastListenSequenceNumber,
|
|
1602
|
-
/**
|
|
1603
|
-
* Denotes the maximum snapshot version at which the associated query view
|
|
1604
|
-
* contained no limbo documents. Undefined for data written prior to
|
|
1605
|
-
* schema version 9.
|
|
1606
|
-
*/
|
|
1607
|
-
lastLimboFreeSnapshotVersion,
|
|
1608
|
-
/**
|
|
1609
|
-
* The query for this target.
|
|
1610
|
-
*
|
|
1611
|
-
* Because canonical ids are not unique we must store the actual query. We
|
|
1612
|
-
* use the proto to have an object we can persist without having to
|
|
1613
|
-
* duplicate translation logic to and from a `Query` object.
|
|
1614
|
-
*/
|
|
1615
|
-
query) {
|
|
1616
|
-
this.targetId = targetId;
|
|
1617
|
-
this.canonicalId = canonicalId;
|
|
1618
|
-
this.readTime = readTime;
|
|
1619
|
-
this.resumeToken = resumeToken;
|
|
1620
|
-
this.lastListenSequenceNumber = lastListenSequenceNumber;
|
|
1621
|
-
this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
|
|
1622
|
-
this.query = query;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
DbTarget.store = 'targets';
|
|
1340
|
+
const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
|
|
1341
|
+
const DbRemoteDocumentCollectionGroupIndexPath = [
|
|
1342
|
+
'collectionGroup',
|
|
1343
|
+
'readTime',
|
|
1344
|
+
'prefixPath',
|
|
1345
|
+
'documentId'
|
|
1346
|
+
];
|
|
1347
|
+
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
1348
|
+
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
1349
|
+
const DbTargetStore = 'targets';
|
|
1626
1350
|
/** Keys are automatically assigned via the targetId property. */
|
|
1627
|
-
|
|
1351
|
+
const DbTargetKeyPath = 'targetId';
|
|
1628
1352
|
/** The name of the queryTargets index. */
|
|
1629
|
-
|
|
1353
|
+
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
1630
1354
|
/**
|
|
1631
1355
|
* The index of all canonicalIds to the targets that they match. This is not
|
|
1632
1356
|
* a unique mapping because canonicalId does not promise a unique name for all
|
|
1633
1357
|
* possible queries, so we append the targetId to make the mapping unique.
|
|
1634
1358
|
*/
|
|
1635
|
-
|
|
1636
|
-
/**
|
|
1637
|
-
* An object representing an association between a target and a document, or a
|
|
1638
|
-
* sentinel row marking the last sequence number at which a document was used.
|
|
1639
|
-
* Each document cached must have a corresponding sentinel row before lru
|
|
1640
|
-
* garbage collection is enabled.
|
|
1641
|
-
*
|
|
1642
|
-
* The target associations and sentinel rows are co-located so that orphaned
|
|
1643
|
-
* documents and their sequence numbers can be identified efficiently via a scan
|
|
1644
|
-
* of this store.
|
|
1645
|
-
*/
|
|
1646
|
-
class DbTargetDocument {
|
|
1647
|
-
constructor(
|
|
1648
|
-
/**
|
|
1649
|
-
* The targetId identifying a target or 0 for a sentinel row.
|
|
1650
|
-
*/
|
|
1651
|
-
targetId,
|
|
1652
|
-
/**
|
|
1653
|
-
* The path to the document, as encoded in the key.
|
|
1654
|
-
*/
|
|
1655
|
-
path,
|
|
1656
|
-
/**
|
|
1657
|
-
* If this is a sentinel row, this should be the sequence number of the last
|
|
1658
|
-
* time the document specified by `path` was used. Otherwise, it should be
|
|
1659
|
-
* `undefined`.
|
|
1660
|
-
*/
|
|
1661
|
-
sequenceNumber) {
|
|
1662
|
-
this.targetId = targetId;
|
|
1663
|
-
this.path = path;
|
|
1664
|
-
this.sequenceNumber = sequenceNumber;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1359
|
+
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
1667
1360
|
/** Name of the IndexedDb object store. */
|
|
1668
|
-
|
|
1361
|
+
const DbTargetDocumentStore = 'targetDocuments';
|
|
1669
1362
|
/** Keys are automatically assigned via the targetId, path properties. */
|
|
1670
|
-
|
|
1363
|
+
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
1671
1364
|
/** The index name for the reverse index. */
|
|
1672
|
-
|
|
1365
|
+
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
1673
1366
|
/** We also need to create the reverse index for these properties. */
|
|
1674
|
-
|
|
1675
|
-
/**
|
|
1676
|
-
* A record of global state tracked across all Targets, tracked separately
|
|
1677
|
-
* to avoid the need for extra indexes.
|
|
1678
|
-
*
|
|
1679
|
-
* This should be kept in-sync with the proto used in the iOS client.
|
|
1680
|
-
*/
|
|
1681
|
-
class DbTargetGlobal {
|
|
1682
|
-
constructor(
|
|
1683
|
-
/**
|
|
1684
|
-
* The highest numbered target id across all targets.
|
|
1685
|
-
*
|
|
1686
|
-
* See DbTarget.targetId.
|
|
1687
|
-
*/
|
|
1688
|
-
highestTargetId,
|
|
1689
|
-
/**
|
|
1690
|
-
* The highest numbered lastListenSequenceNumber across all targets.
|
|
1691
|
-
*
|
|
1692
|
-
* See DbTarget.lastListenSequenceNumber.
|
|
1693
|
-
*/
|
|
1694
|
-
highestListenSequenceNumber,
|
|
1695
|
-
/**
|
|
1696
|
-
* A global snapshot version representing the last consistent snapshot we
|
|
1697
|
-
* received from the backend. This is monotonically increasing and any
|
|
1698
|
-
* snapshots received from the backend prior to this version (e.g. for
|
|
1699
|
-
* targets resumed with a resumeToken) should be suppressed (buffered)
|
|
1700
|
-
* until the backend has caught up to this snapshot version again. This
|
|
1701
|
-
* prevents our cache from ever going backwards in time.
|
|
1702
|
-
*/
|
|
1703
|
-
lastRemoteSnapshotVersion,
|
|
1704
|
-
/**
|
|
1705
|
-
* The number of targets persisted.
|
|
1706
|
-
*/
|
|
1707
|
-
targetCount) {
|
|
1708
|
-
this.highestTargetId = highestTargetId;
|
|
1709
|
-
this.highestListenSequenceNumber = highestListenSequenceNumber;
|
|
1710
|
-
this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
|
|
1711
|
-
this.targetCount = targetCount;
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1367
|
+
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
1714
1368
|
/**
|
|
1715
1369
|
* The key string used for the single object that exists in the
|
|
1716
1370
|
* DbTargetGlobal store.
|
|
1717
1371
|
*/
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
* An object representing an association between a Collection id (e.g. 'messages')
|
|
1722
|
-
* to a parent path (e.g. '/chats/123') that contains it as a (sub)collection.
|
|
1723
|
-
* This is used to efficiently find all collections to query when performing
|
|
1724
|
-
* a Collection Group query.
|
|
1725
|
-
*/
|
|
1726
|
-
class DbCollectionParent {
|
|
1727
|
-
constructor(
|
|
1728
|
-
/**
|
|
1729
|
-
* The collectionId (e.g. 'messages')
|
|
1730
|
-
*/
|
|
1731
|
-
collectionId,
|
|
1732
|
-
/**
|
|
1733
|
-
* The path to the parent (either a document location or an empty path for
|
|
1734
|
-
* a root-level collection).
|
|
1735
|
-
*/
|
|
1736
|
-
parent) {
|
|
1737
|
-
this.collectionId = collectionId;
|
|
1738
|
-
this.parent = parent;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1372
|
+
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
1373
|
+
const DbTargetGlobalStore = 'targetGlobal';
|
|
1741
1374
|
/** Name of the IndexedDb object store. */
|
|
1742
|
-
|
|
1375
|
+
const DbCollectionParentStore = 'collectionParents';
|
|
1743
1376
|
/** Keys are automatically assigned via the collectionId, parent properties. */
|
|
1744
|
-
|
|
1745
|
-
/**
|
|
1746
|
-
* A record of the metadata state of each client.
|
|
1747
|
-
*
|
|
1748
|
-
* PORTING NOTE: This is used to synchronize multi-tab state and does not need
|
|
1749
|
-
* to be ported to iOS or Android.
|
|
1750
|
-
*/
|
|
1751
|
-
class DbClientMetadata {
|
|
1752
|
-
constructor(
|
|
1753
|
-
// Note: Previous schema versions included a field
|
|
1754
|
-
// "lastProcessedDocumentChangeId". Don't use anymore.
|
|
1755
|
-
/** The auto-generated client id assigned at client startup. */
|
|
1756
|
-
clientId,
|
|
1757
|
-
/** The last time this state was updated. */
|
|
1758
|
-
updateTimeMs,
|
|
1759
|
-
/** Whether the client's network connection is enabled. */
|
|
1760
|
-
networkEnabled,
|
|
1761
|
-
/** Whether this client is running in a foreground tab. */
|
|
1762
|
-
inForeground) {
|
|
1763
|
-
this.clientId = clientId;
|
|
1764
|
-
this.updateTimeMs = updateTimeMs;
|
|
1765
|
-
this.networkEnabled = networkEnabled;
|
|
1766
|
-
this.inForeground = inForeground;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1377
|
+
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
1769
1378
|
/** Name of the IndexedDb object store. */
|
|
1770
|
-
|
|
1379
|
+
const DbClientMetadataStore = 'clientMetadata';
|
|
1771
1380
|
/** Keys are automatically assigned via the clientId properties. */
|
|
1772
|
-
|
|
1773
|
-
/** An object representing a bundle loaded by the SDK. */
|
|
1774
|
-
class DbBundle {
|
|
1775
|
-
constructor(
|
|
1776
|
-
/** The ID of the loaded bundle. */
|
|
1777
|
-
bundleId,
|
|
1778
|
-
/** The create time of the loaded bundle. */
|
|
1779
|
-
createTime,
|
|
1780
|
-
/** The schema version of the loaded bundle. */
|
|
1781
|
-
version) {
|
|
1782
|
-
this.bundleId = bundleId;
|
|
1783
|
-
this.createTime = createTime;
|
|
1784
|
-
this.version = version;
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1381
|
+
const DbClientMetadataKeyPath = 'clientId';
|
|
1787
1382
|
/** Name of the IndexedDb object store. */
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
/** An object representing a named query loaded by the SDK via a bundle. */
|
|
1791
|
-
class DbNamedQuery {
|
|
1792
|
-
constructor(
|
|
1793
|
-
/** The name of the query. */
|
|
1794
|
-
name,
|
|
1795
|
-
/** The read time of the results saved in the bundle from the named query. */
|
|
1796
|
-
readTime,
|
|
1797
|
-
/** The query saved in the bundle. */
|
|
1798
|
-
bundledQuery) {
|
|
1799
|
-
this.name = name;
|
|
1800
|
-
this.readTime = readTime;
|
|
1801
|
-
this.bundledQuery = bundledQuery;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1383
|
+
const DbBundleStore = 'bundles';
|
|
1384
|
+
const DbBundleKeyPath = 'bundleId';
|
|
1804
1385
|
/** Name of the IndexedDb object store. */
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
/** An object representing the global configuration for a field index. */
|
|
1808
|
-
class DbIndexConfiguration {
|
|
1809
|
-
constructor(
|
|
1810
|
-
/**
|
|
1811
|
-
* The index id for this entry. Undefined for indexes that are not yet
|
|
1812
|
-
* persisted.
|
|
1813
|
-
*/
|
|
1814
|
-
indexId,
|
|
1815
|
-
/** The collection group this index belongs to. */
|
|
1816
|
-
collectionGroup,
|
|
1817
|
-
/** The fields to index for this index. */
|
|
1818
|
-
fields) {
|
|
1819
|
-
this.indexId = indexId;
|
|
1820
|
-
this.collectionGroup = collectionGroup;
|
|
1821
|
-
this.fields = fields;
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1386
|
+
const DbNamedQueryStore = 'namedQueries';
|
|
1387
|
+
const DbNamedQueryKeyPath = 'name';
|
|
1824
1388
|
/** Name of the IndexedDb object store. */
|
|
1825
|
-
|
|
1826
|
-
|
|
1389
|
+
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
1390
|
+
const DbIndexConfigurationKeyPath = 'indexId';
|
|
1827
1391
|
/**
|
|
1828
1392
|
* An index that provides access to the index configurations by collection
|
|
1829
1393
|
* group.
|
|
@@ -1832,49 +1396,11 @@ DbIndexConfiguration.keyPath = 'indexId';
|
|
|
1832
1396
|
* not possible here as the Web client supports concurrent access to
|
|
1833
1397
|
* persistence via multi-tab.
|
|
1834
1398
|
*/
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
/**
|
|
1838
|
-
* An object describing how up-to-date the index backfill is for each user and
|
|
1839
|
-
* index.
|
|
1840
|
-
*/
|
|
1841
|
-
class DbIndexState {
|
|
1842
|
-
constructor(
|
|
1843
|
-
/** The index id for this entry. */
|
|
1844
|
-
indexId,
|
|
1845
|
-
/** The user id for this entry. */
|
|
1846
|
-
uid,
|
|
1847
|
-
/**
|
|
1848
|
-
* A number that indicates when the index was last updated (relative to
|
|
1849
|
-
* other indexes).
|
|
1850
|
-
*/
|
|
1851
|
-
sequenceNumber,
|
|
1852
|
-
/**
|
|
1853
|
-
* The latest read time that has been indexed by Firestore for this field
|
|
1854
|
-
* index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
|
|
1855
|
-
*/
|
|
1856
|
-
readTime,
|
|
1857
|
-
/**
|
|
1858
|
-
* The last document that has been indexed for this field index. Empty if
|
|
1859
|
-
* no documents have been indexed.
|
|
1860
|
-
*/
|
|
1861
|
-
documentKey,
|
|
1862
|
-
/**
|
|
1863
|
-
* The largest mutation batch id that has been processed for this index. -1
|
|
1864
|
-
* if no mutations have been indexed.
|
|
1865
|
-
*/
|
|
1866
|
-
largestBatchId) {
|
|
1867
|
-
this.indexId = indexId;
|
|
1868
|
-
this.uid = uid;
|
|
1869
|
-
this.sequenceNumber = sequenceNumber;
|
|
1870
|
-
this.readTime = readTime;
|
|
1871
|
-
this.documentKey = documentKey;
|
|
1872
|
-
this.largestBatchId = largestBatchId;
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1399
|
+
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
1400
|
+
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
1875
1401
|
/** Name of the IndexedDb object store. */
|
|
1876
|
-
|
|
1877
|
-
|
|
1402
|
+
const DbIndexStateStore = 'indexState';
|
|
1403
|
+
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
1878
1404
|
/**
|
|
1879
1405
|
* An index that provides access to documents in a collection sorted by last
|
|
1880
1406
|
* update time. Used by the backfiller.
|
|
@@ -1883,112 +1409,89 @@ DbIndexState.keyPath = ['indexId', 'uid'];
|
|
|
1883
1409
|
* not possible here as the Web client supports concurrent access to
|
|
1884
1410
|
* persistence via multi-tab.
|
|
1885
1411
|
*/
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
/** An object that stores the encoded entries for all documents and fields. */
|
|
1889
|
-
class DbIndexEntry {
|
|
1890
|
-
constructor(
|
|
1891
|
-
/** The index id for this entry. */
|
|
1892
|
-
indexId,
|
|
1893
|
-
/** The user id for this entry. */
|
|
1894
|
-
uid,
|
|
1895
|
-
/** The encoded array index value for this entry. */
|
|
1896
|
-
arrayValue,
|
|
1897
|
-
/** The encoded directional value for equality and inequality filters. */
|
|
1898
|
-
directionalValue,
|
|
1899
|
-
/** The document key this entry points to. */
|
|
1900
|
-
documentKey) {
|
|
1901
|
-
this.indexId = indexId;
|
|
1902
|
-
this.uid = uid;
|
|
1903
|
-
this.arrayValue = arrayValue;
|
|
1904
|
-
this.directionalValue = directionalValue;
|
|
1905
|
-
this.documentKey = documentKey;
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1412
|
+
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
1413
|
+
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1908
1414
|
/** Name of the IndexedDb object store. */
|
|
1909
|
-
|
|
1910
|
-
|
|
1415
|
+
const DbIndexEntryStore = 'indexEntries';
|
|
1416
|
+
const DbIndexEntryKeyPath = [
|
|
1911
1417
|
'indexId',
|
|
1912
1418
|
'uid',
|
|
1913
1419
|
'arrayValue',
|
|
1914
1420
|
'directionalValue',
|
|
1915
1421
|
'documentKey'
|
|
1916
1422
|
];
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
constructor(
|
|
1924
|
-
/** The user ID to whom this overlay belongs. */
|
|
1925
|
-
userId,
|
|
1926
|
-
/** The path to the collection that contains the document. */
|
|
1927
|
-
collectionPath,
|
|
1928
|
-
/** The ID (key) of the document within the collection. */
|
|
1929
|
-
documentId,
|
|
1930
|
-
/** The collection group to which the document belongs. */
|
|
1931
|
-
collectionGroup,
|
|
1932
|
-
/** The largest batch ID that's been applied for this overlay. */
|
|
1933
|
-
largestBatchId,
|
|
1934
|
-
/** The overlay mutation. */
|
|
1935
|
-
overlayMutation) {
|
|
1936
|
-
this.userId = userId;
|
|
1937
|
-
this.collectionPath = collectionPath;
|
|
1938
|
-
this.documentId = documentId;
|
|
1939
|
-
this.collectionGroup = collectionGroup;
|
|
1940
|
-
this.largestBatchId = largestBatchId;
|
|
1941
|
-
this.overlayMutation = overlayMutation;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1423
|
+
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1424
|
+
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1425
|
+
'indexId',
|
|
1426
|
+
'uid',
|
|
1427
|
+
'documentKey'
|
|
1428
|
+
];
|
|
1944
1429
|
/** Name of the IndexedDb object store. */
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1430
|
+
const DbDocumentOverlayStore = 'documentOverlays';
|
|
1431
|
+
const DbDocumentOverlayKeyPath = [
|
|
1432
|
+
'userId',
|
|
1433
|
+
'collectionPath',
|
|
1434
|
+
'documentId'
|
|
1435
|
+
];
|
|
1436
|
+
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1437
|
+
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
1949
1438
|
'userId',
|
|
1950
1439
|
'collectionPath',
|
|
1951
1440
|
'largestBatchId'
|
|
1952
1441
|
];
|
|
1953
|
-
|
|
1954
|
-
|
|
1442
|
+
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1443
|
+
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
1955
1444
|
'userId',
|
|
1956
1445
|
'collectionGroup',
|
|
1957
1446
|
'largestBatchId'
|
|
1958
1447
|
];
|
|
1959
1448
|
// Visible for testing
|
|
1960
1449
|
const V1_STORES = [
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1450
|
+
DbMutationQueueStore,
|
|
1451
|
+
DbMutationBatchStore,
|
|
1452
|
+
DbDocumentMutationStore,
|
|
1453
|
+
DbRemoteDocumentStore$1,
|
|
1454
|
+
DbTargetStore,
|
|
1455
|
+
DbPrimaryClientStore,
|
|
1456
|
+
DbTargetGlobalStore,
|
|
1457
|
+
DbTargetDocumentStore
|
|
1969
1458
|
];
|
|
1970
|
-
// V2 is no longer usable (see comment at top of file)
|
|
1971
1459
|
// Visible for testing
|
|
1972
1460
|
const V3_STORES = V1_STORES;
|
|
1973
1461
|
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
1974
|
-
const V4_STORES = [...V3_STORES,
|
|
1975
|
-
|
|
1976
|
-
const
|
|
1977
|
-
|
|
1978
|
-
const
|
|
1979
|
-
// V9 does not change the set of stores.
|
|
1980
|
-
// V10 does not change the set of stores.
|
|
1981
|
-
const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
|
|
1982
|
-
const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
|
|
1462
|
+
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
1463
|
+
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
1464
|
+
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
1465
|
+
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
1466
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
1983
1467
|
const V13_STORES = [
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1468
|
+
DbMutationQueueStore,
|
|
1469
|
+
DbMutationBatchStore,
|
|
1470
|
+
DbDocumentMutationStore,
|
|
1471
|
+
DbRemoteDocumentStore,
|
|
1472
|
+
DbTargetStore,
|
|
1473
|
+
DbPrimaryClientStore,
|
|
1474
|
+
DbTargetGlobalStore,
|
|
1475
|
+
DbTargetDocumentStore,
|
|
1476
|
+
DbClientMetadataStore,
|
|
1477
|
+
DbRemoteDocumentGlobalStore,
|
|
1478
|
+
DbCollectionParentStore,
|
|
1479
|
+
DbBundleStore,
|
|
1480
|
+
DbNamedQueryStore,
|
|
1481
|
+
DbDocumentOverlayStore
|
|
1482
|
+
];
|
|
1483
|
+
const V14_STORES = [
|
|
1484
|
+
...V13_STORES,
|
|
1485
|
+
DbIndexConfigurationStore,
|
|
1486
|
+
DbIndexStateStore,
|
|
1487
|
+
DbIndexEntryStore
|
|
1988
1488
|
];
|
|
1989
1489
|
/** Returns the object stores for the provided schema. */
|
|
1990
1490
|
function getObjectStores(schemaVersion) {
|
|
1991
|
-
if (schemaVersion ===
|
|
1491
|
+
if (schemaVersion === 14) {
|
|
1492
|
+
return V14_STORES;
|
|
1493
|
+
}
|
|
1494
|
+
else if (schemaVersion === 13) {
|
|
1992
1495
|
return V13_STORES;
|
|
1993
1496
|
}
|
|
1994
1497
|
else if (schemaVersion === 12) {
|
|
@@ -2702,6 +2205,21 @@ class SimpleDbStore {
|
|
|
2702
2205
|
});
|
|
2703
2206
|
}
|
|
2704
2207
|
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2210
|
+
* elements if no limit is provided.
|
|
2211
|
+
*/
|
|
2212
|
+
loadFirst(range, count) {
|
|
2213
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
2214
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2215
|
+
request.onerror = (event) => {
|
|
2216
|
+
reject(event.target.error);
|
|
2217
|
+
};
|
|
2218
|
+
request.onsuccess = (event) => {
|
|
2219
|
+
resolve(event.target.result);
|
|
2220
|
+
};
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2705
2223
|
deleteAll(indexOrRange, range) {
|
|
2706
2224
|
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2707
2225
|
const options = this.options(indexOrRange, range);
|
|
@@ -3184,6 +2702,9 @@ class SnapshotVersion {
|
|
|
3184
2702
|
static min() {
|
|
3185
2703
|
return new SnapshotVersion(new Timestamp(0, 0));
|
|
3186
2704
|
}
|
|
2705
|
+
static max() {
|
|
2706
|
+
return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
|
|
2707
|
+
}
|
|
3187
2708
|
compareTo(other) {
|
|
3188
2709
|
return this.timestamp._compareTo(other.timestamp);
|
|
3189
2710
|
}
|
|
@@ -3312,17 +2833,15 @@ class FieldMask {
|
|
|
3312
2833
|
* See the License for the specific language governing permissions and
|
|
3313
2834
|
* limitations under the License.
|
|
3314
2835
|
*/
|
|
2836
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3315
2837
|
function decodeBase64(encoded) {
|
|
3316
|
-
//
|
|
3317
|
-
//
|
|
3318
|
-
|
|
3319
|
-
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
|
|
3320
|
-
}
|
|
3321
|
-
return new Buffer(encoded, 'base64').toString('binary');
|
|
2838
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
2839
|
+
// This was removed to improve the performance of indexing.
|
|
2840
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3322
2841
|
}
|
|
3323
2842
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3324
2843
|
function encodeBase64(raw) {
|
|
3325
|
-
return
|
|
2844
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3326
2845
|
}
|
|
3327
2846
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3328
2847
|
function isBase64Available() {
|
|
@@ -3363,6 +2882,8 @@ class ByteString {
|
|
|
3363
2882
|
return new ByteString(binaryString);
|
|
3364
2883
|
}
|
|
3365
2884
|
static fromUint8Array(array) {
|
|
2885
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
2886
|
+
// is frequently called during indexing.
|
|
3366
2887
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3367
2888
|
return new ByteString(binaryString);
|
|
3368
2889
|
}
|
|
@@ -3781,13 +3302,17 @@ class DocumentKey {
|
|
|
3781
3302
|
* See the License for the specific language governing permissions and
|
|
3782
3303
|
* limitations under the License.
|
|
3783
3304
|
*/
|
|
3305
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3784
3306
|
const MAX_VALUE = {
|
|
3785
3307
|
mapValue: {
|
|
3786
3308
|
fields: {
|
|
3787
|
-
'__type__': { stringValue:
|
|
3309
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3788
3310
|
}
|
|
3789
3311
|
}
|
|
3790
3312
|
};
|
|
3313
|
+
const MIN_VALUE = {
|
|
3314
|
+
nullValue: 'NULL_VALUE'
|
|
3315
|
+
};
|
|
3791
3316
|
/** Extracts the backend's type order for the provided value. */
|
|
3792
3317
|
function typeOrder(value) {
|
|
3793
3318
|
if ('nullValue' in value) {
|
|
@@ -3821,6 +3346,9 @@ function typeOrder(value) {
|
|
|
3821
3346
|
if (isServerTimestamp(value)) {
|
|
3822
3347
|
return 4 /* ServerTimestampValue */;
|
|
3823
3348
|
}
|
|
3349
|
+
else if (isMaxValue(value)) {
|
|
3350
|
+
return 9 /* ArrayValue */;
|
|
3351
|
+
}
|
|
3824
3352
|
return 10 /* ObjectValue */;
|
|
3825
3353
|
}
|
|
3826
3354
|
else {
|
|
@@ -3860,6 +3388,8 @@ function valueEquals(left, right) {
|
|
|
3860
3388
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3861
3389
|
case 10 /* ObjectValue */:
|
|
3862
3390
|
return objectEquals(left, right);
|
|
3391
|
+
case 9007199254740991 /* MaxValue */:
|
|
3392
|
+
return true;
|
|
3863
3393
|
default:
|
|
3864
3394
|
return fail();
|
|
3865
3395
|
}
|
|
@@ -3932,6 +3462,7 @@ function valueCompare(left, right) {
|
|
|
3932
3462
|
}
|
|
3933
3463
|
switch (leftType) {
|
|
3934
3464
|
case 0 /* NullValue */:
|
|
3465
|
+
case 9007199254740991 /* MaxValue */:
|
|
3935
3466
|
return 0;
|
|
3936
3467
|
case 1 /* BooleanValue */:
|
|
3937
3468
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -4200,7 +3731,102 @@ function deepClone(source) {
|
|
|
4200
3731
|
}
|
|
4201
3732
|
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4202
3733
|
function isMaxValue(value) {
|
|
4203
|
-
return
|
|
3734
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
3735
|
+
MAX_VALUE_TYPE);
|
|
3736
|
+
}
|
|
3737
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
3738
|
+
function valuesGetLowerBound(value) {
|
|
3739
|
+
if ('nullValue' in value) {
|
|
3740
|
+
return MIN_VALUE;
|
|
3741
|
+
}
|
|
3742
|
+
else if ('booleanValue' in value) {
|
|
3743
|
+
return { booleanValue: false };
|
|
3744
|
+
}
|
|
3745
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3746
|
+
return { doubleValue: NaN };
|
|
3747
|
+
}
|
|
3748
|
+
else if ('timestampValue' in value) {
|
|
3749
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3750
|
+
}
|
|
3751
|
+
else if ('stringValue' in value) {
|
|
3752
|
+
return { stringValue: '' };
|
|
3753
|
+
}
|
|
3754
|
+
else if ('bytesValue' in value) {
|
|
3755
|
+
return { bytesValue: '' };
|
|
3756
|
+
}
|
|
3757
|
+
else if ('referenceValue' in value) {
|
|
3758
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3759
|
+
}
|
|
3760
|
+
else if ('geoPointValue' in value) {
|
|
3761
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3762
|
+
}
|
|
3763
|
+
else if ('arrayValue' in value) {
|
|
3764
|
+
return { arrayValue: {} };
|
|
3765
|
+
}
|
|
3766
|
+
else if ('mapValue' in value) {
|
|
3767
|
+
return { mapValue: {} };
|
|
3768
|
+
}
|
|
3769
|
+
else {
|
|
3770
|
+
return fail();
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
3774
|
+
function valuesGetUpperBound(value) {
|
|
3775
|
+
if ('nullValue' in value) {
|
|
3776
|
+
return { booleanValue: false };
|
|
3777
|
+
}
|
|
3778
|
+
else if ('booleanValue' in value) {
|
|
3779
|
+
return { doubleValue: NaN };
|
|
3780
|
+
}
|
|
3781
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3782
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3783
|
+
}
|
|
3784
|
+
else if ('timestampValue' in value) {
|
|
3785
|
+
return { stringValue: '' };
|
|
3786
|
+
}
|
|
3787
|
+
else if ('stringValue' in value) {
|
|
3788
|
+
return { bytesValue: '' };
|
|
3789
|
+
}
|
|
3790
|
+
else if ('bytesValue' in value) {
|
|
3791
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3792
|
+
}
|
|
3793
|
+
else if ('referenceValue' in value) {
|
|
3794
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3795
|
+
}
|
|
3796
|
+
else if ('geoPointValue' in value) {
|
|
3797
|
+
return { arrayValue: {} };
|
|
3798
|
+
}
|
|
3799
|
+
else if ('arrayValue' in value) {
|
|
3800
|
+
return { mapValue: {} };
|
|
3801
|
+
}
|
|
3802
|
+
else if ('mapValue' in value) {
|
|
3803
|
+
return MAX_VALUE;
|
|
3804
|
+
}
|
|
3805
|
+
else {
|
|
3806
|
+
return fail();
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
function valuesMax(left, right) {
|
|
3810
|
+
if (left === undefined) {
|
|
3811
|
+
return right;
|
|
3812
|
+
}
|
|
3813
|
+
else if (right === undefined) {
|
|
3814
|
+
return left;
|
|
3815
|
+
}
|
|
3816
|
+
else {
|
|
3817
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function valuesMin(left, right) {
|
|
3821
|
+
if (left === undefined) {
|
|
3822
|
+
return right;
|
|
3823
|
+
}
|
|
3824
|
+
else if (right === undefined) {
|
|
3825
|
+
return left;
|
|
3826
|
+
}
|
|
3827
|
+
else {
|
|
3828
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
3829
|
+
}
|
|
4204
3830
|
}
|
|
4205
3831
|
|
|
4206
3832
|
/**
|
|
@@ -4595,6 +4221,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4595
4221
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4596
4222
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4597
4223
|
}
|
|
4224
|
+
/** Returns a debug representation of the field index */
|
|
4225
|
+
function fieldIndexToString(fieldIndex) {
|
|
4226
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4227
|
+
}
|
|
4598
4228
|
/** An index component consisting of field path and index type. */
|
|
4599
4229
|
class IndexSegment {
|
|
4600
4230
|
constructor(
|
|
@@ -4626,6 +4256,28 @@ class IndexState {
|
|
|
4626
4256
|
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
4627
4257
|
}
|
|
4628
4258
|
}
|
|
4259
|
+
/**
|
|
4260
|
+
* Creates an offset that matches all documents with a read time higher than
|
|
4261
|
+
* `readTime`.
|
|
4262
|
+
*/
|
|
4263
|
+
function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
|
|
4264
|
+
// We want to create an offset that matches all documents with a read time
|
|
4265
|
+
// greater than the provided read time. To do so, we technically need to
|
|
4266
|
+
// create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
|
|
4267
|
+
// Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
|
|
4268
|
+
// `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
|
|
4269
|
+
// all valid document IDs.
|
|
4270
|
+
const successorSeconds = readTime.toTimestamp().seconds;
|
|
4271
|
+
const successorNanos = readTime.toTimestamp().nanoseconds + 1;
|
|
4272
|
+
const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
|
|
4273
|
+
? new Timestamp(successorSeconds + 1, 0)
|
|
4274
|
+
: new Timestamp(successorSeconds, successorNanos));
|
|
4275
|
+
return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
|
|
4276
|
+
}
|
|
4277
|
+
/** Creates a new offset based on the provided document. */
|
|
4278
|
+
function newIndexOffsetFromDocument(document) {
|
|
4279
|
+
return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
|
|
4280
|
+
}
|
|
4629
4281
|
/**
|
|
4630
4282
|
* Stores the latest read time, document and batch ID that were processed for an
|
|
4631
4283
|
* index.
|
|
@@ -4650,10 +4302,25 @@ class IndexOffset {
|
|
|
4650
4302
|
this.documentKey = documentKey;
|
|
4651
4303
|
this.largestBatchId = largestBatchId;
|
|
4652
4304
|
}
|
|
4653
|
-
/**
|
|
4305
|
+
/** Returns an offset that sorts before all regular offsets. */
|
|
4654
4306
|
static min() {
|
|
4655
4307
|
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4656
4308
|
}
|
|
4309
|
+
/** Returns an offset that sorts after all regular offsets. */
|
|
4310
|
+
static max() {
|
|
4311
|
+
return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
function indexOffsetComparator(left, right) {
|
|
4315
|
+
let cmp = left.readTime.compareTo(right.readTime);
|
|
4316
|
+
if (cmp !== 0) {
|
|
4317
|
+
return cmp;
|
|
4318
|
+
}
|
|
4319
|
+
cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
4320
|
+
if (cmp !== 0) {
|
|
4321
|
+
return cmp;
|
|
4322
|
+
}
|
|
4323
|
+
return primitiveComparator(left.largestBatchId, right.largestBatchId);
|
|
4657
4324
|
}
|
|
4658
4325
|
|
|
4659
4326
|
/**
|
|
@@ -4766,30 +4433,215 @@ function targetEquals(left, right) {
|
|
|
4766
4433
|
if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
|
|
4767
4434
|
return false;
|
|
4768
4435
|
}
|
|
4769
|
-
}
|
|
4770
|
-
if (left.filters.length !== right.filters.length) {
|
|
4771
|
-
return false;
|
|
4772
|
-
}
|
|
4773
|
-
for (let i = 0; i < left.filters.length; i++) {
|
|
4774
|
-
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
4775
|
-
return false;
|
|
4436
|
+
}
|
|
4437
|
+
if (left.filters.length !== right.filters.length) {
|
|
4438
|
+
return false;
|
|
4439
|
+
}
|
|
4440
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
4441
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
4442
|
+
return false;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
4446
|
+
return false;
|
|
4447
|
+
}
|
|
4448
|
+
if (!left.path.isEqual(right.path)) {
|
|
4449
|
+
return false;
|
|
4450
|
+
}
|
|
4451
|
+
if (!boundEquals(left.startAt, right.startAt)) {
|
|
4452
|
+
return false;
|
|
4453
|
+
}
|
|
4454
|
+
return boundEquals(left.endAt, right.endAt);
|
|
4455
|
+
}
|
|
4456
|
+
function targetIsDocumentTarget(target) {
|
|
4457
|
+
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4458
|
+
target.collectionGroup === null &&
|
|
4459
|
+
target.filters.length === 0);
|
|
4460
|
+
}
|
|
4461
|
+
/** Returns the field filters that target the given field path. */
|
|
4462
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4463
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4464
|
+
}
|
|
4465
|
+
/**
|
|
4466
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4467
|
+
* filters. Returns `null` if there are no such filters.
|
|
4468
|
+
*/
|
|
4469
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4470
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4471
|
+
if (segment === undefined) {
|
|
4472
|
+
return null;
|
|
4473
|
+
}
|
|
4474
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4475
|
+
switch (fieldFilter.op) {
|
|
4476
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4477
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4478
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4479
|
+
return [fieldFilter.value];
|
|
4480
|
+
// Remaining filters are not array filters.
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
return null;
|
|
4484
|
+
}
|
|
4485
|
+
/**
|
|
4486
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4487
|
+
* `null` if there are no such filters.
|
|
4488
|
+
*/
|
|
4489
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4490
|
+
const values = new Map();
|
|
4491
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4492
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4493
|
+
switch (fieldFilter.op) {
|
|
4494
|
+
case "==" /* EQUAL */:
|
|
4495
|
+
case "in" /* IN */:
|
|
4496
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4497
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4498
|
+
// `value != 'ab'`).
|
|
4499
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4500
|
+
break;
|
|
4501
|
+
case "not-in" /* NOT_IN */:
|
|
4502
|
+
case "!=" /* NOT_EQUAL */:
|
|
4503
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4504
|
+
// segments and hence we can return early here.
|
|
4505
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4506
|
+
return Array.from(values.values());
|
|
4507
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
return null;
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4515
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4516
|
+
* exists.
|
|
4517
|
+
*/
|
|
4518
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4519
|
+
const values = [];
|
|
4520
|
+
let inclusive = true;
|
|
4521
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4522
|
+
// startAt.
|
|
4523
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4524
|
+
let segmentValue = undefined;
|
|
4525
|
+
let segmentInclusive = true;
|
|
4526
|
+
// Process all filters to find a value for the current field segment
|
|
4527
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4528
|
+
let filterValue = undefined;
|
|
4529
|
+
let filterInclusive = true;
|
|
4530
|
+
switch (fieldFilter.op) {
|
|
4531
|
+
case "<" /* LESS_THAN */:
|
|
4532
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4533
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4534
|
+
break;
|
|
4535
|
+
case "==" /* EQUAL */:
|
|
4536
|
+
case "in" /* IN */:
|
|
4537
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4538
|
+
filterValue = fieldFilter.value;
|
|
4539
|
+
break;
|
|
4540
|
+
case ">" /* GREATER_THAN */:
|
|
4541
|
+
filterValue = fieldFilter.value;
|
|
4542
|
+
filterInclusive = false;
|
|
4543
|
+
break;
|
|
4544
|
+
case "!=" /* NOT_EQUAL */:
|
|
4545
|
+
case "not-in" /* NOT_IN */:
|
|
4546
|
+
filterValue = MIN_VALUE;
|
|
4547
|
+
break;
|
|
4548
|
+
// Remaining filters cannot be used as lower bounds.
|
|
4549
|
+
}
|
|
4550
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4551
|
+
segmentValue = filterValue;
|
|
4552
|
+
segmentInclusive = filterInclusive;
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
// If there is a startAt bound, compare the values against the existing
|
|
4556
|
+
// boundary to see if we can narrow the scope.
|
|
4557
|
+
if (target.startAt !== null) {
|
|
4558
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4559
|
+
const orderBy = target.orderBy[i];
|
|
4560
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4561
|
+
const cursorValue = target.startAt.position[i];
|
|
4562
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4563
|
+
segmentValue = cursorValue;
|
|
4564
|
+
segmentInclusive = target.startAt.inclusive;
|
|
4565
|
+
}
|
|
4566
|
+
break;
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
if (segmentValue === undefined) {
|
|
4571
|
+
// No lower bound exists
|
|
4572
|
+
return null;
|
|
4573
|
+
}
|
|
4574
|
+
values.push(segmentValue);
|
|
4575
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4576
|
+
}
|
|
4577
|
+
return new Bound(values, inclusive);
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
4581
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
4582
|
+
* upper bound exists.
|
|
4583
|
+
*/
|
|
4584
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
4585
|
+
const values = [];
|
|
4586
|
+
let inclusive = true;
|
|
4587
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4588
|
+
// endAt.
|
|
4589
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4590
|
+
let segmentValue = undefined;
|
|
4591
|
+
let segmentInclusive = true;
|
|
4592
|
+
// Process all filters to find a value for the current field segment
|
|
4593
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4594
|
+
let filterValue = undefined;
|
|
4595
|
+
let filterInclusive = true;
|
|
4596
|
+
switch (fieldFilter.op) {
|
|
4597
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4598
|
+
case ">" /* GREATER_THAN */:
|
|
4599
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4600
|
+
filterInclusive = false;
|
|
4601
|
+
break;
|
|
4602
|
+
case "==" /* EQUAL */:
|
|
4603
|
+
case "in" /* IN */:
|
|
4604
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4605
|
+
filterValue = fieldFilter.value;
|
|
4606
|
+
break;
|
|
4607
|
+
case "<" /* LESS_THAN */:
|
|
4608
|
+
filterValue = fieldFilter.value;
|
|
4609
|
+
filterInclusive = false;
|
|
4610
|
+
break;
|
|
4611
|
+
case "!=" /* NOT_EQUAL */:
|
|
4612
|
+
case "not-in" /* NOT_IN */:
|
|
4613
|
+
filterValue = MAX_VALUE;
|
|
4614
|
+
break;
|
|
4615
|
+
// Remaining filters cannot be used as upper bounds.
|
|
4616
|
+
}
|
|
4617
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4618
|
+
segmentValue = filterValue;
|
|
4619
|
+
segmentInclusive = filterInclusive;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
// If there is a endAt bound, compare the values against the existing
|
|
4623
|
+
// boundary to see if we can narrow the scope.
|
|
4624
|
+
if (target.endAt !== null) {
|
|
4625
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4626
|
+
const orderBy = target.orderBy[i];
|
|
4627
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4628
|
+
const cursorValue = target.endAt.position[i];
|
|
4629
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4630
|
+
segmentValue = cursorValue;
|
|
4631
|
+
segmentInclusive = target.endAt.inclusive;
|
|
4632
|
+
}
|
|
4633
|
+
break;
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
if (segmentValue === undefined) {
|
|
4638
|
+
// No upper bound exists
|
|
4639
|
+
return null;
|
|
4776
4640
|
}
|
|
4641
|
+
values.push(segmentValue);
|
|
4642
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4777
4643
|
}
|
|
4778
|
-
|
|
4779
|
-
return false;
|
|
4780
|
-
}
|
|
4781
|
-
if (!left.path.isEqual(right.path)) {
|
|
4782
|
-
return false;
|
|
4783
|
-
}
|
|
4784
|
-
if (!boundEquals(left.startAt, right.startAt)) {
|
|
4785
|
-
return false;
|
|
4786
|
-
}
|
|
4787
|
-
return boundEquals(left.endAt, right.endAt);
|
|
4788
|
-
}
|
|
4789
|
-
function targetIsDocumentTarget(target) {
|
|
4790
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4791
|
-
target.collectionGroup === null &&
|
|
4792
|
-
target.filters.length === 0);
|
|
4644
|
+
return new Bound(values, inclusive);
|
|
4793
4645
|
}
|
|
4794
4646
|
class Filter {
|
|
4795
4647
|
}
|
|
@@ -5361,6 +5213,18 @@ function queryMatchesBounds(query, doc) {
|
|
|
5361
5213
|
}
|
|
5362
5214
|
return true;
|
|
5363
5215
|
}
|
|
5216
|
+
/**
|
|
5217
|
+
* Returns the collection group that this query targets.
|
|
5218
|
+
*
|
|
5219
|
+
* PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
|
|
5220
|
+
* synchronization for query results.
|
|
5221
|
+
*/
|
|
5222
|
+
function queryCollectionGroup(query) {
|
|
5223
|
+
return (query.collectionGroup ||
|
|
5224
|
+
(query.path.length % 2 === 1
|
|
5225
|
+
? query.path.lastSegment()
|
|
5226
|
+
: query.path.get(query.path.length - 2)));
|
|
5227
|
+
}
|
|
5364
5228
|
/**
|
|
5365
5229
|
* Returns a new comparator function that can be used to compare two documents
|
|
5366
5230
|
* based on the Query's ordering constraint.
|
|
@@ -5392,6 +5256,116 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5392
5256
|
}
|
|
5393
5257
|
}
|
|
5394
5258
|
|
|
5259
|
+
/**
|
|
5260
|
+
* @license
|
|
5261
|
+
* Copyright 2017 Google LLC
|
|
5262
|
+
*
|
|
5263
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5264
|
+
* you may not use this file except in compliance with the License.
|
|
5265
|
+
* You may obtain a copy of the License at
|
|
5266
|
+
*
|
|
5267
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5268
|
+
*
|
|
5269
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5270
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5271
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5272
|
+
* See the License for the specific language governing permissions and
|
|
5273
|
+
* limitations under the License.
|
|
5274
|
+
*/
|
|
5275
|
+
/**
|
|
5276
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
5277
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
5278
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
5279
|
+
* automatically handles collisions of keys.
|
|
5280
|
+
*/
|
|
5281
|
+
class ObjectMap {
|
|
5282
|
+
constructor(mapKeyFn, equalsFn) {
|
|
5283
|
+
this.mapKeyFn = mapKeyFn;
|
|
5284
|
+
this.equalsFn = equalsFn;
|
|
5285
|
+
/**
|
|
5286
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
5287
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
5288
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
5289
|
+
* constant time lookups in practice.
|
|
5290
|
+
*/
|
|
5291
|
+
this.inner = {};
|
|
5292
|
+
/** The number of entries stored in the map */
|
|
5293
|
+
this.innerSize = 0;
|
|
5294
|
+
}
|
|
5295
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
5296
|
+
get(key) {
|
|
5297
|
+
const id = this.mapKeyFn(key);
|
|
5298
|
+
const matches = this.inner[id];
|
|
5299
|
+
if (matches === undefined) {
|
|
5300
|
+
return undefined;
|
|
5301
|
+
}
|
|
5302
|
+
for (const [otherKey, value] of matches) {
|
|
5303
|
+
if (this.equalsFn(otherKey, key)) {
|
|
5304
|
+
return value;
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
return undefined;
|
|
5308
|
+
}
|
|
5309
|
+
has(key) {
|
|
5310
|
+
return this.get(key) !== undefined;
|
|
5311
|
+
}
|
|
5312
|
+
/** Put this key and value in the map. */
|
|
5313
|
+
set(key, value) {
|
|
5314
|
+
const id = this.mapKeyFn(key);
|
|
5315
|
+
const matches = this.inner[id];
|
|
5316
|
+
if (matches === undefined) {
|
|
5317
|
+
this.inner[id] = [[key, value]];
|
|
5318
|
+
this.innerSize++;
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5321
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5322
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5323
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
5324
|
+
matches[i] = [key, value];
|
|
5325
|
+
return;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
matches.push([key, value]);
|
|
5329
|
+
this.innerSize++;
|
|
5330
|
+
}
|
|
5331
|
+
/**
|
|
5332
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
5333
|
+
*/
|
|
5334
|
+
delete(key) {
|
|
5335
|
+
const id = this.mapKeyFn(key);
|
|
5336
|
+
const matches = this.inner[id];
|
|
5337
|
+
if (matches === undefined) {
|
|
5338
|
+
return false;
|
|
5339
|
+
}
|
|
5340
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5341
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5342
|
+
if (matches.length === 1) {
|
|
5343
|
+
delete this.inner[id];
|
|
5344
|
+
}
|
|
5345
|
+
else {
|
|
5346
|
+
matches.splice(i, 1);
|
|
5347
|
+
}
|
|
5348
|
+
this.innerSize--;
|
|
5349
|
+
return true;
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
return false;
|
|
5353
|
+
}
|
|
5354
|
+
forEach(fn) {
|
|
5355
|
+
forEach(this.inner, (_, entries) => {
|
|
5356
|
+
for (const [k, v] of entries) {
|
|
5357
|
+
fn(k, v);
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
}
|
|
5361
|
+
isEmpty() {
|
|
5362
|
+
return isEmpty(this.inner);
|
|
5363
|
+
}
|
|
5364
|
+
size() {
|
|
5365
|
+
return this.innerSize;
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5395
5369
|
/**
|
|
5396
5370
|
* @license
|
|
5397
5371
|
* Copyright 2017 Google LLC
|
|
@@ -5534,7 +5508,7 @@ class SortedMapIterator {
|
|
|
5534
5508
|
while (!node.isEmpty()) {
|
|
5535
5509
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5536
5510
|
// flip the comparison if we're going in reverse
|
|
5537
|
-
if (isReverse) {
|
|
5511
|
+
if (startKey && isReverse) {
|
|
5538
5512
|
cmp *= -1;
|
|
5539
5513
|
}
|
|
5540
5514
|
if (cmp < 0) {
|
|
@@ -6103,6 +6077,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
|
6103
6077
|
function documentMap() {
|
|
6104
6078
|
return EMPTY_DOCUMENT_MAP;
|
|
6105
6079
|
}
|
|
6080
|
+
function newOverlayMap() {
|
|
6081
|
+
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
6082
|
+
}
|
|
6106
6083
|
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
|
|
6107
6084
|
function documentVersionMap() {
|
|
6108
6085
|
return EMPTY_DOCUMENT_VERSION_MAP;
|
|
@@ -8781,34 +8758,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
|
8781
8758
|
}
|
|
8782
8759
|
/** Encodes a document for storage locally. */
|
|
8783
8760
|
function toDbRemoteDocument(localSerializer, document) {
|
|
8784
|
-
const
|
|
8785
|
-
const
|
|
8761
|
+
const key = document.key;
|
|
8762
|
+
const remoteDoc = {
|
|
8763
|
+
prefixPath: key.getCollectionPath().popLast().toArray(),
|
|
8764
|
+
collectionGroup: key.collectionGroup,
|
|
8765
|
+
documentId: key.path.lastSegment(),
|
|
8766
|
+
readTime: toDbTimestampKey(document.readTime),
|
|
8767
|
+
hasCommittedMutations: document.hasCommittedMutations
|
|
8768
|
+
};
|
|
8786
8769
|
if (document.isFoundDocument()) {
|
|
8787
|
-
|
|
8788
|
-
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8789
|
-
return new DbRemoteDocument(
|
|
8790
|
-
/* unknownDocument= */ null,
|
|
8791
|
-
/* noDocument= */ null, doc, hasCommittedMutations, dbReadTime, parentPath);
|
|
8770
|
+
remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
|
|
8792
8771
|
}
|
|
8793
8772
|
else if (document.isNoDocument()) {
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
/* unknownDocument= */ null, new DbNoDocument(path, version),
|
|
8799
|
-
/* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
|
|
8773
|
+
remoteDoc.noDocument = {
|
|
8774
|
+
path: key.path.toArray(),
|
|
8775
|
+
readTime: toDbTimestamp(document.version)
|
|
8776
|
+
};
|
|
8800
8777
|
}
|
|
8801
8778
|
else if (document.isUnknownDocument()) {
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
/* document= */ null,
|
|
8807
|
-
/* hasCommittedMutations= */ true, dbReadTime, parentPath);
|
|
8779
|
+
remoteDoc.unknownDocument = {
|
|
8780
|
+
path: key.path.toArray(),
|
|
8781
|
+
version: toDbTimestamp(document.version)
|
|
8782
|
+
};
|
|
8808
8783
|
}
|
|
8809
8784
|
else {
|
|
8810
8785
|
return fail();
|
|
8811
8786
|
}
|
|
8787
|
+
return remoteDoc;
|
|
8812
8788
|
}
|
|
8813
8789
|
function toDbTimestampKey(snapshotVersion) {
|
|
8814
8790
|
const timestamp = snapshotVersion.toTimestamp();
|
|
@@ -8820,7 +8796,7 @@ function fromDbTimestampKey(dbTimestampKey) {
|
|
|
8820
8796
|
}
|
|
8821
8797
|
function toDbTimestamp(snapshotVersion) {
|
|
8822
8798
|
const timestamp = snapshotVersion.toTimestamp();
|
|
8823
|
-
return
|
|
8799
|
+
return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
|
|
8824
8800
|
}
|
|
8825
8801
|
function fromDbTimestamp(dbTimestamp) {
|
|
8826
8802
|
const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
|
|
@@ -8830,7 +8806,13 @@ function fromDbTimestamp(dbTimestamp) {
|
|
|
8830
8806
|
function toDbMutationBatch(localSerializer, userId, batch) {
|
|
8831
8807
|
const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8832
8808
|
const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8833
|
-
return
|
|
8809
|
+
return {
|
|
8810
|
+
userId,
|
|
8811
|
+
batchId: batch.batchId,
|
|
8812
|
+
localWriteTimeMs: batch.localWriteTime.toMillis(),
|
|
8813
|
+
baseMutations: serializedBaseMutations,
|
|
8814
|
+
mutations: serializedMutations
|
|
8815
|
+
};
|
|
8834
8816
|
}
|
|
8835
8817
|
/** Decodes a DbMutationBatch into a MutationBatch */
|
|
8836
8818
|
function fromDbMutationBatch(localSerializer, dbBatch) {
|
|
@@ -8886,7 +8868,15 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8886
8868
|
// convert it to a base64 string for storage.
|
|
8887
8869
|
const resumeToken = targetData.resumeToken.toBase64();
|
|
8888
8870
|
// lastListenSequenceNumber is always 0 until we do real GC.
|
|
8889
|
-
return
|
|
8871
|
+
return {
|
|
8872
|
+
targetId: targetData.targetId,
|
|
8873
|
+
canonicalId: canonifyTarget(targetData.target),
|
|
8874
|
+
readTime: dbTimestamp,
|
|
8875
|
+
resumeToken,
|
|
8876
|
+
lastListenSequenceNumber: targetData.sequenceNumber,
|
|
8877
|
+
lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
|
|
8878
|
+
query: queryProto
|
|
8879
|
+
};
|
|
8890
8880
|
}
|
|
8891
8881
|
/**
|
|
8892
8882
|
* A helper function for figuring out what kind of query has been stored.
|
|
@@ -8965,7 +8955,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
|
8965
8955
|
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
8966
8956
|
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
8967
8957
|
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
8968
|
-
return
|
|
8958
|
+
return {
|
|
8959
|
+
userId,
|
|
8960
|
+
collectionPath,
|
|
8961
|
+
documentId,
|
|
8962
|
+
collectionGroup: overlay.mutation.key.getCollectionGroup(),
|
|
8963
|
+
largestBatchId: overlay.largestBatchId,
|
|
8964
|
+
overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
|
|
8965
|
+
};
|
|
8969
8966
|
}
|
|
8970
8967
|
/**
|
|
8971
8968
|
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
@@ -8977,7 +8974,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
|
|
|
8977
8974
|
return [userId, collectionPath, docId];
|
|
8978
8975
|
}
|
|
8979
8976
|
function toDbIndexConfiguration(index) {
|
|
8980
|
-
return
|
|
8977
|
+
return {
|
|
8978
|
+
indexId: index.indexId,
|
|
8979
|
+
collectionGroup: index.collectionGroup,
|
|
8980
|
+
fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
|
|
8981
|
+
};
|
|
8981
8982
|
}
|
|
8982
8983
|
function fromDbIndexConfiguration(index, state) {
|
|
8983
8984
|
const decodedState = state
|
|
@@ -8987,7 +8988,14 @@ function fromDbIndexConfiguration(index, state) {
|
|
|
8987
8988
|
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
8988
8989
|
}
|
|
8989
8990
|
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
8990
|
-
return
|
|
8991
|
+
return {
|
|
8992
|
+
indexId,
|
|
8993
|
+
uid: user.uid || '',
|
|
8994
|
+
sequenceNumber,
|
|
8995
|
+
readTime: toDbTimestamp(offset.readTime),
|
|
8996
|
+
documentKey: encodeResourcePath(offset.documentKey.path),
|
|
8997
|
+
largestBatchId: offset.largestBatchId
|
|
8998
|
+
};
|
|
8991
8999
|
}
|
|
8992
9000
|
|
|
8993
9001
|
/**
|
|
@@ -9038,13 +9046,13 @@ class IndexedDbBundleCache {
|
|
|
9038
9046
|
* Helper to get a typed SimpleDbStore for the bundles object store.
|
|
9039
9047
|
*/
|
|
9040
9048
|
function bundlesStore(txn) {
|
|
9041
|
-
return getStore(txn,
|
|
9049
|
+
return getStore(txn, DbBundleStore);
|
|
9042
9050
|
}
|
|
9043
9051
|
/**
|
|
9044
9052
|
* Helper to get a typed SimpleDbStore for the namedQueries object store.
|
|
9045
9053
|
*/
|
|
9046
9054
|
function namedQueriesStore(txn) {
|
|
9047
|
-
return getStore(txn,
|
|
9055
|
+
return getStore(txn, DbNamedQueryStore);
|
|
9048
9056
|
}
|
|
9049
9057
|
|
|
9050
9058
|
/**
|
|
@@ -9091,7 +9099,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9091
9099
|
}
|
|
9092
9100
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9093
9101
|
const promises = [];
|
|
9094
|
-
overlays.forEach(mutation => {
|
|
9102
|
+
overlays.forEach((_, mutation) => {
|
|
9095
9103
|
const overlay = new Overlay(largestBatchId, mutation);
|
|
9096
9104
|
promises.push(this.saveOverlay(transaction, overlay));
|
|
9097
9105
|
});
|
|
@@ -9106,19 +9114,19 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9106
9114
|
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9107
9115
|
/*lowerOpen=*/ false,
|
|
9108
9116
|
/*upperOpen=*/ true);
|
|
9109
|
-
promises.push(documentOverlayStore(transaction).deleteAll(
|
|
9117
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
|
|
9110
9118
|
});
|
|
9111
9119
|
return PersistencePromise.waitFor(promises);
|
|
9112
9120
|
}
|
|
9113
9121
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9114
|
-
const result =
|
|
9122
|
+
const result = newOverlayMap();
|
|
9115
9123
|
const collectionPath = encodeResourcePath(collection);
|
|
9116
9124
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9117
9125
|
// is not inclusive.
|
|
9118
9126
|
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9119
9127
|
/*lowerOpen=*/ true);
|
|
9120
9128
|
return documentOverlayStore(transaction)
|
|
9121
|
-
.loadAll(
|
|
9129
|
+
.loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
|
|
9122
9130
|
.next(dbOverlays => {
|
|
9123
9131
|
for (const dbOverlay of dbOverlays) {
|
|
9124
9132
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
@@ -9128,7 +9136,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9128
9136
|
});
|
|
9129
9137
|
}
|
|
9130
9138
|
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9131
|
-
const result =
|
|
9139
|
+
const result = newOverlayMap();
|
|
9132
9140
|
let currentBatchId = undefined;
|
|
9133
9141
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9134
9142
|
// is not inclusive.
|
|
@@ -9136,7 +9144,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9136
9144
|
/*lowerOpen=*/ true);
|
|
9137
9145
|
return documentOverlayStore(transaction)
|
|
9138
9146
|
.iterate({
|
|
9139
|
-
index:
|
|
9147
|
+
index: DbDocumentOverlayCollectionGroupOverlayIndex,
|
|
9140
9148
|
range
|
|
9141
9149
|
}, (_, dbOverlay, control) => {
|
|
9142
9150
|
// We do not want to return partial batch overlays, even if the size
|
|
@@ -9144,7 +9152,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9144
9152
|
// continue to aggregate results even after the result size exceeds
|
|
9145
9153
|
// `count` if there are more overlays from the `currentBatchId`.
|
|
9146
9154
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9147
|
-
if (result.size < count ||
|
|
9155
|
+
if (result.size() < count ||
|
|
9148
9156
|
overlay.largestBatchId === currentBatchId) {
|
|
9149
9157
|
result.set(overlay.getKey(), overlay);
|
|
9150
9158
|
currentBatchId = overlay.largestBatchId;
|
|
@@ -9163,7 +9171,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9163
9171
|
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9164
9172
|
*/
|
|
9165
9173
|
function documentOverlayStore(txn) {
|
|
9166
|
-
return getStore(txn,
|
|
9174
|
+
return getStore(txn, DbDocumentOverlayStore);
|
|
9167
9175
|
}
|
|
9168
9176
|
|
|
9169
9177
|
/**
|
|
@@ -9719,21 +9727,40 @@ class IndexEntry {
|
|
|
9719
9727
|
this.arrayValue = arrayValue;
|
|
9720
9728
|
this.directionalValue = directionalValue;
|
|
9721
9729
|
}
|
|
9730
|
+
/**
|
|
9731
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
9732
|
+
* directional value.
|
|
9733
|
+
*/
|
|
9734
|
+
successor() {
|
|
9735
|
+
const currentLength = this.directionalValue.length;
|
|
9736
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
9737
|
+
? currentLength + 1
|
|
9738
|
+
: currentLength;
|
|
9739
|
+
const successor = new Uint8Array(newLength);
|
|
9740
|
+
successor.set(this.directionalValue, 0);
|
|
9741
|
+
if (newLength !== currentLength) {
|
|
9742
|
+
successor.set([0], this.directionalValue.length);
|
|
9743
|
+
}
|
|
9744
|
+
else {
|
|
9745
|
+
++successor[successor.length - 1];
|
|
9746
|
+
}
|
|
9747
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
9748
|
+
}
|
|
9722
9749
|
}
|
|
9723
9750
|
function indexEntryComparator(left, right) {
|
|
9724
9751
|
let cmp = left.indexId - right.indexId;
|
|
9725
9752
|
if (cmp !== 0) {
|
|
9726
9753
|
return cmp;
|
|
9727
9754
|
}
|
|
9728
|
-
cmp =
|
|
9755
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
9729
9756
|
if (cmp !== 0) {
|
|
9730
9757
|
return cmp;
|
|
9731
9758
|
}
|
|
9732
|
-
cmp = compareByteArrays(left.
|
|
9759
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
9733
9760
|
if (cmp !== 0) {
|
|
9734
9761
|
return cmp;
|
|
9735
9762
|
}
|
|
9736
|
-
return
|
|
9763
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
9737
9764
|
}
|
|
9738
9765
|
function compareByteArrays(left, right) {
|
|
9739
9766
|
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
@@ -9745,6 +9772,160 @@ function compareByteArrays(left, right) {
|
|
|
9745
9772
|
return left.length - right.length;
|
|
9746
9773
|
}
|
|
9747
9774
|
|
|
9775
|
+
/**
|
|
9776
|
+
* @license
|
|
9777
|
+
* Copyright 2022 Google LLC
|
|
9778
|
+
*
|
|
9779
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9780
|
+
* you may not use this file except in compliance with the License.
|
|
9781
|
+
* You may obtain a copy of the License at
|
|
9782
|
+
*
|
|
9783
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9784
|
+
*
|
|
9785
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9786
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9787
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9788
|
+
* See the License for the specific language governing permissions and
|
|
9789
|
+
* limitations under the License.
|
|
9790
|
+
*/
|
|
9791
|
+
/**
|
|
9792
|
+
* A light query planner for Firestore.
|
|
9793
|
+
*
|
|
9794
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
9795
|
+
* determines whether a given index can be used to serve the specified target.
|
|
9796
|
+
*
|
|
9797
|
+
* The following table showcases some possible index configurations:
|
|
9798
|
+
*
|
|
9799
|
+
* Query | Index
|
|
9800
|
+
* -----------------------------------------------------------------------------
|
|
9801
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
9802
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
9803
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
9804
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
9805
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
9806
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
9807
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
9808
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
9809
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
9810
|
+
*/
|
|
9811
|
+
class TargetIndexMatcher {
|
|
9812
|
+
constructor(target) {
|
|
9813
|
+
this.collectionId =
|
|
9814
|
+
target.collectionGroup != null
|
|
9815
|
+
? target.collectionGroup
|
|
9816
|
+
: target.path.lastSegment();
|
|
9817
|
+
this.orderBys = target.orderBy;
|
|
9818
|
+
this.equalityFilters = [];
|
|
9819
|
+
for (const filter of target.filters) {
|
|
9820
|
+
const fieldFilter = filter;
|
|
9821
|
+
if (fieldFilter.isInequality()) {
|
|
9822
|
+
this.inequalityFilter = fieldFilter;
|
|
9823
|
+
}
|
|
9824
|
+
else {
|
|
9825
|
+
this.equalityFilters.push(fieldFilter);
|
|
9826
|
+
}
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
/**
|
|
9830
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
9831
|
+
* target.
|
|
9832
|
+
*
|
|
9833
|
+
* An index is considered capable of serving the target when:
|
|
9834
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
9835
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
9836
|
+
* fewer.
|
|
9837
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
9838
|
+
* have a corresponding `CONTAINS` segment.
|
|
9839
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
9840
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
9841
|
+
* clauses.
|
|
9842
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
9843
|
+
* - The optional segment for the inequality filter must appear after all
|
|
9844
|
+
* equality segments.
|
|
9845
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
9846
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
9847
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
9848
|
+
* omitted.
|
|
9849
|
+
*/
|
|
9850
|
+
servedByIndex(index) {
|
|
9851
|
+
// If there is an array element, find a matching filter.
|
|
9852
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
9853
|
+
if (arraySegment !== undefined &&
|
|
9854
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
9855
|
+
return false;
|
|
9856
|
+
}
|
|
9857
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
9858
|
+
let segmentIndex = 0;
|
|
9859
|
+
let orderBysIndex = 0;
|
|
9860
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
9861
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9862
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
9863
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
9864
|
+
// Since it is not possible to use the same field path in both an equality
|
|
9865
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
9866
|
+
// that a matching equality segment should instead be used to map to an
|
|
9867
|
+
// inequality filter or orderBy clause.
|
|
9868
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
9869
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
9870
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
9871
|
+
// clauses.
|
|
9872
|
+
break;
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
// If we already have processed all segments, all segments are used to serve
|
|
9876
|
+
// the equality filters and we do not need to map any segments to the
|
|
9877
|
+
// target's inequality and orderBy clauses.
|
|
9878
|
+
if (segmentIndex === segments.length) {
|
|
9879
|
+
return true;
|
|
9880
|
+
}
|
|
9881
|
+
// If there is an inequality filter, the next segment must match both the
|
|
9882
|
+
// filter and the first orderBy clause.
|
|
9883
|
+
if (this.inequalityFilter !== undefined) {
|
|
9884
|
+
const segment = segments[segmentIndex];
|
|
9885
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
9886
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9887
|
+
return false;
|
|
9888
|
+
}
|
|
9889
|
+
++segmentIndex;
|
|
9890
|
+
}
|
|
9891
|
+
// All remaining segments need to represent the prefix of the target's
|
|
9892
|
+
// orderBy.
|
|
9893
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9894
|
+
const segment = segments[segmentIndex];
|
|
9895
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
9896
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9897
|
+
return false;
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
return true;
|
|
9901
|
+
}
|
|
9902
|
+
hasMatchingEqualityFilter(segment) {
|
|
9903
|
+
for (const filter of this.equalityFilters) {
|
|
9904
|
+
if (this.matchesFilter(filter, segment)) {
|
|
9905
|
+
return true;
|
|
9906
|
+
}
|
|
9907
|
+
}
|
|
9908
|
+
return false;
|
|
9909
|
+
}
|
|
9910
|
+
matchesFilter(filter, segment) {
|
|
9911
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
9912
|
+
return false;
|
|
9913
|
+
}
|
|
9914
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
9915
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
9916
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
9917
|
+
}
|
|
9918
|
+
matchesOrderBy(orderBy, segment) {
|
|
9919
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
9920
|
+
return false;
|
|
9921
|
+
}
|
|
9922
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
9923
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
9924
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
9925
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
|
|
9748
9929
|
/**
|
|
9749
9930
|
* @license
|
|
9750
9931
|
* Copyright 2019 Google LLC
|
|
@@ -9783,9 +9964,9 @@ class MemoryIndexManager {
|
|
|
9783
9964
|
// Field indices are not supported with memory persistence.
|
|
9784
9965
|
return PersistencePromise.resolve();
|
|
9785
9966
|
}
|
|
9786
|
-
getDocumentsMatchingTarget(transaction,
|
|
9967
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
9787
9968
|
// Field indices are not supported with memory persistence.
|
|
9788
|
-
return PersistencePromise.resolve(
|
|
9969
|
+
return PersistencePromise.resolve(null);
|
|
9789
9970
|
}
|
|
9790
9971
|
getFieldIndex(transaction, target) {
|
|
9791
9972
|
// Field indices are not supported with memory persistence.
|
|
@@ -9857,6 +10038,7 @@ class MemoryCollectionParentIndex {
|
|
|
9857
10038
|
* limitations under the License.
|
|
9858
10039
|
*/
|
|
9859
10040
|
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
10041
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
9860
10042
|
/**
|
|
9861
10043
|
* A persisted implementation of IndexManager.
|
|
9862
10044
|
*
|
|
@@ -9874,6 +10056,11 @@ class IndexedDbIndexManager {
|
|
|
9874
10056
|
* used to satisfy reads.
|
|
9875
10057
|
*/
|
|
9876
10058
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
10059
|
+
/**
|
|
10060
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
10061
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
10062
|
+
*/
|
|
10063
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
9877
10064
|
this.uid = user.uid || '';
|
|
9878
10065
|
}
|
|
9879
10066
|
/**
|
|
@@ -9942,13 +10129,111 @@ class IndexedDbIndexManager {
|
|
|
9942
10129
|
/*lowerOpen=*/ false,
|
|
9943
10130
|
/*upperOpen=*/ true)));
|
|
9944
10131
|
}
|
|
9945
|
-
getDocumentsMatchingTarget(transaction,
|
|
9946
|
-
|
|
9947
|
-
|
|
10132
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10133
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10134
|
+
let canServeTarget = true;
|
|
10135
|
+
const indexes = new Map();
|
|
10136
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10137
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10138
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10139
|
+
indexes.set(subTarget, index);
|
|
10140
|
+
});
|
|
10141
|
+
}).next(() => {
|
|
10142
|
+
if (!canServeTarget) {
|
|
10143
|
+
return PersistencePromise.resolve(null);
|
|
10144
|
+
}
|
|
10145
|
+
else {
|
|
10146
|
+
let result = documentKeySet();
|
|
10147
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10148
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10149
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10150
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10151
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10152
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10153
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10154
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10155
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10156
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10157
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10158
|
+
return indexEntries
|
|
10159
|
+
.loadFirst(indexRange, target.limit)
|
|
10160
|
+
.next(entries => {
|
|
10161
|
+
entries.forEach(entry => {
|
|
10162
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10163
|
+
});
|
|
10164
|
+
});
|
|
10165
|
+
});
|
|
10166
|
+
}).next(() => result);
|
|
10167
|
+
}
|
|
10168
|
+
});
|
|
10169
|
+
}
|
|
10170
|
+
getSubTargets(target) {
|
|
10171
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10172
|
+
if (subTargets) {
|
|
10173
|
+
return subTargets;
|
|
10174
|
+
}
|
|
10175
|
+
// TODO(orquery): Implement DNF transform
|
|
10176
|
+
subTargets = [target];
|
|
10177
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10178
|
+
return subTargets;
|
|
10179
|
+
}
|
|
10180
|
+
/**
|
|
10181
|
+
* Constructs a key range query on `DbIndexEntryStore` that unions all
|
|
10182
|
+
* bounds.
|
|
10183
|
+
*/
|
|
10184
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10185
|
+
// The number of total index scans we union together. This is similar to a
|
|
10186
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10187
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10188
|
+
// combined with the values from the query bounds.
|
|
10189
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10190
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10191
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10192
|
+
const indexRanges = [];
|
|
10193
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10194
|
+
const arrayValue = arrayValues
|
|
10195
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10196
|
+
: EMPTY_VALUE;
|
|
10197
|
+
const lowerBound = lowerBounds
|
|
10198
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10199
|
+
: this.generateEmptyBound(indexId);
|
|
10200
|
+
const upperBound = upperBounds
|
|
10201
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10202
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10203
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10204
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10205
|
+
/* inclusive= */ true))));
|
|
10206
|
+
}
|
|
10207
|
+
return indexRanges;
|
|
10208
|
+
}
|
|
10209
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10210
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10211
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10212
|
+
return inclusive ? entry : entry.successor();
|
|
10213
|
+
}
|
|
10214
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10215
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10216
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10217
|
+
return inclusive ? entry.successor() : entry;
|
|
10218
|
+
}
|
|
10219
|
+
/**
|
|
10220
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10221
|
+
* and user.
|
|
10222
|
+
*/
|
|
10223
|
+
generateEmptyBound(indexId) {
|
|
10224
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
9948
10225
|
}
|
|
9949
10226
|
getFieldIndex(transaction, target) {
|
|
9950
|
-
|
|
9951
|
-
|
|
10227
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10228
|
+
const collectionGroup = target.collectionGroup != null
|
|
10229
|
+
? target.collectionGroup
|
|
10230
|
+
: target.path.lastSegment();
|
|
10231
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10232
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10233
|
+
// Return the index that matches the most number of segments.
|
|
10234
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10235
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10236
|
+
});
|
|
9952
10237
|
}
|
|
9953
10238
|
/**
|
|
9954
10239
|
* Returns the byte encoded form of the directional values in the field index.
|
|
@@ -9962,22 +10247,90 @@ class IndexedDbIndexManager {
|
|
|
9962
10247
|
if (field == null) {
|
|
9963
10248
|
return null;
|
|
9964
10249
|
}
|
|
9965
|
-
const directionalEncoder = encoder.forKind(segment.kind);
|
|
9966
|
-
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10250
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10251
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10252
|
+
}
|
|
10253
|
+
return encoder.encodedBytes();
|
|
10254
|
+
}
|
|
10255
|
+
/** Encodes a single value to the ascending index format. */
|
|
10256
|
+
encodeSingleElement(value) {
|
|
10257
|
+
const encoder = new IndexByteEncoder();
|
|
10258
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10259
|
+
return encoder.encodedBytes();
|
|
10260
|
+
}
|
|
10261
|
+
/**
|
|
10262
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10263
|
+
* For IN queries, a list of possible values is returned.
|
|
10264
|
+
*/
|
|
10265
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10266
|
+
if (bound === null) {
|
|
10267
|
+
return [];
|
|
10268
|
+
}
|
|
10269
|
+
let encoders = [];
|
|
10270
|
+
encoders.push(new IndexByteEncoder());
|
|
10271
|
+
let boundIdx = 0;
|
|
10272
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10273
|
+
const value = bound[boundIdx++];
|
|
10274
|
+
for (const encoder of encoders) {
|
|
10275
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10276
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10277
|
+
}
|
|
10278
|
+
else {
|
|
10279
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10280
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10281
|
+
}
|
|
10282
|
+
}
|
|
10283
|
+
}
|
|
10284
|
+
return this.getEncodedBytes(encoders);
|
|
10285
|
+
}
|
|
10286
|
+
/**
|
|
10287
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10288
|
+
* queries, a list of possible values is returned.
|
|
10289
|
+
*/
|
|
10290
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10291
|
+
if (bound == null) {
|
|
10292
|
+
return null;
|
|
10293
|
+
}
|
|
10294
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10295
|
+
}
|
|
10296
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10297
|
+
getEncodedBytes(encoders) {
|
|
10298
|
+
const result = [];
|
|
10299
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10300
|
+
result[i] = encoders[i].encodedBytes();
|
|
10301
|
+
}
|
|
10302
|
+
return result;
|
|
10303
|
+
}
|
|
10304
|
+
/**
|
|
10305
|
+
* Creates a separate encoder for each element of an array.
|
|
10306
|
+
*
|
|
10307
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10308
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10309
|
+
* list of new encoders is returned.
|
|
10310
|
+
*/
|
|
10311
|
+
expandIndexValues(encoders, segment, value) {
|
|
10312
|
+
const prefixes = [...encoders];
|
|
10313
|
+
const results = [];
|
|
10314
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10315
|
+
for (const prefix of prefixes) {
|
|
10316
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10317
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10318
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10319
|
+
results.push(clonedEncoder);
|
|
10320
|
+
}
|
|
9967
10321
|
}
|
|
9968
|
-
return
|
|
10322
|
+
return results;
|
|
9969
10323
|
}
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
return encoder.encodedBytes();
|
|
10324
|
+
isInFilter(target, fieldPath) {
|
|
10325
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10326
|
+
f.field.isEqual(fieldPath) &&
|
|
10327
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
9975
10328
|
}
|
|
9976
10329
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9977
10330
|
const indexes = indexConfigurationStore(transaction);
|
|
9978
10331
|
const states = indexStateStore(transaction);
|
|
9979
10332
|
return (collectionGroup
|
|
9980
|
-
? indexes.loadAll(
|
|
10333
|
+
? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9981
10334
|
: indexes.loadAll()).next(indexConfigs => {
|
|
9982
10335
|
const result = [];
|
|
9983
10336
|
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
@@ -9994,7 +10347,12 @@ class IndexedDbIndexManager {
|
|
|
9994
10347
|
if (indexes.length === 0) {
|
|
9995
10348
|
return null;
|
|
9996
10349
|
}
|
|
9997
|
-
indexes.sort((l, r) =>
|
|
10350
|
+
indexes.sort((l, r) => {
|
|
10351
|
+
const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
|
|
10352
|
+
return cmp !== 0
|
|
10353
|
+
? cmp
|
|
10354
|
+
: primitiveComparator(l.collectionGroup, r.collectionGroup);
|
|
10355
|
+
});
|
|
9998
10356
|
return indexes[0].collectionGroup;
|
|
9999
10357
|
});
|
|
10000
10358
|
}
|
|
@@ -10002,7 +10360,7 @@ class IndexedDbIndexManager {
|
|
|
10002
10360
|
const indexes = indexConfigurationStore(transaction);
|
|
10003
10361
|
const states = indexStateStore(transaction);
|
|
10004
10362
|
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
10005
|
-
.loadAll(
|
|
10363
|
+
.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10006
10364
|
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
10007
10365
|
}
|
|
10008
10366
|
updateIndexEntries(transaction, documents) {
|
|
@@ -10033,7 +10391,13 @@ class IndexedDbIndexManager {
|
|
|
10033
10391
|
}
|
|
10034
10392
|
addIndexEntry(transaction, document, indexEntry) {
|
|
10035
10393
|
const indexEntries = indexEntriesStore(transaction);
|
|
10036
|
-
return indexEntries.put(
|
|
10394
|
+
return indexEntries.put({
|
|
10395
|
+
indexId: indexEntry.indexId,
|
|
10396
|
+
uid: this.uid,
|
|
10397
|
+
arrayValue: indexEntry.arrayValue,
|
|
10398
|
+
directionalValue: indexEntry.directionalValue,
|
|
10399
|
+
documentKey: encodeResourcePath(document.key.path)
|
|
10400
|
+
});
|
|
10037
10401
|
}
|
|
10038
10402
|
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10039
10403
|
const indexEntries = indexEntriesStore(transaction);
|
|
@@ -10050,7 +10414,7 @@ class IndexedDbIndexManager {
|
|
|
10050
10414
|
let results = new SortedSet(indexEntryComparator);
|
|
10051
10415
|
return indexEntries
|
|
10052
10416
|
.iterate({
|
|
10053
|
-
index:
|
|
10417
|
+
index: DbIndexEntryDocumentKeyIndex,
|
|
10054
10418
|
range: IDBKeyRange.only([
|
|
10055
10419
|
fieldIndex.indexId,
|
|
10056
10420
|
this.uid,
|
|
@@ -10078,7 +10442,7 @@ class IndexedDbIndexManager {
|
|
|
10078
10442
|
}
|
|
10079
10443
|
}
|
|
10080
10444
|
else {
|
|
10081
|
-
results = results.add(new IndexEntry(fieldIndex.indexId, document.key,
|
|
10445
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10082
10446
|
}
|
|
10083
10447
|
return results;
|
|
10084
10448
|
}
|
|
@@ -10104,7 +10468,7 @@ class IndexedDbIndexManager {
|
|
|
10104
10468
|
const states = indexStateStore(transaction);
|
|
10105
10469
|
return states
|
|
10106
10470
|
.iterate({
|
|
10107
|
-
index:
|
|
10471
|
+
index: DbIndexStateSequenceNumberIndex,
|
|
10108
10472
|
reverse: true,
|
|
10109
10473
|
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10110
10474
|
}, (_, state, controller) => {
|
|
@@ -10113,31 +10477,81 @@ class IndexedDbIndexManager {
|
|
|
10113
10477
|
})
|
|
10114
10478
|
.next(() => nextSequenceNumber);
|
|
10115
10479
|
}
|
|
10480
|
+
/**
|
|
10481
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10482
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10483
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10484
|
+
*/
|
|
10485
|
+
createRange(lower, upper, notInValues) {
|
|
10486
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10487
|
+
// sorted set of non-overlapping ranges.
|
|
10488
|
+
notInValues = notInValues
|
|
10489
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10490
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10491
|
+
const bounds = [];
|
|
10492
|
+
bounds.push(lower);
|
|
10493
|
+
for (const notInValue of notInValues) {
|
|
10494
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10495
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10496
|
+
if (cmpToLower === 0) {
|
|
10497
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10498
|
+
// to the next value.
|
|
10499
|
+
bounds[0] = lower.successor();
|
|
10500
|
+
}
|
|
10501
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10502
|
+
// `notInValue` is in the middle of the range
|
|
10503
|
+
bounds.push(notInValue);
|
|
10504
|
+
bounds.push(notInValue.successor());
|
|
10505
|
+
}
|
|
10506
|
+
else if (cmpToUpper > 0) {
|
|
10507
|
+
// `notInValue` (and all following values) are out of the range
|
|
10508
|
+
break;
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
bounds.push(upper);
|
|
10512
|
+
const ranges = [];
|
|
10513
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10514
|
+
ranges.push(IDBKeyRange.bound([
|
|
10515
|
+
bounds[i].indexId,
|
|
10516
|
+
this.uid,
|
|
10517
|
+
bounds[i].arrayValue,
|
|
10518
|
+
bounds[i].directionalValue,
|
|
10519
|
+
''
|
|
10520
|
+
], [
|
|
10521
|
+
bounds[i + 1].indexId,
|
|
10522
|
+
this.uid,
|
|
10523
|
+
bounds[i + 1].arrayValue,
|
|
10524
|
+
bounds[i + 1].directionalValue,
|
|
10525
|
+
''
|
|
10526
|
+
]));
|
|
10527
|
+
}
|
|
10528
|
+
return ranges;
|
|
10529
|
+
}
|
|
10116
10530
|
}
|
|
10117
10531
|
/**
|
|
10118
10532
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
10119
10533
|
* document store.
|
|
10120
10534
|
*/
|
|
10121
10535
|
function collectionParentsStore(txn) {
|
|
10122
|
-
return getStore(txn,
|
|
10536
|
+
return getStore(txn, DbCollectionParentStore);
|
|
10123
10537
|
}
|
|
10124
10538
|
/**
|
|
10125
10539
|
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10126
10540
|
*/
|
|
10127
10541
|
function indexEntriesStore(txn) {
|
|
10128
|
-
return getStore(txn,
|
|
10542
|
+
return getStore(txn, DbIndexEntryStore);
|
|
10129
10543
|
}
|
|
10130
10544
|
/**
|
|
10131
10545
|
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10132
10546
|
*/
|
|
10133
10547
|
function indexConfigurationStore(txn) {
|
|
10134
|
-
return getStore(txn,
|
|
10548
|
+
return getStore(txn, DbIndexConfigurationStore);
|
|
10135
10549
|
}
|
|
10136
10550
|
/**
|
|
10137
10551
|
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10138
10552
|
*/
|
|
10139
10553
|
function indexStateStore(txn) {
|
|
10140
|
-
return getStore(txn,
|
|
10554
|
+
return getStore(txn, DbIndexStateStore);
|
|
10141
10555
|
}
|
|
10142
10556
|
|
|
10143
10557
|
/**
|
|
@@ -10161,8 +10575,8 @@ function indexStateStore(txn) {
|
|
|
10161
10575
|
* @returns A PersistencePromise of the document mutations that were removed.
|
|
10162
10576
|
*/
|
|
10163
10577
|
function removeMutationBatch(txn, userId, batch) {
|
|
10164
|
-
const mutationStore = txn.store(
|
|
10165
|
-
const indexTxn = txn.store(
|
|
10578
|
+
const mutationStore = txn.store(DbMutationBatchStore);
|
|
10579
|
+
const indexTxn = txn.store(DbDocumentMutationStore);
|
|
10166
10580
|
const promises = [];
|
|
10167
10581
|
const range = IDBKeyRange.only(batch.batchId);
|
|
10168
10582
|
let numDeleted = 0;
|
|
@@ -10175,7 +10589,7 @@ function removeMutationBatch(txn, userId, batch) {
|
|
|
10175
10589
|
}));
|
|
10176
10590
|
const removedDocuments = [];
|
|
10177
10591
|
for (const mutation of batch.mutations) {
|
|
10178
|
-
const indexKey =
|
|
10592
|
+
const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
|
|
10179
10593
|
promises.push(indexTxn.delete(indexKey));
|
|
10180
10594
|
removedDocuments.push(mutation.key);
|
|
10181
10595
|
}
|
|
@@ -10264,7 +10678,7 @@ class IndexedDbMutationQueue {
|
|
|
10264
10678
|
let empty = true;
|
|
10265
10679
|
const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10266
10680
|
return mutationsStore(transaction)
|
|
10267
|
-
.iterate({ index:
|
|
10681
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
|
|
10268
10682
|
empty = false;
|
|
10269
10683
|
control.done();
|
|
10270
10684
|
})
|
|
@@ -10289,10 +10703,10 @@ class IndexedDbMutationQueue {
|
|
|
10289
10703
|
const promises = [];
|
|
10290
10704
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10291
10705
|
for (const mutation of mutations) {
|
|
10292
|
-
const indexKey =
|
|
10706
|
+
const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
|
|
10293
10707
|
collectionParents = collectionParents.add(mutation.key.path.popLast());
|
|
10294
10708
|
promises.push(mutationStore.put(dbBatch));
|
|
10295
|
-
promises.push(documentStore.put(indexKey,
|
|
10709
|
+
promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
|
|
10296
10710
|
}
|
|
10297
10711
|
collectionParents.forEach(parent => {
|
|
10298
10712
|
promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
|
|
@@ -10343,7 +10757,7 @@ class IndexedDbMutationQueue {
|
|
|
10343
10757
|
const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
|
|
10344
10758
|
let foundBatch = null;
|
|
10345
10759
|
return mutationsStore(transaction)
|
|
10346
|
-
.iterate({ index:
|
|
10760
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
|
|
10347
10761
|
if (dbBatch.userId === this.userId) {
|
|
10348
10762
|
hardAssert(dbBatch.batchId >= nextBatchId);
|
|
10349
10763
|
foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
|
|
@@ -10359,7 +10773,7 @@ class IndexedDbMutationQueue {
|
|
|
10359
10773
|
]);
|
|
10360
10774
|
let batchId = BATCHID_UNKNOWN;
|
|
10361
10775
|
return mutationsStore(transaction)
|
|
10362
|
-
.iterate({ index:
|
|
10776
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
|
|
10363
10777
|
batchId = dbBatch.batchId;
|
|
10364
10778
|
control.done();
|
|
10365
10779
|
})
|
|
@@ -10368,13 +10782,13 @@ class IndexedDbMutationQueue {
|
|
|
10368
10782
|
getAllMutationBatches(transaction) {
|
|
10369
10783
|
const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10370
10784
|
return mutationsStore(transaction)
|
|
10371
|
-
.loadAll(
|
|
10785
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
10372
10786
|
.next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
|
|
10373
10787
|
}
|
|
10374
10788
|
getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
|
|
10375
10789
|
// Scan the document-mutation index starting with a prefix starting with
|
|
10376
10790
|
// the given documentKey.
|
|
10377
|
-
const indexPrefix =
|
|
10791
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10378
10792
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10379
10793
|
const results = [];
|
|
10380
10794
|
return documentMutationsStore(transaction)
|
|
@@ -10409,7 +10823,7 @@ class IndexedDbMutationQueue {
|
|
|
10409
10823
|
let uniqueBatchIDs = new SortedSet(primitiveComparator);
|
|
10410
10824
|
const promises = [];
|
|
10411
10825
|
documentKeys.forEach(documentKey => {
|
|
10412
|
-
const indexStart =
|
|
10826
|
+
const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10413
10827
|
const range = IDBKeyRange.lowerBound(indexStart);
|
|
10414
10828
|
const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
|
|
10415
10829
|
const [userID, encodedPath, batchID] = indexKey;
|
|
@@ -10445,7 +10859,7 @@ class IndexedDbMutationQueue {
|
|
|
10445
10859
|
// Since we don't yet index the actual properties in the mutations, our
|
|
10446
10860
|
// current approach is to just return all mutation batches that affect
|
|
10447
10861
|
// documents in the collection being queried.
|
|
10448
|
-
const indexPrefix =
|
|
10862
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
|
|
10449
10863
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10450
10864
|
// Collect up unique batchIDs encountered during a scan of the index. Use a
|
|
10451
10865
|
// SortedSet to accumulate batch IDs so they can be traversed in order in a
|
|
@@ -10517,7 +10931,7 @@ class IndexedDbMutationQueue {
|
|
|
10517
10931
|
}
|
|
10518
10932
|
// Verify that there are no entries in the documentMutations index if
|
|
10519
10933
|
// the queue is empty.
|
|
10520
|
-
const startRange = IDBKeyRange.lowerBound(
|
|
10934
|
+
const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
|
|
10521
10935
|
const danglingMutationReferences = [];
|
|
10522
10936
|
return documentMutationsStore(txn)
|
|
10523
10937
|
.iterate({ range: startRange }, (key, _, control) => {
|
|
@@ -10545,9 +10959,11 @@ class IndexedDbMutationQueue {
|
|
|
10545
10959
|
return mutationQueuesStore(transaction)
|
|
10546
10960
|
.get(this.userId)
|
|
10547
10961
|
.next((metadata) => {
|
|
10548
|
-
return (metadata ||
|
|
10549
|
-
|
|
10550
|
-
|
|
10962
|
+
return (metadata || {
|
|
10963
|
+
userId: this.userId,
|
|
10964
|
+
lastAcknowledgedBatchId: BATCHID_UNKNOWN,
|
|
10965
|
+
lastStreamToken: ''
|
|
10966
|
+
});
|
|
10551
10967
|
});
|
|
10552
10968
|
}
|
|
10553
10969
|
}
|
|
@@ -10556,7 +10972,7 @@ class IndexedDbMutationQueue {
|
|
|
10556
10972
|
* mutation for the given key.
|
|
10557
10973
|
*/
|
|
10558
10974
|
function mutationQueueContainsKey(txn, userId, key) {
|
|
10559
|
-
const indexKey =
|
|
10975
|
+
const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
|
|
10560
10976
|
const encodedPath = indexKey[1];
|
|
10561
10977
|
const startRange = IDBKeyRange.lowerBound(indexKey);
|
|
10562
10978
|
let containsKey = false;
|
|
@@ -10588,19 +11004,19 @@ function mutationQueuesContainKey(txn, docKey) {
|
|
|
10588
11004
|
* Helper to get a typed SimpleDbStore for the mutations object store.
|
|
10589
11005
|
*/
|
|
10590
11006
|
function mutationsStore(txn) {
|
|
10591
|
-
return getStore(txn,
|
|
11007
|
+
return getStore(txn, DbMutationBatchStore);
|
|
10592
11008
|
}
|
|
10593
11009
|
/**
|
|
10594
11010
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10595
11011
|
*/
|
|
10596
11012
|
function documentMutationsStore(txn) {
|
|
10597
|
-
return getStore(txn,
|
|
11013
|
+
return getStore(txn, DbDocumentMutationStore);
|
|
10598
11014
|
}
|
|
10599
11015
|
/**
|
|
10600
11016
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10601
11017
|
*/
|
|
10602
11018
|
function mutationQueuesStore(txn) {
|
|
10603
|
-
return getStore(txn,
|
|
11019
|
+
return getStore(txn, DbMutationQueueStore);
|
|
10604
11020
|
}
|
|
10605
11021
|
|
|
10606
11022
|
/**
|
|
@@ -10764,14 +11180,14 @@ class IndexedDbTargetCache {
|
|
|
10764
11180
|
}
|
|
10765
11181
|
retrieveMetadata(transaction) {
|
|
10766
11182
|
return globalTargetStore(transaction)
|
|
10767
|
-
.get(
|
|
11183
|
+
.get(DbTargetGlobalKey)
|
|
10768
11184
|
.next(metadata => {
|
|
10769
11185
|
hardAssert(metadata !== null);
|
|
10770
11186
|
return metadata;
|
|
10771
11187
|
});
|
|
10772
11188
|
}
|
|
10773
11189
|
saveMetadata(transaction, metadata) {
|
|
10774
|
-
return globalTargetStore(transaction).put(
|
|
11190
|
+
return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
|
|
10775
11191
|
}
|
|
10776
11192
|
saveTargetData(transaction, targetData) {
|
|
10777
11193
|
return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
|
|
@@ -10804,7 +11220,7 @@ class IndexedDbTargetCache {
|
|
|
10804
11220
|
const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
|
|
10805
11221
|
let result = null;
|
|
10806
11222
|
return targetsStore(transaction)
|
|
10807
|
-
.iterate({ range, index:
|
|
11223
|
+
.iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
|
|
10808
11224
|
const found = fromDbTarget(value);
|
|
10809
11225
|
// After finding a potential match, check that the target is
|
|
10810
11226
|
// actually equal to the requested target.
|
|
@@ -10822,7 +11238,7 @@ class IndexedDbTargetCache {
|
|
|
10822
11238
|
const store = documentTargetStore(txn);
|
|
10823
11239
|
keys.forEach(key => {
|
|
10824
11240
|
const path = encodeResourcePath(key.path);
|
|
10825
|
-
promises.push(store.put(
|
|
11241
|
+
promises.push(store.put({ targetId, path }));
|
|
10826
11242
|
promises.push(this.referenceDelegate.addReference(txn, targetId, key));
|
|
10827
11243
|
});
|
|
10828
11244
|
return PersistencePromise.waitFor(promises);
|
|
@@ -10868,7 +11284,7 @@ class IndexedDbTargetCache {
|
|
|
10868
11284
|
let count = 0;
|
|
10869
11285
|
return documentTargetStore(txn)
|
|
10870
11286
|
.iterate({
|
|
10871
|
-
index:
|
|
11287
|
+
index: DbTargetDocumentDocumentTargetsIndex,
|
|
10872
11288
|
keysOnly: true,
|
|
10873
11289
|
range
|
|
10874
11290
|
}, ([targetId, path], _, control) => {
|
|
@@ -10907,19 +11323,19 @@ class IndexedDbTargetCache {
|
|
|
10907
11323
|
* Helper to get a typed SimpleDbStore for the queries object store.
|
|
10908
11324
|
*/
|
|
10909
11325
|
function targetsStore(txn) {
|
|
10910
|
-
return getStore(txn,
|
|
11326
|
+
return getStore(txn, DbTargetStore);
|
|
10911
11327
|
}
|
|
10912
11328
|
/**
|
|
10913
11329
|
* Helper to get a typed SimpleDbStore for the target globals object store.
|
|
10914
11330
|
*/
|
|
10915
11331
|
function globalTargetStore(txn) {
|
|
10916
|
-
return getStore(txn,
|
|
11332
|
+
return getStore(txn, DbTargetGlobalStore);
|
|
10917
11333
|
}
|
|
10918
11334
|
/**
|
|
10919
11335
|
* Helper to get a typed SimpleDbStore for the document target object store.
|
|
10920
11336
|
*/
|
|
10921
11337
|
function documentTargetStore(txn) {
|
|
10922
|
-
return getStore(txn,
|
|
11338
|
+
return getStore(txn, DbTargetDocumentStore);
|
|
10923
11339
|
}
|
|
10924
11340
|
|
|
10925
11341
|
/**
|
|
@@ -11333,7 +11749,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
11333
11749
|
let nextPath;
|
|
11334
11750
|
return store
|
|
11335
11751
|
.iterate({
|
|
11336
|
-
index:
|
|
11752
|
+
index: DbTargetDocumentDocumentTargetsIndex
|
|
11337
11753
|
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
11338
11754
|
if (targetId === 0) {
|
|
11339
11755
|
// if nextToReport is valid, report it, this is a new key so the
|
|
@@ -11375,113 +11791,12 @@ function sentinelKey$1(key) {
|
|
|
11375
11791
|
* store.
|
|
11376
11792
|
*/
|
|
11377
11793
|
function sentinelRow(key, sequenceNumber) {
|
|
11378
|
-
return
|
|
11794
|
+
return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
|
|
11379
11795
|
}
|
|
11380
11796
|
function writeSentinelKey(txn, key) {
|
|
11381
11797
|
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
11382
11798
|
}
|
|
11383
11799
|
|
|
11384
|
-
/**
|
|
11385
|
-
* @license
|
|
11386
|
-
* Copyright 2017 Google LLC
|
|
11387
|
-
*
|
|
11388
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
11389
|
-
* you may not use this file except in compliance with the License.
|
|
11390
|
-
* You may obtain a copy of the License at
|
|
11391
|
-
*
|
|
11392
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11393
|
-
*
|
|
11394
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11395
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11396
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11397
|
-
* See the License for the specific language governing permissions and
|
|
11398
|
-
* limitations under the License.
|
|
11399
|
-
*/
|
|
11400
|
-
/**
|
|
11401
|
-
* A map implementation that uses objects as keys. Objects must have an
|
|
11402
|
-
* associated equals function and must be immutable. Entries in the map are
|
|
11403
|
-
* stored together with the key being produced from the mapKeyFn. This map
|
|
11404
|
-
* automatically handles collisions of keys.
|
|
11405
|
-
*/
|
|
11406
|
-
class ObjectMap {
|
|
11407
|
-
constructor(mapKeyFn, equalsFn) {
|
|
11408
|
-
this.mapKeyFn = mapKeyFn;
|
|
11409
|
-
this.equalsFn = equalsFn;
|
|
11410
|
-
/**
|
|
11411
|
-
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
11412
|
-
* keep a list of entries that we do a linear search through to find an actual
|
|
11413
|
-
* match. Note that collisions should be rare, so we still expect near
|
|
11414
|
-
* constant time lookups in practice.
|
|
11415
|
-
*/
|
|
11416
|
-
this.inner = {};
|
|
11417
|
-
}
|
|
11418
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
11419
|
-
get(key) {
|
|
11420
|
-
const id = this.mapKeyFn(key);
|
|
11421
|
-
const matches = this.inner[id];
|
|
11422
|
-
if (matches === undefined) {
|
|
11423
|
-
return undefined;
|
|
11424
|
-
}
|
|
11425
|
-
for (const [otherKey, value] of matches) {
|
|
11426
|
-
if (this.equalsFn(otherKey, key)) {
|
|
11427
|
-
return value;
|
|
11428
|
-
}
|
|
11429
|
-
}
|
|
11430
|
-
return undefined;
|
|
11431
|
-
}
|
|
11432
|
-
has(key) {
|
|
11433
|
-
return this.get(key) !== undefined;
|
|
11434
|
-
}
|
|
11435
|
-
/** Put this key and value in the map. */
|
|
11436
|
-
set(key, value) {
|
|
11437
|
-
const id = this.mapKeyFn(key);
|
|
11438
|
-
const matches = this.inner[id];
|
|
11439
|
-
if (matches === undefined) {
|
|
11440
|
-
this.inner[id] = [[key, value]];
|
|
11441
|
-
return;
|
|
11442
|
-
}
|
|
11443
|
-
for (let i = 0; i < matches.length; i++) {
|
|
11444
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
11445
|
-
matches[i] = [key, value];
|
|
11446
|
-
return;
|
|
11447
|
-
}
|
|
11448
|
-
}
|
|
11449
|
-
matches.push([key, value]);
|
|
11450
|
-
}
|
|
11451
|
-
/**
|
|
11452
|
-
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
11453
|
-
*/
|
|
11454
|
-
delete(key) {
|
|
11455
|
-
const id = this.mapKeyFn(key);
|
|
11456
|
-
const matches = this.inner[id];
|
|
11457
|
-
if (matches === undefined) {
|
|
11458
|
-
return false;
|
|
11459
|
-
}
|
|
11460
|
-
for (let i = 0; i < matches.length; i++) {
|
|
11461
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
11462
|
-
if (matches.length === 1) {
|
|
11463
|
-
delete this.inner[id];
|
|
11464
|
-
}
|
|
11465
|
-
else {
|
|
11466
|
-
matches.splice(i, 1);
|
|
11467
|
-
}
|
|
11468
|
-
return true;
|
|
11469
|
-
}
|
|
11470
|
-
}
|
|
11471
|
-
return false;
|
|
11472
|
-
}
|
|
11473
|
-
forEach(fn) {
|
|
11474
|
-
forEach(this.inner, (_, entries) => {
|
|
11475
|
-
for (const [k, v] of entries) {
|
|
11476
|
-
fn(k, v);
|
|
11477
|
-
}
|
|
11478
|
-
});
|
|
11479
|
-
}
|
|
11480
|
-
isEmpty() {
|
|
11481
|
-
return isEmpty(this.inner);
|
|
11482
|
-
}
|
|
11483
|
-
}
|
|
11484
|
-
|
|
11485
11800
|
/**
|
|
11486
11801
|
* @license
|
|
11487
11802
|
* Copyright 2017 Google LLC
|
|
@@ -11621,7 +11936,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11621
11936
|
*/
|
|
11622
11937
|
addEntry(transaction, key, doc) {
|
|
11623
11938
|
const documentStore = remoteDocumentsStore(transaction);
|
|
11624
|
-
return documentStore.put(
|
|
11939
|
+
return documentStore.put(doc);
|
|
11625
11940
|
}
|
|
11626
11941
|
/**
|
|
11627
11942
|
* Removes a document from the cache.
|
|
@@ -11629,10 +11944,9 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11629
11944
|
* All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
|
|
11630
11945
|
* returned by `newChangeBuffer()` to ensure proper accounting of metadata.
|
|
11631
11946
|
*/
|
|
11632
|
-
removeEntry(transaction, documentKey) {
|
|
11947
|
+
removeEntry(transaction, documentKey, readTime) {
|
|
11633
11948
|
const store = remoteDocumentsStore(transaction);
|
|
11634
|
-
|
|
11635
|
-
return store.delete(key);
|
|
11949
|
+
return store.delete(dbReadTimeKey(documentKey, readTime));
|
|
11636
11950
|
}
|
|
11637
11951
|
/**
|
|
11638
11952
|
* Updates the current cache size.
|
|
@@ -11647,11 +11961,15 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11647
11961
|
});
|
|
11648
11962
|
}
|
|
11649
11963
|
getEntry(transaction, documentKey) {
|
|
11964
|
+
let doc = MutableDocument.newInvalidDocument(documentKey);
|
|
11650
11965
|
return remoteDocumentsStore(transaction)
|
|
11651
|
-
.
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
})
|
|
11966
|
+
.iterate({
|
|
11967
|
+
index: DbRemoteDocumentDocumentKeyIndex,
|
|
11968
|
+
range: IDBKeyRange.only(dbKey(documentKey))
|
|
11969
|
+
}, (_, dbRemoteDoc) => {
|
|
11970
|
+
doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
|
|
11971
|
+
})
|
|
11972
|
+
.next(() => doc);
|
|
11655
11973
|
}
|
|
11656
11974
|
/**
|
|
11657
11975
|
* Looks up an entry in the cache.
|
|
@@ -11660,15 +11978,21 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11660
11978
|
* @returns The cached document entry and its size.
|
|
11661
11979
|
*/
|
|
11662
11980
|
getSizedEntry(transaction, documentKey) {
|
|
11981
|
+
let result = {
|
|
11982
|
+
size: 0,
|
|
11983
|
+
document: MutableDocument.newInvalidDocument(documentKey)
|
|
11984
|
+
};
|
|
11663
11985
|
return remoteDocumentsStore(transaction)
|
|
11664
|
-
.
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11986
|
+
.iterate({
|
|
11987
|
+
index: DbRemoteDocumentDocumentKeyIndex,
|
|
11988
|
+
range: IDBKeyRange.only(dbKey(documentKey))
|
|
11989
|
+
}, (_, dbRemoteDoc) => {
|
|
11990
|
+
result = {
|
|
11991
|
+
document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
|
|
11669
11992
|
size: dbDocumentSize(dbRemoteDoc)
|
|
11670
11993
|
};
|
|
11671
|
-
})
|
|
11994
|
+
})
|
|
11995
|
+
.next(() => result);
|
|
11672
11996
|
}
|
|
11673
11997
|
getEntries(transaction, documentKeys) {
|
|
11674
11998
|
let results = mutableDocumentMap();
|
|
@@ -11699,14 +12023,20 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11699
12023
|
if (documentKeys.isEmpty()) {
|
|
11700
12024
|
return PersistencePromise.resolve();
|
|
11701
12025
|
}
|
|
11702
|
-
|
|
11703
|
-
|
|
12026
|
+
let sortedKeys = new SortedSet(dbKeyComparator);
|
|
12027
|
+
documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
|
|
12028
|
+
const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
|
|
12029
|
+
const keyIter = sortedKeys.getIterator();
|
|
11704
12030
|
let nextKey = keyIter.getNext();
|
|
11705
12031
|
return remoteDocumentsStore(transaction)
|
|
11706
|
-
.iterate({ range }, (
|
|
11707
|
-
const potentialKey = DocumentKey.fromSegments(
|
|
12032
|
+
.iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
|
|
12033
|
+
const potentialKey = DocumentKey.fromSegments([
|
|
12034
|
+
...dbRemoteDoc.prefixPath,
|
|
12035
|
+
dbRemoteDoc.collectionGroup,
|
|
12036
|
+
dbRemoteDoc.documentId
|
|
12037
|
+
]);
|
|
11708
12038
|
// Go through keys not found in cache.
|
|
11709
|
-
while (nextKey &&
|
|
12039
|
+
while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
|
|
11710
12040
|
callback(nextKey, null);
|
|
11711
12041
|
nextKey = keyIter.getNext();
|
|
11712
12042
|
}
|
|
@@ -11717,7 +12047,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11717
12047
|
}
|
|
11718
12048
|
// Skip to the next key (if there is one).
|
|
11719
12049
|
if (nextKey) {
|
|
11720
|
-
control.skip(nextKey
|
|
12050
|
+
control.skip(dbKey(nextKey));
|
|
11721
12051
|
}
|
|
11722
12052
|
else {
|
|
11723
12053
|
control.done();
|
|
@@ -11732,41 +12062,44 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11732
12062
|
}
|
|
11733
12063
|
});
|
|
11734
12064
|
}
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
11751
|
-
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
11752
|
-
/* open= */ true);
|
|
11753
|
-
iterationOptions.index = DbRemoteDocument.collectionReadTimeIndex;
|
|
11754
|
-
}
|
|
12065
|
+
getAllFromCollection(transaction, collection, offset) {
|
|
12066
|
+
const startKey = [
|
|
12067
|
+
collection.popLast().toArray(),
|
|
12068
|
+
collection.lastSegment(),
|
|
12069
|
+
toDbTimestampKey(offset.readTime),
|
|
12070
|
+
offset.documentKey.path.isEmpty()
|
|
12071
|
+
? ''
|
|
12072
|
+
: offset.documentKey.path.lastSegment()
|
|
12073
|
+
];
|
|
12074
|
+
const endKey = [
|
|
12075
|
+
collection.popLast().toArray(),
|
|
12076
|
+
collection.lastSegment(),
|
|
12077
|
+
[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
12078
|
+
''
|
|
12079
|
+
];
|
|
11755
12080
|
return remoteDocumentsStore(transaction)
|
|
11756
|
-
.
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
// more than one segment longer than the query path.
|
|
11762
|
-
if (key.length !== immediateChildrenPathLength) {
|
|
11763
|
-
return;
|
|
11764
|
-
}
|
|
11765
|
-
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
|
|
11766
|
-
if (collection.isPrefixOf(document.key.path)) {
|
|
12081
|
+
.loadAll(IDBKeyRange.bound(startKey, endKey, true))
|
|
12082
|
+
.next(dbRemoteDocs => {
|
|
12083
|
+
let results = mutableDocumentMap();
|
|
12084
|
+
for (const dbRemoteDoc of dbRemoteDocs) {
|
|
12085
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
|
|
11767
12086
|
results = results.insert(document.key, document);
|
|
11768
12087
|
}
|
|
11769
|
-
|
|
12088
|
+
return results;
|
|
12089
|
+
});
|
|
12090
|
+
}
|
|
12091
|
+
getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
|
|
12092
|
+
let results = mutableDocumentMap();
|
|
12093
|
+
const startKey = dbCollectionGroupKey(collectionGroup, offset);
|
|
12094
|
+
const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
|
|
12095
|
+
return remoteDocumentsStore(transaction)
|
|
12096
|
+
.iterate({
|
|
12097
|
+
index: DbRemoteDocumentCollectionGroupIndex,
|
|
12098
|
+
range: IDBKeyRange.bound(startKey, endKey, true)
|
|
12099
|
+
}, (_, dbRemoteDoc, control) => {
|
|
12100
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
|
|
12101
|
+
results = results.insert(document.key, document);
|
|
12102
|
+
if (results.size === limit) {
|
|
11770
12103
|
control.done();
|
|
11771
12104
|
}
|
|
11772
12105
|
})
|
|
@@ -11780,14 +12113,14 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11780
12113
|
}
|
|
11781
12114
|
getMetadata(txn) {
|
|
11782
12115
|
return documentGlobalStore(txn)
|
|
11783
|
-
.get(
|
|
12116
|
+
.get(DbRemoteDocumentGlobalKey)
|
|
11784
12117
|
.next(metadata => {
|
|
11785
12118
|
hardAssert(!!metadata);
|
|
11786
12119
|
return metadata;
|
|
11787
12120
|
});
|
|
11788
12121
|
}
|
|
11789
12122
|
setMetadata(txn, metadata) {
|
|
11790
|
-
return documentGlobalStore(txn).put(
|
|
12123
|
+
return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
|
|
11791
12124
|
}
|
|
11792
12125
|
/**
|
|
11793
12126
|
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
@@ -11810,50 +12143,6 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11810
12143
|
function newIndexedDbRemoteDocumentCache(serializer) {
|
|
11811
12144
|
return new IndexedDbRemoteDocumentCacheImpl(serializer);
|
|
11812
12145
|
}
|
|
11813
|
-
/**
|
|
11814
|
-
* Returns the set of documents that have changed since the specified read
|
|
11815
|
-
* time.
|
|
11816
|
-
*/
|
|
11817
|
-
// PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11818
|
-
function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
|
|
11819
|
-
const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
|
|
11820
|
-
let changedDocs = mutableDocumentMap();
|
|
11821
|
-
let lastReadTime = toDbTimestampKey(sinceReadTime);
|
|
11822
|
-
const documentsStore = remoteDocumentsStore(transaction);
|
|
11823
|
-
const range = IDBKeyRange.lowerBound(lastReadTime, true);
|
|
11824
|
-
return documentsStore
|
|
11825
|
-
.iterate({ index: DbRemoteDocument.readTimeIndex, range }, (_, dbRemoteDoc) => {
|
|
11826
|
-
// Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
|
|
11827
|
-
// the documents directly since we want to keep sentinel deletes.
|
|
11828
|
-
const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
|
|
11829
|
-
changedDocs = changedDocs.insert(doc.key, doc);
|
|
11830
|
-
lastReadTime = dbRemoteDoc.readTime;
|
|
11831
|
-
})
|
|
11832
|
-
.next(() => {
|
|
11833
|
-
return {
|
|
11834
|
-
changedDocs,
|
|
11835
|
-
readTime: fromDbTimestampKey(lastReadTime)
|
|
11836
|
-
};
|
|
11837
|
-
});
|
|
11838
|
-
}
|
|
11839
|
-
/**
|
|
11840
|
-
* Returns the read time of the most recently read document in the cache, or
|
|
11841
|
-
* SnapshotVersion.min() if not available.
|
|
11842
|
-
*/
|
|
11843
|
-
// PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11844
|
-
function remoteDocumentCacheGetLastReadTime(transaction) {
|
|
11845
|
-
const documentsStore = remoteDocumentsStore(transaction);
|
|
11846
|
-
// If there are no existing entries, we return SnapshotVersion.min().
|
|
11847
|
-
let readTime = SnapshotVersion.min();
|
|
11848
|
-
return documentsStore
|
|
11849
|
-
.iterate({ index: DbRemoteDocument.readTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
|
|
11850
|
-
if (dbRemoteDoc.readTime) {
|
|
11851
|
-
readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
|
|
11852
|
-
}
|
|
11853
|
-
control.done();
|
|
11854
|
-
})
|
|
11855
|
-
.next(() => readTime);
|
|
11856
|
-
}
|
|
11857
12146
|
/**
|
|
11858
12147
|
* Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
|
|
11859
12148
|
*
|
|
@@ -11871,24 +12160,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11871
12160
|
super();
|
|
11872
12161
|
this.documentCache = documentCache;
|
|
11873
12162
|
this.trackRemovals = trackRemovals;
|
|
11874
|
-
// A map of document sizes prior to applying the changes in
|
|
11875
|
-
this
|
|
12163
|
+
// A map of document sizes and read times prior to applying the changes in
|
|
12164
|
+
// this buffer.
|
|
12165
|
+
this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
11876
12166
|
}
|
|
11877
12167
|
applyChanges(transaction) {
|
|
11878
12168
|
const promises = [];
|
|
11879
12169
|
let sizeDelta = 0;
|
|
11880
12170
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
11881
12171
|
this.changes.forEach((key, documentChange) => {
|
|
11882
|
-
const
|
|
12172
|
+
const previousDoc = this.documentStates.get(key);
|
|
12173
|
+
promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
|
|
11883
12174
|
if (documentChange.isValidDocument()) {
|
|
11884
12175
|
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
|
|
11885
12176
|
collectionParents = collectionParents.add(key.path.popLast());
|
|
11886
12177
|
const size = dbDocumentSize(doc);
|
|
11887
|
-
sizeDelta += size -
|
|
12178
|
+
sizeDelta += size - previousDoc.size;
|
|
11888
12179
|
promises.push(this.documentCache.addEntry(transaction, key, doc));
|
|
11889
12180
|
}
|
|
11890
12181
|
else {
|
|
11891
|
-
sizeDelta -=
|
|
12182
|
+
sizeDelta -= previousDoc.size;
|
|
11892
12183
|
if (this.trackRemovals) {
|
|
11893
12184
|
// In order to track removals, we store a "sentinel delete" in the
|
|
11894
12185
|
// RemoteDocumentCache. This entry is represented by a NoDocument
|
|
@@ -11897,9 +12188,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11897
12188
|
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
|
|
11898
12189
|
promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
|
|
11899
12190
|
}
|
|
11900
|
-
else {
|
|
11901
|
-
promises.push(this.documentCache.removeEntry(transaction, key));
|
|
11902
|
-
}
|
|
11903
12191
|
}
|
|
11904
12192
|
});
|
|
11905
12193
|
collectionParents.forEach(parent => {
|
|
@@ -11913,7 +12201,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11913
12201
|
return this.documentCache
|
|
11914
12202
|
.getSizedEntry(transaction, documentKey)
|
|
11915
12203
|
.next(getResult => {
|
|
11916
|
-
this.
|
|
12204
|
+
this.documentStates.set(documentKey, {
|
|
12205
|
+
size: getResult.size,
|
|
12206
|
+
readTime: getResult.document.readTime
|
|
12207
|
+
});
|
|
11917
12208
|
return getResult.document;
|
|
11918
12209
|
});
|
|
11919
12210
|
}
|
|
@@ -11927,24 +12218,95 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11927
12218
|
// keys to `DocumentSizeEntry`s. This is to allow returning the
|
|
11928
12219
|
// `MutableDocumentMap` directly, without a conversion.
|
|
11929
12220
|
sizeMap.forEach((documentKey, size) => {
|
|
11930
|
-
this.
|
|
12221
|
+
this.documentStates.set(documentKey, {
|
|
12222
|
+
size,
|
|
12223
|
+
readTime: documents.get(documentKey).readTime
|
|
12224
|
+
});
|
|
11931
12225
|
});
|
|
11932
12226
|
return documents;
|
|
11933
12227
|
});
|
|
11934
12228
|
}
|
|
11935
12229
|
}
|
|
11936
12230
|
function documentGlobalStore(txn) {
|
|
11937
|
-
return getStore(txn,
|
|
12231
|
+
return getStore(txn, DbRemoteDocumentGlobalStore);
|
|
11938
12232
|
}
|
|
11939
12233
|
/**
|
|
11940
12234
|
* Helper to get a typed SimpleDbStore for the remoteDocuments object store.
|
|
11941
12235
|
*/
|
|
11942
12236
|
function remoteDocumentsStore(txn) {
|
|
11943
|
-
return getStore(txn,
|
|
12237
|
+
return getStore(txn, DbRemoteDocumentStore);
|
|
12238
|
+
}
|
|
12239
|
+
/**
|
|
12240
|
+
* Returns a key that can be used for document lookups on the
|
|
12241
|
+
* `DbRemoteDocumentDocumentKeyIndex` index.
|
|
12242
|
+
*/
|
|
12243
|
+
function dbKey(documentKey) {
|
|
12244
|
+
const path = documentKey.path.toArray();
|
|
12245
|
+
return [
|
|
12246
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12247
|
+
/* collection id */ path[path.length - 2],
|
|
12248
|
+
/* document id */ path[path.length - 1]
|
|
12249
|
+
];
|
|
12250
|
+
}
|
|
12251
|
+
/**
|
|
12252
|
+
* Returns a key that can be used for document lookups via the primary key of
|
|
12253
|
+
* the DbRemoteDocument object store.
|
|
12254
|
+
*/
|
|
12255
|
+
function dbReadTimeKey(documentKey, readTime) {
|
|
12256
|
+
const path = documentKey.path.toArray();
|
|
12257
|
+
return [
|
|
12258
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12259
|
+
/* collection id */ path[path.length - 2],
|
|
12260
|
+
toDbTimestampKey(readTime),
|
|
12261
|
+
/* document id */ path[path.length - 1]
|
|
12262
|
+
];
|
|
12263
|
+
}
|
|
12264
|
+
/**
|
|
12265
|
+
* Returns a key that can be used for document lookups on the
|
|
12266
|
+
* `DbRemoteDocumentDocumentCollectionGroupIndex` index.
|
|
12267
|
+
*/
|
|
12268
|
+
function dbCollectionGroupKey(collectionGroup, offset) {
|
|
12269
|
+
const path = offset.documentKey.path.toArray();
|
|
12270
|
+
return [
|
|
12271
|
+
/* collection id */ collectionGroup,
|
|
12272
|
+
toDbTimestampKey(offset.readTime),
|
|
12273
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12274
|
+
/* document id */ path.length > 0 ? path[path.length - 1] : ''
|
|
12275
|
+
];
|
|
11944
12276
|
}
|
|
11945
|
-
|
|
11946
|
-
|
|
12277
|
+
/**
|
|
12278
|
+
* Comparator that compares document keys according to the primary key sorting
|
|
12279
|
+
* used by the `DbRemoteDocumentDocument` store (by collection path and then
|
|
12280
|
+
* document ID).
|
|
12281
|
+
*/
|
|
12282
|
+
function dbKeyComparator(l, r) {
|
|
12283
|
+
const cmp = l.path.length - r.path.length;
|
|
12284
|
+
return cmp !== 0 ? cmp : DocumentKey.comparator(l, r);
|
|
11947
12285
|
}
|
|
12286
|
+
/**
|
|
12287
|
+
* Schema Version for the Web client:
|
|
12288
|
+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
12289
|
+
* Document Cache
|
|
12290
|
+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
12291
|
+
* longer required because migration 3 unconditionally clears it.
|
|
12292
|
+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
12293
|
+
* to limbo resolution. Addresses
|
|
12294
|
+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
12295
|
+
* 4. Multi-Tab Support.
|
|
12296
|
+
* 5. Removal of held write acks.
|
|
12297
|
+
* 6. Create document global for tracking document cache size.
|
|
12298
|
+
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
12299
|
+
* 8. Add collection-parent index for Collection Group queries.
|
|
12300
|
+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
12301
|
+
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
12302
|
+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
12303
|
+
* 11. Add bundles and named_queries for bundle support.
|
|
12304
|
+
* 12. Add document overlays.
|
|
12305
|
+
* 13. Rewrite the keys of the remote document cache to allow for efficient
|
|
12306
|
+
* document lookup via `getAll()`.
|
|
12307
|
+
* 14. Add indexing support.
|
|
12308
|
+
*/
|
|
12309
|
+
const SCHEMA_VERSION = 13;
|
|
11948
12310
|
|
|
11949
12311
|
/**
|
|
11950
12312
|
* @license
|
|
@@ -11980,7 +12342,7 @@ class SchemaConverter {
|
|
|
11980
12342
|
createPrimaryClientStore(db);
|
|
11981
12343
|
createMutationQueue(db);
|
|
11982
12344
|
createQueryCache(db);
|
|
11983
|
-
|
|
12345
|
+
createLegacyRemoteDocumentCache(db);
|
|
11984
12346
|
}
|
|
11985
12347
|
// Migration 2 to populate the targetGlobal object no longer needed since
|
|
11986
12348
|
// migration 3 unconditionally clears it.
|
|
@@ -12029,7 +12391,8 @@ class SchemaConverter {
|
|
|
12029
12391
|
// to the DbRemoteDocument object store itself. Since the previous change
|
|
12030
12392
|
// log only contained transient data, we can drop its object store.
|
|
12031
12393
|
dropRemoteDocumentChangesStore(db);
|
|
12032
|
-
|
|
12394
|
+
// Note: Schema version 9 used to create a read time index for the
|
|
12395
|
+
// RemoteDocumentCache. This is now done with schema version 13.
|
|
12033
12396
|
});
|
|
12034
12397
|
}
|
|
12035
12398
|
if (fromVersion < 10 && toVersion >= 10) {
|
|
@@ -12047,6 +12410,12 @@ class SchemaConverter {
|
|
|
12047
12410
|
});
|
|
12048
12411
|
}
|
|
12049
12412
|
if (fromVersion < 13 && toVersion >= 13) {
|
|
12413
|
+
p = p
|
|
12414
|
+
.next(() => createRemoteDocumentCache(db))
|
|
12415
|
+
.next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
|
|
12416
|
+
.next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
|
|
12417
|
+
}
|
|
12418
|
+
if (fromVersion < 14 && toVersion >= 14) {
|
|
12050
12419
|
p = p.next(() => {
|
|
12051
12420
|
createFieldIndex(db);
|
|
12052
12421
|
});
|
|
@@ -12054,27 +12423,27 @@ class SchemaConverter {
|
|
|
12054
12423
|
return p;
|
|
12055
12424
|
}
|
|
12056
12425
|
addDocumentGlobal(txn) {
|
|
12057
|
-
let
|
|
12426
|
+
let byteSize = 0;
|
|
12058
12427
|
return txn
|
|
12059
|
-
.store(
|
|
12428
|
+
.store(DbRemoteDocumentStore$1)
|
|
12060
12429
|
.iterate((_, doc) => {
|
|
12061
|
-
|
|
12430
|
+
byteSize += dbDocumentSize(doc);
|
|
12062
12431
|
})
|
|
12063
12432
|
.next(() => {
|
|
12064
|
-
const metadata =
|
|
12433
|
+
const metadata = { byteSize };
|
|
12065
12434
|
return txn
|
|
12066
|
-
.store(
|
|
12067
|
-
.put(
|
|
12435
|
+
.store(DbRemoteDocumentGlobalStore)
|
|
12436
|
+
.put(DbRemoteDocumentGlobalKey, metadata);
|
|
12068
12437
|
});
|
|
12069
12438
|
}
|
|
12070
12439
|
removeAcknowledgedMutations(txn) {
|
|
12071
|
-
const queuesStore = txn.store(
|
|
12072
|
-
const mutationsStore = txn.store(
|
|
12440
|
+
const queuesStore = txn.store(DbMutationQueueStore);
|
|
12441
|
+
const mutationsStore = txn.store(DbMutationBatchStore);
|
|
12073
12442
|
return queuesStore.loadAll().next(queues => {
|
|
12074
12443
|
return PersistencePromise.forEach(queues, (queue) => {
|
|
12075
12444
|
const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
|
|
12076
12445
|
return mutationsStore
|
|
12077
|
-
.loadAll(
|
|
12446
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
12078
12447
|
.next(dbBatches => {
|
|
12079
12448
|
return PersistencePromise.forEach(dbBatches, (dbBatch) => {
|
|
12080
12449
|
hardAssert(dbBatch.userId === queue.userId);
|
|
@@ -12090,12 +12459,16 @@ class SchemaConverter {
|
|
|
12090
12459
|
* with a sequence number. Missing rows are given the most recently used sequence number.
|
|
12091
12460
|
*/
|
|
12092
12461
|
ensureSequenceNumbers(txn) {
|
|
12093
|
-
const documentTargetStore = txn.store(
|
|
12094
|
-
const documentsStore = txn.store(
|
|
12095
|
-
const globalTargetStore = txn.store(
|
|
12096
|
-
return globalTargetStore.get(
|
|
12462
|
+
const documentTargetStore = txn.store(DbTargetDocumentStore);
|
|
12463
|
+
const documentsStore = txn.store(DbRemoteDocumentStore$1);
|
|
12464
|
+
const globalTargetStore = txn.store(DbTargetGlobalStore);
|
|
12465
|
+
return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
|
|
12097
12466
|
const writeSentinelKey = (path) => {
|
|
12098
|
-
return documentTargetStore.put(
|
|
12467
|
+
return documentTargetStore.put({
|
|
12468
|
+
targetId: 0,
|
|
12469
|
+
path: encodeResourcePath(path),
|
|
12470
|
+
sequenceNumber: metadata.highestListenSequenceNumber
|
|
12471
|
+
});
|
|
12099
12472
|
};
|
|
12100
12473
|
const promises = [];
|
|
12101
12474
|
return documentsStore
|
|
@@ -12116,10 +12489,10 @@ class SchemaConverter {
|
|
|
12116
12489
|
}
|
|
12117
12490
|
createCollectionParentIndex(db, txn) {
|
|
12118
12491
|
// Create the index.
|
|
12119
|
-
db.createObjectStore(
|
|
12120
|
-
keyPath:
|
|
12492
|
+
db.createObjectStore(DbCollectionParentStore, {
|
|
12493
|
+
keyPath: DbCollectionParentKeyPath
|
|
12121
12494
|
});
|
|
12122
|
-
const collectionParentsStore = txn.store(
|
|
12495
|
+
const collectionParentsStore = txn.store(DbCollectionParentStore);
|
|
12123
12496
|
// Helper to add an index entry iff we haven't already written it.
|
|
12124
12497
|
const cache = new MemoryCollectionParentIndex();
|
|
12125
12498
|
const addEntry = (collectionPath) => {
|
|
@@ -12134,7 +12507,7 @@ class SchemaConverter {
|
|
|
12134
12507
|
};
|
|
12135
12508
|
// Index existing remote documents.
|
|
12136
12509
|
return txn
|
|
12137
|
-
.store(
|
|
12510
|
+
.store(DbRemoteDocumentStore$1)
|
|
12138
12511
|
.iterate({ keysOnly: true }, (pathSegments, _) => {
|
|
12139
12512
|
const path = new ResourcePath(pathSegments);
|
|
12140
12513
|
return addEntry(path.popLast());
|
|
@@ -12142,7 +12515,7 @@ class SchemaConverter {
|
|
|
12142
12515
|
.next(() => {
|
|
12143
12516
|
// Index existing mutations.
|
|
12144
12517
|
return txn
|
|
12145
|
-
.store(
|
|
12518
|
+
.store(DbDocumentMutationStore)
|
|
12146
12519
|
.iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
|
|
12147
12520
|
const path = decodeResourcePath(encodedPath);
|
|
12148
12521
|
return addEntry(path.popLast());
|
|
@@ -12150,71 +12523,99 @@ class SchemaConverter {
|
|
|
12150
12523
|
});
|
|
12151
12524
|
}
|
|
12152
12525
|
rewriteCanonicalIds(txn) {
|
|
12153
|
-
const targetStore = txn.store(
|
|
12526
|
+
const targetStore = txn.store(DbTargetStore);
|
|
12154
12527
|
return targetStore.iterate((key, originalDbTarget) => {
|
|
12155
12528
|
const originalTargetData = fromDbTarget(originalDbTarget);
|
|
12156
12529
|
const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
|
|
12157
12530
|
return targetStore.put(updatedDbTarget);
|
|
12158
12531
|
});
|
|
12159
12532
|
}
|
|
12533
|
+
rewriteRemoteDocumentCache(db, transaction) {
|
|
12534
|
+
const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
|
|
12535
|
+
const writes = [];
|
|
12536
|
+
return legacyRemoteDocumentStore
|
|
12537
|
+
.iterate((_, legacyDocument) => {
|
|
12538
|
+
const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
|
|
12539
|
+
const path = extractKey(legacyDocument).path.toArray();
|
|
12540
|
+
const dbRemoteDocument = {
|
|
12541
|
+
prefixPath: path.slice(0, path.length - 2),
|
|
12542
|
+
collectionGroup: path[path.length - 2],
|
|
12543
|
+
documentId: path[path.length - 1],
|
|
12544
|
+
readTime: legacyDocument.readTime || [0, 0],
|
|
12545
|
+
unknownDocument: legacyDocument.unknownDocument,
|
|
12546
|
+
noDocument: legacyDocument.noDocument,
|
|
12547
|
+
document: legacyDocument.document,
|
|
12548
|
+
hasCommittedMutations: !!legacyDocument.hasCommittedMutations
|
|
12549
|
+
};
|
|
12550
|
+
writes.push(remoteDocumentStore.put(dbRemoteDocument));
|
|
12551
|
+
})
|
|
12552
|
+
.next(() => PersistencePromise.waitFor(writes));
|
|
12553
|
+
}
|
|
12160
12554
|
}
|
|
12161
12555
|
function sentinelKey(path) {
|
|
12162
12556
|
return [0, encodeResourcePath(path)];
|
|
12163
12557
|
}
|
|
12164
12558
|
function createPrimaryClientStore(db) {
|
|
12165
|
-
db.createObjectStore(
|
|
12559
|
+
db.createObjectStore(DbPrimaryClientStore);
|
|
12166
12560
|
}
|
|
12167
12561
|
function createMutationQueue(db) {
|
|
12168
|
-
db.createObjectStore(
|
|
12169
|
-
keyPath:
|
|
12562
|
+
db.createObjectStore(DbMutationQueueStore, {
|
|
12563
|
+
keyPath: DbMutationQueueKeyPath
|
|
12170
12564
|
});
|
|
12171
|
-
const mutationBatchesStore = db.createObjectStore(
|
|
12172
|
-
keyPath:
|
|
12565
|
+
const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12566
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12173
12567
|
autoIncrement: true
|
|
12174
12568
|
});
|
|
12175
|
-
mutationBatchesStore.createIndex(
|
|
12176
|
-
db.createObjectStore(
|
|
12569
|
+
mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12570
|
+
db.createObjectStore(DbDocumentMutationStore);
|
|
12177
12571
|
}
|
|
12178
12572
|
/**
|
|
12179
12573
|
* Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
|
|
12180
12574
|
* and rewrites all data.
|
|
12181
12575
|
*/
|
|
12182
12576
|
function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
|
|
12183
|
-
const v1MutationsStore = txn.store(
|
|
12577
|
+
const v1MutationsStore = txn.store(DbMutationBatchStore);
|
|
12184
12578
|
return v1MutationsStore.loadAll().next(existingMutations => {
|
|
12185
|
-
db.deleteObjectStore(
|
|
12186
|
-
const mutationsStore = db.createObjectStore(
|
|
12187
|
-
keyPath:
|
|
12579
|
+
db.deleteObjectStore(DbMutationBatchStore);
|
|
12580
|
+
const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12581
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12188
12582
|
autoIncrement: true
|
|
12189
12583
|
});
|
|
12190
|
-
mutationsStore.createIndex(
|
|
12191
|
-
const v3MutationsStore = txn.store(
|
|
12584
|
+
mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12585
|
+
const v3MutationsStore = txn.store(DbMutationBatchStore);
|
|
12192
12586
|
const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
|
|
12193
12587
|
return PersistencePromise.waitFor(writeAll);
|
|
12194
12588
|
});
|
|
12195
12589
|
}
|
|
12590
|
+
function createLegacyRemoteDocumentCache(db) {
|
|
12591
|
+
db.createObjectStore(DbRemoteDocumentStore$1);
|
|
12592
|
+
}
|
|
12196
12593
|
function createRemoteDocumentCache(db) {
|
|
12197
|
-
db.createObjectStore(
|
|
12594
|
+
const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
|
|
12595
|
+
keyPath: DbRemoteDocumentKeyPath
|
|
12596
|
+
});
|
|
12597
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
|
|
12598
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
|
|
12198
12599
|
}
|
|
12199
12600
|
function createDocumentGlobalStore(db) {
|
|
12200
|
-
db.createObjectStore(
|
|
12601
|
+
db.createObjectStore(DbRemoteDocumentGlobalStore);
|
|
12201
12602
|
}
|
|
12202
12603
|
function createQueryCache(db) {
|
|
12203
|
-
const targetDocumentsStore = db.createObjectStore(
|
|
12204
|
-
keyPath:
|
|
12604
|
+
const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
|
|
12605
|
+
keyPath: DbTargetDocumentKeyPath
|
|
12205
12606
|
});
|
|
12206
|
-
targetDocumentsStore.createIndex(
|
|
12207
|
-
const targetStore = db.createObjectStore(
|
|
12208
|
-
keyPath:
|
|
12607
|
+
targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
|
|
12608
|
+
const targetStore = db.createObjectStore(DbTargetStore, {
|
|
12609
|
+
keyPath: DbTargetKeyPath
|
|
12209
12610
|
});
|
|
12210
12611
|
// NOTE: This is unique only because the TargetId is the suffix.
|
|
12211
|
-
targetStore.createIndex(
|
|
12212
|
-
db.createObjectStore(
|
|
12612
|
+
targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
|
|
12613
|
+
db.createObjectStore(DbTargetGlobalStore);
|
|
12213
12614
|
}
|
|
12214
12615
|
function dropQueryCache(db) {
|
|
12215
|
-
db.deleteObjectStore(
|
|
12216
|
-
db.deleteObjectStore(
|
|
12217
|
-
db.deleteObjectStore(
|
|
12616
|
+
db.deleteObjectStore(DbTargetDocumentStore);
|
|
12617
|
+
db.deleteObjectStore(DbTargetStore);
|
|
12618
|
+
db.deleteObjectStore(DbTargetGlobalStore);
|
|
12218
12619
|
}
|
|
12219
12620
|
function dropRemoteDocumentChangesStore(db) {
|
|
12220
12621
|
if (db.objectStoreNames.contains('remoteDocumentChanges')) {
|
|
@@ -12227,58 +12628,65 @@ function dropRemoteDocumentChangesStore(db) {
|
|
|
12227
12628
|
* @param txn - The version upgrade transaction for indexeddb
|
|
12228
12629
|
*/
|
|
12229
12630
|
function writeEmptyTargetGlobalEntry(txn) {
|
|
12230
|
-
const globalStore = txn.store(
|
|
12231
|
-
const metadata =
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
}
|
|
12237
|
-
|
|
12238
|
-
* Creates indices on the RemoteDocuments store used for both multi-tab
|
|
12239
|
-
* and Index-Free queries.
|
|
12240
|
-
*/
|
|
12241
|
-
function createRemoteDocumentReadTimeIndex(txn) {
|
|
12242
|
-
const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
|
|
12243
|
-
remoteDocumentStore.createIndex(DbRemoteDocument.readTimeIndex, DbRemoteDocument.readTimeIndexPath, { unique: false });
|
|
12244
|
-
remoteDocumentStore.createIndex(DbRemoteDocument.collectionReadTimeIndex, DbRemoteDocument.collectionReadTimeIndexPath, { unique: false });
|
|
12631
|
+
const globalStore = txn.store(DbTargetGlobalStore);
|
|
12632
|
+
const metadata = {
|
|
12633
|
+
highestTargetId: 0,
|
|
12634
|
+
highestListenSequenceNumber: 0,
|
|
12635
|
+
lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
|
|
12636
|
+
targetCount: 0
|
|
12637
|
+
};
|
|
12638
|
+
return globalStore.put(DbTargetGlobalKey, metadata);
|
|
12245
12639
|
}
|
|
12246
12640
|
function createClientMetadataStore(db) {
|
|
12247
|
-
db.createObjectStore(
|
|
12248
|
-
keyPath:
|
|
12641
|
+
db.createObjectStore(DbClientMetadataStore, {
|
|
12642
|
+
keyPath: DbClientMetadataKeyPath
|
|
12249
12643
|
});
|
|
12250
12644
|
}
|
|
12251
12645
|
function createBundlesStore(db) {
|
|
12252
|
-
db.createObjectStore(
|
|
12253
|
-
keyPath:
|
|
12646
|
+
db.createObjectStore(DbBundleStore, {
|
|
12647
|
+
keyPath: DbBundleKeyPath
|
|
12254
12648
|
});
|
|
12255
12649
|
}
|
|
12256
12650
|
function createNamedQueriesStore(db) {
|
|
12257
|
-
db.createObjectStore(
|
|
12258
|
-
keyPath:
|
|
12651
|
+
db.createObjectStore(DbNamedQueryStore, {
|
|
12652
|
+
keyPath: DbNamedQueryKeyPath
|
|
12259
12653
|
});
|
|
12260
12654
|
}
|
|
12261
12655
|
function createFieldIndex(db) {
|
|
12262
|
-
const
|
|
12263
|
-
keyPath:
|
|
12656
|
+
const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
|
|
12657
|
+
keyPath: DbIndexConfigurationKeyPath,
|
|
12264
12658
|
autoIncrement: true
|
|
12265
12659
|
});
|
|
12266
|
-
|
|
12267
|
-
const indexStateStore = db.createObjectStore(
|
|
12268
|
-
keyPath:
|
|
12660
|
+
indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
|
|
12661
|
+
const indexStateStore = db.createObjectStore(DbIndexStateStore, {
|
|
12662
|
+
keyPath: DbIndexStateKeyPath
|
|
12269
12663
|
});
|
|
12270
|
-
indexStateStore.createIndex(
|
|
12271
|
-
const indexEntryStore = db.createObjectStore(
|
|
12272
|
-
keyPath:
|
|
12664
|
+
indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
|
|
12665
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
|
|
12666
|
+
keyPath: DbIndexEntryKeyPath
|
|
12273
12667
|
});
|
|
12274
|
-
indexEntryStore.createIndex(
|
|
12668
|
+
indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
|
|
12275
12669
|
}
|
|
12276
12670
|
function createDocumentOverlayStore(db) {
|
|
12277
|
-
const documentOverlayStore = db.createObjectStore(
|
|
12278
|
-
keyPath:
|
|
12671
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
|
|
12672
|
+
keyPath: DbDocumentOverlayKeyPath
|
|
12279
12673
|
});
|
|
12280
|
-
documentOverlayStore.createIndex(
|
|
12281
|
-
documentOverlayStore.createIndex(
|
|
12674
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
|
|
12675
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
|
|
12676
|
+
}
|
|
12677
|
+
function extractKey(remoteDoc) {
|
|
12678
|
+
if (remoteDoc.document) {
|
|
12679
|
+
return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
|
|
12680
|
+
}
|
|
12681
|
+
else if (remoteDoc.noDocument) {
|
|
12682
|
+
return DocumentKey.fromSegments(remoteDoc.noDocument.path);
|
|
12683
|
+
}
|
|
12684
|
+
else if (remoteDoc.unknownDocument) {
|
|
12685
|
+
return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
|
|
12686
|
+
}
|
|
12687
|
+
else {
|
|
12688
|
+
return fail();
|
|
12689
|
+
}
|
|
12282
12690
|
}
|
|
12283
12691
|
|
|
12284
12692
|
/**
|
|
@@ -12526,7 +12934,12 @@ class IndexedDbPersistence {
|
|
|
12526
12934
|
return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
|
|
12527
12935
|
const metadataStore = clientMetadataStore(txn);
|
|
12528
12936
|
return metadataStore
|
|
12529
|
-
.put(
|
|
12937
|
+
.put({
|
|
12938
|
+
clientId: this.clientId,
|
|
12939
|
+
updateTimeMs: Date.now(),
|
|
12940
|
+
networkEnabled: this.networkEnabled,
|
|
12941
|
+
inForeground: this.inForeground
|
|
12942
|
+
})
|
|
12530
12943
|
.next(() => {
|
|
12531
12944
|
if (this.isPrimary) {
|
|
12532
12945
|
return this.verifyPrimaryLease(txn).next(success => {
|
|
@@ -12572,7 +12985,7 @@ class IndexedDbPersistence {
|
|
|
12572
12985
|
}
|
|
12573
12986
|
verifyPrimaryLease(txn) {
|
|
12574
12987
|
const store = primaryClientStore(txn);
|
|
12575
|
-
return store.get(
|
|
12988
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12576
12989
|
return PersistencePromise.resolve(this.isLocalClient(primaryClient));
|
|
12577
12990
|
});
|
|
12578
12991
|
}
|
|
@@ -12590,7 +13003,7 @@ class IndexedDbPersistence {
|
|
|
12590
13003
|
!this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
|
|
12591
13004
|
this.lastGarbageCollectionTime = Date.now();
|
|
12592
13005
|
const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
|
|
12593
|
-
const metadataStore = getStore(txn,
|
|
13006
|
+
const metadataStore = getStore(txn, DbClientMetadataStore);
|
|
12594
13007
|
return metadataStore.loadAll().next(existingClients => {
|
|
12595
13008
|
const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
|
|
12596
13009
|
const inactive = existingClients.filter(client => active.indexOf(client) === -1);
|
|
@@ -12644,7 +13057,7 @@ class IndexedDbPersistence {
|
|
|
12644
13057
|
}
|
|
12645
13058
|
const store = primaryClientStore(txn);
|
|
12646
13059
|
return store
|
|
12647
|
-
.get(
|
|
13060
|
+
.get(DbPrimaryClientKey)
|
|
12648
13061
|
.next(currentPrimary => {
|
|
12649
13062
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12650
13063
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
@@ -12725,7 +13138,7 @@ class IndexedDbPersistence {
|
|
|
12725
13138
|
this.detachWindowUnloadHook();
|
|
12726
13139
|
// Use `SimpleDb.runTransaction` directly to avoid failing if another tab
|
|
12727
13140
|
// has obtained the primary lease.
|
|
12728
|
-
await this.simpleDb.runTransaction('shutdown', 'readwrite', [
|
|
13141
|
+
await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
|
|
12729
13142
|
const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
|
|
12730
13143
|
return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
|
|
12731
13144
|
});
|
|
@@ -12832,7 +13245,7 @@ class IndexedDbPersistence {
|
|
|
12832
13245
|
// be turned off.
|
|
12833
13246
|
verifyAllowTabSynchronization(txn) {
|
|
12834
13247
|
const store = primaryClientStore(txn);
|
|
12835
|
-
return store.get(
|
|
13248
|
+
return store.get(DbPrimaryClientKey).next(currentPrimary => {
|
|
12836
13249
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12837
13250
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
12838
13251
|
!this.isClientZombied(currentPrimary.ownerId);
|
|
@@ -12850,8 +13263,12 @@ class IndexedDbPersistence {
|
|
|
12850
13263
|
* method does not verify that the client is eligible for this lease.
|
|
12851
13264
|
*/
|
|
12852
13265
|
acquireOrExtendPrimaryLease(txn) {
|
|
12853
|
-
const newPrimary =
|
|
12854
|
-
|
|
13266
|
+
const newPrimary = {
|
|
13267
|
+
ownerId: this.clientId,
|
|
13268
|
+
allowTabSynchronization: this.allowTabSynchronization,
|
|
13269
|
+
leaseTimestampMs: Date.now()
|
|
13270
|
+
};
|
|
13271
|
+
return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
|
|
12855
13272
|
}
|
|
12856
13273
|
static isAvailable() {
|
|
12857
13274
|
return SimpleDb.isAvailable();
|
|
@@ -12859,10 +13276,10 @@ class IndexedDbPersistence {
|
|
|
12859
13276
|
/** Checks the primary lease and removes it if we are the current primary. */
|
|
12860
13277
|
releasePrimaryLeaseIfHeld(txn) {
|
|
12861
13278
|
const store = primaryClientStore(txn);
|
|
12862
|
-
return store.get(
|
|
13279
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12863
13280
|
if (this.isLocalClient(primaryClient)) {
|
|
12864
13281
|
logDebug(LOG_TAG$d, 'Releasing primary lease.');
|
|
12865
|
-
return store.delete(
|
|
13282
|
+
return store.delete(DbPrimaryClientKey);
|
|
12866
13283
|
}
|
|
12867
13284
|
else {
|
|
12868
13285
|
return PersistencePromise.resolve();
|
|
@@ -12996,13 +13413,13 @@ class IndexedDbPersistence {
|
|
|
12996
13413
|
* Helper to get a typed SimpleDbStore for the primary client object store.
|
|
12997
13414
|
*/
|
|
12998
13415
|
function primaryClientStore(txn) {
|
|
12999
|
-
return getStore(txn,
|
|
13416
|
+
return getStore(txn, DbPrimaryClientStore);
|
|
13000
13417
|
}
|
|
13001
13418
|
/**
|
|
13002
13419
|
* Helper to get a typed SimpleDbStore for the client metadata object store.
|
|
13003
13420
|
*/
|
|
13004
13421
|
function clientMetadataStore(txn) {
|
|
13005
|
-
return getStore(txn,
|
|
13422
|
+
return getStore(txn, DbClientMetadataStore);
|
|
13006
13423
|
}
|
|
13007
13424
|
/**
|
|
13008
13425
|
* Generates a string used as a prefix when storing data in IndexedDB and
|
|
@@ -13112,18 +13529,17 @@ class LocalDocumentsView {
|
|
|
13112
13529
|
*
|
|
13113
13530
|
* @param transaction - The persistence transaction.
|
|
13114
13531
|
* @param query - The query to match documents against.
|
|
13115
|
-
* @param
|
|
13116
|
-
* documents that have been read since this snapshot version (exclusive).
|
|
13532
|
+
* @param offset - Read time and key to start scanning by (exclusive).
|
|
13117
13533
|
*/
|
|
13118
|
-
getDocumentsMatchingQuery(transaction, query,
|
|
13534
|
+
getDocumentsMatchingQuery(transaction, query, offset) {
|
|
13119
13535
|
if (isDocumentQuery$1(query)) {
|
|
13120
13536
|
return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
|
|
13121
13537
|
}
|
|
13122
13538
|
else if (isCollectionGroupQuery(query)) {
|
|
13123
|
-
return this.getDocumentsMatchingCollectionGroupQuery(transaction, query,
|
|
13539
|
+
return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
|
|
13124
13540
|
}
|
|
13125
13541
|
else {
|
|
13126
|
-
return this.getDocumentsMatchingCollectionQuery(transaction, query,
|
|
13542
|
+
return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
|
|
13127
13543
|
}
|
|
13128
13544
|
}
|
|
13129
13545
|
getDocumentsMatchingDocumentQuery(transaction, docPath) {
|
|
@@ -13136,7 +13552,7 @@ class LocalDocumentsView {
|
|
|
13136
13552
|
return result;
|
|
13137
13553
|
});
|
|
13138
13554
|
}
|
|
13139
|
-
getDocumentsMatchingCollectionGroupQuery(transaction, query,
|
|
13555
|
+
getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
|
|
13140
13556
|
const collectionId = query.collectionGroup;
|
|
13141
13557
|
let results = documentMap();
|
|
13142
13558
|
return this.indexManager
|
|
@@ -13146,7 +13562,7 @@ class LocalDocumentsView {
|
|
|
13146
13562
|
// collectionId and aggregate the results.
|
|
13147
13563
|
return PersistencePromise.forEach(parents, (parent) => {
|
|
13148
13564
|
const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
|
|
13149
|
-
return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery,
|
|
13565
|
+
return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
|
|
13150
13566
|
r.forEach((key, doc) => {
|
|
13151
13567
|
results = results.insert(key, doc);
|
|
13152
13568
|
});
|
|
@@ -13154,11 +13570,11 @@ class LocalDocumentsView {
|
|
|
13154
13570
|
}).next(() => results);
|
|
13155
13571
|
});
|
|
13156
13572
|
}
|
|
13157
|
-
getDocumentsMatchingCollectionQuery(transaction, query,
|
|
13573
|
+
getDocumentsMatchingCollectionQuery(transaction, query, offset) {
|
|
13158
13574
|
// Query the remote documents and overlay mutations.
|
|
13159
13575
|
let results;
|
|
13160
13576
|
return this.remoteDocumentCache
|
|
13161
|
-
.
|
|
13577
|
+
.getAllFromCollection(transaction, query.path, offset)
|
|
13162
13578
|
.next(queryResults => {
|
|
13163
13579
|
results = queryResults;
|
|
13164
13580
|
return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
|
|
@@ -13244,11 +13660,12 @@ class LocalStoreImpl {
|
|
|
13244
13660
|
// TODO(wuandy): Evaluate if TargetId can be part of Target.
|
|
13245
13661
|
this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
|
|
13246
13662
|
/**
|
|
13247
|
-
*
|
|
13663
|
+
* A per collection group index of the last read time processed by
|
|
13664
|
+
* `getNewDocumentChanges()`.
|
|
13248
13665
|
*
|
|
13249
13666
|
* PORTING NOTE: This is only used for multi-tab synchronization.
|
|
13250
13667
|
*/
|
|
13251
|
-
this.
|
|
13668
|
+
this.collectionGroupReadTime = new Map();
|
|
13252
13669
|
this.remoteDocuments = persistence.getRemoteDocumentCache();
|
|
13253
13670
|
this.targetCache = persistence.getTargetCache();
|
|
13254
13671
|
this.bundleCache = persistence.getBundleCache();
|
|
@@ -13783,6 +14200,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
|
|
|
13783
14200
|
? lastLimboFreeSnapshotVersion
|
|
13784
14201
|
: SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
|
|
13785
14202
|
.next(documents => {
|
|
14203
|
+
setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
|
|
13786
14204
|
return { documents, remoteKeys };
|
|
13787
14205
|
});
|
|
13788
14206
|
});
|
|
@@ -13860,28 +14278,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
|
|
|
13860
14278
|
* since the prior call.
|
|
13861
14279
|
*/
|
|
13862
14280
|
// PORTING NOTE: Multi-Tab only.
|
|
13863
|
-
function localStoreGetNewDocumentChanges(localStore) {
|
|
14281
|
+
function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
|
|
13864
14282
|
const localStoreImpl = debugCast(localStore);
|
|
14283
|
+
// Get the current maximum read time for the collection. This should always
|
|
14284
|
+
// exist, but to reduce the chance for regressions we default to
|
|
14285
|
+
// SnapshotVersion.Min()
|
|
14286
|
+
// TODO(indexing): Consider removing the default value.
|
|
14287
|
+
const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
|
|
14288
|
+
SnapshotVersion.min();
|
|
13865
14289
|
return localStoreImpl.persistence
|
|
13866
|
-
.runTransaction('Get new document changes', 'readonly', txn =>
|
|
13867
|
-
|
|
13868
|
-
|
|
14290
|
+
.runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
|
|
14291
|
+
/* limit= */ Number.MAX_SAFE_INTEGER))
|
|
14292
|
+
.then(changedDocs => {
|
|
14293
|
+
setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
|
|
13869
14294
|
return changedDocs;
|
|
13870
14295
|
});
|
|
13871
14296
|
}
|
|
13872
|
-
/**
|
|
13873
|
-
* Reads the newest document change from persistence and moves the internal
|
|
13874
|
-
* synchronization marker forward so that calls to `getNewDocumentChanges()`
|
|
13875
|
-
* only return changes that happened after client initialization.
|
|
13876
|
-
*/
|
|
14297
|
+
/** Sets the collection group's maximum read time from the given documents. */
|
|
13877
14298
|
// PORTING NOTE: Multi-Tab only.
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
.
|
|
13882
|
-
|
|
13883
|
-
|
|
14299
|
+
function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
|
|
14300
|
+
let readTime = SnapshotVersion.min();
|
|
14301
|
+
changedDocs.forEach((_, doc) => {
|
|
14302
|
+
if (doc.readTime.compareTo(readTime) > 0) {
|
|
14303
|
+
readTime = doc.readTime;
|
|
14304
|
+
}
|
|
13884
14305
|
});
|
|
14306
|
+
localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
|
|
13885
14307
|
}
|
|
13886
14308
|
/**
|
|
13887
14309
|
* Creates a new target using the given bundle name, which will be used to
|
|
@@ -14065,7 +14487,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14065
14487
|
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14066
14488
|
}
|
|
14067
14489
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14068
|
-
overlays.forEach(mutation => {
|
|
14490
|
+
overlays.forEach((_, mutation) => {
|
|
14069
14491
|
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14070
14492
|
});
|
|
14071
14493
|
return PersistencePromise.resolve();
|
|
@@ -14079,7 +14501,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14079
14501
|
return PersistencePromise.resolve();
|
|
14080
14502
|
}
|
|
14081
14503
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14082
|
-
const result =
|
|
14504
|
+
const result = newOverlayMap();
|
|
14083
14505
|
const immediateChildrenPathLength = collection.length + 1;
|
|
14084
14506
|
const prefix = new DocumentKey(collection.child(''));
|
|
14085
14507
|
const iter = this.overlays.getIteratorFrom(prefix);
|
|
@@ -14113,19 +14535,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14113
14535
|
if (overlay.largestBatchId > sinceBatchId) {
|
|
14114
14536
|
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14115
14537
|
if (overlaysForBatchId === null) {
|
|
14116
|
-
overlaysForBatchId =
|
|
14538
|
+
overlaysForBatchId = newOverlayMap();
|
|
14117
14539
|
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14118
14540
|
}
|
|
14119
14541
|
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14120
14542
|
}
|
|
14121
14543
|
}
|
|
14122
|
-
const result =
|
|
14544
|
+
const result = newOverlayMap();
|
|
14123
14545
|
const batchIter = batchIdToOverlays.getIterator();
|
|
14124
14546
|
while (batchIter.hasNext()) {
|
|
14125
14547
|
const entry = batchIter.getNext();
|
|
14126
14548
|
const overlays = entry.value;
|
|
14127
|
-
overlays.forEach((
|
|
14128
|
-
if (result.size >= count) {
|
|
14549
|
+
overlays.forEach((key, overlay) => result.set(key, overlay));
|
|
14550
|
+
if (result.size() >= count) {
|
|
14129
14551
|
break;
|
|
14130
14552
|
}
|
|
14131
14553
|
}
|
|
@@ -14138,16 +14560,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14138
14560
|
// Remove the association of the overlay to its batch id.
|
|
14139
14561
|
const existing = this.overlays.get(mutation.key);
|
|
14140
14562
|
if (existing !== null) {
|
|
14141
|
-
this.overlayByBatchId
|
|
14563
|
+
const newSet = this.overlayByBatchId
|
|
14564
|
+
.get(existing.largestBatchId)
|
|
14565
|
+
.delete(mutation.key);
|
|
14566
|
+
this.overlayByBatchId.set(existing.largestBatchId, newSet);
|
|
14142
14567
|
}
|
|
14143
14568
|
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14144
14569
|
// Create the association of this overlay to the given largestBatchId.
|
|
14145
14570
|
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14146
14571
|
if (batch === undefined) {
|
|
14147
|
-
batch =
|
|
14572
|
+
batch = documentKeySet();
|
|
14148
14573
|
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14149
14574
|
}
|
|
14150
|
-
batch.add(mutation.key);
|
|
14575
|
+
this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
|
|
14151
14576
|
}
|
|
14152
14577
|
}
|
|
14153
14578
|
|
|
@@ -14561,7 +14986,7 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
14561
14986
|
});
|
|
14562
14987
|
return PersistencePromise.resolve(results);
|
|
14563
14988
|
}
|
|
14564
|
-
|
|
14989
|
+
getAllFromCollection(transaction, collectionPath, offset) {
|
|
14565
14990
|
let results = mutableDocumentMap();
|
|
14566
14991
|
// Documents are ordered by key, so we can use a prefix scan to narrow down
|
|
14567
14992
|
// the documents we need to match the query against.
|
|
@@ -14576,13 +15001,19 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
14576
15001
|
// Exclude entries from subcollections.
|
|
14577
15002
|
continue;
|
|
14578
15003
|
}
|
|
14579
|
-
if (document
|
|
15004
|
+
if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
|
|
15005
|
+
// The document sorts before the offset.
|
|
14580
15006
|
continue;
|
|
14581
15007
|
}
|
|
14582
15008
|
results = results.insert(document.key, document.mutableCopy());
|
|
14583
15009
|
}
|
|
14584
15010
|
return PersistencePromise.resolve(results);
|
|
14585
15011
|
}
|
|
15012
|
+
getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
|
|
15013
|
+
// This method should only be called from the IndexBackfiller if persistence
|
|
15014
|
+
// is enabled.
|
|
15015
|
+
fail();
|
|
15016
|
+
}
|
|
14586
15017
|
forEachDocumentKey(transaction, f) {
|
|
14587
15018
|
return PersistencePromise.forEach(this.docs, (key) => f(key));
|
|
14588
15019
|
}
|
|
@@ -15035,7 +15466,7 @@ class QueryEngine {
|
|
|
15035
15466
|
}
|
|
15036
15467
|
// Retrieve all results for documents that were updated since the last
|
|
15037
15468
|
// limbo-document free remote snapshot.
|
|
15038
|
-
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
|
|
15469
|
+
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
|
|
15039
15470
|
// We merge `previousResults` into `updateResults`, since
|
|
15040
15471
|
// `updateResults` is already a DocumentMap. If a document is
|
|
15041
15472
|
// contained in both lists, then its contents are the same.
|
|
@@ -15097,7 +15528,7 @@ class QueryEngine {
|
|
|
15097
15528
|
if (getLogLevel() <= logger.LogLevel.DEBUG) {
|
|
15098
15529
|
logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
|
|
15099
15530
|
}
|
|
15100
|
-
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query,
|
|
15531
|
+
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15101
15532
|
}
|
|
15102
15533
|
}
|
|
15103
15534
|
|
|
@@ -15158,8 +15589,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
|
|
|
15158
15589
|
// The WebStorage prefix that plays as a event to indicate the remote documents
|
|
15159
15590
|
// might have changed due to some secondary tabs loading a bundle.
|
|
15160
15591
|
// format of the key is:
|
|
15161
|
-
//
|
|
15162
|
-
|
|
15592
|
+
// firestore_bundle_loaded_v2_<persistenceKey>
|
|
15593
|
+
// The version ending with "v2" stores the list of modified collection groups.
|
|
15594
|
+
const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
|
|
15163
15595
|
function createBundleLoadedKey(persistenceKey) {
|
|
15164
15596
|
return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
|
|
15165
15597
|
}
|
|
@@ -15537,8 +15969,8 @@ class WebStorageSharedClientState {
|
|
|
15537
15969
|
setOnlineState(onlineState) {
|
|
15538
15970
|
this.persistOnlineState(onlineState);
|
|
15539
15971
|
}
|
|
15540
|
-
notifyBundleLoaded() {
|
|
15541
|
-
this.persistBundleLoadedState();
|
|
15972
|
+
notifyBundleLoaded(collectionGroups) {
|
|
15973
|
+
this.persistBundleLoadedState(collectionGroups);
|
|
15542
15974
|
}
|
|
15543
15975
|
shutdown() {
|
|
15544
15976
|
if (this.started) {
|
|
@@ -15622,7 +16054,8 @@ class WebStorageSharedClientState {
|
|
|
15622
16054
|
}
|
|
15623
16055
|
}
|
|
15624
16056
|
else if (storageEvent.key === this.bundleLoadedKey) {
|
|
15625
|
-
|
|
16057
|
+
const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
|
|
16058
|
+
await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
|
|
15626
16059
|
}
|
|
15627
16060
|
});
|
|
15628
16061
|
}
|
|
@@ -15654,8 +16087,9 @@ class WebStorageSharedClientState {
|
|
|
15654
16087
|
const targetMetadata = new QueryTargetMetadata(targetId, state, error);
|
|
15655
16088
|
this.setItem(targetKey, targetMetadata.toWebStorageJSON());
|
|
15656
16089
|
}
|
|
15657
|
-
persistBundleLoadedState() {
|
|
15658
|
-
|
|
16090
|
+
persistBundleLoadedState(collectionGroups) {
|
|
16091
|
+
const json = JSON.stringify(Array.from(collectionGroups));
|
|
16092
|
+
this.setItem(this.bundleLoadedKey, json);
|
|
15659
16093
|
}
|
|
15660
16094
|
/**
|
|
15661
16095
|
* Parses a client state key in WebStorage. Returns null if the key does not
|
|
@@ -15699,6 +16133,9 @@ class WebStorageSharedClientState {
|
|
|
15699
16133
|
fromWebStorageOnlineState(value) {
|
|
15700
16134
|
return SharedOnlineState.fromWebStorageEntry(value);
|
|
15701
16135
|
}
|
|
16136
|
+
fromWebStoreBundleLoadedState(value) {
|
|
16137
|
+
return JSON.parse(value);
|
|
16138
|
+
}
|
|
15702
16139
|
async handleMutationBatchEvent(mutationBatch) {
|
|
15703
16140
|
if (mutationBatch.user.uid !== this.currentUser.uid) {
|
|
15704
16141
|
logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
|
|
@@ -15815,7 +16252,7 @@ class MemorySharedClientState {
|
|
|
15815
16252
|
}
|
|
15816
16253
|
shutdown() { }
|
|
15817
16254
|
writeSequenceNumber(sequenceNumber) { }
|
|
15818
|
-
notifyBundleLoaded() {
|
|
16255
|
+
notifyBundleLoaded(collectionGroups) {
|
|
15819
16256
|
// No op.
|
|
15820
16257
|
}
|
|
15821
16258
|
}
|
|
@@ -15998,9 +16435,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
|
15998
16435
|
metadata.set('X-Firebase-GMPID', appId);
|
|
15999
16436
|
}
|
|
16000
16437
|
metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
|
|
16001
|
-
//
|
|
16438
|
+
// These headers are used to improve routing and project isolation by the
|
|
16002
16439
|
// backend.
|
|
16440
|
+
// TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
|
|
16441
|
+
// released with cl/428820046. Currently blocked because Emulators are now built with Java
|
|
16442
|
+
// 11 from Google3.
|
|
16003
16443
|
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
|
|
16444
|
+
metadata.set('x-goog-request-params', databasePath);
|
|
16004
16445
|
return metadata;
|
|
16005
16446
|
}
|
|
16006
16447
|
/**
|
|
@@ -21111,29 +21552,6 @@ class LocalViewChanges {
|
|
|
21111
21552
|
}
|
|
21112
21553
|
}
|
|
21113
21554
|
|
|
21114
|
-
/**
|
|
21115
|
-
* @license
|
|
21116
|
-
* Copyright 2020 Google LLC
|
|
21117
|
-
*
|
|
21118
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
21119
|
-
* you may not use this file except in compliance with the License.
|
|
21120
|
-
* You may obtain a copy of the License at
|
|
21121
|
-
*
|
|
21122
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
21123
|
-
*
|
|
21124
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
21125
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
21126
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21127
|
-
* See the License for the specific language governing permissions and
|
|
21128
|
-
* limitations under the License.
|
|
21129
|
-
*/
|
|
21130
|
-
class BundleLoadResult {
|
|
21131
|
-
constructor(progress, changedDocs) {
|
|
21132
|
-
this.progress = progress;
|
|
21133
|
-
this.changedDocs = changedDocs;
|
|
21134
|
-
}
|
|
21135
|
-
}
|
|
21136
|
-
|
|
21137
21555
|
/**
|
|
21138
21556
|
* @license
|
|
21139
21557
|
* Copyright 2020 Google LLC
|
|
@@ -21188,6 +21606,8 @@ class BundleLoader {
|
|
|
21188
21606
|
this.queries = [];
|
|
21189
21607
|
/** Batched documents to be saved into storage */
|
|
21190
21608
|
this.documents = [];
|
|
21609
|
+
/** The collection groups affected by this bundle. */
|
|
21610
|
+
this.collectionGroups = new Set();
|
|
21191
21611
|
this.progress = bundleInitialProgress(bundleMetadata);
|
|
21192
21612
|
}
|
|
21193
21613
|
/**
|
|
@@ -21207,6 +21627,8 @@ class BundleLoader {
|
|
|
21207
21627
|
if (!element.payload.documentMetadata.exists) {
|
|
21208
21628
|
++documentsLoaded;
|
|
21209
21629
|
}
|
|
21630
|
+
const path = ResourcePath.fromString(element.payload.documentMetadata.name);
|
|
21631
|
+
this.collectionGroups.add(path.get(path.length - 2));
|
|
21210
21632
|
}
|
|
21211
21633
|
else if (element.payload.document) {
|
|
21212
21634
|
this.documents[this.documents.length - 1].document =
|
|
@@ -21237,13 +21659,17 @@ class BundleLoader {
|
|
|
21237
21659
|
* Update the progress to 'Success' and return the updated progress.
|
|
21238
21660
|
*/
|
|
21239
21661
|
async complete() {
|
|
21240
|
-
const
|
|
21662
|
+
const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
|
|
21241
21663
|
const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
|
|
21242
21664
|
for (const q of this.queries) {
|
|
21243
21665
|
await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
|
|
21244
21666
|
}
|
|
21245
21667
|
this.progress.taskState = 'Success';
|
|
21246
|
-
return
|
|
21668
|
+
return {
|
|
21669
|
+
progress: this.progress,
|
|
21670
|
+
changedCollectionGroups: this.collectionGroups,
|
|
21671
|
+
changedDocs
|
|
21672
|
+
};
|
|
21247
21673
|
}
|
|
21248
21674
|
}
|
|
21249
21675
|
/**
|
|
@@ -22322,9 +22748,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
|
|
|
22322
22748
|
* snapshots if needed.
|
|
22323
22749
|
*/
|
|
22324
22750
|
// PORTING NOTE: Multi-Tab only.
|
|
22325
|
-
async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
|
|
22751
|
+
async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
|
|
22326
22752
|
const syncEngineImpl = debugCast(syncEngine);
|
|
22327
|
-
return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
|
|
22753
|
+
return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
|
|
22328
22754
|
}
|
|
22329
22755
|
/** Applies a mutation state to an existing batch. */
|
|
22330
22756
|
// PORTING NOTE: Multi-Tab only.
|
|
@@ -22489,11 +22915,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
|
|
|
22489
22915
|
logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
|
|
22490
22916
|
return;
|
|
22491
22917
|
}
|
|
22492
|
-
|
|
22918
|
+
const query = syncEngineImpl.queriesByTarget.get(targetId);
|
|
22919
|
+
if (query && query.length > 0) {
|
|
22493
22920
|
switch (state) {
|
|
22494
22921
|
case 'current':
|
|
22495
22922
|
case 'not-current': {
|
|
22496
|
-
const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
|
|
22923
|
+
const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
|
|
22497
22924
|
const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
|
|
22498
22925
|
await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
|
|
22499
22926
|
break;
|
|
@@ -22576,10 +23003,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
|
|
|
22576
23003
|
function syncEngineLoadBundle(syncEngine, bundleReader, task) {
|
|
22577
23004
|
const syncEngineImpl = debugCast(syncEngine);
|
|
22578
23005
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
22579
|
-
loadBundleImpl(syncEngineImpl, bundleReader, task).then(
|
|
22580
|
-
syncEngineImpl.sharedClientState.notifyBundleLoaded();
|
|
23006
|
+
loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
|
|
23007
|
+
syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
|
|
22581
23008
|
});
|
|
22582
23009
|
}
|
|
23010
|
+
/** Loads a bundle and returns the list of affected collection groups. */
|
|
22583
23011
|
async function loadBundleImpl(syncEngine, reader, task) {
|
|
22584
23012
|
try {
|
|
22585
23013
|
const metadata = await reader.getMetadata();
|
|
@@ -22587,7 +23015,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
|
|
|
22587
23015
|
if (skip) {
|
|
22588
23016
|
await reader.close();
|
|
22589
23017
|
task._completeWith(bundleSuccessProgress(metadata));
|
|
22590
|
-
return;
|
|
23018
|
+
return Promise.resolve(new Set());
|
|
22591
23019
|
}
|
|
22592
23020
|
task._updateProgress(bundleInitialProgress(metadata));
|
|
22593
23021
|
const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
|
|
@@ -22601,18 +23029,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
|
|
|
22601
23029
|
element = await reader.nextElement();
|
|
22602
23030
|
}
|
|
22603
23031
|
const result = await loader.complete();
|
|
22604
|
-
// TODO(b/160876443): This currently raises snapshots with
|
|
22605
|
-
// `fromCache=false` if users already listen to some queries and bundles
|
|
22606
|
-
// has newer version.
|
|
22607
23032
|
await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
|
|
22608
23033
|
/* remoteEvent */ undefined);
|
|
22609
23034
|
// Save metadata, so loading the same bundle will skip.
|
|
22610
23035
|
await localStoreSaveBundle(syncEngine.localStore, metadata);
|
|
22611
23036
|
task._completeWith(result.progress);
|
|
23037
|
+
return Promise.resolve(result.changedCollectionGroups);
|
|
22612
23038
|
}
|
|
22613
23039
|
catch (e) {
|
|
22614
23040
|
logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
|
|
22615
23041
|
task._failWith(e);
|
|
23042
|
+
return Promise.resolve(new Set());
|
|
22616
23043
|
}
|
|
22617
23044
|
}
|
|
22618
23045
|
|
|
@@ -22681,7 +23108,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
|
|
|
22681
23108
|
}
|
|
22682
23109
|
async initialize(cfg) {
|
|
22683
23110
|
await super.initialize(cfg);
|
|
22684
|
-
await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
|
|
22685
23111
|
await this.onlineComponentProvider.initialize(this, cfg);
|
|
22686
23112
|
// Enqueue writes from a previous session
|
|
22687
23113
|
await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);
|