@firebase/firestore 3.4.5 → 3.4.6-2022216223411
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 +9 -0
- 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_schema.d.ts +34 -553
- package/dist/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
- package/dist/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/firestore/src/local/local_serializer.d.ts +2 -1
- 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/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 +2 -0
- 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/index.esm2017.js +4160 -4201
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4332 -4311
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1223 -956
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1223 -956
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4157 -4198
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +12 -5
- 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_schema.d.ts +34 -553
- package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
- package/dist/lite/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/firestore/src/local/local_serializer.d.ts +2 -1
- 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/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 +2 -0
- 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/index.browser.esm2017.js +34 -24
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +12 -23
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +21 -9
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +21 -9
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +34 -24
- package/dist/lite/index.rn.esm2017.js.map +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_schema.d.ts +34 -553
- package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +2 -1
- 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/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 +2 -0
- 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/packages/firestore/dist/index.esm2017.d.ts +191 -191
- 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_schema.d.ts +34 -553
- package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
- package/dist/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/packages/firestore/src/local/local_serializer.d.ts +2 -1
- 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/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 +2 -0
- 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/private.d.ts +12 -5
- package/package.json +7 -7
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-2022216223411";
|
|
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-2022216223411";
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @license
|
|
@@ -1224,55 +1224,23 @@ function decodeResourcePath(path) {
|
|
|
1224
1224
|
}
|
|
1225
1225
|
return new ResourcePath(segments);
|
|
1226
1226
|
}
|
|
1227
|
+
|
|
1227
1228
|
/**
|
|
1228
|
-
*
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
* to limbo resolution. Addresses
|
|
1235
|
-
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
1236
|
-
* 4. Multi-Tab Support.
|
|
1237
|
-
* 5. Removal of held write acks.
|
|
1238
|
-
* 6. Create document global for tracking document cache size.
|
|
1239
|
-
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
1240
|
-
* 8. Add collection-parent index for Collection Group queries.
|
|
1241
|
-
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
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.
|
|
1251
|
-
*/
|
|
1252
|
-
class DbTimestamp {
|
|
1253
|
-
constructor(seconds, nanoseconds) {
|
|
1254
|
-
this.seconds = seconds;
|
|
1255
|
-
this.nanoseconds = nanoseconds;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* A singleton object to be stored in the 'owner' store in IndexedDb.
|
|
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
|
|
1260
1235
|
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
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.
|
|
1266
1243
|
*/
|
|
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
1244
|
/**
|
|
1277
1245
|
* Name of the IndexedDb object store.
|
|
1278
1246
|
*
|
|
@@ -1280,239 +1248,62 @@ class DbPrimaryClient {
|
|
|
1280
1248
|
* older clients that only supported single locked access to the persistence
|
|
1281
1249
|
* layer.
|
|
1282
1250
|
*/
|
|
1283
|
-
|
|
1251
|
+
const DbPrimaryClientStore = 'owner';
|
|
1284
1252
|
/**
|
|
1285
1253
|
* The key string used for the single object that exists in the
|
|
1286
1254
|
* DbPrimaryClient store.
|
|
1287
1255
|
*/
|
|
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
|
-
}
|
|
1256
|
+
const DbPrimaryClientKey = 'owner';
|
|
1327
1257
|
/** Name of the IndexedDb object store. */
|
|
1328
|
-
|
|
1258
|
+
const DbMutationQueueStore = 'mutationQueues';
|
|
1329
1259
|
/** 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
|
-
}
|
|
1260
|
+
const DbMutationQueueKeyPath = 'userId';
|
|
1379
1261
|
/** Name of the IndexedDb object store. */
|
|
1380
|
-
|
|
1262
|
+
const DbMutationBatchStore = 'mutations';
|
|
1381
1263
|
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
1382
|
-
|
|
1264
|
+
const DbMutationBatchKeyPath = 'batchId';
|
|
1383
1265
|
/** The index name for lookup of mutations by user. */
|
|
1384
|
-
|
|
1266
|
+
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
1385
1267
|
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
1386
|
-
|
|
1268
|
+
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
1387
1269
|
/**
|
|
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.
|
|
1270
|
+
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1271
|
+
* over all of a user's document mutations.
|
|
1393
1272
|
*/
|
|
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
|
-
}
|
|
1273
|
+
function newDbDocumentMutationPrefixForUser(userId) {
|
|
1274
|
+
return [userId];
|
|
1417
1275
|
}
|
|
1418
|
-
DbDocumentMutation.store = 'documentMutations';
|
|
1419
1276
|
/**
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1422
|
-
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1423
|
-
* information.
|
|
1424
|
-
*/
|
|
1425
|
-
DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
|
|
1426
|
-
/**
|
|
1427
|
-
* Represents the known absence of a document at a particular version.
|
|
1428
|
-
* Stored in IndexedDb as part of a DbRemoteDocument object.
|
|
1277
|
+
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1278
|
+
* index to iterate over all at document mutations for a given path or lower.
|
|
1429
1279
|
*/
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
this.path = path;
|
|
1433
|
-
this.readTime = readTime;
|
|
1434
|
-
}
|
|
1280
|
+
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
1281
|
+
return [userId, encodeResourcePath(path)];
|
|
1435
1282
|
}
|
|
1436
1283
|
/**
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1284
|
+
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1285
|
+
* and deleting into the DbDocumentMutations index.
|
|
1439
1286
|
*/
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
this.path = path;
|
|
1443
|
-
this.version = version;
|
|
1444
|
-
}
|
|
1287
|
+
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
1288
|
+
return [userId, encodeResourcePath(path), batchId];
|
|
1445
1289
|
}
|
|
1446
1290
|
/**
|
|
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.
|
|
1291
|
+
* Because we store all the useful information for this store in the key,
|
|
1292
|
+
* there is no useful information to store as the value. The raw (unencoded)
|
|
1293
|
+
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1294
|
+
* information.
|
|
1458
1295
|
*/
|
|
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';
|
|
1296
|
+
const DbDocumentMutationPlaceholder = {};
|
|
1297
|
+
const DbDocumentMutationStore = 'documentMutations';
|
|
1298
|
+
const DbRemoteDocumentStore = 'remoteDocuments';
|
|
1508
1299
|
/**
|
|
1509
1300
|
* An index that provides access to all entries sorted by read time (which
|
|
1510
1301
|
* corresponds to the last modification time of each row).
|
|
1511
1302
|
*
|
|
1512
1303
|
* This index is used to provide a changelog for Multi-Tab.
|
|
1513
1304
|
*/
|
|
1514
|
-
|
|
1515
|
-
|
|
1305
|
+
const DbRemoteDocumentReadTimeIndex = 'readTimeIndex';
|
|
1306
|
+
const DbRemoteDocumentReadTimeIndexPath = 'readTime';
|
|
1516
1307
|
/**
|
|
1517
1308
|
* An index that provides access to documents in a collection sorted by read
|
|
1518
1309
|
* time.
|
|
@@ -1520,361 +1311,68 @@ DbRemoteDocument.readTimeIndexPath = 'readTime';
|
|
|
1520
1311
|
* This index is used to allow the RemoteDocumentCache to fetch newly changed
|
|
1521
1312
|
* documents in a collection.
|
|
1522
1313
|
*/
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
* documents in the document cache.
|
|
1532
|
-
*/
|
|
1533
|
-
constructor(byteSize) {
|
|
1534
|
-
this.byteSize = byteSize;
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
DbRemoteDocumentGlobal.store = 'remoteDocumentGlobal';
|
|
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';
|
|
1314
|
+
const DbRemoteDocumentCollectionReadTimeIndex = 'collectionReadTimeIndex';
|
|
1315
|
+
const DbRemoteDocumentCollectionReadTimeIndexPath = [
|
|
1316
|
+
'parentPath',
|
|
1317
|
+
'readTime'
|
|
1318
|
+
];
|
|
1319
|
+
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
1320
|
+
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
1321
|
+
const DbTargetStore = 'targets';
|
|
1626
1322
|
/** Keys are automatically assigned via the targetId property. */
|
|
1627
|
-
|
|
1323
|
+
const DbTargetKeyPath = 'targetId';
|
|
1628
1324
|
/** The name of the queryTargets index. */
|
|
1629
|
-
|
|
1325
|
+
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
1630
1326
|
/**
|
|
1631
1327
|
* The index of all canonicalIds to the targets that they match. This is not
|
|
1632
1328
|
* a unique mapping because canonicalId does not promise a unique name for all
|
|
1633
1329
|
* possible queries, so we append the targetId to make the mapping unique.
|
|
1634
1330
|
*/
|
|
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
|
-
}
|
|
1331
|
+
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
1667
1332
|
/** Name of the IndexedDb object store. */
|
|
1668
|
-
|
|
1333
|
+
const DbTargetDocumentStore = 'targetDocuments';
|
|
1669
1334
|
/** Keys are automatically assigned via the targetId, path properties. */
|
|
1670
|
-
|
|
1335
|
+
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
1671
1336
|
/** The index name for the reverse index. */
|
|
1672
|
-
|
|
1337
|
+
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
1673
1338
|
/** 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
|
-
}
|
|
1339
|
+
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
1714
1340
|
/**
|
|
1715
1341
|
* The key string used for the single object that exists in the
|
|
1716
1342
|
* DbTargetGlobal store.
|
|
1717
1343
|
*/
|
|
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
|
-
}
|
|
1344
|
+
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
1345
|
+
const DbTargetGlobalStore = 'targetGlobal';
|
|
1741
1346
|
/** Name of the IndexedDb object store. */
|
|
1742
|
-
|
|
1347
|
+
const DbCollectionParentStore = 'collectionParents';
|
|
1743
1348
|
/** 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
|
-
}
|
|
1349
|
+
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
1769
1350
|
/** Name of the IndexedDb object store. */
|
|
1770
|
-
|
|
1351
|
+
const DbClientMetadataStore = 'clientMetadata';
|
|
1771
1352
|
/** 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
|
-
}
|
|
1353
|
+
const DbClientMetadataKeyPath = 'clientId';
|
|
1787
1354
|
/** 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
|
-
}
|
|
1355
|
+
const DbBundleStore = 'bundles';
|
|
1356
|
+
const DbBundleKeyPath = 'bundleId';
|
|
1804
1357
|
/** 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
|
-
}
|
|
1358
|
+
const DbNamedQueryStore = 'namedQueries';
|
|
1359
|
+
const DbNamedQueryKeyPath = 'name';
|
|
1824
1360
|
/** Name of the IndexedDb object store. */
|
|
1825
|
-
|
|
1826
|
-
|
|
1361
|
+
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
1362
|
+
const DbIndexConfigurationKeyPath = 'indexId';
|
|
1827
1363
|
/**
|
|
1828
|
-
* An index that provides access to the index configurations by collection
|
|
1829
|
-
* group.
|
|
1830
|
-
*
|
|
1831
|
-
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1832
|
-
* not possible here as the Web client supports concurrent access to
|
|
1833
|
-
* persistence via multi-tab.
|
|
1834
|
-
*/
|
|
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
|
-
}
|
|
1364
|
+
* An index that provides access to the index configurations by collection
|
|
1365
|
+
* group.
|
|
1366
|
+
*
|
|
1367
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1368
|
+
* not possible here as the Web client supports concurrent access to
|
|
1369
|
+
* persistence via multi-tab.
|
|
1370
|
+
*/
|
|
1371
|
+
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
1372
|
+
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
1875
1373
|
/** Name of the IndexedDb object store. */
|
|
1876
|
-
|
|
1877
|
-
|
|
1374
|
+
const DbIndexStateStore = 'indexState';
|
|
1375
|
+
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
1878
1376
|
/**
|
|
1879
1377
|
* An index that provides access to documents in a collection sorted by last
|
|
1880
1378
|
* update time. Used by the backfiller.
|
|
@@ -1883,108 +1381,66 @@ DbIndexState.keyPath = ['indexId', 'uid'];
|
|
|
1883
1381
|
* not possible here as the Web client supports concurrent access to
|
|
1884
1382
|
* persistence via multi-tab.
|
|
1885
1383
|
*/
|
|
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
|
-
}
|
|
1384
|
+
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
1385
|
+
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1908
1386
|
/** Name of the IndexedDb object store. */
|
|
1909
|
-
|
|
1910
|
-
|
|
1387
|
+
const DbIndexEntryStore = 'indexEntries';
|
|
1388
|
+
const DbIndexEntryKeyPath = [
|
|
1911
1389
|
'indexId',
|
|
1912
1390
|
'uid',
|
|
1913
1391
|
'arrayValue',
|
|
1914
1392
|
'directionalValue',
|
|
1915
1393
|
'documentKey'
|
|
1916
1394
|
];
|
|
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
|
-
}
|
|
1395
|
+
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1396
|
+
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1397
|
+
'indexId',
|
|
1398
|
+
'uid',
|
|
1399
|
+
'documentKey'
|
|
1400
|
+
];
|
|
1944
1401
|
/** Name of the IndexedDb object store. */
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1402
|
+
const DbDocumentOverlayStore = 'documentOverlays';
|
|
1403
|
+
const DbDocumentOverlayKeyPath = [
|
|
1404
|
+
'userId',
|
|
1405
|
+
'collectionPath',
|
|
1406
|
+
'documentId'
|
|
1407
|
+
];
|
|
1408
|
+
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1409
|
+
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
1949
1410
|
'userId',
|
|
1950
1411
|
'collectionPath',
|
|
1951
1412
|
'largestBatchId'
|
|
1952
1413
|
];
|
|
1953
|
-
|
|
1954
|
-
|
|
1414
|
+
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1415
|
+
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
1955
1416
|
'userId',
|
|
1956
1417
|
'collectionGroup',
|
|
1957
1418
|
'largestBatchId'
|
|
1958
1419
|
];
|
|
1959
1420
|
// Visible for testing
|
|
1960
1421
|
const V1_STORES = [
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1422
|
+
DbMutationQueueStore,
|
|
1423
|
+
DbMutationBatchStore,
|
|
1424
|
+
DbDocumentMutationStore,
|
|
1425
|
+
DbRemoteDocumentStore,
|
|
1426
|
+
DbTargetStore,
|
|
1427
|
+
DbPrimaryClientStore,
|
|
1428
|
+
DbTargetGlobalStore,
|
|
1429
|
+
DbTargetDocumentStore
|
|
1969
1430
|
];
|
|
1970
|
-
// V2 is no longer usable (see comment at top of file)
|
|
1971
1431
|
// Visible for testing
|
|
1972
1432
|
const V3_STORES = V1_STORES;
|
|
1973
1433
|
// 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];
|
|
1434
|
+
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
1435
|
+
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
1436
|
+
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
1437
|
+
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
1438
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
1983
1439
|
const V13_STORES = [
|
|
1984
1440
|
...V12_STORES,
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1441
|
+
DbIndexConfigurationStore,
|
|
1442
|
+
DbIndexStateStore,
|
|
1443
|
+
DbIndexEntryStore
|
|
1988
1444
|
];
|
|
1989
1445
|
/** Returns the object stores for the provided schema. */
|
|
1990
1446
|
function getObjectStores(schemaVersion) {
|
|
@@ -2702,6 +2158,21 @@ class SimpleDbStore {
|
|
|
2702
2158
|
});
|
|
2703
2159
|
}
|
|
2704
2160
|
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2163
|
+
* elements if no limit is provided.
|
|
2164
|
+
*/
|
|
2165
|
+
loadFirst(range, count) {
|
|
2166
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
2167
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2168
|
+
request.onerror = (event) => {
|
|
2169
|
+
reject(event.target.error);
|
|
2170
|
+
};
|
|
2171
|
+
request.onsuccess = (event) => {
|
|
2172
|
+
resolve(event.target.result);
|
|
2173
|
+
};
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2705
2176
|
deleteAll(indexOrRange, range) {
|
|
2706
2177
|
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2707
2178
|
const options = this.options(indexOrRange, range);
|
|
@@ -3312,17 +2783,15 @@ class FieldMask {
|
|
|
3312
2783
|
* See the License for the specific language governing permissions and
|
|
3313
2784
|
* limitations under the License.
|
|
3314
2785
|
*/
|
|
2786
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3315
2787
|
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');
|
|
2788
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
2789
|
+
// This was removed to improve the performance of indexing.
|
|
2790
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3322
2791
|
}
|
|
3323
2792
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3324
2793
|
function encodeBase64(raw) {
|
|
3325
|
-
return
|
|
2794
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3326
2795
|
}
|
|
3327
2796
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3328
2797
|
function isBase64Available() {
|
|
@@ -3363,6 +2832,8 @@ class ByteString {
|
|
|
3363
2832
|
return new ByteString(binaryString);
|
|
3364
2833
|
}
|
|
3365
2834
|
static fromUint8Array(array) {
|
|
2835
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
2836
|
+
// is frequently called during indexing.
|
|
3366
2837
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3367
2838
|
return new ByteString(binaryString);
|
|
3368
2839
|
}
|
|
@@ -3781,13 +3252,17 @@ class DocumentKey {
|
|
|
3781
3252
|
* See the License for the specific language governing permissions and
|
|
3782
3253
|
* limitations under the License.
|
|
3783
3254
|
*/
|
|
3255
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3784
3256
|
const MAX_VALUE = {
|
|
3785
3257
|
mapValue: {
|
|
3786
3258
|
fields: {
|
|
3787
|
-
'__type__': { stringValue:
|
|
3259
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3788
3260
|
}
|
|
3789
3261
|
}
|
|
3790
3262
|
};
|
|
3263
|
+
const MIN_VALUE = {
|
|
3264
|
+
nullValue: 'NULL_VALUE'
|
|
3265
|
+
};
|
|
3791
3266
|
/** Extracts the backend's type order for the provided value. */
|
|
3792
3267
|
function typeOrder(value) {
|
|
3793
3268
|
if ('nullValue' in value) {
|
|
@@ -3821,6 +3296,9 @@ function typeOrder(value) {
|
|
|
3821
3296
|
if (isServerTimestamp(value)) {
|
|
3822
3297
|
return 4 /* ServerTimestampValue */;
|
|
3823
3298
|
}
|
|
3299
|
+
else if (isMaxValue(value)) {
|
|
3300
|
+
return 9 /* ArrayValue */;
|
|
3301
|
+
}
|
|
3824
3302
|
return 10 /* ObjectValue */;
|
|
3825
3303
|
}
|
|
3826
3304
|
else {
|
|
@@ -3860,6 +3338,8 @@ function valueEquals(left, right) {
|
|
|
3860
3338
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3861
3339
|
case 10 /* ObjectValue */:
|
|
3862
3340
|
return objectEquals(left, right);
|
|
3341
|
+
case 9007199254740991 /* MaxValue */:
|
|
3342
|
+
return true;
|
|
3863
3343
|
default:
|
|
3864
3344
|
return fail();
|
|
3865
3345
|
}
|
|
@@ -3932,6 +3412,7 @@ function valueCompare(left, right) {
|
|
|
3932
3412
|
}
|
|
3933
3413
|
switch (leftType) {
|
|
3934
3414
|
case 0 /* NullValue */:
|
|
3415
|
+
case 9007199254740991 /* MaxValue */:
|
|
3935
3416
|
return 0;
|
|
3936
3417
|
case 1 /* BooleanValue */:
|
|
3937
3418
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -4200,7 +3681,102 @@ function deepClone(source) {
|
|
|
4200
3681
|
}
|
|
4201
3682
|
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4202
3683
|
function isMaxValue(value) {
|
|
4203
|
-
return
|
|
3684
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
3685
|
+
MAX_VALUE_TYPE);
|
|
3686
|
+
}
|
|
3687
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
3688
|
+
function valuesGetLowerBound(value) {
|
|
3689
|
+
if ('nullValue' in value) {
|
|
3690
|
+
return MIN_VALUE;
|
|
3691
|
+
}
|
|
3692
|
+
else if ('booleanValue' in value) {
|
|
3693
|
+
return { booleanValue: false };
|
|
3694
|
+
}
|
|
3695
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3696
|
+
return { doubleValue: NaN };
|
|
3697
|
+
}
|
|
3698
|
+
else if ('timestampValue' in value) {
|
|
3699
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3700
|
+
}
|
|
3701
|
+
else if ('stringValue' in value) {
|
|
3702
|
+
return { stringValue: '' };
|
|
3703
|
+
}
|
|
3704
|
+
else if ('bytesValue' in value) {
|
|
3705
|
+
return { bytesValue: '' };
|
|
3706
|
+
}
|
|
3707
|
+
else if ('referenceValue' in value) {
|
|
3708
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3709
|
+
}
|
|
3710
|
+
else if ('geoPointValue' in value) {
|
|
3711
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3712
|
+
}
|
|
3713
|
+
else if ('arrayValue' in value) {
|
|
3714
|
+
return { arrayValue: {} };
|
|
3715
|
+
}
|
|
3716
|
+
else if ('mapValue' in value) {
|
|
3717
|
+
return { mapValue: {} };
|
|
3718
|
+
}
|
|
3719
|
+
else {
|
|
3720
|
+
return fail();
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
3724
|
+
function valuesGetUpperBound(value) {
|
|
3725
|
+
if ('nullValue' in value) {
|
|
3726
|
+
return { booleanValue: false };
|
|
3727
|
+
}
|
|
3728
|
+
else if ('booleanValue' in value) {
|
|
3729
|
+
return { doubleValue: NaN };
|
|
3730
|
+
}
|
|
3731
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3732
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3733
|
+
}
|
|
3734
|
+
else if ('timestampValue' in value) {
|
|
3735
|
+
return { stringValue: '' };
|
|
3736
|
+
}
|
|
3737
|
+
else if ('stringValue' in value) {
|
|
3738
|
+
return { bytesValue: '' };
|
|
3739
|
+
}
|
|
3740
|
+
else if ('bytesValue' in value) {
|
|
3741
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3742
|
+
}
|
|
3743
|
+
else if ('referenceValue' in value) {
|
|
3744
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3745
|
+
}
|
|
3746
|
+
else if ('geoPointValue' in value) {
|
|
3747
|
+
return { arrayValue: {} };
|
|
3748
|
+
}
|
|
3749
|
+
else if ('arrayValue' in value) {
|
|
3750
|
+
return { mapValue: {} };
|
|
3751
|
+
}
|
|
3752
|
+
else if ('mapValue' in value) {
|
|
3753
|
+
return MAX_VALUE;
|
|
3754
|
+
}
|
|
3755
|
+
else {
|
|
3756
|
+
return fail();
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
function valuesMax(left, right) {
|
|
3760
|
+
if (left === undefined) {
|
|
3761
|
+
return right;
|
|
3762
|
+
}
|
|
3763
|
+
else if (right === undefined) {
|
|
3764
|
+
return left;
|
|
3765
|
+
}
|
|
3766
|
+
else {
|
|
3767
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
function valuesMin(left, right) {
|
|
3771
|
+
if (left === undefined) {
|
|
3772
|
+
return right;
|
|
3773
|
+
}
|
|
3774
|
+
else if (right === undefined) {
|
|
3775
|
+
return left;
|
|
3776
|
+
}
|
|
3777
|
+
else {
|
|
3778
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
3779
|
+
}
|
|
4204
3780
|
}
|
|
4205
3781
|
|
|
4206
3782
|
/**
|
|
@@ -4595,6 +4171,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4595
4171
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4596
4172
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4597
4173
|
}
|
|
4174
|
+
/** Returns a debug representation of the field index */
|
|
4175
|
+
function fieldIndexToString(fieldIndex) {
|
|
4176
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4177
|
+
}
|
|
4598
4178
|
/** An index component consisting of field path and index type. */
|
|
4599
4179
|
class IndexSegment {
|
|
4600
4180
|
constructor(
|
|
@@ -4791,6 +4371,191 @@ function targetIsDocumentTarget(target) {
|
|
|
4791
4371
|
target.collectionGroup === null &&
|
|
4792
4372
|
target.filters.length === 0);
|
|
4793
4373
|
}
|
|
4374
|
+
/** Returns the field filters that target the given field path. */
|
|
4375
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4376
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4377
|
+
}
|
|
4378
|
+
/**
|
|
4379
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4380
|
+
* filters. Returns `null` if there are no such filters.
|
|
4381
|
+
*/
|
|
4382
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4383
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4384
|
+
if (segment === undefined) {
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4388
|
+
switch (fieldFilter.op) {
|
|
4389
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4390
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4391
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4392
|
+
return [fieldFilter.value];
|
|
4393
|
+
// Remaining filters are not array filters.
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
return null;
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4400
|
+
* `null` if there are no such filters.
|
|
4401
|
+
*/
|
|
4402
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4403
|
+
const values = new Map();
|
|
4404
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4405
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4406
|
+
switch (fieldFilter.op) {
|
|
4407
|
+
case "==" /* EQUAL */:
|
|
4408
|
+
case "in" /* IN */:
|
|
4409
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4410
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4411
|
+
// `value != 'ab'`).
|
|
4412
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4413
|
+
break;
|
|
4414
|
+
case "not-in" /* NOT_IN */:
|
|
4415
|
+
case "!=" /* NOT_EQUAL */:
|
|
4416
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4417
|
+
// segments and hence we can return early here.
|
|
4418
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4419
|
+
return Array.from(values.values());
|
|
4420
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
return null;
|
|
4425
|
+
}
|
|
4426
|
+
/**
|
|
4427
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4428
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4429
|
+
* exists.
|
|
4430
|
+
*/
|
|
4431
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4432
|
+
const values = [];
|
|
4433
|
+
let inclusive = true;
|
|
4434
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4435
|
+
// startAt.
|
|
4436
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4437
|
+
let segmentValue = undefined;
|
|
4438
|
+
let segmentInclusive = true;
|
|
4439
|
+
// Process all filters to find a value for the current field segment
|
|
4440
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4441
|
+
let filterValue = undefined;
|
|
4442
|
+
let filterInclusive = true;
|
|
4443
|
+
switch (fieldFilter.op) {
|
|
4444
|
+
case "<" /* LESS_THAN */:
|
|
4445
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4446
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4447
|
+
break;
|
|
4448
|
+
case "==" /* EQUAL */:
|
|
4449
|
+
case "in" /* IN */:
|
|
4450
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4451
|
+
filterValue = fieldFilter.value;
|
|
4452
|
+
break;
|
|
4453
|
+
case ">" /* GREATER_THAN */:
|
|
4454
|
+
filterValue = fieldFilter.value;
|
|
4455
|
+
filterInclusive = false;
|
|
4456
|
+
break;
|
|
4457
|
+
case "!=" /* NOT_EQUAL */:
|
|
4458
|
+
case "not-in" /* NOT_IN */:
|
|
4459
|
+
filterValue = MIN_VALUE;
|
|
4460
|
+
break;
|
|
4461
|
+
// Remaining filters cannot be used as lower bounds.
|
|
4462
|
+
}
|
|
4463
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4464
|
+
segmentValue = filterValue;
|
|
4465
|
+
segmentInclusive = filterInclusive;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
// If there is a startAt bound, compare the values against the existing
|
|
4469
|
+
// boundary to see if we can narrow the scope.
|
|
4470
|
+
if (target.startAt !== null) {
|
|
4471
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4472
|
+
const orderBy = target.orderBy[i];
|
|
4473
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4474
|
+
const cursorValue = target.startAt.position[i];
|
|
4475
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4476
|
+
segmentValue = cursorValue;
|
|
4477
|
+
segmentInclusive = target.startAt.inclusive;
|
|
4478
|
+
}
|
|
4479
|
+
break;
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
if (segmentValue === undefined) {
|
|
4484
|
+
// No lower bound exists
|
|
4485
|
+
return null;
|
|
4486
|
+
}
|
|
4487
|
+
values.push(segmentValue);
|
|
4488
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4489
|
+
}
|
|
4490
|
+
return new Bound(values, inclusive);
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
4494
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
4495
|
+
* upper bound exists.
|
|
4496
|
+
*/
|
|
4497
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
4498
|
+
const values = [];
|
|
4499
|
+
let inclusive = true;
|
|
4500
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4501
|
+
// endAt.
|
|
4502
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4503
|
+
let segmentValue = undefined;
|
|
4504
|
+
let segmentInclusive = true;
|
|
4505
|
+
// Process all filters to find a value for the current field segment
|
|
4506
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4507
|
+
let filterValue = undefined;
|
|
4508
|
+
let filterInclusive = true;
|
|
4509
|
+
switch (fieldFilter.op) {
|
|
4510
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4511
|
+
case ">" /* GREATER_THAN */:
|
|
4512
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4513
|
+
filterInclusive = false;
|
|
4514
|
+
break;
|
|
4515
|
+
case "==" /* EQUAL */:
|
|
4516
|
+
case "in" /* IN */:
|
|
4517
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4518
|
+
filterValue = fieldFilter.value;
|
|
4519
|
+
break;
|
|
4520
|
+
case "<" /* LESS_THAN */:
|
|
4521
|
+
filterValue = fieldFilter.value;
|
|
4522
|
+
filterInclusive = false;
|
|
4523
|
+
break;
|
|
4524
|
+
case "!=" /* NOT_EQUAL */:
|
|
4525
|
+
case "not-in" /* NOT_IN */:
|
|
4526
|
+
filterValue = MAX_VALUE;
|
|
4527
|
+
break;
|
|
4528
|
+
// Remaining filters cannot be used as upper bounds.
|
|
4529
|
+
}
|
|
4530
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4531
|
+
segmentValue = filterValue;
|
|
4532
|
+
segmentInclusive = filterInclusive;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
// If there is a endAt bound, compare the values against the existing
|
|
4536
|
+
// boundary to see if we can narrow the scope.
|
|
4537
|
+
if (target.endAt !== null) {
|
|
4538
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4539
|
+
const orderBy = target.orderBy[i];
|
|
4540
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4541
|
+
const cursorValue = target.endAt.position[i];
|
|
4542
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4543
|
+
segmentValue = cursorValue;
|
|
4544
|
+
segmentInclusive = target.endAt.inclusive;
|
|
4545
|
+
}
|
|
4546
|
+
break;
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
if (segmentValue === undefined) {
|
|
4551
|
+
// No upper bound exists
|
|
4552
|
+
return null;
|
|
4553
|
+
}
|
|
4554
|
+
values.push(segmentValue);
|
|
4555
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4556
|
+
}
|
|
4557
|
+
return new Bound(values, inclusive);
|
|
4558
|
+
}
|
|
4794
4559
|
class Filter {
|
|
4795
4560
|
}
|
|
4796
4561
|
class FieldFilter extends Filter {
|
|
@@ -5392,6 +5157,116 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5392
5157
|
}
|
|
5393
5158
|
}
|
|
5394
5159
|
|
|
5160
|
+
/**
|
|
5161
|
+
* @license
|
|
5162
|
+
* Copyright 2017 Google LLC
|
|
5163
|
+
*
|
|
5164
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5165
|
+
* you may not use this file except in compliance with the License.
|
|
5166
|
+
* You may obtain a copy of the License at
|
|
5167
|
+
*
|
|
5168
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5169
|
+
*
|
|
5170
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5171
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5172
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5173
|
+
* See the License for the specific language governing permissions and
|
|
5174
|
+
* limitations under the License.
|
|
5175
|
+
*/
|
|
5176
|
+
/**
|
|
5177
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
5178
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
5179
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
5180
|
+
* automatically handles collisions of keys.
|
|
5181
|
+
*/
|
|
5182
|
+
class ObjectMap {
|
|
5183
|
+
constructor(mapKeyFn, equalsFn) {
|
|
5184
|
+
this.mapKeyFn = mapKeyFn;
|
|
5185
|
+
this.equalsFn = equalsFn;
|
|
5186
|
+
/**
|
|
5187
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
5188
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
5189
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
5190
|
+
* constant time lookups in practice.
|
|
5191
|
+
*/
|
|
5192
|
+
this.inner = {};
|
|
5193
|
+
/** The number of entries stored in the map */
|
|
5194
|
+
this.innerSize = 0;
|
|
5195
|
+
}
|
|
5196
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
5197
|
+
get(key) {
|
|
5198
|
+
const id = this.mapKeyFn(key);
|
|
5199
|
+
const matches = this.inner[id];
|
|
5200
|
+
if (matches === undefined) {
|
|
5201
|
+
return undefined;
|
|
5202
|
+
}
|
|
5203
|
+
for (const [otherKey, value] of matches) {
|
|
5204
|
+
if (this.equalsFn(otherKey, key)) {
|
|
5205
|
+
return value;
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
return undefined;
|
|
5209
|
+
}
|
|
5210
|
+
has(key) {
|
|
5211
|
+
return this.get(key) !== undefined;
|
|
5212
|
+
}
|
|
5213
|
+
/** Put this key and value in the map. */
|
|
5214
|
+
set(key, value) {
|
|
5215
|
+
const id = this.mapKeyFn(key);
|
|
5216
|
+
const matches = this.inner[id];
|
|
5217
|
+
if (matches === undefined) {
|
|
5218
|
+
this.inner[id] = [[key, value]];
|
|
5219
|
+
this.innerSize++;
|
|
5220
|
+
return;
|
|
5221
|
+
}
|
|
5222
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5223
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5224
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
5225
|
+
matches[i] = [key, value];
|
|
5226
|
+
return;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
matches.push([key, value]);
|
|
5230
|
+
this.innerSize++;
|
|
5231
|
+
}
|
|
5232
|
+
/**
|
|
5233
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
5234
|
+
*/
|
|
5235
|
+
delete(key) {
|
|
5236
|
+
const id = this.mapKeyFn(key);
|
|
5237
|
+
const matches = this.inner[id];
|
|
5238
|
+
if (matches === undefined) {
|
|
5239
|
+
return false;
|
|
5240
|
+
}
|
|
5241
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5242
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5243
|
+
if (matches.length === 1) {
|
|
5244
|
+
delete this.inner[id];
|
|
5245
|
+
}
|
|
5246
|
+
else {
|
|
5247
|
+
matches.splice(i, 1);
|
|
5248
|
+
}
|
|
5249
|
+
this.innerSize--;
|
|
5250
|
+
return true;
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
return false;
|
|
5254
|
+
}
|
|
5255
|
+
forEach(fn) {
|
|
5256
|
+
forEach(this.inner, (_, entries) => {
|
|
5257
|
+
for (const [k, v] of entries) {
|
|
5258
|
+
fn(k, v);
|
|
5259
|
+
}
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
isEmpty() {
|
|
5263
|
+
return isEmpty(this.inner);
|
|
5264
|
+
}
|
|
5265
|
+
size() {
|
|
5266
|
+
return this.innerSize;
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5395
5270
|
/**
|
|
5396
5271
|
* @license
|
|
5397
5272
|
* Copyright 2017 Google LLC
|
|
@@ -5534,7 +5409,7 @@ class SortedMapIterator {
|
|
|
5534
5409
|
while (!node.isEmpty()) {
|
|
5535
5410
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5536
5411
|
// flip the comparison if we're going in reverse
|
|
5537
|
-
if (isReverse) {
|
|
5412
|
+
if (startKey && isReverse) {
|
|
5538
5413
|
cmp *= -1;
|
|
5539
5414
|
}
|
|
5540
5415
|
if (cmp < 0) {
|
|
@@ -6103,6 +5978,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
|
6103
5978
|
function documentMap() {
|
|
6104
5979
|
return EMPTY_DOCUMENT_MAP;
|
|
6105
5980
|
}
|
|
5981
|
+
function newOverlayMap() {
|
|
5982
|
+
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
5983
|
+
}
|
|
6106
5984
|
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
|
|
6107
5985
|
function documentVersionMap() {
|
|
6108
5986
|
return EMPTY_DOCUMENT_VERSION_MAP;
|
|
@@ -8781,30 +8659,36 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
|
8781
8659
|
}
|
|
8782
8660
|
/** Encodes a document for storage locally. */
|
|
8783
8661
|
function toDbRemoteDocument(localSerializer, document) {
|
|
8784
|
-
const dbReadTime = toDbTimestampKey(document.readTime);
|
|
8785
8662
|
const parentPath = document.key.path.popLast().toArray();
|
|
8663
|
+
const readTime = toDbTimestampKey(document.readTime);
|
|
8786
8664
|
if (document.isFoundDocument()) {
|
|
8787
8665
|
const doc = toDocument(localSerializer.remoteSerializer, document);
|
|
8788
8666
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8789
|
-
return
|
|
8790
|
-
|
|
8791
|
-
|
|
8667
|
+
return {
|
|
8668
|
+
document: doc,
|
|
8669
|
+
hasCommittedMutations,
|
|
8670
|
+
readTime,
|
|
8671
|
+
parentPath
|
|
8672
|
+
};
|
|
8792
8673
|
}
|
|
8793
8674
|
else if (document.isNoDocument()) {
|
|
8794
8675
|
const path = document.key.path.toArray();
|
|
8795
|
-
const version = toDbTimestamp(document.version);
|
|
8796
8676
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8797
|
-
return
|
|
8798
|
-
|
|
8799
|
-
|
|
8677
|
+
return {
|
|
8678
|
+
noDocument: { path, readTime: toDbTimestamp(document.version) },
|
|
8679
|
+
hasCommittedMutations,
|
|
8680
|
+
readTime,
|
|
8681
|
+
parentPath
|
|
8682
|
+
};
|
|
8800
8683
|
}
|
|
8801
8684
|
else if (document.isUnknownDocument()) {
|
|
8802
8685
|
const path = document.key.path.toArray();
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8686
|
+
return {
|
|
8687
|
+
unknownDocument: { path, version: toDbTimestamp(document.version) },
|
|
8688
|
+
hasCommittedMutations: true,
|
|
8689
|
+
readTime,
|
|
8690
|
+
parentPath
|
|
8691
|
+
};
|
|
8808
8692
|
}
|
|
8809
8693
|
else {
|
|
8810
8694
|
return fail();
|
|
@@ -8820,7 +8704,7 @@ function fromDbTimestampKey(dbTimestampKey) {
|
|
|
8820
8704
|
}
|
|
8821
8705
|
function toDbTimestamp(snapshotVersion) {
|
|
8822
8706
|
const timestamp = snapshotVersion.toTimestamp();
|
|
8823
|
-
return
|
|
8707
|
+
return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
|
|
8824
8708
|
}
|
|
8825
8709
|
function fromDbTimestamp(dbTimestamp) {
|
|
8826
8710
|
const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
|
|
@@ -8830,7 +8714,13 @@ function fromDbTimestamp(dbTimestamp) {
|
|
|
8830
8714
|
function toDbMutationBatch(localSerializer, userId, batch) {
|
|
8831
8715
|
const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8832
8716
|
const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8833
|
-
return
|
|
8717
|
+
return {
|
|
8718
|
+
userId,
|
|
8719
|
+
batchId: batch.batchId,
|
|
8720
|
+
localWriteTimeMs: batch.localWriteTime.toMillis(),
|
|
8721
|
+
baseMutations: serializedBaseMutations,
|
|
8722
|
+
mutations: serializedMutations
|
|
8723
|
+
};
|
|
8834
8724
|
}
|
|
8835
8725
|
/** Decodes a DbMutationBatch into a MutationBatch */
|
|
8836
8726
|
function fromDbMutationBatch(localSerializer, dbBatch) {
|
|
@@ -8886,7 +8776,15 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8886
8776
|
// convert it to a base64 string for storage.
|
|
8887
8777
|
const resumeToken = targetData.resumeToken.toBase64();
|
|
8888
8778
|
// lastListenSequenceNumber is always 0 until we do real GC.
|
|
8889
|
-
return
|
|
8779
|
+
return {
|
|
8780
|
+
targetId: targetData.targetId,
|
|
8781
|
+
canonicalId: canonifyTarget(targetData.target),
|
|
8782
|
+
readTime: dbTimestamp,
|
|
8783
|
+
resumeToken,
|
|
8784
|
+
lastListenSequenceNumber: targetData.sequenceNumber,
|
|
8785
|
+
lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
|
|
8786
|
+
query: queryProto
|
|
8787
|
+
};
|
|
8890
8788
|
}
|
|
8891
8789
|
/**
|
|
8892
8790
|
* A helper function for figuring out what kind of query has been stored.
|
|
@@ -8965,7 +8863,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
|
8965
8863
|
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
8966
8864
|
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
8967
8865
|
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
8968
|
-
return
|
|
8866
|
+
return {
|
|
8867
|
+
userId,
|
|
8868
|
+
collectionPath,
|
|
8869
|
+
documentId,
|
|
8870
|
+
collectionGroup: overlay.mutation.key.getCollectionGroup(),
|
|
8871
|
+
largestBatchId: overlay.largestBatchId,
|
|
8872
|
+
overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
|
|
8873
|
+
};
|
|
8969
8874
|
}
|
|
8970
8875
|
/**
|
|
8971
8876
|
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
@@ -8977,7 +8882,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
|
|
|
8977
8882
|
return [userId, collectionPath, docId];
|
|
8978
8883
|
}
|
|
8979
8884
|
function toDbIndexConfiguration(index) {
|
|
8980
|
-
return
|
|
8885
|
+
return {
|
|
8886
|
+
indexId: index.indexId,
|
|
8887
|
+
collectionGroup: index.collectionGroup,
|
|
8888
|
+
fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
|
|
8889
|
+
};
|
|
8981
8890
|
}
|
|
8982
8891
|
function fromDbIndexConfiguration(index, state) {
|
|
8983
8892
|
const decodedState = state
|
|
@@ -8987,7 +8896,14 @@ function fromDbIndexConfiguration(index, state) {
|
|
|
8987
8896
|
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
8988
8897
|
}
|
|
8989
8898
|
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
8990
|
-
return
|
|
8899
|
+
return {
|
|
8900
|
+
indexId,
|
|
8901
|
+
uid: user.uid || '',
|
|
8902
|
+
sequenceNumber,
|
|
8903
|
+
readTime: toDbTimestamp(offset.readTime),
|
|
8904
|
+
documentKey: encodeResourcePath(offset.documentKey.path),
|
|
8905
|
+
largestBatchId: offset.largestBatchId
|
|
8906
|
+
};
|
|
8991
8907
|
}
|
|
8992
8908
|
|
|
8993
8909
|
/**
|
|
@@ -9038,13 +8954,13 @@ class IndexedDbBundleCache {
|
|
|
9038
8954
|
* Helper to get a typed SimpleDbStore for the bundles object store.
|
|
9039
8955
|
*/
|
|
9040
8956
|
function bundlesStore(txn) {
|
|
9041
|
-
return getStore(txn,
|
|
8957
|
+
return getStore(txn, DbBundleStore);
|
|
9042
8958
|
}
|
|
9043
8959
|
/**
|
|
9044
8960
|
* Helper to get a typed SimpleDbStore for the namedQueries object store.
|
|
9045
8961
|
*/
|
|
9046
8962
|
function namedQueriesStore(txn) {
|
|
9047
|
-
return getStore(txn,
|
|
8963
|
+
return getStore(txn, DbNamedQueryStore);
|
|
9048
8964
|
}
|
|
9049
8965
|
|
|
9050
8966
|
/**
|
|
@@ -9091,7 +9007,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9091
9007
|
}
|
|
9092
9008
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9093
9009
|
const promises = [];
|
|
9094
|
-
overlays.forEach(mutation => {
|
|
9010
|
+
overlays.forEach((_, mutation) => {
|
|
9095
9011
|
const overlay = new Overlay(largestBatchId, mutation);
|
|
9096
9012
|
promises.push(this.saveOverlay(transaction, overlay));
|
|
9097
9013
|
});
|
|
@@ -9106,19 +9022,19 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9106
9022
|
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9107
9023
|
/*lowerOpen=*/ false,
|
|
9108
9024
|
/*upperOpen=*/ true);
|
|
9109
|
-
promises.push(documentOverlayStore(transaction).deleteAll(
|
|
9025
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
|
|
9110
9026
|
});
|
|
9111
9027
|
return PersistencePromise.waitFor(promises);
|
|
9112
9028
|
}
|
|
9113
9029
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9114
|
-
const result =
|
|
9030
|
+
const result = newOverlayMap();
|
|
9115
9031
|
const collectionPath = encodeResourcePath(collection);
|
|
9116
9032
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9117
9033
|
// is not inclusive.
|
|
9118
9034
|
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9119
9035
|
/*lowerOpen=*/ true);
|
|
9120
9036
|
return documentOverlayStore(transaction)
|
|
9121
|
-
.loadAll(
|
|
9037
|
+
.loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
|
|
9122
9038
|
.next(dbOverlays => {
|
|
9123
9039
|
for (const dbOverlay of dbOverlays) {
|
|
9124
9040
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
@@ -9128,7 +9044,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9128
9044
|
});
|
|
9129
9045
|
}
|
|
9130
9046
|
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9131
|
-
const result =
|
|
9047
|
+
const result = newOverlayMap();
|
|
9132
9048
|
let currentBatchId = undefined;
|
|
9133
9049
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9134
9050
|
// is not inclusive.
|
|
@@ -9136,7 +9052,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9136
9052
|
/*lowerOpen=*/ true);
|
|
9137
9053
|
return documentOverlayStore(transaction)
|
|
9138
9054
|
.iterate({
|
|
9139
|
-
index:
|
|
9055
|
+
index: DbDocumentOverlayCollectionGroupOverlayIndex,
|
|
9140
9056
|
range
|
|
9141
9057
|
}, (_, dbOverlay, control) => {
|
|
9142
9058
|
// We do not want to return partial batch overlays, even if the size
|
|
@@ -9144,7 +9060,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9144
9060
|
// continue to aggregate results even after the result size exceeds
|
|
9145
9061
|
// `count` if there are more overlays from the `currentBatchId`.
|
|
9146
9062
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9147
|
-
if (result.size < count ||
|
|
9063
|
+
if (result.size() < count ||
|
|
9148
9064
|
overlay.largestBatchId === currentBatchId) {
|
|
9149
9065
|
result.set(overlay.getKey(), overlay);
|
|
9150
9066
|
currentBatchId = overlay.largestBatchId;
|
|
@@ -9163,7 +9079,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9163
9079
|
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9164
9080
|
*/
|
|
9165
9081
|
function documentOverlayStore(txn) {
|
|
9166
|
-
return getStore(txn,
|
|
9082
|
+
return getStore(txn, DbDocumentOverlayStore);
|
|
9167
9083
|
}
|
|
9168
9084
|
|
|
9169
9085
|
/**
|
|
@@ -9719,21 +9635,40 @@ class IndexEntry {
|
|
|
9719
9635
|
this.arrayValue = arrayValue;
|
|
9720
9636
|
this.directionalValue = directionalValue;
|
|
9721
9637
|
}
|
|
9638
|
+
/**
|
|
9639
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
9640
|
+
* directional value.
|
|
9641
|
+
*/
|
|
9642
|
+
successor() {
|
|
9643
|
+
const currentLength = this.directionalValue.length;
|
|
9644
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
9645
|
+
? currentLength + 1
|
|
9646
|
+
: currentLength;
|
|
9647
|
+
const successor = new Uint8Array(newLength);
|
|
9648
|
+
successor.set(this.directionalValue, 0);
|
|
9649
|
+
if (newLength !== currentLength) {
|
|
9650
|
+
successor.set([0], this.directionalValue.length);
|
|
9651
|
+
}
|
|
9652
|
+
else {
|
|
9653
|
+
++successor[successor.length - 1];
|
|
9654
|
+
}
|
|
9655
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
9656
|
+
}
|
|
9722
9657
|
}
|
|
9723
9658
|
function indexEntryComparator(left, right) {
|
|
9724
9659
|
let cmp = left.indexId - right.indexId;
|
|
9725
9660
|
if (cmp !== 0) {
|
|
9726
9661
|
return cmp;
|
|
9727
9662
|
}
|
|
9728
|
-
cmp =
|
|
9663
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
9729
9664
|
if (cmp !== 0) {
|
|
9730
9665
|
return cmp;
|
|
9731
9666
|
}
|
|
9732
|
-
cmp = compareByteArrays(left.
|
|
9667
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
9733
9668
|
if (cmp !== 0) {
|
|
9734
9669
|
return cmp;
|
|
9735
9670
|
}
|
|
9736
|
-
return
|
|
9671
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
9737
9672
|
}
|
|
9738
9673
|
function compareByteArrays(left, right) {
|
|
9739
9674
|
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
@@ -9745,6 +9680,160 @@ function compareByteArrays(left, right) {
|
|
|
9745
9680
|
return left.length - right.length;
|
|
9746
9681
|
}
|
|
9747
9682
|
|
|
9683
|
+
/**
|
|
9684
|
+
* @license
|
|
9685
|
+
* Copyright 2022 Google LLC
|
|
9686
|
+
*
|
|
9687
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9688
|
+
* you may not use this file except in compliance with the License.
|
|
9689
|
+
* You may obtain a copy of the License at
|
|
9690
|
+
*
|
|
9691
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9692
|
+
*
|
|
9693
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9694
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9695
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9696
|
+
* See the License for the specific language governing permissions and
|
|
9697
|
+
* limitations under the License.
|
|
9698
|
+
*/
|
|
9699
|
+
/**
|
|
9700
|
+
* A light query planner for Firestore.
|
|
9701
|
+
*
|
|
9702
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
9703
|
+
* determines whether a given index can be used to serve the specified target.
|
|
9704
|
+
*
|
|
9705
|
+
* The following table showcases some possible index configurations:
|
|
9706
|
+
*
|
|
9707
|
+
* Query | Index
|
|
9708
|
+
* -----------------------------------------------------------------------------
|
|
9709
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
9710
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
9711
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
9712
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
9713
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
9714
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
9715
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
9716
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
9717
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
9718
|
+
*/
|
|
9719
|
+
class TargetIndexMatcher {
|
|
9720
|
+
constructor(target) {
|
|
9721
|
+
this.collectionId =
|
|
9722
|
+
target.collectionGroup != null
|
|
9723
|
+
? target.collectionGroup
|
|
9724
|
+
: target.path.lastSegment();
|
|
9725
|
+
this.orderBys = target.orderBy;
|
|
9726
|
+
this.equalityFilters = [];
|
|
9727
|
+
for (const filter of target.filters) {
|
|
9728
|
+
const fieldFilter = filter;
|
|
9729
|
+
if (fieldFilter.isInequality()) {
|
|
9730
|
+
this.inequalityFilter = fieldFilter;
|
|
9731
|
+
}
|
|
9732
|
+
else {
|
|
9733
|
+
this.equalityFilters.push(fieldFilter);
|
|
9734
|
+
}
|
|
9735
|
+
}
|
|
9736
|
+
}
|
|
9737
|
+
/**
|
|
9738
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
9739
|
+
* target.
|
|
9740
|
+
*
|
|
9741
|
+
* An index is considered capable of serving the target when:
|
|
9742
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
9743
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
9744
|
+
* fewer.
|
|
9745
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
9746
|
+
* have a corresponding `CONTAINS` segment.
|
|
9747
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
9748
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
9749
|
+
* clauses.
|
|
9750
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
9751
|
+
* - The optional segment for the inequality filter must appear after all
|
|
9752
|
+
* equality segments.
|
|
9753
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
9754
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
9755
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
9756
|
+
* omitted.
|
|
9757
|
+
*/
|
|
9758
|
+
servedByIndex(index) {
|
|
9759
|
+
// If there is an array element, find a matching filter.
|
|
9760
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
9761
|
+
if (arraySegment !== undefined &&
|
|
9762
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
9763
|
+
return false;
|
|
9764
|
+
}
|
|
9765
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
9766
|
+
let segmentIndex = 0;
|
|
9767
|
+
let orderBysIndex = 0;
|
|
9768
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
9769
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9770
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
9771
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
9772
|
+
// Since it is not possible to use the same field path in both an equality
|
|
9773
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
9774
|
+
// that a matching equality segment should instead be used to map to an
|
|
9775
|
+
// inequality filter or orderBy clause.
|
|
9776
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
9777
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
9778
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
9779
|
+
// clauses.
|
|
9780
|
+
break;
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
// If we already have processed all segments, all segments are used to serve
|
|
9784
|
+
// the equality filters and we do not need to map any segments to the
|
|
9785
|
+
// target's inequality and orderBy clauses.
|
|
9786
|
+
if (segmentIndex === segments.length) {
|
|
9787
|
+
return true;
|
|
9788
|
+
}
|
|
9789
|
+
// If there is an inequality filter, the next segment must match both the
|
|
9790
|
+
// filter and the first orderBy clause.
|
|
9791
|
+
if (this.inequalityFilter !== undefined) {
|
|
9792
|
+
const segment = segments[segmentIndex];
|
|
9793
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
9794
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9795
|
+
return false;
|
|
9796
|
+
}
|
|
9797
|
+
++segmentIndex;
|
|
9798
|
+
}
|
|
9799
|
+
// All remaining segments need to represent the prefix of the target's
|
|
9800
|
+
// orderBy.
|
|
9801
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9802
|
+
const segment = segments[segmentIndex];
|
|
9803
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
9804
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9805
|
+
return false;
|
|
9806
|
+
}
|
|
9807
|
+
}
|
|
9808
|
+
return true;
|
|
9809
|
+
}
|
|
9810
|
+
hasMatchingEqualityFilter(segment) {
|
|
9811
|
+
for (const filter of this.equalityFilters) {
|
|
9812
|
+
if (this.matchesFilter(filter, segment)) {
|
|
9813
|
+
return true;
|
|
9814
|
+
}
|
|
9815
|
+
}
|
|
9816
|
+
return false;
|
|
9817
|
+
}
|
|
9818
|
+
matchesFilter(filter, segment) {
|
|
9819
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
9820
|
+
return false;
|
|
9821
|
+
}
|
|
9822
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
9823
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
9824
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
9825
|
+
}
|
|
9826
|
+
matchesOrderBy(orderBy, segment) {
|
|
9827
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
9828
|
+
return false;
|
|
9829
|
+
}
|
|
9830
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
9831
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
9832
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
9833
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
9834
|
+
}
|
|
9835
|
+
}
|
|
9836
|
+
|
|
9748
9837
|
/**
|
|
9749
9838
|
* @license
|
|
9750
9839
|
* Copyright 2019 Google LLC
|
|
@@ -9783,9 +9872,9 @@ class MemoryIndexManager {
|
|
|
9783
9872
|
// Field indices are not supported with memory persistence.
|
|
9784
9873
|
return PersistencePromise.resolve();
|
|
9785
9874
|
}
|
|
9786
|
-
getDocumentsMatchingTarget(transaction,
|
|
9875
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
9787
9876
|
// Field indices are not supported with memory persistence.
|
|
9788
|
-
return PersistencePromise.resolve(
|
|
9877
|
+
return PersistencePromise.resolve(null);
|
|
9789
9878
|
}
|
|
9790
9879
|
getFieldIndex(transaction, target) {
|
|
9791
9880
|
// Field indices are not supported with memory persistence.
|
|
@@ -9857,6 +9946,7 @@ class MemoryCollectionParentIndex {
|
|
|
9857
9946
|
* limitations under the License.
|
|
9858
9947
|
*/
|
|
9859
9948
|
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
9949
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
9860
9950
|
/**
|
|
9861
9951
|
* A persisted implementation of IndexManager.
|
|
9862
9952
|
*
|
|
@@ -9874,6 +9964,11 @@ class IndexedDbIndexManager {
|
|
|
9874
9964
|
* used to satisfy reads.
|
|
9875
9965
|
*/
|
|
9876
9966
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
9967
|
+
/**
|
|
9968
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
9969
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
9970
|
+
*/
|
|
9971
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
9877
9972
|
this.uid = user.uid || '';
|
|
9878
9973
|
}
|
|
9879
9974
|
/**
|
|
@@ -9942,13 +10037,111 @@ class IndexedDbIndexManager {
|
|
|
9942
10037
|
/*lowerOpen=*/ false,
|
|
9943
10038
|
/*upperOpen=*/ true)));
|
|
9944
10039
|
}
|
|
9945
|
-
getDocumentsMatchingTarget(transaction,
|
|
9946
|
-
|
|
9947
|
-
|
|
10040
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10041
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10042
|
+
let canServeTarget = true;
|
|
10043
|
+
const indexes = new Map();
|
|
10044
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10045
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10046
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10047
|
+
indexes.set(subTarget, index);
|
|
10048
|
+
});
|
|
10049
|
+
}).next(() => {
|
|
10050
|
+
if (!canServeTarget) {
|
|
10051
|
+
return PersistencePromise.resolve(null);
|
|
10052
|
+
}
|
|
10053
|
+
else {
|
|
10054
|
+
let result = documentKeySet();
|
|
10055
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10056
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10057
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10058
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10059
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10060
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10061
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10062
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10063
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10064
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10065
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10066
|
+
return indexEntries
|
|
10067
|
+
.loadFirst(indexRange, target.limit)
|
|
10068
|
+
.next(entries => {
|
|
10069
|
+
entries.forEach(entry => {
|
|
10070
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10071
|
+
});
|
|
10072
|
+
});
|
|
10073
|
+
});
|
|
10074
|
+
}).next(() => result);
|
|
10075
|
+
}
|
|
10076
|
+
});
|
|
10077
|
+
}
|
|
10078
|
+
getSubTargets(target) {
|
|
10079
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10080
|
+
if (subTargets) {
|
|
10081
|
+
return subTargets;
|
|
10082
|
+
}
|
|
10083
|
+
// TODO(orquery): Implement DNF transform
|
|
10084
|
+
subTargets = [target];
|
|
10085
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10086
|
+
return subTargets;
|
|
10087
|
+
}
|
|
10088
|
+
/**
|
|
10089
|
+
* Constructs a key range query on `DbIndexEntryStore` that unions all
|
|
10090
|
+
* bounds.
|
|
10091
|
+
*/
|
|
10092
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10093
|
+
// The number of total index scans we union together. This is similar to a
|
|
10094
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10095
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10096
|
+
// combined with the values from the query bounds.
|
|
10097
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10098
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10099
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10100
|
+
const indexRanges = [];
|
|
10101
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10102
|
+
const arrayValue = arrayValues
|
|
10103
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10104
|
+
: EMPTY_VALUE;
|
|
10105
|
+
const lowerBound = lowerBounds
|
|
10106
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10107
|
+
: this.generateEmptyBound(indexId);
|
|
10108
|
+
const upperBound = upperBounds
|
|
10109
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10110
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10111
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10112
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10113
|
+
/* inclusive= */ true))));
|
|
10114
|
+
}
|
|
10115
|
+
return indexRanges;
|
|
10116
|
+
}
|
|
10117
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10118
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10119
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10120
|
+
return inclusive ? entry : entry.successor();
|
|
10121
|
+
}
|
|
10122
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10123
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10124
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10125
|
+
return inclusive ? entry.successor() : entry;
|
|
10126
|
+
}
|
|
10127
|
+
/**
|
|
10128
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10129
|
+
* and user.
|
|
10130
|
+
*/
|
|
10131
|
+
generateEmptyBound(indexId) {
|
|
10132
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
9948
10133
|
}
|
|
9949
10134
|
getFieldIndex(transaction, target) {
|
|
9950
|
-
|
|
9951
|
-
|
|
10135
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10136
|
+
const collectionGroup = target.collectionGroup != null
|
|
10137
|
+
? target.collectionGroup
|
|
10138
|
+
: target.path.lastSegment();
|
|
10139
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10140
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10141
|
+
// Return the index that matches the most number of segments.
|
|
10142
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10143
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10144
|
+
});
|
|
9952
10145
|
}
|
|
9953
10146
|
/**
|
|
9954
10147
|
* Returns the byte encoded form of the directional values in the field index.
|
|
@@ -9973,11 +10166,79 @@ class IndexedDbIndexManager {
|
|
|
9973
10166
|
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
9974
10167
|
return encoder.encodedBytes();
|
|
9975
10168
|
}
|
|
10169
|
+
/**
|
|
10170
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10171
|
+
* For IN queries, a list of possible values is returned.
|
|
10172
|
+
*/
|
|
10173
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10174
|
+
if (bound === null) {
|
|
10175
|
+
return [];
|
|
10176
|
+
}
|
|
10177
|
+
let encoders = [];
|
|
10178
|
+
encoders.push(new IndexByteEncoder());
|
|
10179
|
+
let boundIdx = 0;
|
|
10180
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10181
|
+
const value = bound[boundIdx++];
|
|
10182
|
+
for (const encoder of encoders) {
|
|
10183
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10184
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10185
|
+
}
|
|
10186
|
+
else {
|
|
10187
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10188
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10189
|
+
}
|
|
10190
|
+
}
|
|
10191
|
+
}
|
|
10192
|
+
return this.getEncodedBytes(encoders);
|
|
10193
|
+
}
|
|
10194
|
+
/**
|
|
10195
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10196
|
+
* queries, a list of possible values is returned.
|
|
10197
|
+
*/
|
|
10198
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10199
|
+
if (bound == null) {
|
|
10200
|
+
return null;
|
|
10201
|
+
}
|
|
10202
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10203
|
+
}
|
|
10204
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10205
|
+
getEncodedBytes(encoders) {
|
|
10206
|
+
const result = [];
|
|
10207
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10208
|
+
result[i] = encoders[i].encodedBytes();
|
|
10209
|
+
}
|
|
10210
|
+
return result;
|
|
10211
|
+
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Creates a separate encoder for each element of an array.
|
|
10214
|
+
*
|
|
10215
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10216
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10217
|
+
* list of new encoders is returned.
|
|
10218
|
+
*/
|
|
10219
|
+
expandIndexValues(encoders, segment, value) {
|
|
10220
|
+
const prefixes = [...encoders];
|
|
10221
|
+
const results = [];
|
|
10222
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10223
|
+
for (const prefix of prefixes) {
|
|
10224
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10225
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10226
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10227
|
+
results.push(clonedEncoder);
|
|
10228
|
+
}
|
|
10229
|
+
}
|
|
10230
|
+
return results;
|
|
10231
|
+
}
|
|
10232
|
+
isInFilter(target, fieldPath) {
|
|
10233
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10234
|
+
f.field.isEqual(fieldPath) &&
|
|
10235
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
10236
|
+
}
|
|
9976
10237
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9977
10238
|
const indexes = indexConfigurationStore(transaction);
|
|
9978
10239
|
const states = indexStateStore(transaction);
|
|
9979
10240
|
return (collectionGroup
|
|
9980
|
-
? indexes.loadAll(
|
|
10241
|
+
? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9981
10242
|
: indexes.loadAll()).next(indexConfigs => {
|
|
9982
10243
|
const result = [];
|
|
9983
10244
|
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
@@ -9994,7 +10255,12 @@ class IndexedDbIndexManager {
|
|
|
9994
10255
|
if (indexes.length === 0) {
|
|
9995
10256
|
return null;
|
|
9996
10257
|
}
|
|
9997
|
-
indexes.sort((l, r) =>
|
|
10258
|
+
indexes.sort((l, r) => {
|
|
10259
|
+
const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
|
|
10260
|
+
return cmp !== 0
|
|
10261
|
+
? cmp
|
|
10262
|
+
: primitiveComparator(l.collectionGroup, r.collectionGroup);
|
|
10263
|
+
});
|
|
9998
10264
|
return indexes[0].collectionGroup;
|
|
9999
10265
|
});
|
|
10000
10266
|
}
|
|
@@ -10002,7 +10268,7 @@ class IndexedDbIndexManager {
|
|
|
10002
10268
|
const indexes = indexConfigurationStore(transaction);
|
|
10003
10269
|
const states = indexStateStore(transaction);
|
|
10004
10270
|
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
10005
|
-
.loadAll(
|
|
10271
|
+
.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10006
10272
|
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
10007
10273
|
}
|
|
10008
10274
|
updateIndexEntries(transaction, documents) {
|
|
@@ -10033,7 +10299,13 @@ class IndexedDbIndexManager {
|
|
|
10033
10299
|
}
|
|
10034
10300
|
addIndexEntry(transaction, document, indexEntry) {
|
|
10035
10301
|
const indexEntries = indexEntriesStore(transaction);
|
|
10036
|
-
return indexEntries.put(
|
|
10302
|
+
return indexEntries.put({
|
|
10303
|
+
indexId: indexEntry.indexId,
|
|
10304
|
+
uid: this.uid,
|
|
10305
|
+
arrayValue: indexEntry.arrayValue,
|
|
10306
|
+
directionalValue: indexEntry.directionalValue,
|
|
10307
|
+
documentKey: encodeResourcePath(document.key.path)
|
|
10308
|
+
});
|
|
10037
10309
|
}
|
|
10038
10310
|
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10039
10311
|
const indexEntries = indexEntriesStore(transaction);
|
|
@@ -10050,7 +10322,7 @@ class IndexedDbIndexManager {
|
|
|
10050
10322
|
let results = new SortedSet(indexEntryComparator);
|
|
10051
10323
|
return indexEntries
|
|
10052
10324
|
.iterate({
|
|
10053
|
-
index:
|
|
10325
|
+
index: DbIndexEntryDocumentKeyIndex,
|
|
10054
10326
|
range: IDBKeyRange.only([
|
|
10055
10327
|
fieldIndex.indexId,
|
|
10056
10328
|
this.uid,
|
|
@@ -10078,7 +10350,7 @@ class IndexedDbIndexManager {
|
|
|
10078
10350
|
}
|
|
10079
10351
|
}
|
|
10080
10352
|
else {
|
|
10081
|
-
results = results.add(new IndexEntry(fieldIndex.indexId, document.key,
|
|
10353
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10082
10354
|
}
|
|
10083
10355
|
return results;
|
|
10084
10356
|
}
|
|
@@ -10104,7 +10376,7 @@ class IndexedDbIndexManager {
|
|
|
10104
10376
|
const states = indexStateStore(transaction);
|
|
10105
10377
|
return states
|
|
10106
10378
|
.iterate({
|
|
10107
|
-
index:
|
|
10379
|
+
index: DbIndexStateSequenceNumberIndex,
|
|
10108
10380
|
reverse: true,
|
|
10109
10381
|
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10110
10382
|
}, (_, state, controller) => {
|
|
@@ -10113,31 +10385,81 @@ class IndexedDbIndexManager {
|
|
|
10113
10385
|
})
|
|
10114
10386
|
.next(() => nextSequenceNumber);
|
|
10115
10387
|
}
|
|
10388
|
+
/**
|
|
10389
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10390
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10391
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10392
|
+
*/
|
|
10393
|
+
createRange(lower, upper, notInValues) {
|
|
10394
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10395
|
+
// sorted set of non-overlapping ranges.
|
|
10396
|
+
notInValues = notInValues
|
|
10397
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10398
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10399
|
+
const bounds = [];
|
|
10400
|
+
bounds.push(lower);
|
|
10401
|
+
for (const notInValue of notInValues) {
|
|
10402
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10403
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10404
|
+
if (cmpToLower === 0) {
|
|
10405
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10406
|
+
// to the next value.
|
|
10407
|
+
bounds[0] = lower.successor();
|
|
10408
|
+
}
|
|
10409
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10410
|
+
// `notInValue` is in the middle of the range
|
|
10411
|
+
bounds.push(notInValue);
|
|
10412
|
+
bounds.push(notInValue.successor());
|
|
10413
|
+
}
|
|
10414
|
+
else if (cmpToUpper > 0) {
|
|
10415
|
+
// `notInValue` (and all following values) are out of the range
|
|
10416
|
+
break;
|
|
10417
|
+
}
|
|
10418
|
+
}
|
|
10419
|
+
bounds.push(upper);
|
|
10420
|
+
const ranges = [];
|
|
10421
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10422
|
+
ranges.push(IDBKeyRange.bound([
|
|
10423
|
+
bounds[i].indexId,
|
|
10424
|
+
this.uid,
|
|
10425
|
+
bounds[i].arrayValue,
|
|
10426
|
+
bounds[i].directionalValue,
|
|
10427
|
+
''
|
|
10428
|
+
], [
|
|
10429
|
+
bounds[i + 1].indexId,
|
|
10430
|
+
this.uid,
|
|
10431
|
+
bounds[i + 1].arrayValue,
|
|
10432
|
+
bounds[i + 1].directionalValue,
|
|
10433
|
+
''
|
|
10434
|
+
]));
|
|
10435
|
+
}
|
|
10436
|
+
return ranges;
|
|
10437
|
+
}
|
|
10116
10438
|
}
|
|
10117
10439
|
/**
|
|
10118
10440
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
10119
10441
|
* document store.
|
|
10120
10442
|
*/
|
|
10121
10443
|
function collectionParentsStore(txn) {
|
|
10122
|
-
return getStore(txn,
|
|
10444
|
+
return getStore(txn, DbCollectionParentStore);
|
|
10123
10445
|
}
|
|
10124
10446
|
/**
|
|
10125
10447
|
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10126
10448
|
*/
|
|
10127
10449
|
function indexEntriesStore(txn) {
|
|
10128
|
-
return getStore(txn,
|
|
10450
|
+
return getStore(txn, DbIndexEntryStore);
|
|
10129
10451
|
}
|
|
10130
10452
|
/**
|
|
10131
10453
|
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10132
10454
|
*/
|
|
10133
10455
|
function indexConfigurationStore(txn) {
|
|
10134
|
-
return getStore(txn,
|
|
10456
|
+
return getStore(txn, DbIndexConfigurationStore);
|
|
10135
10457
|
}
|
|
10136
10458
|
/**
|
|
10137
10459
|
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10138
10460
|
*/
|
|
10139
10461
|
function indexStateStore(txn) {
|
|
10140
|
-
return getStore(txn,
|
|
10462
|
+
return getStore(txn, DbIndexStateStore);
|
|
10141
10463
|
}
|
|
10142
10464
|
|
|
10143
10465
|
/**
|
|
@@ -10161,8 +10483,8 @@ function indexStateStore(txn) {
|
|
|
10161
10483
|
* @returns A PersistencePromise of the document mutations that were removed.
|
|
10162
10484
|
*/
|
|
10163
10485
|
function removeMutationBatch(txn, userId, batch) {
|
|
10164
|
-
const mutationStore = txn.store(
|
|
10165
|
-
const indexTxn = txn.store(
|
|
10486
|
+
const mutationStore = txn.store(DbMutationBatchStore);
|
|
10487
|
+
const indexTxn = txn.store(DbDocumentMutationStore);
|
|
10166
10488
|
const promises = [];
|
|
10167
10489
|
const range = IDBKeyRange.only(batch.batchId);
|
|
10168
10490
|
let numDeleted = 0;
|
|
@@ -10175,7 +10497,7 @@ function removeMutationBatch(txn, userId, batch) {
|
|
|
10175
10497
|
}));
|
|
10176
10498
|
const removedDocuments = [];
|
|
10177
10499
|
for (const mutation of batch.mutations) {
|
|
10178
|
-
const indexKey =
|
|
10500
|
+
const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
|
|
10179
10501
|
promises.push(indexTxn.delete(indexKey));
|
|
10180
10502
|
removedDocuments.push(mutation.key);
|
|
10181
10503
|
}
|
|
@@ -10264,7 +10586,7 @@ class IndexedDbMutationQueue {
|
|
|
10264
10586
|
let empty = true;
|
|
10265
10587
|
const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10266
10588
|
return mutationsStore(transaction)
|
|
10267
|
-
.iterate({ index:
|
|
10589
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
|
|
10268
10590
|
empty = false;
|
|
10269
10591
|
control.done();
|
|
10270
10592
|
})
|
|
@@ -10289,10 +10611,10 @@ class IndexedDbMutationQueue {
|
|
|
10289
10611
|
const promises = [];
|
|
10290
10612
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10291
10613
|
for (const mutation of mutations) {
|
|
10292
|
-
const indexKey =
|
|
10614
|
+
const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
|
|
10293
10615
|
collectionParents = collectionParents.add(mutation.key.path.popLast());
|
|
10294
10616
|
promises.push(mutationStore.put(dbBatch));
|
|
10295
|
-
promises.push(documentStore.put(indexKey,
|
|
10617
|
+
promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
|
|
10296
10618
|
}
|
|
10297
10619
|
collectionParents.forEach(parent => {
|
|
10298
10620
|
promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
|
|
@@ -10343,7 +10665,7 @@ class IndexedDbMutationQueue {
|
|
|
10343
10665
|
const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
|
|
10344
10666
|
let foundBatch = null;
|
|
10345
10667
|
return mutationsStore(transaction)
|
|
10346
|
-
.iterate({ index:
|
|
10668
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
|
|
10347
10669
|
if (dbBatch.userId === this.userId) {
|
|
10348
10670
|
hardAssert(dbBatch.batchId >= nextBatchId);
|
|
10349
10671
|
foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
|
|
@@ -10359,7 +10681,7 @@ class IndexedDbMutationQueue {
|
|
|
10359
10681
|
]);
|
|
10360
10682
|
let batchId = BATCHID_UNKNOWN;
|
|
10361
10683
|
return mutationsStore(transaction)
|
|
10362
|
-
.iterate({ index:
|
|
10684
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
|
|
10363
10685
|
batchId = dbBatch.batchId;
|
|
10364
10686
|
control.done();
|
|
10365
10687
|
})
|
|
@@ -10368,13 +10690,13 @@ class IndexedDbMutationQueue {
|
|
|
10368
10690
|
getAllMutationBatches(transaction) {
|
|
10369
10691
|
const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10370
10692
|
return mutationsStore(transaction)
|
|
10371
|
-
.loadAll(
|
|
10693
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
10372
10694
|
.next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
|
|
10373
10695
|
}
|
|
10374
10696
|
getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
|
|
10375
10697
|
// Scan the document-mutation index starting with a prefix starting with
|
|
10376
10698
|
// the given documentKey.
|
|
10377
|
-
const indexPrefix =
|
|
10699
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10378
10700
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10379
10701
|
const results = [];
|
|
10380
10702
|
return documentMutationsStore(transaction)
|
|
@@ -10409,7 +10731,7 @@ class IndexedDbMutationQueue {
|
|
|
10409
10731
|
let uniqueBatchIDs = new SortedSet(primitiveComparator);
|
|
10410
10732
|
const promises = [];
|
|
10411
10733
|
documentKeys.forEach(documentKey => {
|
|
10412
|
-
const indexStart =
|
|
10734
|
+
const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10413
10735
|
const range = IDBKeyRange.lowerBound(indexStart);
|
|
10414
10736
|
const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
|
|
10415
10737
|
const [userID, encodedPath, batchID] = indexKey;
|
|
@@ -10445,7 +10767,7 @@ class IndexedDbMutationQueue {
|
|
|
10445
10767
|
// Since we don't yet index the actual properties in the mutations, our
|
|
10446
10768
|
// current approach is to just return all mutation batches that affect
|
|
10447
10769
|
// documents in the collection being queried.
|
|
10448
|
-
const indexPrefix =
|
|
10770
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
|
|
10449
10771
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10450
10772
|
// Collect up unique batchIDs encountered during a scan of the index. Use a
|
|
10451
10773
|
// SortedSet to accumulate batch IDs so they can be traversed in order in a
|
|
@@ -10517,7 +10839,7 @@ class IndexedDbMutationQueue {
|
|
|
10517
10839
|
}
|
|
10518
10840
|
// Verify that there are no entries in the documentMutations index if
|
|
10519
10841
|
// the queue is empty.
|
|
10520
|
-
const startRange = IDBKeyRange.lowerBound(
|
|
10842
|
+
const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
|
|
10521
10843
|
const danglingMutationReferences = [];
|
|
10522
10844
|
return documentMutationsStore(txn)
|
|
10523
10845
|
.iterate({ range: startRange }, (key, _, control) => {
|
|
@@ -10545,9 +10867,11 @@ class IndexedDbMutationQueue {
|
|
|
10545
10867
|
return mutationQueuesStore(transaction)
|
|
10546
10868
|
.get(this.userId)
|
|
10547
10869
|
.next((metadata) => {
|
|
10548
|
-
return (metadata ||
|
|
10549
|
-
|
|
10550
|
-
|
|
10870
|
+
return (metadata || {
|
|
10871
|
+
userId: this.userId,
|
|
10872
|
+
lastAcknowledgedBatchId: BATCHID_UNKNOWN,
|
|
10873
|
+
lastStreamToken: ''
|
|
10874
|
+
});
|
|
10551
10875
|
});
|
|
10552
10876
|
}
|
|
10553
10877
|
}
|
|
@@ -10556,7 +10880,7 @@ class IndexedDbMutationQueue {
|
|
|
10556
10880
|
* mutation for the given key.
|
|
10557
10881
|
*/
|
|
10558
10882
|
function mutationQueueContainsKey(txn, userId, key) {
|
|
10559
|
-
const indexKey =
|
|
10883
|
+
const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
|
|
10560
10884
|
const encodedPath = indexKey[1];
|
|
10561
10885
|
const startRange = IDBKeyRange.lowerBound(indexKey);
|
|
10562
10886
|
let containsKey = false;
|
|
@@ -10588,19 +10912,19 @@ function mutationQueuesContainKey(txn, docKey) {
|
|
|
10588
10912
|
* Helper to get a typed SimpleDbStore for the mutations object store.
|
|
10589
10913
|
*/
|
|
10590
10914
|
function mutationsStore(txn) {
|
|
10591
|
-
return getStore(txn,
|
|
10915
|
+
return getStore(txn, DbMutationBatchStore);
|
|
10592
10916
|
}
|
|
10593
10917
|
/**
|
|
10594
10918
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10595
10919
|
*/
|
|
10596
10920
|
function documentMutationsStore(txn) {
|
|
10597
|
-
return getStore(txn,
|
|
10921
|
+
return getStore(txn, DbDocumentMutationStore);
|
|
10598
10922
|
}
|
|
10599
10923
|
/**
|
|
10600
10924
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10601
10925
|
*/
|
|
10602
10926
|
function mutationQueuesStore(txn) {
|
|
10603
|
-
return getStore(txn,
|
|
10927
|
+
return getStore(txn, DbMutationQueueStore);
|
|
10604
10928
|
}
|
|
10605
10929
|
|
|
10606
10930
|
/**
|
|
@@ -10764,14 +11088,14 @@ class IndexedDbTargetCache {
|
|
|
10764
11088
|
}
|
|
10765
11089
|
retrieveMetadata(transaction) {
|
|
10766
11090
|
return globalTargetStore(transaction)
|
|
10767
|
-
.get(
|
|
11091
|
+
.get(DbTargetGlobalKey)
|
|
10768
11092
|
.next(metadata => {
|
|
10769
11093
|
hardAssert(metadata !== null);
|
|
10770
11094
|
return metadata;
|
|
10771
11095
|
});
|
|
10772
11096
|
}
|
|
10773
11097
|
saveMetadata(transaction, metadata) {
|
|
10774
|
-
return globalTargetStore(transaction).put(
|
|
11098
|
+
return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
|
|
10775
11099
|
}
|
|
10776
11100
|
saveTargetData(transaction, targetData) {
|
|
10777
11101
|
return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
|
|
@@ -10804,7 +11128,7 @@ class IndexedDbTargetCache {
|
|
|
10804
11128
|
const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
|
|
10805
11129
|
let result = null;
|
|
10806
11130
|
return targetsStore(transaction)
|
|
10807
|
-
.iterate({ range, index:
|
|
11131
|
+
.iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
|
|
10808
11132
|
const found = fromDbTarget(value);
|
|
10809
11133
|
// After finding a potential match, check that the target is
|
|
10810
11134
|
// actually equal to the requested target.
|
|
@@ -10822,7 +11146,7 @@ class IndexedDbTargetCache {
|
|
|
10822
11146
|
const store = documentTargetStore(txn);
|
|
10823
11147
|
keys.forEach(key => {
|
|
10824
11148
|
const path = encodeResourcePath(key.path);
|
|
10825
|
-
promises.push(store.put(
|
|
11149
|
+
promises.push(store.put({ targetId, path }));
|
|
10826
11150
|
promises.push(this.referenceDelegate.addReference(txn, targetId, key));
|
|
10827
11151
|
});
|
|
10828
11152
|
return PersistencePromise.waitFor(promises);
|
|
@@ -10868,7 +11192,7 @@ class IndexedDbTargetCache {
|
|
|
10868
11192
|
let count = 0;
|
|
10869
11193
|
return documentTargetStore(txn)
|
|
10870
11194
|
.iterate({
|
|
10871
|
-
index:
|
|
11195
|
+
index: DbTargetDocumentDocumentTargetsIndex,
|
|
10872
11196
|
keysOnly: true,
|
|
10873
11197
|
range
|
|
10874
11198
|
}, ([targetId, path], _, control) => {
|
|
@@ -10907,19 +11231,19 @@ class IndexedDbTargetCache {
|
|
|
10907
11231
|
* Helper to get a typed SimpleDbStore for the queries object store.
|
|
10908
11232
|
*/
|
|
10909
11233
|
function targetsStore(txn) {
|
|
10910
|
-
return getStore(txn,
|
|
11234
|
+
return getStore(txn, DbTargetStore);
|
|
10911
11235
|
}
|
|
10912
11236
|
/**
|
|
10913
11237
|
* Helper to get a typed SimpleDbStore for the target globals object store.
|
|
10914
11238
|
*/
|
|
10915
11239
|
function globalTargetStore(txn) {
|
|
10916
|
-
return getStore(txn,
|
|
11240
|
+
return getStore(txn, DbTargetGlobalStore);
|
|
10917
11241
|
}
|
|
10918
11242
|
/**
|
|
10919
11243
|
* Helper to get a typed SimpleDbStore for the document target object store.
|
|
10920
11244
|
*/
|
|
10921
11245
|
function documentTargetStore(txn) {
|
|
10922
|
-
return getStore(txn,
|
|
11246
|
+
return getStore(txn, DbTargetDocumentStore);
|
|
10923
11247
|
}
|
|
10924
11248
|
|
|
10925
11249
|
/**
|
|
@@ -11333,7 +11657,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
11333
11657
|
let nextPath;
|
|
11334
11658
|
return store
|
|
11335
11659
|
.iterate({
|
|
11336
|
-
index:
|
|
11660
|
+
index: DbTargetDocumentDocumentTargetsIndex
|
|
11337
11661
|
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
11338
11662
|
if (targetId === 0) {
|
|
11339
11663
|
// if nextToReport is valid, report it, this is a new key so the
|
|
@@ -11375,113 +11699,12 @@ function sentinelKey$1(key) {
|
|
|
11375
11699
|
* store.
|
|
11376
11700
|
*/
|
|
11377
11701
|
function sentinelRow(key, sequenceNumber) {
|
|
11378
|
-
return
|
|
11702
|
+
return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
|
|
11379
11703
|
}
|
|
11380
11704
|
function writeSentinelKey(txn, key) {
|
|
11381
11705
|
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
11382
11706
|
}
|
|
11383
11707
|
|
|
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
11708
|
/**
|
|
11486
11709
|
* @license
|
|
11487
11710
|
* Copyright 2017 Google LLC
|
|
@@ -11750,7 +11973,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11750
11973
|
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
11751
11974
|
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
11752
11975
|
/* open= */ true);
|
|
11753
|
-
iterationOptions.index =
|
|
11976
|
+
iterationOptions.index = DbRemoteDocumentCollectionReadTimeIndex;
|
|
11754
11977
|
}
|
|
11755
11978
|
return remoteDocumentsStore(transaction)
|
|
11756
11979
|
.iterate(iterationOptions, (key, dbRemoteDoc, control) => {
|
|
@@ -11780,14 +12003,14 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11780
12003
|
}
|
|
11781
12004
|
getMetadata(txn) {
|
|
11782
12005
|
return documentGlobalStore(txn)
|
|
11783
|
-
.get(
|
|
12006
|
+
.get(DbRemoteDocumentGlobalKey)
|
|
11784
12007
|
.next(metadata => {
|
|
11785
12008
|
hardAssert(!!metadata);
|
|
11786
12009
|
return metadata;
|
|
11787
12010
|
});
|
|
11788
12011
|
}
|
|
11789
12012
|
setMetadata(txn, metadata) {
|
|
11790
|
-
return documentGlobalStore(txn).put(
|
|
12013
|
+
return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
|
|
11791
12014
|
}
|
|
11792
12015
|
/**
|
|
11793
12016
|
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
@@ -11822,7 +12045,7 @@ function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transacti
|
|
|
11822
12045
|
const documentsStore = remoteDocumentsStore(transaction);
|
|
11823
12046
|
const range = IDBKeyRange.lowerBound(lastReadTime, true);
|
|
11824
12047
|
return documentsStore
|
|
11825
|
-
.iterate({ index:
|
|
12048
|
+
.iterate({ index: DbRemoteDocumentReadTimeIndex, range }, (_, dbRemoteDoc) => {
|
|
11826
12049
|
// Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
|
|
11827
12050
|
// the documents directly since we want to keep sentinel deletes.
|
|
11828
12051
|
const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
|
|
@@ -11846,7 +12069,7 @@ function remoteDocumentCacheGetLastReadTime(transaction) {
|
|
|
11846
12069
|
// If there are no existing entries, we return SnapshotVersion.min().
|
|
11847
12070
|
let readTime = SnapshotVersion.min();
|
|
11848
12071
|
return documentsStore
|
|
11849
|
-
.iterate({ index:
|
|
12072
|
+
.iterate({ index: DbRemoteDocumentReadTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
|
|
11850
12073
|
if (dbRemoteDoc.readTime) {
|
|
11851
12074
|
readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
|
|
11852
12075
|
}
|
|
@@ -11934,17 +12157,39 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11934
12157
|
}
|
|
11935
12158
|
}
|
|
11936
12159
|
function documentGlobalStore(txn) {
|
|
11937
|
-
return getStore(txn,
|
|
12160
|
+
return getStore(txn, DbRemoteDocumentGlobalStore);
|
|
11938
12161
|
}
|
|
11939
12162
|
/**
|
|
11940
12163
|
* Helper to get a typed SimpleDbStore for the remoteDocuments object store.
|
|
11941
12164
|
*/
|
|
11942
12165
|
function remoteDocumentsStore(txn) {
|
|
11943
|
-
return getStore(txn,
|
|
12166
|
+
return getStore(txn, DbRemoteDocumentStore);
|
|
11944
12167
|
}
|
|
11945
12168
|
function dbKey(docKey) {
|
|
11946
12169
|
return docKey.path.toArray();
|
|
11947
12170
|
}
|
|
12171
|
+
/**
|
|
12172
|
+
* Schema Version for the Web client:
|
|
12173
|
+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
12174
|
+
* Document Cache
|
|
12175
|
+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
12176
|
+
* longer required because migration 3 unconditionally clears it.
|
|
12177
|
+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
12178
|
+
* to limbo resolution. Addresses
|
|
12179
|
+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
12180
|
+
* 4. Multi-Tab Support.
|
|
12181
|
+
* 5. Removal of held write acks.
|
|
12182
|
+
* 6. Create document global for tracking document cache size.
|
|
12183
|
+
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
12184
|
+
* 8. Add collection-parent index for Collection Group queries.
|
|
12185
|
+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
12186
|
+
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
12187
|
+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
12188
|
+
* 11. Add bundles and named_queries for bundle support.
|
|
12189
|
+
* 12. Add document overlays.
|
|
12190
|
+
* 13. Add indexing support.
|
|
12191
|
+
*/
|
|
12192
|
+
const SCHEMA_VERSION = 12;
|
|
11948
12193
|
|
|
11949
12194
|
/**
|
|
11950
12195
|
* @license
|
|
@@ -12054,27 +12299,27 @@ class SchemaConverter {
|
|
|
12054
12299
|
return p;
|
|
12055
12300
|
}
|
|
12056
12301
|
addDocumentGlobal(txn) {
|
|
12057
|
-
let
|
|
12302
|
+
let byteSize = 0;
|
|
12058
12303
|
return txn
|
|
12059
|
-
.store(
|
|
12304
|
+
.store(DbRemoteDocumentStore)
|
|
12060
12305
|
.iterate((_, doc) => {
|
|
12061
|
-
|
|
12306
|
+
byteSize += dbDocumentSize(doc);
|
|
12062
12307
|
})
|
|
12063
12308
|
.next(() => {
|
|
12064
|
-
const metadata =
|
|
12309
|
+
const metadata = { byteSize };
|
|
12065
12310
|
return txn
|
|
12066
|
-
.store(
|
|
12067
|
-
.put(
|
|
12311
|
+
.store(DbRemoteDocumentGlobalStore)
|
|
12312
|
+
.put(DbRemoteDocumentGlobalKey, metadata);
|
|
12068
12313
|
});
|
|
12069
12314
|
}
|
|
12070
12315
|
removeAcknowledgedMutations(txn) {
|
|
12071
|
-
const queuesStore = txn.store(
|
|
12072
|
-
const mutationsStore = txn.store(
|
|
12316
|
+
const queuesStore = txn.store(DbMutationQueueStore);
|
|
12317
|
+
const mutationsStore = txn.store(DbMutationBatchStore);
|
|
12073
12318
|
return queuesStore.loadAll().next(queues => {
|
|
12074
12319
|
return PersistencePromise.forEach(queues, (queue) => {
|
|
12075
12320
|
const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
|
|
12076
12321
|
return mutationsStore
|
|
12077
|
-
.loadAll(
|
|
12322
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
12078
12323
|
.next(dbBatches => {
|
|
12079
12324
|
return PersistencePromise.forEach(dbBatches, (dbBatch) => {
|
|
12080
12325
|
hardAssert(dbBatch.userId === queue.userId);
|
|
@@ -12090,12 +12335,16 @@ class SchemaConverter {
|
|
|
12090
12335
|
* with a sequence number. Missing rows are given the most recently used sequence number.
|
|
12091
12336
|
*/
|
|
12092
12337
|
ensureSequenceNumbers(txn) {
|
|
12093
|
-
const documentTargetStore = txn.store(
|
|
12094
|
-
const documentsStore = txn.store(
|
|
12095
|
-
const globalTargetStore = txn.store(
|
|
12096
|
-
return globalTargetStore.get(
|
|
12338
|
+
const documentTargetStore = txn.store(DbTargetDocumentStore);
|
|
12339
|
+
const documentsStore = txn.store(DbRemoteDocumentStore);
|
|
12340
|
+
const globalTargetStore = txn.store(DbTargetGlobalStore);
|
|
12341
|
+
return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
|
|
12097
12342
|
const writeSentinelKey = (path) => {
|
|
12098
|
-
return documentTargetStore.put(
|
|
12343
|
+
return documentTargetStore.put({
|
|
12344
|
+
targetId: 0,
|
|
12345
|
+
path: encodeResourcePath(path),
|
|
12346
|
+
sequenceNumber: metadata.highestListenSequenceNumber
|
|
12347
|
+
});
|
|
12099
12348
|
};
|
|
12100
12349
|
const promises = [];
|
|
12101
12350
|
return documentsStore
|
|
@@ -12116,10 +12365,10 @@ class SchemaConverter {
|
|
|
12116
12365
|
}
|
|
12117
12366
|
createCollectionParentIndex(db, txn) {
|
|
12118
12367
|
// Create the index.
|
|
12119
|
-
db.createObjectStore(
|
|
12120
|
-
keyPath:
|
|
12368
|
+
db.createObjectStore(DbCollectionParentStore, {
|
|
12369
|
+
keyPath: DbCollectionParentKeyPath
|
|
12121
12370
|
});
|
|
12122
|
-
const collectionParentsStore = txn.store(
|
|
12371
|
+
const collectionParentsStore = txn.store(DbCollectionParentStore);
|
|
12123
12372
|
// Helper to add an index entry iff we haven't already written it.
|
|
12124
12373
|
const cache = new MemoryCollectionParentIndex();
|
|
12125
12374
|
const addEntry = (collectionPath) => {
|
|
@@ -12134,7 +12383,7 @@ class SchemaConverter {
|
|
|
12134
12383
|
};
|
|
12135
12384
|
// Index existing remote documents.
|
|
12136
12385
|
return txn
|
|
12137
|
-
.store(
|
|
12386
|
+
.store(DbRemoteDocumentStore)
|
|
12138
12387
|
.iterate({ keysOnly: true }, (pathSegments, _) => {
|
|
12139
12388
|
const path = new ResourcePath(pathSegments);
|
|
12140
12389
|
return addEntry(path.popLast());
|
|
@@ -12142,7 +12391,7 @@ class SchemaConverter {
|
|
|
12142
12391
|
.next(() => {
|
|
12143
12392
|
// Index existing mutations.
|
|
12144
12393
|
return txn
|
|
12145
|
-
.store(
|
|
12394
|
+
.store(DbDocumentMutationStore)
|
|
12146
12395
|
.iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
|
|
12147
12396
|
const path = decodeResourcePath(encodedPath);
|
|
12148
12397
|
return addEntry(path.popLast());
|
|
@@ -12150,7 +12399,7 @@ class SchemaConverter {
|
|
|
12150
12399
|
});
|
|
12151
12400
|
}
|
|
12152
12401
|
rewriteCanonicalIds(txn) {
|
|
12153
|
-
const targetStore = txn.store(
|
|
12402
|
+
const targetStore = txn.store(DbTargetStore);
|
|
12154
12403
|
return targetStore.iterate((key, originalDbTarget) => {
|
|
12155
12404
|
const originalTargetData = fromDbTarget(originalDbTarget);
|
|
12156
12405
|
const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
|
|
@@ -12162,59 +12411,59 @@ function sentinelKey(path) {
|
|
|
12162
12411
|
return [0, encodeResourcePath(path)];
|
|
12163
12412
|
}
|
|
12164
12413
|
function createPrimaryClientStore(db) {
|
|
12165
|
-
db.createObjectStore(
|
|
12414
|
+
db.createObjectStore(DbPrimaryClientStore);
|
|
12166
12415
|
}
|
|
12167
12416
|
function createMutationQueue(db) {
|
|
12168
|
-
db.createObjectStore(
|
|
12169
|
-
keyPath:
|
|
12417
|
+
db.createObjectStore(DbMutationQueueStore, {
|
|
12418
|
+
keyPath: DbMutationQueueKeyPath
|
|
12170
12419
|
});
|
|
12171
|
-
const mutationBatchesStore = db.createObjectStore(
|
|
12172
|
-
keyPath:
|
|
12420
|
+
const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12421
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12173
12422
|
autoIncrement: true
|
|
12174
12423
|
});
|
|
12175
|
-
mutationBatchesStore.createIndex(
|
|
12176
|
-
db.createObjectStore(
|
|
12424
|
+
mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12425
|
+
db.createObjectStore(DbDocumentMutationStore);
|
|
12177
12426
|
}
|
|
12178
12427
|
/**
|
|
12179
12428
|
* Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
|
|
12180
12429
|
* and rewrites all data.
|
|
12181
12430
|
*/
|
|
12182
12431
|
function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
|
|
12183
|
-
const v1MutationsStore = txn.store(
|
|
12432
|
+
const v1MutationsStore = txn.store(DbMutationBatchStore);
|
|
12184
12433
|
return v1MutationsStore.loadAll().next(existingMutations => {
|
|
12185
|
-
db.deleteObjectStore(
|
|
12186
|
-
const mutationsStore = db.createObjectStore(
|
|
12187
|
-
keyPath:
|
|
12434
|
+
db.deleteObjectStore(DbMutationBatchStore);
|
|
12435
|
+
const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12436
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12188
12437
|
autoIncrement: true
|
|
12189
12438
|
});
|
|
12190
|
-
mutationsStore.createIndex(
|
|
12191
|
-
const v3MutationsStore = txn.store(
|
|
12439
|
+
mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12440
|
+
const v3MutationsStore = txn.store(DbMutationBatchStore);
|
|
12192
12441
|
const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
|
|
12193
12442
|
return PersistencePromise.waitFor(writeAll);
|
|
12194
12443
|
});
|
|
12195
12444
|
}
|
|
12196
12445
|
function createRemoteDocumentCache(db) {
|
|
12197
|
-
db.createObjectStore(
|
|
12446
|
+
db.createObjectStore(DbRemoteDocumentStore);
|
|
12198
12447
|
}
|
|
12199
12448
|
function createDocumentGlobalStore(db) {
|
|
12200
|
-
db.createObjectStore(
|
|
12449
|
+
db.createObjectStore(DbRemoteDocumentGlobalStore);
|
|
12201
12450
|
}
|
|
12202
12451
|
function createQueryCache(db) {
|
|
12203
|
-
const targetDocumentsStore = db.createObjectStore(
|
|
12204
|
-
keyPath:
|
|
12452
|
+
const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
|
|
12453
|
+
keyPath: DbTargetDocumentKeyPath
|
|
12205
12454
|
});
|
|
12206
|
-
targetDocumentsStore.createIndex(
|
|
12207
|
-
const targetStore = db.createObjectStore(
|
|
12208
|
-
keyPath:
|
|
12455
|
+
targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
|
|
12456
|
+
const targetStore = db.createObjectStore(DbTargetStore, {
|
|
12457
|
+
keyPath: DbTargetKeyPath
|
|
12209
12458
|
});
|
|
12210
12459
|
// NOTE: This is unique only because the TargetId is the suffix.
|
|
12211
|
-
targetStore.createIndex(
|
|
12212
|
-
db.createObjectStore(
|
|
12460
|
+
targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
|
|
12461
|
+
db.createObjectStore(DbTargetGlobalStore);
|
|
12213
12462
|
}
|
|
12214
12463
|
function dropQueryCache(db) {
|
|
12215
|
-
db.deleteObjectStore(
|
|
12216
|
-
db.deleteObjectStore(
|
|
12217
|
-
db.deleteObjectStore(
|
|
12464
|
+
db.deleteObjectStore(DbTargetDocumentStore);
|
|
12465
|
+
db.deleteObjectStore(DbTargetStore);
|
|
12466
|
+
db.deleteObjectStore(DbTargetGlobalStore);
|
|
12218
12467
|
}
|
|
12219
12468
|
function dropRemoteDocumentChangesStore(db) {
|
|
12220
12469
|
if (db.objectStoreNames.contains('remoteDocumentChanges')) {
|
|
@@ -12227,58 +12476,60 @@ function dropRemoteDocumentChangesStore(db) {
|
|
|
12227
12476
|
* @param txn - The version upgrade transaction for indexeddb
|
|
12228
12477
|
*/
|
|
12229
12478
|
function writeEmptyTargetGlobalEntry(txn) {
|
|
12230
|
-
const globalStore = txn.store(
|
|
12231
|
-
const metadata =
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12479
|
+
const globalStore = txn.store(DbTargetGlobalStore);
|
|
12480
|
+
const metadata = {
|
|
12481
|
+
highestTargetId: 0,
|
|
12482
|
+
highestListenSequenceNumber: 0,
|
|
12483
|
+
lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
|
|
12484
|
+
targetCount: 0
|
|
12485
|
+
};
|
|
12486
|
+
return globalStore.put(DbTargetGlobalKey, metadata);
|
|
12236
12487
|
}
|
|
12237
12488
|
/**
|
|
12238
12489
|
* Creates indices on the RemoteDocuments store used for both multi-tab
|
|
12239
12490
|
* and Index-Free queries.
|
|
12240
12491
|
*/
|
|
12241
12492
|
function createRemoteDocumentReadTimeIndex(txn) {
|
|
12242
|
-
const remoteDocumentStore = txn.objectStore(
|
|
12243
|
-
remoteDocumentStore.createIndex(
|
|
12244
|
-
remoteDocumentStore.createIndex(
|
|
12493
|
+
const remoteDocumentStore = txn.objectStore(DbRemoteDocumentStore);
|
|
12494
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentReadTimeIndex, DbRemoteDocumentReadTimeIndexPath, { unique: false });
|
|
12495
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentCollectionReadTimeIndex, DbRemoteDocumentCollectionReadTimeIndexPath, { unique: false });
|
|
12245
12496
|
}
|
|
12246
12497
|
function createClientMetadataStore(db) {
|
|
12247
|
-
db.createObjectStore(
|
|
12248
|
-
keyPath:
|
|
12498
|
+
db.createObjectStore(DbClientMetadataStore, {
|
|
12499
|
+
keyPath: DbClientMetadataKeyPath
|
|
12249
12500
|
});
|
|
12250
12501
|
}
|
|
12251
12502
|
function createBundlesStore(db) {
|
|
12252
|
-
db.createObjectStore(
|
|
12253
|
-
keyPath:
|
|
12503
|
+
db.createObjectStore(DbBundleStore, {
|
|
12504
|
+
keyPath: DbBundleKeyPath
|
|
12254
12505
|
});
|
|
12255
12506
|
}
|
|
12256
12507
|
function createNamedQueriesStore(db) {
|
|
12257
|
-
db.createObjectStore(
|
|
12258
|
-
keyPath:
|
|
12508
|
+
db.createObjectStore(DbNamedQueryStore, {
|
|
12509
|
+
keyPath: DbNamedQueryKeyPath
|
|
12259
12510
|
});
|
|
12260
12511
|
}
|
|
12261
12512
|
function createFieldIndex(db) {
|
|
12262
|
-
const indexConfiguratioStore = db.createObjectStore(
|
|
12263
|
-
keyPath:
|
|
12513
|
+
const indexConfiguratioStore = db.createObjectStore(DbIndexConfigurationStore, {
|
|
12514
|
+
keyPath: DbIndexConfigurationKeyPath,
|
|
12264
12515
|
autoIncrement: true
|
|
12265
12516
|
});
|
|
12266
|
-
indexConfiguratioStore.createIndex(
|
|
12267
|
-
const indexStateStore = db.createObjectStore(
|
|
12268
|
-
keyPath:
|
|
12517
|
+
indexConfiguratioStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
|
|
12518
|
+
const indexStateStore = db.createObjectStore(DbIndexStateStore, {
|
|
12519
|
+
keyPath: DbIndexStateKeyPath
|
|
12269
12520
|
});
|
|
12270
|
-
indexStateStore.createIndex(
|
|
12271
|
-
const indexEntryStore = db.createObjectStore(
|
|
12272
|
-
keyPath:
|
|
12521
|
+
indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
|
|
12522
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
|
|
12523
|
+
keyPath: DbIndexEntryKeyPath
|
|
12273
12524
|
});
|
|
12274
|
-
indexEntryStore.createIndex(
|
|
12525
|
+
indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
|
|
12275
12526
|
}
|
|
12276
12527
|
function createDocumentOverlayStore(db) {
|
|
12277
|
-
const documentOverlayStore = db.createObjectStore(
|
|
12278
|
-
keyPath:
|
|
12528
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
|
|
12529
|
+
keyPath: DbDocumentOverlayKeyPath
|
|
12279
12530
|
});
|
|
12280
|
-
documentOverlayStore.createIndex(
|
|
12281
|
-
documentOverlayStore.createIndex(
|
|
12531
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
|
|
12532
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
|
|
12282
12533
|
}
|
|
12283
12534
|
|
|
12284
12535
|
/**
|
|
@@ -12526,7 +12777,12 @@ class IndexedDbPersistence {
|
|
|
12526
12777
|
return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
|
|
12527
12778
|
const metadataStore = clientMetadataStore(txn);
|
|
12528
12779
|
return metadataStore
|
|
12529
|
-
.put(
|
|
12780
|
+
.put({
|
|
12781
|
+
clientId: this.clientId,
|
|
12782
|
+
updateTimeMs: Date.now(),
|
|
12783
|
+
networkEnabled: this.networkEnabled,
|
|
12784
|
+
inForeground: this.inForeground
|
|
12785
|
+
})
|
|
12530
12786
|
.next(() => {
|
|
12531
12787
|
if (this.isPrimary) {
|
|
12532
12788
|
return this.verifyPrimaryLease(txn).next(success => {
|
|
@@ -12572,7 +12828,7 @@ class IndexedDbPersistence {
|
|
|
12572
12828
|
}
|
|
12573
12829
|
verifyPrimaryLease(txn) {
|
|
12574
12830
|
const store = primaryClientStore(txn);
|
|
12575
|
-
return store.get(
|
|
12831
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12576
12832
|
return PersistencePromise.resolve(this.isLocalClient(primaryClient));
|
|
12577
12833
|
});
|
|
12578
12834
|
}
|
|
@@ -12590,7 +12846,7 @@ class IndexedDbPersistence {
|
|
|
12590
12846
|
!this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
|
|
12591
12847
|
this.lastGarbageCollectionTime = Date.now();
|
|
12592
12848
|
const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
|
|
12593
|
-
const metadataStore = getStore(txn,
|
|
12849
|
+
const metadataStore = getStore(txn, DbClientMetadataStore);
|
|
12594
12850
|
return metadataStore.loadAll().next(existingClients => {
|
|
12595
12851
|
const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
|
|
12596
12852
|
const inactive = existingClients.filter(client => active.indexOf(client) === -1);
|
|
@@ -12644,7 +12900,7 @@ class IndexedDbPersistence {
|
|
|
12644
12900
|
}
|
|
12645
12901
|
const store = primaryClientStore(txn);
|
|
12646
12902
|
return store
|
|
12647
|
-
.get(
|
|
12903
|
+
.get(DbPrimaryClientKey)
|
|
12648
12904
|
.next(currentPrimary => {
|
|
12649
12905
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12650
12906
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
@@ -12725,7 +12981,7 @@ class IndexedDbPersistence {
|
|
|
12725
12981
|
this.detachWindowUnloadHook();
|
|
12726
12982
|
// Use `SimpleDb.runTransaction` directly to avoid failing if another tab
|
|
12727
12983
|
// has obtained the primary lease.
|
|
12728
|
-
await this.simpleDb.runTransaction('shutdown', 'readwrite', [
|
|
12984
|
+
await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
|
|
12729
12985
|
const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
|
|
12730
12986
|
return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
|
|
12731
12987
|
});
|
|
@@ -12832,7 +13088,7 @@ class IndexedDbPersistence {
|
|
|
12832
13088
|
// be turned off.
|
|
12833
13089
|
verifyAllowTabSynchronization(txn) {
|
|
12834
13090
|
const store = primaryClientStore(txn);
|
|
12835
|
-
return store.get(
|
|
13091
|
+
return store.get(DbPrimaryClientKey).next(currentPrimary => {
|
|
12836
13092
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12837
13093
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
12838
13094
|
!this.isClientZombied(currentPrimary.ownerId);
|
|
@@ -12850,8 +13106,12 @@ class IndexedDbPersistence {
|
|
|
12850
13106
|
* method does not verify that the client is eligible for this lease.
|
|
12851
13107
|
*/
|
|
12852
13108
|
acquireOrExtendPrimaryLease(txn) {
|
|
12853
|
-
const newPrimary =
|
|
12854
|
-
|
|
13109
|
+
const newPrimary = {
|
|
13110
|
+
ownerId: this.clientId,
|
|
13111
|
+
allowTabSynchronization: this.allowTabSynchronization,
|
|
13112
|
+
leaseTimestampMs: Date.now()
|
|
13113
|
+
};
|
|
13114
|
+
return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
|
|
12855
13115
|
}
|
|
12856
13116
|
static isAvailable() {
|
|
12857
13117
|
return SimpleDb.isAvailable();
|
|
@@ -12859,10 +13119,10 @@ class IndexedDbPersistence {
|
|
|
12859
13119
|
/** Checks the primary lease and removes it if we are the current primary. */
|
|
12860
13120
|
releasePrimaryLeaseIfHeld(txn) {
|
|
12861
13121
|
const store = primaryClientStore(txn);
|
|
12862
|
-
return store.get(
|
|
13122
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12863
13123
|
if (this.isLocalClient(primaryClient)) {
|
|
12864
13124
|
logDebug(LOG_TAG$d, 'Releasing primary lease.');
|
|
12865
|
-
return store.delete(
|
|
13125
|
+
return store.delete(DbPrimaryClientKey);
|
|
12866
13126
|
}
|
|
12867
13127
|
else {
|
|
12868
13128
|
return PersistencePromise.resolve();
|
|
@@ -12996,13 +13256,13 @@ class IndexedDbPersistence {
|
|
|
12996
13256
|
* Helper to get a typed SimpleDbStore for the primary client object store.
|
|
12997
13257
|
*/
|
|
12998
13258
|
function primaryClientStore(txn) {
|
|
12999
|
-
return getStore(txn,
|
|
13259
|
+
return getStore(txn, DbPrimaryClientStore);
|
|
13000
13260
|
}
|
|
13001
13261
|
/**
|
|
13002
13262
|
* Helper to get a typed SimpleDbStore for the client metadata object store.
|
|
13003
13263
|
*/
|
|
13004
13264
|
function clientMetadataStore(txn) {
|
|
13005
|
-
return getStore(txn,
|
|
13265
|
+
return getStore(txn, DbClientMetadataStore);
|
|
13006
13266
|
}
|
|
13007
13267
|
/**
|
|
13008
13268
|
* Generates a string used as a prefix when storing data in IndexedDB and
|
|
@@ -14065,7 +14325,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14065
14325
|
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14066
14326
|
}
|
|
14067
14327
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14068
|
-
overlays.forEach(mutation => {
|
|
14328
|
+
overlays.forEach((_, mutation) => {
|
|
14069
14329
|
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14070
14330
|
});
|
|
14071
14331
|
return PersistencePromise.resolve();
|
|
@@ -14079,7 +14339,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14079
14339
|
return PersistencePromise.resolve();
|
|
14080
14340
|
}
|
|
14081
14341
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14082
|
-
const result =
|
|
14342
|
+
const result = newOverlayMap();
|
|
14083
14343
|
const immediateChildrenPathLength = collection.length + 1;
|
|
14084
14344
|
const prefix = new DocumentKey(collection.child(''));
|
|
14085
14345
|
const iter = this.overlays.getIteratorFrom(prefix);
|
|
@@ -14113,19 +14373,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14113
14373
|
if (overlay.largestBatchId > sinceBatchId) {
|
|
14114
14374
|
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14115
14375
|
if (overlaysForBatchId === null) {
|
|
14116
|
-
overlaysForBatchId =
|
|
14376
|
+
overlaysForBatchId = newOverlayMap();
|
|
14117
14377
|
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14118
14378
|
}
|
|
14119
14379
|
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14120
14380
|
}
|
|
14121
14381
|
}
|
|
14122
|
-
const result =
|
|
14382
|
+
const result = newOverlayMap();
|
|
14123
14383
|
const batchIter = batchIdToOverlays.getIterator();
|
|
14124
14384
|
while (batchIter.hasNext()) {
|
|
14125
14385
|
const entry = batchIter.getNext();
|
|
14126
14386
|
const overlays = entry.value;
|
|
14127
|
-
overlays.forEach((
|
|
14128
|
-
if (result.size >= count) {
|
|
14387
|
+
overlays.forEach((key, overlay) => result.set(key, overlay));
|
|
14388
|
+
if (result.size() >= count) {
|
|
14129
14389
|
break;
|
|
14130
14390
|
}
|
|
14131
14391
|
}
|
|
@@ -14138,16 +14398,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14138
14398
|
// Remove the association of the overlay to its batch id.
|
|
14139
14399
|
const existing = this.overlays.get(mutation.key);
|
|
14140
14400
|
if (existing !== null) {
|
|
14141
|
-
this.overlayByBatchId
|
|
14401
|
+
const newSet = this.overlayByBatchId
|
|
14402
|
+
.get(existing.largestBatchId)
|
|
14403
|
+
.delete(mutation.key);
|
|
14404
|
+
this.overlayByBatchId.set(existing.largestBatchId, newSet);
|
|
14142
14405
|
}
|
|
14143
14406
|
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14144
14407
|
// Create the association of this overlay to the given largestBatchId.
|
|
14145
14408
|
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14146
14409
|
if (batch === undefined) {
|
|
14147
|
-
batch =
|
|
14410
|
+
batch = documentKeySet();
|
|
14148
14411
|
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14149
14412
|
}
|
|
14150
|
-
batch.add(mutation.key);
|
|
14413
|
+
this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
|
|
14151
14414
|
}
|
|
14152
14415
|
}
|
|
14153
14416
|
|
|
@@ -15998,9 +16261,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
|
15998
16261
|
metadata.set('X-Firebase-GMPID', appId);
|
|
15999
16262
|
}
|
|
16000
16263
|
metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
|
|
16001
|
-
//
|
|
16264
|
+
// These headers are used to improve routing and project isolation by the
|
|
16002
16265
|
// backend.
|
|
16266
|
+
// TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
|
|
16267
|
+
// released with cl/428820046. Currently blocked because Emulators are now built with Java
|
|
16268
|
+
// 11 from Google3.
|
|
16003
16269
|
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
|
|
16270
|
+
metadata.set('x-goog-request-params', databasePath);
|
|
16004
16271
|
return metadata;
|
|
16005
16272
|
}
|
|
16006
16273
|
/**
|