@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.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
|
|
|
8
8
|
import * as protoLoader from '@grpc/proto-loader';
|
|
9
9
|
|
|
10
10
|
const name = "@firebase/firestore";
|
|
11
|
-
const version$1 = "3.4.
|
|
11
|
+
const version$1 = "3.4.6-2022216223411";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
61
61
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
62
62
|
User.MOCK_USER = new User('mock-user');
|
|
63
63
|
|
|
64
|
-
const version = "9.6.
|
|
64
|
+
const version = "9.6.9-2022216223411";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @license
|
|
@@ -1199,55 +1199,23 @@ function decodeResourcePath(path) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
return new ResourcePath(segments);
|
|
1201
1201
|
}
|
|
1202
|
+
|
|
1202
1203
|
/**
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
* to limbo resolution. Addresses
|
|
1210
|
-
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
1211
|
-
* 4. Multi-Tab Support.
|
|
1212
|
-
* 5. Removal of held write acks.
|
|
1213
|
-
* 6. Create document global for tracking document cache size.
|
|
1214
|
-
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
1215
|
-
* 8. Add collection-parent index for Collection Group queries.
|
|
1216
|
-
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
1217
|
-
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
1218
|
-
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
1219
|
-
* 11. Add bundles and named_queries for bundle support.
|
|
1220
|
-
* 12. Add document overlays.
|
|
1221
|
-
* 13. Add indexing support.
|
|
1222
|
-
*/
|
|
1223
|
-
const SCHEMA_VERSION = 12;
|
|
1224
|
-
/**
|
|
1225
|
-
* Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
|
|
1226
|
-
*/
|
|
1227
|
-
class DbTimestamp {
|
|
1228
|
-
constructor(seconds, nanoseconds) {
|
|
1229
|
-
this.seconds = seconds;
|
|
1230
|
-
this.nanoseconds = nanoseconds;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
/**
|
|
1234
|
-
* A singleton object to be stored in the 'owner' store in IndexedDb.
|
|
1204
|
+
* @license
|
|
1205
|
+
* Copyright 2022 Google LLC
|
|
1206
|
+
*
|
|
1207
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1208
|
+
* you may not use this file except in compliance with the License.
|
|
1209
|
+
* You may obtain a copy of the License at
|
|
1235
1210
|
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1211
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1212
|
+
*
|
|
1213
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1214
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1215
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1216
|
+
* See the License for the specific language governing permissions and
|
|
1217
|
+
* limitations under the License.
|
|
1241
1218
|
*/
|
|
1242
|
-
class DbPrimaryClient {
|
|
1243
|
-
constructor(ownerId,
|
|
1244
|
-
/** Whether to allow shared access from multiple tabs. */
|
|
1245
|
-
allowTabSynchronization, leaseTimestampMs) {
|
|
1246
|
-
this.ownerId = ownerId;
|
|
1247
|
-
this.allowTabSynchronization = allowTabSynchronization;
|
|
1248
|
-
this.leaseTimestampMs = leaseTimestampMs;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
1219
|
/**
|
|
1252
1220
|
* Name of the IndexedDb object store.
|
|
1253
1221
|
*
|
|
@@ -1255,239 +1223,62 @@ class DbPrimaryClient {
|
|
|
1255
1223
|
* older clients that only supported single locked access to the persistence
|
|
1256
1224
|
* layer.
|
|
1257
1225
|
*/
|
|
1258
|
-
|
|
1226
|
+
const DbPrimaryClientStore = 'owner';
|
|
1259
1227
|
/**
|
|
1260
1228
|
* The key string used for the single object that exists in the
|
|
1261
1229
|
* DbPrimaryClient store.
|
|
1262
1230
|
*/
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* An object to be stored in the 'mutationQueues' store in IndexedDb.
|
|
1266
|
-
*
|
|
1267
|
-
* Each user gets a single queue of MutationBatches to apply to the server.
|
|
1268
|
-
* DbMutationQueue tracks the metadata about the queue.
|
|
1269
|
-
*/
|
|
1270
|
-
class DbMutationQueue {
|
|
1271
|
-
constructor(
|
|
1272
|
-
/**
|
|
1273
|
-
* The normalized user ID to which this queue belongs.
|
|
1274
|
-
*/
|
|
1275
|
-
userId,
|
|
1276
|
-
/**
|
|
1277
|
-
* An identifier for the highest numbered batch that has been acknowledged
|
|
1278
|
-
* by the server. All MutationBatches in this queue with batchIds less
|
|
1279
|
-
* than or equal to this value are considered to have been acknowledged by
|
|
1280
|
-
* the server.
|
|
1281
|
-
*
|
|
1282
|
-
* NOTE: this is deprecated and no longer used by the code.
|
|
1283
|
-
*/
|
|
1284
|
-
lastAcknowledgedBatchId,
|
|
1285
|
-
/**
|
|
1286
|
-
* A stream token that was previously sent by the server.
|
|
1287
|
-
*
|
|
1288
|
-
* See StreamingWriteRequest in datastore.proto for more details about
|
|
1289
|
-
* usage.
|
|
1290
|
-
*
|
|
1291
|
-
* After sending this token, earlier tokens may not be used anymore so
|
|
1292
|
-
* only a single stream token is retained.
|
|
1293
|
-
*
|
|
1294
|
-
* NOTE: this is deprecated and no longer used by the code.
|
|
1295
|
-
*/
|
|
1296
|
-
lastStreamToken) {
|
|
1297
|
-
this.userId = userId;
|
|
1298
|
-
this.lastAcknowledgedBatchId = lastAcknowledgedBatchId;
|
|
1299
|
-
this.lastStreamToken = lastStreamToken;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1231
|
+
const DbPrimaryClientKey = 'owner';
|
|
1302
1232
|
/** Name of the IndexedDb object store. */
|
|
1303
|
-
|
|
1233
|
+
const DbMutationQueueStore = 'mutationQueues';
|
|
1304
1234
|
/** Keys are automatically assigned via the userId property. */
|
|
1305
|
-
|
|
1306
|
-
/**
|
|
1307
|
-
* An object to be stored in the 'mutations' store in IndexedDb.
|
|
1308
|
-
*
|
|
1309
|
-
* Represents a batch of user-level mutations intended to be sent to the server
|
|
1310
|
-
* in a single write. Each user-level batch gets a separate DbMutationBatch
|
|
1311
|
-
* with a new batchId.
|
|
1312
|
-
*/
|
|
1313
|
-
class DbMutationBatch {
|
|
1314
|
-
constructor(
|
|
1315
|
-
/**
|
|
1316
|
-
* The normalized user ID to which this batch belongs.
|
|
1317
|
-
*/
|
|
1318
|
-
userId,
|
|
1319
|
-
/**
|
|
1320
|
-
* An identifier for this batch, allocated using an auto-generated key.
|
|
1321
|
-
*/
|
|
1322
|
-
batchId,
|
|
1323
|
-
/**
|
|
1324
|
-
* The local write time of the batch, stored as milliseconds since the
|
|
1325
|
-
* epoch.
|
|
1326
|
-
*/
|
|
1327
|
-
localWriteTimeMs,
|
|
1328
|
-
/**
|
|
1329
|
-
* A list of "mutations" that represent a partial base state from when this
|
|
1330
|
-
* write batch was initially created. During local application of the write
|
|
1331
|
-
* batch, these baseMutations are applied prior to the real writes in order
|
|
1332
|
-
* to override certain document fields from the remote document cache. This
|
|
1333
|
-
* is necessary in the case of non-idempotent writes (e.g. `increment()`
|
|
1334
|
-
* transforms) to make sure that the local view of the modified documents
|
|
1335
|
-
* doesn't flicker if the remote document cache receives the result of the
|
|
1336
|
-
* non-idempotent write before the write is removed from the queue.
|
|
1337
|
-
*
|
|
1338
|
-
* These mutations are never sent to the backend.
|
|
1339
|
-
*/
|
|
1340
|
-
baseMutations,
|
|
1341
|
-
/**
|
|
1342
|
-
* A list of mutations to apply. All mutations will be applied atomically.
|
|
1343
|
-
*
|
|
1344
|
-
* Mutations are serialized via toMutation().
|
|
1345
|
-
*/
|
|
1346
|
-
mutations) {
|
|
1347
|
-
this.userId = userId;
|
|
1348
|
-
this.batchId = batchId;
|
|
1349
|
-
this.localWriteTimeMs = localWriteTimeMs;
|
|
1350
|
-
this.baseMutations = baseMutations;
|
|
1351
|
-
this.mutations = mutations;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1235
|
+
const DbMutationQueueKeyPath = 'userId';
|
|
1354
1236
|
/** Name of the IndexedDb object store. */
|
|
1355
|
-
|
|
1237
|
+
const DbMutationBatchStore = 'mutations';
|
|
1356
1238
|
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
1357
|
-
|
|
1239
|
+
const DbMutationBatchKeyPath = 'batchId';
|
|
1358
1240
|
/** The index name for lookup of mutations by user. */
|
|
1359
|
-
|
|
1241
|
+
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
1360
1242
|
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
1361
|
-
|
|
1243
|
+
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
1362
1244
|
/**
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
* A manually maintained index of all the mutation batches that affect a given
|
|
1366
|
-
* document key. The rows in this table are references based on the contents of
|
|
1367
|
-
* DbMutationBatch.mutations.
|
|
1245
|
+
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1246
|
+
* over all of a user's document mutations.
|
|
1368
1247
|
*/
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
/**
|
|
1372
|
-
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1373
|
-
* over all of a user's document mutations.
|
|
1374
|
-
*/
|
|
1375
|
-
static prefixForUser(userId) {
|
|
1376
|
-
return [userId];
|
|
1377
|
-
}
|
|
1378
|
-
/**
|
|
1379
|
-
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1380
|
-
* index to iterate over all at document mutations for a given path or lower.
|
|
1381
|
-
*/
|
|
1382
|
-
static prefixForPath(userId, path) {
|
|
1383
|
-
return [userId, encodeResourcePath(path)];
|
|
1384
|
-
}
|
|
1385
|
-
/**
|
|
1386
|
-
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1387
|
-
* and deleting into the DbDocumentMutations index.
|
|
1388
|
-
*/
|
|
1389
|
-
static key(userId, path, batchId) {
|
|
1390
|
-
return [userId, encodeResourcePath(path), batchId];
|
|
1391
|
-
}
|
|
1248
|
+
function newDbDocumentMutationPrefixForUser(userId) {
|
|
1249
|
+
return [userId];
|
|
1392
1250
|
}
|
|
1393
|
-
DbDocumentMutation.store = 'documentMutations';
|
|
1394
1251
|
/**
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1398
|
-
* information.
|
|
1399
|
-
*/
|
|
1400
|
-
DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
|
|
1401
|
-
/**
|
|
1402
|
-
* Represents the known absence of a document at a particular version.
|
|
1403
|
-
* Stored in IndexedDb as part of a DbRemoteDocument object.
|
|
1252
|
+
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1253
|
+
* index to iterate over all at document mutations for a given path or lower.
|
|
1404
1254
|
*/
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
this.path = path;
|
|
1408
|
-
this.readTime = readTime;
|
|
1409
|
-
}
|
|
1255
|
+
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
1256
|
+
return [userId, encodeResourcePath(path)];
|
|
1410
1257
|
}
|
|
1411
1258
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1259
|
+
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1260
|
+
* and deleting into the DbDocumentMutations index.
|
|
1414
1261
|
*/
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
this.path = path;
|
|
1418
|
-
this.version = version;
|
|
1419
|
-
}
|
|
1262
|
+
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
1263
|
+
return [userId, encodeResourcePath(path), batchId];
|
|
1420
1264
|
}
|
|
1421
1265
|
/**
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
* - A "no document" representing a document that is known not to exist (at
|
|
1427
|
-
* some version).
|
|
1428
|
-
* - An "unknown document" representing a document that is known to exist (at
|
|
1429
|
-
* some version) but whose contents are unknown.
|
|
1430
|
-
*
|
|
1431
|
-
* Note: This is the persisted equivalent of a MaybeDocument and could perhaps
|
|
1432
|
-
* be made more general if necessary.
|
|
1266
|
+
* Because we store all the useful information for this store in the key,
|
|
1267
|
+
* there is no useful information to store as the value. The raw (unencoded)
|
|
1268
|
+
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1269
|
+
* information.
|
|
1433
1270
|
*/
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
// inside the encoded documents). During our next migration, we should
|
|
1438
|
-
// rewrite the primary key as parentPath + document ID which would allow us
|
|
1439
|
-
// to drop one value.
|
|
1440
|
-
constructor(
|
|
1441
|
-
/**
|
|
1442
|
-
* Set to an instance of DbUnknownDocument if the data for a document is
|
|
1443
|
-
* not known, but it is known that a document exists at the specified
|
|
1444
|
-
* version (e.g. it had a successful update applied to it)
|
|
1445
|
-
*/
|
|
1446
|
-
unknownDocument,
|
|
1447
|
-
/**
|
|
1448
|
-
* Set to an instance of a DbNoDocument if it is known that no document
|
|
1449
|
-
* exists.
|
|
1450
|
-
*/
|
|
1451
|
-
noDocument,
|
|
1452
|
-
/**
|
|
1453
|
-
* Set to an instance of a Document if there's a cached version of the
|
|
1454
|
-
* document.
|
|
1455
|
-
*/
|
|
1456
|
-
document,
|
|
1457
|
-
/**
|
|
1458
|
-
* Documents that were written to the remote document store based on
|
|
1459
|
-
* a write acknowledgment are marked with `hasCommittedMutations`. These
|
|
1460
|
-
* documents are potentially inconsistent with the backend's copy and use
|
|
1461
|
-
* the write's commit version as their document version.
|
|
1462
|
-
*/
|
|
1463
|
-
hasCommittedMutations,
|
|
1464
|
-
/**
|
|
1465
|
-
* When the document was read from the backend. Undefined for data written
|
|
1466
|
-
* prior to schema version 9.
|
|
1467
|
-
*/
|
|
1468
|
-
readTime,
|
|
1469
|
-
/**
|
|
1470
|
-
* The path of the collection this document is part of. Undefined for data
|
|
1471
|
-
* written prior to schema version 9.
|
|
1472
|
-
*/
|
|
1473
|
-
parentPath) {
|
|
1474
|
-
this.unknownDocument = unknownDocument;
|
|
1475
|
-
this.noDocument = noDocument;
|
|
1476
|
-
this.document = document;
|
|
1477
|
-
this.hasCommittedMutations = hasCommittedMutations;
|
|
1478
|
-
this.readTime = readTime;
|
|
1479
|
-
this.parentPath = parentPath;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
DbRemoteDocument.store = 'remoteDocuments';
|
|
1271
|
+
const DbDocumentMutationPlaceholder = {};
|
|
1272
|
+
const DbDocumentMutationStore = 'documentMutations';
|
|
1273
|
+
const DbRemoteDocumentStore = 'remoteDocuments';
|
|
1483
1274
|
/**
|
|
1484
1275
|
* An index that provides access to all entries sorted by read time (which
|
|
1485
1276
|
* corresponds to the last modification time of each row).
|
|
1486
1277
|
*
|
|
1487
1278
|
* This index is used to provide a changelog for Multi-Tab.
|
|
1488
1279
|
*/
|
|
1489
|
-
|
|
1490
|
-
|
|
1280
|
+
const DbRemoteDocumentReadTimeIndex = 'readTimeIndex';
|
|
1281
|
+
const DbRemoteDocumentReadTimeIndexPath = 'readTime';
|
|
1491
1282
|
/**
|
|
1492
1283
|
* An index that provides access to documents in a collection sorted by read
|
|
1493
1284
|
* time.
|
|
@@ -1495,361 +1286,68 @@ DbRemoteDocument.readTimeIndexPath = 'readTime';
|
|
|
1495
1286
|
* This index is used to allow the RemoteDocumentCache to fetch newly changed
|
|
1496
1287
|
* documents in a collection.
|
|
1497
1288
|
*/
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
* documents in the document cache.
|
|
1507
|
-
*/
|
|
1508
|
-
constructor(byteSize) {
|
|
1509
|
-
this.byteSize = byteSize;
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
DbRemoteDocumentGlobal.store = 'remoteDocumentGlobal';
|
|
1513
|
-
DbRemoteDocumentGlobal.key = 'remoteDocumentGlobalKey';
|
|
1514
|
-
/**
|
|
1515
|
-
* An object to be stored in the 'targets' store in IndexedDb.
|
|
1516
|
-
*
|
|
1517
|
-
* This is based on and should be kept in sync with the proto used in the iOS
|
|
1518
|
-
* client.
|
|
1519
|
-
*
|
|
1520
|
-
* Each query the client listens to against the server is tracked on disk so
|
|
1521
|
-
* that the query can be efficiently resumed on restart.
|
|
1522
|
-
*/
|
|
1523
|
-
class DbTarget {
|
|
1524
|
-
constructor(
|
|
1525
|
-
/**
|
|
1526
|
-
* An auto-generated sequential numeric identifier for the query.
|
|
1527
|
-
*
|
|
1528
|
-
* Queries are stored using their canonicalId as the key, but these
|
|
1529
|
-
* canonicalIds can be quite long so we additionally assign a unique
|
|
1530
|
-
* queryId which can be used by referenced data structures (e.g.
|
|
1531
|
-
* indexes) to minimize the on-disk cost.
|
|
1532
|
-
*/
|
|
1533
|
-
targetId,
|
|
1534
|
-
/**
|
|
1535
|
-
* The canonical string representing this query. This is not unique.
|
|
1536
|
-
*/
|
|
1537
|
-
canonicalId,
|
|
1538
|
-
/**
|
|
1539
|
-
* The last readTime received from the Watch Service for this query.
|
|
1540
|
-
*
|
|
1541
|
-
* This is the same value as TargetChange.read_time in the protos.
|
|
1542
|
-
*/
|
|
1543
|
-
readTime,
|
|
1544
|
-
/**
|
|
1545
|
-
* An opaque, server-assigned token that allows watching a query to be
|
|
1546
|
-
* resumed after disconnecting without retransmitting all the data
|
|
1547
|
-
* that matches the query. The resume token essentially identifies a
|
|
1548
|
-
* point in time from which the server should resume sending results.
|
|
1549
|
-
*
|
|
1550
|
-
* This is related to the snapshotVersion in that the resumeToken
|
|
1551
|
-
* effectively also encodes that value, but the resumeToken is opaque
|
|
1552
|
-
* and sometimes encodes additional information.
|
|
1553
|
-
*
|
|
1554
|
-
* A consequence of this is that the resumeToken should be used when
|
|
1555
|
-
* asking the server to reason about where this client is in the watch
|
|
1556
|
-
* stream, but the client should use the snapshotVersion for its own
|
|
1557
|
-
* purposes.
|
|
1558
|
-
*
|
|
1559
|
-
* This is the same value as TargetChange.resume_token in the protos.
|
|
1560
|
-
*/
|
|
1561
|
-
resumeToken,
|
|
1562
|
-
/**
|
|
1563
|
-
* A sequence number representing the last time this query was
|
|
1564
|
-
* listened to, used for garbage collection purposes.
|
|
1565
|
-
*
|
|
1566
|
-
* Conventionally this would be a timestamp value, but device-local
|
|
1567
|
-
* clocks are unreliable and they must be able to create new listens
|
|
1568
|
-
* even while disconnected. Instead this should be a monotonically
|
|
1569
|
-
* increasing number that's incremented on each listen call.
|
|
1570
|
-
*
|
|
1571
|
-
* This is different from the queryId since the queryId is an
|
|
1572
|
-
* immutable identifier assigned to the Query on first use while
|
|
1573
|
-
* lastListenSequenceNumber is updated every time the query is
|
|
1574
|
-
* listened to.
|
|
1575
|
-
*/
|
|
1576
|
-
lastListenSequenceNumber,
|
|
1577
|
-
/**
|
|
1578
|
-
* Denotes the maximum snapshot version at which the associated query view
|
|
1579
|
-
* contained no limbo documents. Undefined for data written prior to
|
|
1580
|
-
* schema version 9.
|
|
1581
|
-
*/
|
|
1582
|
-
lastLimboFreeSnapshotVersion,
|
|
1583
|
-
/**
|
|
1584
|
-
* The query for this target.
|
|
1585
|
-
*
|
|
1586
|
-
* Because canonical ids are not unique we must store the actual query. We
|
|
1587
|
-
* use the proto to have an object we can persist without having to
|
|
1588
|
-
* duplicate translation logic to and from a `Query` object.
|
|
1589
|
-
*/
|
|
1590
|
-
query) {
|
|
1591
|
-
this.targetId = targetId;
|
|
1592
|
-
this.canonicalId = canonicalId;
|
|
1593
|
-
this.readTime = readTime;
|
|
1594
|
-
this.resumeToken = resumeToken;
|
|
1595
|
-
this.lastListenSequenceNumber = lastListenSequenceNumber;
|
|
1596
|
-
this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
|
|
1597
|
-
this.query = query;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
DbTarget.store = 'targets';
|
|
1289
|
+
const DbRemoteDocumentCollectionReadTimeIndex = 'collectionReadTimeIndex';
|
|
1290
|
+
const DbRemoteDocumentCollectionReadTimeIndexPath = [
|
|
1291
|
+
'parentPath',
|
|
1292
|
+
'readTime'
|
|
1293
|
+
];
|
|
1294
|
+
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
1295
|
+
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
1296
|
+
const DbTargetStore = 'targets';
|
|
1601
1297
|
/** Keys are automatically assigned via the targetId property. */
|
|
1602
|
-
|
|
1298
|
+
const DbTargetKeyPath = 'targetId';
|
|
1603
1299
|
/** The name of the queryTargets index. */
|
|
1604
|
-
|
|
1300
|
+
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
1605
1301
|
/**
|
|
1606
1302
|
* The index of all canonicalIds to the targets that they match. This is not
|
|
1607
1303
|
* a unique mapping because canonicalId does not promise a unique name for all
|
|
1608
1304
|
* possible queries, so we append the targetId to make the mapping unique.
|
|
1609
1305
|
*/
|
|
1610
|
-
|
|
1611
|
-
/**
|
|
1612
|
-
* An object representing an association between a target and a document, or a
|
|
1613
|
-
* sentinel row marking the last sequence number at which a document was used.
|
|
1614
|
-
* Each document cached must have a corresponding sentinel row before lru
|
|
1615
|
-
* garbage collection is enabled.
|
|
1616
|
-
*
|
|
1617
|
-
* The target associations and sentinel rows are co-located so that orphaned
|
|
1618
|
-
* documents and their sequence numbers can be identified efficiently via a scan
|
|
1619
|
-
* of this store.
|
|
1620
|
-
*/
|
|
1621
|
-
class DbTargetDocument {
|
|
1622
|
-
constructor(
|
|
1623
|
-
/**
|
|
1624
|
-
* The targetId identifying a target or 0 for a sentinel row.
|
|
1625
|
-
*/
|
|
1626
|
-
targetId,
|
|
1627
|
-
/**
|
|
1628
|
-
* The path to the document, as encoded in the key.
|
|
1629
|
-
*/
|
|
1630
|
-
path,
|
|
1631
|
-
/**
|
|
1632
|
-
* If this is a sentinel row, this should be the sequence number of the last
|
|
1633
|
-
* time the document specified by `path` was used. Otherwise, it should be
|
|
1634
|
-
* `undefined`.
|
|
1635
|
-
*/
|
|
1636
|
-
sequenceNumber) {
|
|
1637
|
-
this.targetId = targetId;
|
|
1638
|
-
this.path = path;
|
|
1639
|
-
this.sequenceNumber = sequenceNumber;
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1306
|
+
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
1642
1307
|
/** Name of the IndexedDb object store. */
|
|
1643
|
-
|
|
1308
|
+
const DbTargetDocumentStore = 'targetDocuments';
|
|
1644
1309
|
/** Keys are automatically assigned via the targetId, path properties. */
|
|
1645
|
-
|
|
1310
|
+
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
1646
1311
|
/** The index name for the reverse index. */
|
|
1647
|
-
|
|
1312
|
+
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
1648
1313
|
/** We also need to create the reverse index for these properties. */
|
|
1649
|
-
|
|
1650
|
-
/**
|
|
1651
|
-
* A record of global state tracked across all Targets, tracked separately
|
|
1652
|
-
* to avoid the need for extra indexes.
|
|
1653
|
-
*
|
|
1654
|
-
* This should be kept in-sync with the proto used in the iOS client.
|
|
1655
|
-
*/
|
|
1656
|
-
class DbTargetGlobal {
|
|
1657
|
-
constructor(
|
|
1658
|
-
/**
|
|
1659
|
-
* The highest numbered target id across all targets.
|
|
1660
|
-
*
|
|
1661
|
-
* See DbTarget.targetId.
|
|
1662
|
-
*/
|
|
1663
|
-
highestTargetId,
|
|
1664
|
-
/**
|
|
1665
|
-
* The highest numbered lastListenSequenceNumber across all targets.
|
|
1666
|
-
*
|
|
1667
|
-
* See DbTarget.lastListenSequenceNumber.
|
|
1668
|
-
*/
|
|
1669
|
-
highestListenSequenceNumber,
|
|
1670
|
-
/**
|
|
1671
|
-
* A global snapshot version representing the last consistent snapshot we
|
|
1672
|
-
* received from the backend. This is monotonically increasing and any
|
|
1673
|
-
* snapshots received from the backend prior to this version (e.g. for
|
|
1674
|
-
* targets resumed with a resumeToken) should be suppressed (buffered)
|
|
1675
|
-
* until the backend has caught up to this snapshot version again. This
|
|
1676
|
-
* prevents our cache from ever going backwards in time.
|
|
1677
|
-
*/
|
|
1678
|
-
lastRemoteSnapshotVersion,
|
|
1679
|
-
/**
|
|
1680
|
-
* The number of targets persisted.
|
|
1681
|
-
*/
|
|
1682
|
-
targetCount) {
|
|
1683
|
-
this.highestTargetId = highestTargetId;
|
|
1684
|
-
this.highestListenSequenceNumber = highestListenSequenceNumber;
|
|
1685
|
-
this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
|
|
1686
|
-
this.targetCount = targetCount;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1314
|
+
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
1689
1315
|
/**
|
|
1690
1316
|
* The key string used for the single object that exists in the
|
|
1691
1317
|
* DbTargetGlobal store.
|
|
1692
1318
|
*/
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
/**
|
|
1696
|
-
* An object representing an association between a Collection id (e.g. 'messages')
|
|
1697
|
-
* to a parent path (e.g. '/chats/123') that contains it as a (sub)collection.
|
|
1698
|
-
* This is used to efficiently find all collections to query when performing
|
|
1699
|
-
* a Collection Group query.
|
|
1700
|
-
*/
|
|
1701
|
-
class DbCollectionParent {
|
|
1702
|
-
constructor(
|
|
1703
|
-
/**
|
|
1704
|
-
* The collectionId (e.g. 'messages')
|
|
1705
|
-
*/
|
|
1706
|
-
collectionId,
|
|
1707
|
-
/**
|
|
1708
|
-
* The path to the parent (either a document location or an empty path for
|
|
1709
|
-
* a root-level collection).
|
|
1710
|
-
*/
|
|
1711
|
-
parent) {
|
|
1712
|
-
this.collectionId = collectionId;
|
|
1713
|
-
this.parent = parent;
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1319
|
+
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
1320
|
+
const DbTargetGlobalStore = 'targetGlobal';
|
|
1716
1321
|
/** Name of the IndexedDb object store. */
|
|
1717
|
-
|
|
1322
|
+
const DbCollectionParentStore = 'collectionParents';
|
|
1718
1323
|
/** Keys are automatically assigned via the collectionId, parent properties. */
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
* A record of the metadata state of each client.
|
|
1722
|
-
*
|
|
1723
|
-
* PORTING NOTE: This is used to synchronize multi-tab state and does not need
|
|
1724
|
-
* to be ported to iOS or Android.
|
|
1725
|
-
*/
|
|
1726
|
-
class DbClientMetadata {
|
|
1727
|
-
constructor(
|
|
1728
|
-
// Note: Previous schema versions included a field
|
|
1729
|
-
// "lastProcessedDocumentChangeId". Don't use anymore.
|
|
1730
|
-
/** The auto-generated client id assigned at client startup. */
|
|
1731
|
-
clientId,
|
|
1732
|
-
/** The last time this state was updated. */
|
|
1733
|
-
updateTimeMs,
|
|
1734
|
-
/** Whether the client's network connection is enabled. */
|
|
1735
|
-
networkEnabled,
|
|
1736
|
-
/** Whether this client is running in a foreground tab. */
|
|
1737
|
-
inForeground) {
|
|
1738
|
-
this.clientId = clientId;
|
|
1739
|
-
this.updateTimeMs = updateTimeMs;
|
|
1740
|
-
this.networkEnabled = networkEnabled;
|
|
1741
|
-
this.inForeground = inForeground;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1324
|
+
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
1744
1325
|
/** Name of the IndexedDb object store. */
|
|
1745
|
-
|
|
1326
|
+
const DbClientMetadataStore = 'clientMetadata';
|
|
1746
1327
|
/** Keys are automatically assigned via the clientId properties. */
|
|
1747
|
-
|
|
1748
|
-
/** An object representing a bundle loaded by the SDK. */
|
|
1749
|
-
class DbBundle {
|
|
1750
|
-
constructor(
|
|
1751
|
-
/** The ID of the loaded bundle. */
|
|
1752
|
-
bundleId,
|
|
1753
|
-
/** The create time of the loaded bundle. */
|
|
1754
|
-
createTime,
|
|
1755
|
-
/** The schema version of the loaded bundle. */
|
|
1756
|
-
version) {
|
|
1757
|
-
this.bundleId = bundleId;
|
|
1758
|
-
this.createTime = createTime;
|
|
1759
|
-
this.version = version;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1328
|
+
const DbClientMetadataKeyPath = 'clientId';
|
|
1762
1329
|
/** Name of the IndexedDb object store. */
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
/** An object representing a named query loaded by the SDK via a bundle. */
|
|
1766
|
-
class DbNamedQuery {
|
|
1767
|
-
constructor(
|
|
1768
|
-
/** The name of the query. */
|
|
1769
|
-
name,
|
|
1770
|
-
/** The read time of the results saved in the bundle from the named query. */
|
|
1771
|
-
readTime,
|
|
1772
|
-
/** The query saved in the bundle. */
|
|
1773
|
-
bundledQuery) {
|
|
1774
|
-
this.name = name;
|
|
1775
|
-
this.readTime = readTime;
|
|
1776
|
-
this.bundledQuery = bundledQuery;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1330
|
+
const DbBundleStore = 'bundles';
|
|
1331
|
+
const DbBundleKeyPath = 'bundleId';
|
|
1779
1332
|
/** Name of the IndexedDb object store. */
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
/** An object representing the global configuration for a field index. */
|
|
1783
|
-
class DbIndexConfiguration {
|
|
1784
|
-
constructor(
|
|
1785
|
-
/**
|
|
1786
|
-
* The index id for this entry. Undefined for indexes that are not yet
|
|
1787
|
-
* persisted.
|
|
1788
|
-
*/
|
|
1789
|
-
indexId,
|
|
1790
|
-
/** The collection group this index belongs to. */
|
|
1791
|
-
collectionGroup,
|
|
1792
|
-
/** The fields to index for this index. */
|
|
1793
|
-
fields) {
|
|
1794
|
-
this.indexId = indexId;
|
|
1795
|
-
this.collectionGroup = collectionGroup;
|
|
1796
|
-
this.fields = fields;
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1333
|
+
const DbNamedQueryStore = 'namedQueries';
|
|
1334
|
+
const DbNamedQueryKeyPath = 'name';
|
|
1799
1335
|
/** Name of the IndexedDb object store. */
|
|
1800
|
-
|
|
1801
|
-
|
|
1336
|
+
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
1337
|
+
const DbIndexConfigurationKeyPath = 'indexId';
|
|
1802
1338
|
/**
|
|
1803
|
-
* An index that provides access to the index configurations by collection
|
|
1804
|
-
* group.
|
|
1805
|
-
*
|
|
1806
|
-
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1807
|
-
* not possible here as the Web client supports concurrent access to
|
|
1808
|
-
* persistence via multi-tab.
|
|
1809
|
-
*/
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
/**
|
|
1813
|
-
* An object describing how up-to-date the index backfill is for each user and
|
|
1814
|
-
* index.
|
|
1815
|
-
*/
|
|
1816
|
-
class DbIndexState {
|
|
1817
|
-
constructor(
|
|
1818
|
-
/** The index id for this entry. */
|
|
1819
|
-
indexId,
|
|
1820
|
-
/** The user id for this entry. */
|
|
1821
|
-
uid,
|
|
1822
|
-
/**
|
|
1823
|
-
* A number that indicates when the index was last updated (relative to
|
|
1824
|
-
* other indexes).
|
|
1825
|
-
*/
|
|
1826
|
-
sequenceNumber,
|
|
1827
|
-
/**
|
|
1828
|
-
* The latest read time that has been indexed by Firestore for this field
|
|
1829
|
-
* index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
|
|
1830
|
-
*/
|
|
1831
|
-
readTime,
|
|
1832
|
-
/**
|
|
1833
|
-
* The last document that has been indexed for this field index. Empty if
|
|
1834
|
-
* no documents have been indexed.
|
|
1835
|
-
*/
|
|
1836
|
-
documentKey,
|
|
1837
|
-
/**
|
|
1838
|
-
* The largest mutation batch id that has been processed for this index. -1
|
|
1839
|
-
* if no mutations have been indexed.
|
|
1840
|
-
*/
|
|
1841
|
-
largestBatchId) {
|
|
1842
|
-
this.indexId = indexId;
|
|
1843
|
-
this.uid = uid;
|
|
1844
|
-
this.sequenceNumber = sequenceNumber;
|
|
1845
|
-
this.readTime = readTime;
|
|
1846
|
-
this.documentKey = documentKey;
|
|
1847
|
-
this.largestBatchId = largestBatchId;
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1339
|
+
* An index that provides access to the index configurations by collection
|
|
1340
|
+
* group.
|
|
1341
|
+
*
|
|
1342
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1343
|
+
* not possible here as the Web client supports concurrent access to
|
|
1344
|
+
* persistence via multi-tab.
|
|
1345
|
+
*/
|
|
1346
|
+
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
1347
|
+
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
1850
1348
|
/** Name of the IndexedDb object store. */
|
|
1851
|
-
|
|
1852
|
-
|
|
1349
|
+
const DbIndexStateStore = 'indexState';
|
|
1350
|
+
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
1853
1351
|
/**
|
|
1854
1352
|
* An index that provides access to documents in a collection sorted by last
|
|
1855
1353
|
* update time. Used by the backfiller.
|
|
@@ -1858,108 +1356,66 @@ DbIndexState.keyPath = ['indexId', 'uid'];
|
|
|
1858
1356
|
* not possible here as the Web client supports concurrent access to
|
|
1859
1357
|
* persistence via multi-tab.
|
|
1860
1358
|
*/
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
/** An object that stores the encoded entries for all documents and fields. */
|
|
1864
|
-
class DbIndexEntry {
|
|
1865
|
-
constructor(
|
|
1866
|
-
/** The index id for this entry. */
|
|
1867
|
-
indexId,
|
|
1868
|
-
/** The user id for this entry. */
|
|
1869
|
-
uid,
|
|
1870
|
-
/** The encoded array index value for this entry. */
|
|
1871
|
-
arrayValue,
|
|
1872
|
-
/** The encoded directional value for equality and inequality filters. */
|
|
1873
|
-
directionalValue,
|
|
1874
|
-
/** The document key this entry points to. */
|
|
1875
|
-
documentKey) {
|
|
1876
|
-
this.indexId = indexId;
|
|
1877
|
-
this.uid = uid;
|
|
1878
|
-
this.arrayValue = arrayValue;
|
|
1879
|
-
this.directionalValue = directionalValue;
|
|
1880
|
-
this.documentKey = documentKey;
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1359
|
+
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
1360
|
+
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1883
1361
|
/** Name of the IndexedDb object store. */
|
|
1884
|
-
|
|
1885
|
-
|
|
1362
|
+
const DbIndexEntryStore = 'indexEntries';
|
|
1363
|
+
const DbIndexEntryKeyPath = [
|
|
1886
1364
|
'indexId',
|
|
1887
1365
|
'uid',
|
|
1888
1366
|
'arrayValue',
|
|
1889
1367
|
'directionalValue',
|
|
1890
1368
|
'documentKey'
|
|
1891
1369
|
];
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
constructor(
|
|
1899
|
-
/** The user ID to whom this overlay belongs. */
|
|
1900
|
-
userId,
|
|
1901
|
-
/** The path to the collection that contains the document. */
|
|
1902
|
-
collectionPath,
|
|
1903
|
-
/** The ID (key) of the document within the collection. */
|
|
1904
|
-
documentId,
|
|
1905
|
-
/** The collection group to which the document belongs. */
|
|
1906
|
-
collectionGroup,
|
|
1907
|
-
/** The largest batch ID that's been applied for this overlay. */
|
|
1908
|
-
largestBatchId,
|
|
1909
|
-
/** The overlay mutation. */
|
|
1910
|
-
overlayMutation) {
|
|
1911
|
-
this.userId = userId;
|
|
1912
|
-
this.collectionPath = collectionPath;
|
|
1913
|
-
this.documentId = documentId;
|
|
1914
|
-
this.collectionGroup = collectionGroup;
|
|
1915
|
-
this.largestBatchId = largestBatchId;
|
|
1916
|
-
this.overlayMutation = overlayMutation;
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1370
|
+
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1371
|
+
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1372
|
+
'indexId',
|
|
1373
|
+
'uid',
|
|
1374
|
+
'documentKey'
|
|
1375
|
+
];
|
|
1919
1376
|
/** Name of the IndexedDb object store. */
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1377
|
+
const DbDocumentOverlayStore = 'documentOverlays';
|
|
1378
|
+
const DbDocumentOverlayKeyPath = [
|
|
1379
|
+
'userId',
|
|
1380
|
+
'collectionPath',
|
|
1381
|
+
'documentId'
|
|
1382
|
+
];
|
|
1383
|
+
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1384
|
+
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
1924
1385
|
'userId',
|
|
1925
1386
|
'collectionPath',
|
|
1926
1387
|
'largestBatchId'
|
|
1927
1388
|
];
|
|
1928
|
-
|
|
1929
|
-
|
|
1389
|
+
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1390
|
+
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
1930
1391
|
'userId',
|
|
1931
1392
|
'collectionGroup',
|
|
1932
1393
|
'largestBatchId'
|
|
1933
1394
|
];
|
|
1934
1395
|
// Visible for testing
|
|
1935
1396
|
const V1_STORES = [
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1397
|
+
DbMutationQueueStore,
|
|
1398
|
+
DbMutationBatchStore,
|
|
1399
|
+
DbDocumentMutationStore,
|
|
1400
|
+
DbRemoteDocumentStore,
|
|
1401
|
+
DbTargetStore,
|
|
1402
|
+
DbPrimaryClientStore,
|
|
1403
|
+
DbTargetGlobalStore,
|
|
1404
|
+
DbTargetDocumentStore
|
|
1944
1405
|
];
|
|
1945
|
-
// V2 is no longer usable (see comment at top of file)
|
|
1946
1406
|
// Visible for testing
|
|
1947
1407
|
const V3_STORES = V1_STORES;
|
|
1948
1408
|
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
1949
|
-
const V4_STORES = [...V3_STORES,
|
|
1950
|
-
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
const
|
|
1954
|
-
// V9 does not change the set of stores.
|
|
1955
|
-
// V10 does not change the set of stores.
|
|
1956
|
-
const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
|
|
1957
|
-
const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
|
|
1409
|
+
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
1410
|
+
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
1411
|
+
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
1412
|
+
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
1413
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
1958
1414
|
const V13_STORES = [
|
|
1959
1415
|
...V12_STORES,
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1416
|
+
DbIndexConfigurationStore,
|
|
1417
|
+
DbIndexStateStore,
|
|
1418
|
+
DbIndexEntryStore
|
|
1963
1419
|
];
|
|
1964
1420
|
/** Returns the object stores for the provided schema. */
|
|
1965
1421
|
function getObjectStores(schemaVersion) {
|
|
@@ -2677,6 +2133,21 @@ class SimpleDbStore {
|
|
|
2677
2133
|
});
|
|
2678
2134
|
}
|
|
2679
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2138
|
+
* elements if no limit is provided.
|
|
2139
|
+
*/
|
|
2140
|
+
loadFirst(range, count) {
|
|
2141
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
2142
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2143
|
+
request.onerror = (event) => {
|
|
2144
|
+
reject(event.target.error);
|
|
2145
|
+
};
|
|
2146
|
+
request.onsuccess = (event) => {
|
|
2147
|
+
resolve(event.target.result);
|
|
2148
|
+
};
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2680
2151
|
deleteAll(indexOrRange, range) {
|
|
2681
2152
|
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2682
2153
|
const options = this.options(indexOrRange, range);
|
|
@@ -3287,17 +2758,15 @@ class FieldMask {
|
|
|
3287
2758
|
* See the License for the specific language governing permissions and
|
|
3288
2759
|
* limitations under the License.
|
|
3289
2760
|
*/
|
|
2761
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3290
2762
|
function decodeBase64(encoded) {
|
|
3291
|
-
//
|
|
3292
|
-
//
|
|
3293
|
-
|
|
3294
|
-
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
|
|
3295
|
-
}
|
|
3296
|
-
return new Buffer(encoded, 'base64').toString('binary');
|
|
2763
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
2764
|
+
// This was removed to improve the performance of indexing.
|
|
2765
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3297
2766
|
}
|
|
3298
2767
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3299
2768
|
function encodeBase64(raw) {
|
|
3300
|
-
return
|
|
2769
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3301
2770
|
}
|
|
3302
2771
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3303
2772
|
function isBase64Available() {
|
|
@@ -3338,6 +2807,8 @@ class ByteString {
|
|
|
3338
2807
|
return new ByteString(binaryString);
|
|
3339
2808
|
}
|
|
3340
2809
|
static fromUint8Array(array) {
|
|
2810
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
2811
|
+
// is frequently called during indexing.
|
|
3341
2812
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3342
2813
|
return new ByteString(binaryString);
|
|
3343
2814
|
}
|
|
@@ -3756,13 +3227,17 @@ class DocumentKey {
|
|
|
3756
3227
|
* See the License for the specific language governing permissions and
|
|
3757
3228
|
* limitations under the License.
|
|
3758
3229
|
*/
|
|
3230
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3759
3231
|
const MAX_VALUE = {
|
|
3760
3232
|
mapValue: {
|
|
3761
3233
|
fields: {
|
|
3762
|
-
'__type__': { stringValue:
|
|
3234
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3763
3235
|
}
|
|
3764
3236
|
}
|
|
3765
3237
|
};
|
|
3238
|
+
const MIN_VALUE = {
|
|
3239
|
+
nullValue: 'NULL_VALUE'
|
|
3240
|
+
};
|
|
3766
3241
|
/** Extracts the backend's type order for the provided value. */
|
|
3767
3242
|
function typeOrder(value) {
|
|
3768
3243
|
if ('nullValue' in value) {
|
|
@@ -3796,6 +3271,9 @@ function typeOrder(value) {
|
|
|
3796
3271
|
if (isServerTimestamp(value)) {
|
|
3797
3272
|
return 4 /* ServerTimestampValue */;
|
|
3798
3273
|
}
|
|
3274
|
+
else if (isMaxValue(value)) {
|
|
3275
|
+
return 9 /* ArrayValue */;
|
|
3276
|
+
}
|
|
3799
3277
|
return 10 /* ObjectValue */;
|
|
3800
3278
|
}
|
|
3801
3279
|
else {
|
|
@@ -3835,6 +3313,8 @@ function valueEquals(left, right) {
|
|
|
3835
3313
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3836
3314
|
case 10 /* ObjectValue */:
|
|
3837
3315
|
return objectEquals(left, right);
|
|
3316
|
+
case 9007199254740991 /* MaxValue */:
|
|
3317
|
+
return true;
|
|
3838
3318
|
default:
|
|
3839
3319
|
return fail();
|
|
3840
3320
|
}
|
|
@@ -3907,6 +3387,7 @@ function valueCompare(left, right) {
|
|
|
3907
3387
|
}
|
|
3908
3388
|
switch (leftType) {
|
|
3909
3389
|
case 0 /* NullValue */:
|
|
3390
|
+
case 9007199254740991 /* MaxValue */:
|
|
3910
3391
|
return 0;
|
|
3911
3392
|
case 1 /* BooleanValue */:
|
|
3912
3393
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -4175,7 +3656,102 @@ function deepClone(source) {
|
|
|
4175
3656
|
}
|
|
4176
3657
|
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4177
3658
|
function isMaxValue(value) {
|
|
4178
|
-
return
|
|
3659
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
3660
|
+
MAX_VALUE_TYPE);
|
|
3661
|
+
}
|
|
3662
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
3663
|
+
function valuesGetLowerBound(value) {
|
|
3664
|
+
if ('nullValue' in value) {
|
|
3665
|
+
return MIN_VALUE;
|
|
3666
|
+
}
|
|
3667
|
+
else if ('booleanValue' in value) {
|
|
3668
|
+
return { booleanValue: false };
|
|
3669
|
+
}
|
|
3670
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3671
|
+
return { doubleValue: NaN };
|
|
3672
|
+
}
|
|
3673
|
+
else if ('timestampValue' in value) {
|
|
3674
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3675
|
+
}
|
|
3676
|
+
else if ('stringValue' in value) {
|
|
3677
|
+
return { stringValue: '' };
|
|
3678
|
+
}
|
|
3679
|
+
else if ('bytesValue' in value) {
|
|
3680
|
+
return { bytesValue: '' };
|
|
3681
|
+
}
|
|
3682
|
+
else if ('referenceValue' in value) {
|
|
3683
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3684
|
+
}
|
|
3685
|
+
else if ('geoPointValue' in value) {
|
|
3686
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3687
|
+
}
|
|
3688
|
+
else if ('arrayValue' in value) {
|
|
3689
|
+
return { arrayValue: {} };
|
|
3690
|
+
}
|
|
3691
|
+
else if ('mapValue' in value) {
|
|
3692
|
+
return { mapValue: {} };
|
|
3693
|
+
}
|
|
3694
|
+
else {
|
|
3695
|
+
return fail();
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
3699
|
+
function valuesGetUpperBound(value) {
|
|
3700
|
+
if ('nullValue' in value) {
|
|
3701
|
+
return { booleanValue: false };
|
|
3702
|
+
}
|
|
3703
|
+
else if ('booleanValue' in value) {
|
|
3704
|
+
return { doubleValue: NaN };
|
|
3705
|
+
}
|
|
3706
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3707
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3708
|
+
}
|
|
3709
|
+
else if ('timestampValue' in value) {
|
|
3710
|
+
return { stringValue: '' };
|
|
3711
|
+
}
|
|
3712
|
+
else if ('stringValue' in value) {
|
|
3713
|
+
return { bytesValue: '' };
|
|
3714
|
+
}
|
|
3715
|
+
else if ('bytesValue' in value) {
|
|
3716
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3717
|
+
}
|
|
3718
|
+
else if ('referenceValue' in value) {
|
|
3719
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3720
|
+
}
|
|
3721
|
+
else if ('geoPointValue' in value) {
|
|
3722
|
+
return { arrayValue: {} };
|
|
3723
|
+
}
|
|
3724
|
+
else if ('arrayValue' in value) {
|
|
3725
|
+
return { mapValue: {} };
|
|
3726
|
+
}
|
|
3727
|
+
else if ('mapValue' in value) {
|
|
3728
|
+
return MAX_VALUE;
|
|
3729
|
+
}
|
|
3730
|
+
else {
|
|
3731
|
+
return fail();
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
function valuesMax(left, right) {
|
|
3735
|
+
if (left === undefined) {
|
|
3736
|
+
return right;
|
|
3737
|
+
}
|
|
3738
|
+
else if (right === undefined) {
|
|
3739
|
+
return left;
|
|
3740
|
+
}
|
|
3741
|
+
else {
|
|
3742
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
function valuesMin(left, right) {
|
|
3746
|
+
if (left === undefined) {
|
|
3747
|
+
return right;
|
|
3748
|
+
}
|
|
3749
|
+
else if (right === undefined) {
|
|
3750
|
+
return left;
|
|
3751
|
+
}
|
|
3752
|
+
else {
|
|
3753
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
3754
|
+
}
|
|
4179
3755
|
}
|
|
4180
3756
|
|
|
4181
3757
|
/**
|
|
@@ -4570,6 +4146,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4570
4146
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4571
4147
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4572
4148
|
}
|
|
4149
|
+
/** Returns a debug representation of the field index */
|
|
4150
|
+
function fieldIndexToString(fieldIndex) {
|
|
4151
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4152
|
+
}
|
|
4573
4153
|
/** An index component consisting of field path and index type. */
|
|
4574
4154
|
class IndexSegment {
|
|
4575
4155
|
constructor(
|
|
@@ -4766,6 +4346,191 @@ function targetIsDocumentTarget(target) {
|
|
|
4766
4346
|
target.collectionGroup === null &&
|
|
4767
4347
|
target.filters.length === 0);
|
|
4768
4348
|
}
|
|
4349
|
+
/** Returns the field filters that target the given field path. */
|
|
4350
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4351
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4352
|
+
}
|
|
4353
|
+
/**
|
|
4354
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4355
|
+
* filters. Returns `null` if there are no such filters.
|
|
4356
|
+
*/
|
|
4357
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4358
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4359
|
+
if (segment === undefined) {
|
|
4360
|
+
return null;
|
|
4361
|
+
}
|
|
4362
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4363
|
+
switch (fieldFilter.op) {
|
|
4364
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4365
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4366
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4367
|
+
return [fieldFilter.value];
|
|
4368
|
+
// Remaining filters are not array filters.
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
return null;
|
|
4372
|
+
}
|
|
4373
|
+
/**
|
|
4374
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4375
|
+
* `null` if there are no such filters.
|
|
4376
|
+
*/
|
|
4377
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4378
|
+
const values = new Map();
|
|
4379
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4380
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4381
|
+
switch (fieldFilter.op) {
|
|
4382
|
+
case "==" /* EQUAL */:
|
|
4383
|
+
case "in" /* IN */:
|
|
4384
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4385
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4386
|
+
// `value != 'ab'`).
|
|
4387
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4388
|
+
break;
|
|
4389
|
+
case "not-in" /* NOT_IN */:
|
|
4390
|
+
case "!=" /* NOT_EQUAL */:
|
|
4391
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4392
|
+
// segments and hence we can return early here.
|
|
4393
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4394
|
+
return Array.from(values.values());
|
|
4395
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
return null;
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4403
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4404
|
+
* exists.
|
|
4405
|
+
*/
|
|
4406
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4407
|
+
const values = [];
|
|
4408
|
+
let inclusive = true;
|
|
4409
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4410
|
+
// startAt.
|
|
4411
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4412
|
+
let segmentValue = undefined;
|
|
4413
|
+
let segmentInclusive = true;
|
|
4414
|
+
// Process all filters to find a value for the current field segment
|
|
4415
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4416
|
+
let filterValue = undefined;
|
|
4417
|
+
let filterInclusive = true;
|
|
4418
|
+
switch (fieldFilter.op) {
|
|
4419
|
+
case "<" /* LESS_THAN */:
|
|
4420
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4421
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4422
|
+
break;
|
|
4423
|
+
case "==" /* EQUAL */:
|
|
4424
|
+
case "in" /* IN */:
|
|
4425
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4426
|
+
filterValue = fieldFilter.value;
|
|
4427
|
+
break;
|
|
4428
|
+
case ">" /* GREATER_THAN */:
|
|
4429
|
+
filterValue = fieldFilter.value;
|
|
4430
|
+
filterInclusive = false;
|
|
4431
|
+
break;
|
|
4432
|
+
case "!=" /* NOT_EQUAL */:
|
|
4433
|
+
case "not-in" /* NOT_IN */:
|
|
4434
|
+
filterValue = MIN_VALUE;
|
|
4435
|
+
break;
|
|
4436
|
+
// Remaining filters cannot be used as lower bounds.
|
|
4437
|
+
}
|
|
4438
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4439
|
+
segmentValue = filterValue;
|
|
4440
|
+
segmentInclusive = filterInclusive;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
// If there is a startAt bound, compare the values against the existing
|
|
4444
|
+
// boundary to see if we can narrow the scope.
|
|
4445
|
+
if (target.startAt !== null) {
|
|
4446
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4447
|
+
const orderBy = target.orderBy[i];
|
|
4448
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4449
|
+
const cursorValue = target.startAt.position[i];
|
|
4450
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4451
|
+
segmentValue = cursorValue;
|
|
4452
|
+
segmentInclusive = target.startAt.inclusive;
|
|
4453
|
+
}
|
|
4454
|
+
break;
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
if (segmentValue === undefined) {
|
|
4459
|
+
// No lower bound exists
|
|
4460
|
+
return null;
|
|
4461
|
+
}
|
|
4462
|
+
values.push(segmentValue);
|
|
4463
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4464
|
+
}
|
|
4465
|
+
return new Bound(values, inclusive);
|
|
4466
|
+
}
|
|
4467
|
+
/**
|
|
4468
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
4469
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
4470
|
+
* upper bound exists.
|
|
4471
|
+
*/
|
|
4472
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
4473
|
+
const values = [];
|
|
4474
|
+
let inclusive = true;
|
|
4475
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4476
|
+
// endAt.
|
|
4477
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4478
|
+
let segmentValue = undefined;
|
|
4479
|
+
let segmentInclusive = true;
|
|
4480
|
+
// Process all filters to find a value for the current field segment
|
|
4481
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4482
|
+
let filterValue = undefined;
|
|
4483
|
+
let filterInclusive = true;
|
|
4484
|
+
switch (fieldFilter.op) {
|
|
4485
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4486
|
+
case ">" /* GREATER_THAN */:
|
|
4487
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4488
|
+
filterInclusive = false;
|
|
4489
|
+
break;
|
|
4490
|
+
case "==" /* EQUAL */:
|
|
4491
|
+
case "in" /* IN */:
|
|
4492
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4493
|
+
filterValue = fieldFilter.value;
|
|
4494
|
+
break;
|
|
4495
|
+
case "<" /* LESS_THAN */:
|
|
4496
|
+
filterValue = fieldFilter.value;
|
|
4497
|
+
filterInclusive = false;
|
|
4498
|
+
break;
|
|
4499
|
+
case "!=" /* NOT_EQUAL */:
|
|
4500
|
+
case "not-in" /* NOT_IN */:
|
|
4501
|
+
filterValue = MAX_VALUE;
|
|
4502
|
+
break;
|
|
4503
|
+
// Remaining filters cannot be used as upper bounds.
|
|
4504
|
+
}
|
|
4505
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4506
|
+
segmentValue = filterValue;
|
|
4507
|
+
segmentInclusive = filterInclusive;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
// If there is a endAt bound, compare the values against the existing
|
|
4511
|
+
// boundary to see if we can narrow the scope.
|
|
4512
|
+
if (target.endAt !== null) {
|
|
4513
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4514
|
+
const orderBy = target.orderBy[i];
|
|
4515
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4516
|
+
const cursorValue = target.endAt.position[i];
|
|
4517
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4518
|
+
segmentValue = cursorValue;
|
|
4519
|
+
segmentInclusive = target.endAt.inclusive;
|
|
4520
|
+
}
|
|
4521
|
+
break;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
if (segmentValue === undefined) {
|
|
4526
|
+
// No upper bound exists
|
|
4527
|
+
return null;
|
|
4528
|
+
}
|
|
4529
|
+
values.push(segmentValue);
|
|
4530
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4531
|
+
}
|
|
4532
|
+
return new Bound(values, inclusive);
|
|
4533
|
+
}
|
|
4769
4534
|
class Filter {
|
|
4770
4535
|
}
|
|
4771
4536
|
class FieldFilter extends Filter {
|
|
@@ -5367,6 +5132,116 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5367
5132
|
}
|
|
5368
5133
|
}
|
|
5369
5134
|
|
|
5135
|
+
/**
|
|
5136
|
+
* @license
|
|
5137
|
+
* Copyright 2017 Google LLC
|
|
5138
|
+
*
|
|
5139
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5140
|
+
* you may not use this file except in compliance with the License.
|
|
5141
|
+
* You may obtain a copy of the License at
|
|
5142
|
+
*
|
|
5143
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5144
|
+
*
|
|
5145
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5146
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5147
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5148
|
+
* See the License for the specific language governing permissions and
|
|
5149
|
+
* limitations under the License.
|
|
5150
|
+
*/
|
|
5151
|
+
/**
|
|
5152
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
5153
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
5154
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
5155
|
+
* automatically handles collisions of keys.
|
|
5156
|
+
*/
|
|
5157
|
+
class ObjectMap {
|
|
5158
|
+
constructor(mapKeyFn, equalsFn) {
|
|
5159
|
+
this.mapKeyFn = mapKeyFn;
|
|
5160
|
+
this.equalsFn = equalsFn;
|
|
5161
|
+
/**
|
|
5162
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
5163
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
5164
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
5165
|
+
* constant time lookups in practice.
|
|
5166
|
+
*/
|
|
5167
|
+
this.inner = {};
|
|
5168
|
+
/** The number of entries stored in the map */
|
|
5169
|
+
this.innerSize = 0;
|
|
5170
|
+
}
|
|
5171
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
5172
|
+
get(key) {
|
|
5173
|
+
const id = this.mapKeyFn(key);
|
|
5174
|
+
const matches = this.inner[id];
|
|
5175
|
+
if (matches === undefined) {
|
|
5176
|
+
return undefined;
|
|
5177
|
+
}
|
|
5178
|
+
for (const [otherKey, value] of matches) {
|
|
5179
|
+
if (this.equalsFn(otherKey, key)) {
|
|
5180
|
+
return value;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
return undefined;
|
|
5184
|
+
}
|
|
5185
|
+
has(key) {
|
|
5186
|
+
return this.get(key) !== undefined;
|
|
5187
|
+
}
|
|
5188
|
+
/** Put this key and value in the map. */
|
|
5189
|
+
set(key, value) {
|
|
5190
|
+
const id = this.mapKeyFn(key);
|
|
5191
|
+
const matches = this.inner[id];
|
|
5192
|
+
if (matches === undefined) {
|
|
5193
|
+
this.inner[id] = [[key, value]];
|
|
5194
|
+
this.innerSize++;
|
|
5195
|
+
return;
|
|
5196
|
+
}
|
|
5197
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5198
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5199
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
5200
|
+
matches[i] = [key, value];
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
matches.push([key, value]);
|
|
5205
|
+
this.innerSize++;
|
|
5206
|
+
}
|
|
5207
|
+
/**
|
|
5208
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
5209
|
+
*/
|
|
5210
|
+
delete(key) {
|
|
5211
|
+
const id = this.mapKeyFn(key);
|
|
5212
|
+
const matches = this.inner[id];
|
|
5213
|
+
if (matches === undefined) {
|
|
5214
|
+
return false;
|
|
5215
|
+
}
|
|
5216
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5217
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5218
|
+
if (matches.length === 1) {
|
|
5219
|
+
delete this.inner[id];
|
|
5220
|
+
}
|
|
5221
|
+
else {
|
|
5222
|
+
matches.splice(i, 1);
|
|
5223
|
+
}
|
|
5224
|
+
this.innerSize--;
|
|
5225
|
+
return true;
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
return false;
|
|
5229
|
+
}
|
|
5230
|
+
forEach(fn) {
|
|
5231
|
+
forEach(this.inner, (_, entries) => {
|
|
5232
|
+
for (const [k, v] of entries) {
|
|
5233
|
+
fn(k, v);
|
|
5234
|
+
}
|
|
5235
|
+
});
|
|
5236
|
+
}
|
|
5237
|
+
isEmpty() {
|
|
5238
|
+
return isEmpty(this.inner);
|
|
5239
|
+
}
|
|
5240
|
+
size() {
|
|
5241
|
+
return this.innerSize;
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5370
5245
|
/**
|
|
5371
5246
|
* @license
|
|
5372
5247
|
* Copyright 2017 Google LLC
|
|
@@ -5509,7 +5384,7 @@ class SortedMapIterator {
|
|
|
5509
5384
|
while (!node.isEmpty()) {
|
|
5510
5385
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5511
5386
|
// flip the comparison if we're going in reverse
|
|
5512
|
-
if (isReverse) {
|
|
5387
|
+
if (startKey && isReverse) {
|
|
5513
5388
|
cmp *= -1;
|
|
5514
5389
|
}
|
|
5515
5390
|
if (cmp < 0) {
|
|
@@ -6078,6 +5953,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
|
6078
5953
|
function documentMap() {
|
|
6079
5954
|
return EMPTY_DOCUMENT_MAP;
|
|
6080
5955
|
}
|
|
5956
|
+
function newOverlayMap() {
|
|
5957
|
+
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
5958
|
+
}
|
|
6081
5959
|
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
|
|
6082
5960
|
function documentVersionMap() {
|
|
6083
5961
|
return EMPTY_DOCUMENT_VERSION_MAP;
|
|
@@ -8756,30 +8634,36 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
|
8756
8634
|
}
|
|
8757
8635
|
/** Encodes a document for storage locally. */
|
|
8758
8636
|
function toDbRemoteDocument(localSerializer, document) {
|
|
8759
|
-
const dbReadTime = toDbTimestampKey(document.readTime);
|
|
8760
8637
|
const parentPath = document.key.path.popLast().toArray();
|
|
8638
|
+
const readTime = toDbTimestampKey(document.readTime);
|
|
8761
8639
|
if (document.isFoundDocument()) {
|
|
8762
8640
|
const doc = toDocument(localSerializer.remoteSerializer, document);
|
|
8763
8641
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8764
|
-
return
|
|
8765
|
-
|
|
8766
|
-
|
|
8642
|
+
return {
|
|
8643
|
+
document: doc,
|
|
8644
|
+
hasCommittedMutations,
|
|
8645
|
+
readTime,
|
|
8646
|
+
parentPath
|
|
8647
|
+
};
|
|
8767
8648
|
}
|
|
8768
8649
|
else if (document.isNoDocument()) {
|
|
8769
8650
|
const path = document.key.path.toArray();
|
|
8770
|
-
const version = toDbTimestamp(document.version);
|
|
8771
8651
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8772
|
-
return
|
|
8773
|
-
|
|
8774
|
-
|
|
8652
|
+
return {
|
|
8653
|
+
noDocument: { path, readTime: toDbTimestamp(document.version) },
|
|
8654
|
+
hasCommittedMutations,
|
|
8655
|
+
readTime,
|
|
8656
|
+
parentPath
|
|
8657
|
+
};
|
|
8775
8658
|
}
|
|
8776
8659
|
else if (document.isUnknownDocument()) {
|
|
8777
8660
|
const path = document.key.path.toArray();
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8661
|
+
return {
|
|
8662
|
+
unknownDocument: { path, version: toDbTimestamp(document.version) },
|
|
8663
|
+
hasCommittedMutations: true,
|
|
8664
|
+
readTime,
|
|
8665
|
+
parentPath
|
|
8666
|
+
};
|
|
8783
8667
|
}
|
|
8784
8668
|
else {
|
|
8785
8669
|
return fail();
|
|
@@ -8795,7 +8679,7 @@ function fromDbTimestampKey(dbTimestampKey) {
|
|
|
8795
8679
|
}
|
|
8796
8680
|
function toDbTimestamp(snapshotVersion) {
|
|
8797
8681
|
const timestamp = snapshotVersion.toTimestamp();
|
|
8798
|
-
return
|
|
8682
|
+
return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
|
|
8799
8683
|
}
|
|
8800
8684
|
function fromDbTimestamp(dbTimestamp) {
|
|
8801
8685
|
const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
|
|
@@ -8805,7 +8689,13 @@ function fromDbTimestamp(dbTimestamp) {
|
|
|
8805
8689
|
function toDbMutationBatch(localSerializer, userId, batch) {
|
|
8806
8690
|
const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8807
8691
|
const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8808
|
-
return
|
|
8692
|
+
return {
|
|
8693
|
+
userId,
|
|
8694
|
+
batchId: batch.batchId,
|
|
8695
|
+
localWriteTimeMs: batch.localWriteTime.toMillis(),
|
|
8696
|
+
baseMutations: serializedBaseMutations,
|
|
8697
|
+
mutations: serializedMutations
|
|
8698
|
+
};
|
|
8809
8699
|
}
|
|
8810
8700
|
/** Decodes a DbMutationBatch into a MutationBatch */
|
|
8811
8701
|
function fromDbMutationBatch(localSerializer, dbBatch) {
|
|
@@ -8861,7 +8751,15 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8861
8751
|
// convert it to a base64 string for storage.
|
|
8862
8752
|
const resumeToken = targetData.resumeToken.toBase64();
|
|
8863
8753
|
// lastListenSequenceNumber is always 0 until we do real GC.
|
|
8864
|
-
return
|
|
8754
|
+
return {
|
|
8755
|
+
targetId: targetData.targetId,
|
|
8756
|
+
canonicalId: canonifyTarget(targetData.target),
|
|
8757
|
+
readTime: dbTimestamp,
|
|
8758
|
+
resumeToken,
|
|
8759
|
+
lastListenSequenceNumber: targetData.sequenceNumber,
|
|
8760
|
+
lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
|
|
8761
|
+
query: queryProto
|
|
8762
|
+
};
|
|
8865
8763
|
}
|
|
8866
8764
|
/**
|
|
8867
8765
|
* A helper function for figuring out what kind of query has been stored.
|
|
@@ -8940,7 +8838,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
|
8940
8838
|
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
8941
8839
|
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
8942
8840
|
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
8943
|
-
return
|
|
8841
|
+
return {
|
|
8842
|
+
userId,
|
|
8843
|
+
collectionPath,
|
|
8844
|
+
documentId,
|
|
8845
|
+
collectionGroup: overlay.mutation.key.getCollectionGroup(),
|
|
8846
|
+
largestBatchId: overlay.largestBatchId,
|
|
8847
|
+
overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
|
|
8848
|
+
};
|
|
8944
8849
|
}
|
|
8945
8850
|
/**
|
|
8946
8851
|
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
@@ -8952,7 +8857,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
|
|
|
8952
8857
|
return [userId, collectionPath, docId];
|
|
8953
8858
|
}
|
|
8954
8859
|
function toDbIndexConfiguration(index) {
|
|
8955
|
-
return
|
|
8860
|
+
return {
|
|
8861
|
+
indexId: index.indexId,
|
|
8862
|
+
collectionGroup: index.collectionGroup,
|
|
8863
|
+
fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
|
|
8864
|
+
};
|
|
8956
8865
|
}
|
|
8957
8866
|
function fromDbIndexConfiguration(index, state) {
|
|
8958
8867
|
const decodedState = state
|
|
@@ -8962,7 +8871,14 @@ function fromDbIndexConfiguration(index, state) {
|
|
|
8962
8871
|
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
8963
8872
|
}
|
|
8964
8873
|
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
8965
|
-
return
|
|
8874
|
+
return {
|
|
8875
|
+
indexId,
|
|
8876
|
+
uid: user.uid || '',
|
|
8877
|
+
sequenceNumber,
|
|
8878
|
+
readTime: toDbTimestamp(offset.readTime),
|
|
8879
|
+
documentKey: encodeResourcePath(offset.documentKey.path),
|
|
8880
|
+
largestBatchId: offset.largestBatchId
|
|
8881
|
+
};
|
|
8966
8882
|
}
|
|
8967
8883
|
|
|
8968
8884
|
/**
|
|
@@ -9013,13 +8929,13 @@ class IndexedDbBundleCache {
|
|
|
9013
8929
|
* Helper to get a typed SimpleDbStore for the bundles object store.
|
|
9014
8930
|
*/
|
|
9015
8931
|
function bundlesStore(txn) {
|
|
9016
|
-
return getStore(txn,
|
|
8932
|
+
return getStore(txn, DbBundleStore);
|
|
9017
8933
|
}
|
|
9018
8934
|
/**
|
|
9019
8935
|
* Helper to get a typed SimpleDbStore for the namedQueries object store.
|
|
9020
8936
|
*/
|
|
9021
8937
|
function namedQueriesStore(txn) {
|
|
9022
|
-
return getStore(txn,
|
|
8938
|
+
return getStore(txn, DbNamedQueryStore);
|
|
9023
8939
|
}
|
|
9024
8940
|
|
|
9025
8941
|
/**
|
|
@@ -9066,7 +8982,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9066
8982
|
}
|
|
9067
8983
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9068
8984
|
const promises = [];
|
|
9069
|
-
overlays.forEach(mutation => {
|
|
8985
|
+
overlays.forEach((_, mutation) => {
|
|
9070
8986
|
const overlay = new Overlay(largestBatchId, mutation);
|
|
9071
8987
|
promises.push(this.saveOverlay(transaction, overlay));
|
|
9072
8988
|
});
|
|
@@ -9081,19 +8997,19 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9081
8997
|
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9082
8998
|
/*lowerOpen=*/ false,
|
|
9083
8999
|
/*upperOpen=*/ true);
|
|
9084
|
-
promises.push(documentOverlayStore(transaction).deleteAll(
|
|
9000
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
|
|
9085
9001
|
});
|
|
9086
9002
|
return PersistencePromise.waitFor(promises);
|
|
9087
9003
|
}
|
|
9088
9004
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9089
|
-
const result =
|
|
9005
|
+
const result = newOverlayMap();
|
|
9090
9006
|
const collectionPath = encodeResourcePath(collection);
|
|
9091
9007
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9092
9008
|
// is not inclusive.
|
|
9093
9009
|
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9094
9010
|
/*lowerOpen=*/ true);
|
|
9095
9011
|
return documentOverlayStore(transaction)
|
|
9096
|
-
.loadAll(
|
|
9012
|
+
.loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
|
|
9097
9013
|
.next(dbOverlays => {
|
|
9098
9014
|
for (const dbOverlay of dbOverlays) {
|
|
9099
9015
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
@@ -9103,7 +9019,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9103
9019
|
});
|
|
9104
9020
|
}
|
|
9105
9021
|
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9106
|
-
const result =
|
|
9022
|
+
const result = newOverlayMap();
|
|
9107
9023
|
let currentBatchId = undefined;
|
|
9108
9024
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9109
9025
|
// is not inclusive.
|
|
@@ -9111,7 +9027,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9111
9027
|
/*lowerOpen=*/ true);
|
|
9112
9028
|
return documentOverlayStore(transaction)
|
|
9113
9029
|
.iterate({
|
|
9114
|
-
index:
|
|
9030
|
+
index: DbDocumentOverlayCollectionGroupOverlayIndex,
|
|
9115
9031
|
range
|
|
9116
9032
|
}, (_, dbOverlay, control) => {
|
|
9117
9033
|
// We do not want to return partial batch overlays, even if the size
|
|
@@ -9119,7 +9035,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9119
9035
|
// continue to aggregate results even after the result size exceeds
|
|
9120
9036
|
// `count` if there are more overlays from the `currentBatchId`.
|
|
9121
9037
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9122
|
-
if (result.size < count ||
|
|
9038
|
+
if (result.size() < count ||
|
|
9123
9039
|
overlay.largestBatchId === currentBatchId) {
|
|
9124
9040
|
result.set(overlay.getKey(), overlay);
|
|
9125
9041
|
currentBatchId = overlay.largestBatchId;
|
|
@@ -9138,7 +9054,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9138
9054
|
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9139
9055
|
*/
|
|
9140
9056
|
function documentOverlayStore(txn) {
|
|
9141
|
-
return getStore(txn,
|
|
9057
|
+
return getStore(txn, DbDocumentOverlayStore);
|
|
9142
9058
|
}
|
|
9143
9059
|
|
|
9144
9060
|
/**
|
|
@@ -9694,21 +9610,40 @@ class IndexEntry {
|
|
|
9694
9610
|
this.arrayValue = arrayValue;
|
|
9695
9611
|
this.directionalValue = directionalValue;
|
|
9696
9612
|
}
|
|
9613
|
+
/**
|
|
9614
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
9615
|
+
* directional value.
|
|
9616
|
+
*/
|
|
9617
|
+
successor() {
|
|
9618
|
+
const currentLength = this.directionalValue.length;
|
|
9619
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
9620
|
+
? currentLength + 1
|
|
9621
|
+
: currentLength;
|
|
9622
|
+
const successor = new Uint8Array(newLength);
|
|
9623
|
+
successor.set(this.directionalValue, 0);
|
|
9624
|
+
if (newLength !== currentLength) {
|
|
9625
|
+
successor.set([0], this.directionalValue.length);
|
|
9626
|
+
}
|
|
9627
|
+
else {
|
|
9628
|
+
++successor[successor.length - 1];
|
|
9629
|
+
}
|
|
9630
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
9631
|
+
}
|
|
9697
9632
|
}
|
|
9698
9633
|
function indexEntryComparator(left, right) {
|
|
9699
9634
|
let cmp = left.indexId - right.indexId;
|
|
9700
9635
|
if (cmp !== 0) {
|
|
9701
9636
|
return cmp;
|
|
9702
9637
|
}
|
|
9703
|
-
cmp =
|
|
9638
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
9704
9639
|
if (cmp !== 0) {
|
|
9705
9640
|
return cmp;
|
|
9706
9641
|
}
|
|
9707
|
-
cmp = compareByteArrays(left.
|
|
9642
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
9708
9643
|
if (cmp !== 0) {
|
|
9709
9644
|
return cmp;
|
|
9710
9645
|
}
|
|
9711
|
-
return
|
|
9646
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
9712
9647
|
}
|
|
9713
9648
|
function compareByteArrays(left, right) {
|
|
9714
9649
|
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
@@ -9720,6 +9655,160 @@ function compareByteArrays(left, right) {
|
|
|
9720
9655
|
return left.length - right.length;
|
|
9721
9656
|
}
|
|
9722
9657
|
|
|
9658
|
+
/**
|
|
9659
|
+
* @license
|
|
9660
|
+
* Copyright 2022 Google LLC
|
|
9661
|
+
*
|
|
9662
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9663
|
+
* you may not use this file except in compliance with the License.
|
|
9664
|
+
* You may obtain a copy of the License at
|
|
9665
|
+
*
|
|
9666
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9667
|
+
*
|
|
9668
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9669
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9670
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9671
|
+
* See the License for the specific language governing permissions and
|
|
9672
|
+
* limitations under the License.
|
|
9673
|
+
*/
|
|
9674
|
+
/**
|
|
9675
|
+
* A light query planner for Firestore.
|
|
9676
|
+
*
|
|
9677
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
9678
|
+
* determines whether a given index can be used to serve the specified target.
|
|
9679
|
+
*
|
|
9680
|
+
* The following table showcases some possible index configurations:
|
|
9681
|
+
*
|
|
9682
|
+
* Query | Index
|
|
9683
|
+
* -----------------------------------------------------------------------------
|
|
9684
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
9685
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
9686
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
9687
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
9688
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
9689
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
9690
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
9691
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
9692
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
9693
|
+
*/
|
|
9694
|
+
class TargetIndexMatcher {
|
|
9695
|
+
constructor(target) {
|
|
9696
|
+
this.collectionId =
|
|
9697
|
+
target.collectionGroup != null
|
|
9698
|
+
? target.collectionGroup
|
|
9699
|
+
: target.path.lastSegment();
|
|
9700
|
+
this.orderBys = target.orderBy;
|
|
9701
|
+
this.equalityFilters = [];
|
|
9702
|
+
for (const filter of target.filters) {
|
|
9703
|
+
const fieldFilter = filter;
|
|
9704
|
+
if (fieldFilter.isInequality()) {
|
|
9705
|
+
this.inequalityFilter = fieldFilter;
|
|
9706
|
+
}
|
|
9707
|
+
else {
|
|
9708
|
+
this.equalityFilters.push(fieldFilter);
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
/**
|
|
9713
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
9714
|
+
* target.
|
|
9715
|
+
*
|
|
9716
|
+
* An index is considered capable of serving the target when:
|
|
9717
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
9718
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
9719
|
+
* fewer.
|
|
9720
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
9721
|
+
* have a corresponding `CONTAINS` segment.
|
|
9722
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
9723
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
9724
|
+
* clauses.
|
|
9725
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
9726
|
+
* - The optional segment for the inequality filter must appear after all
|
|
9727
|
+
* equality segments.
|
|
9728
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
9729
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
9730
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
9731
|
+
* omitted.
|
|
9732
|
+
*/
|
|
9733
|
+
servedByIndex(index) {
|
|
9734
|
+
// If there is an array element, find a matching filter.
|
|
9735
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
9736
|
+
if (arraySegment !== undefined &&
|
|
9737
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
9738
|
+
return false;
|
|
9739
|
+
}
|
|
9740
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
9741
|
+
let segmentIndex = 0;
|
|
9742
|
+
let orderBysIndex = 0;
|
|
9743
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
9744
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9745
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
9746
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
9747
|
+
// Since it is not possible to use the same field path in both an equality
|
|
9748
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
9749
|
+
// that a matching equality segment should instead be used to map to an
|
|
9750
|
+
// inequality filter or orderBy clause.
|
|
9751
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
9752
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
9753
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
9754
|
+
// clauses.
|
|
9755
|
+
break;
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
// If we already have processed all segments, all segments are used to serve
|
|
9759
|
+
// the equality filters and we do not need to map any segments to the
|
|
9760
|
+
// target's inequality and orderBy clauses.
|
|
9761
|
+
if (segmentIndex === segments.length) {
|
|
9762
|
+
return true;
|
|
9763
|
+
}
|
|
9764
|
+
// If there is an inequality filter, the next segment must match both the
|
|
9765
|
+
// filter and the first orderBy clause.
|
|
9766
|
+
if (this.inequalityFilter !== undefined) {
|
|
9767
|
+
const segment = segments[segmentIndex];
|
|
9768
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
9769
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9770
|
+
return false;
|
|
9771
|
+
}
|
|
9772
|
+
++segmentIndex;
|
|
9773
|
+
}
|
|
9774
|
+
// All remaining segments need to represent the prefix of the target's
|
|
9775
|
+
// orderBy.
|
|
9776
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9777
|
+
const segment = segments[segmentIndex];
|
|
9778
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
9779
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9780
|
+
return false;
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
return true;
|
|
9784
|
+
}
|
|
9785
|
+
hasMatchingEqualityFilter(segment) {
|
|
9786
|
+
for (const filter of this.equalityFilters) {
|
|
9787
|
+
if (this.matchesFilter(filter, segment)) {
|
|
9788
|
+
return true;
|
|
9789
|
+
}
|
|
9790
|
+
}
|
|
9791
|
+
return false;
|
|
9792
|
+
}
|
|
9793
|
+
matchesFilter(filter, segment) {
|
|
9794
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
9795
|
+
return false;
|
|
9796
|
+
}
|
|
9797
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
9798
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
9799
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
9800
|
+
}
|
|
9801
|
+
matchesOrderBy(orderBy, segment) {
|
|
9802
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
9803
|
+
return false;
|
|
9804
|
+
}
|
|
9805
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
9806
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
9807
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
9808
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
9809
|
+
}
|
|
9810
|
+
}
|
|
9811
|
+
|
|
9723
9812
|
/**
|
|
9724
9813
|
* @license
|
|
9725
9814
|
* Copyright 2019 Google LLC
|
|
@@ -9758,9 +9847,9 @@ class MemoryIndexManager {
|
|
|
9758
9847
|
// Field indices are not supported with memory persistence.
|
|
9759
9848
|
return PersistencePromise.resolve();
|
|
9760
9849
|
}
|
|
9761
|
-
getDocumentsMatchingTarget(transaction,
|
|
9850
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
9762
9851
|
// Field indices are not supported with memory persistence.
|
|
9763
|
-
return PersistencePromise.resolve(
|
|
9852
|
+
return PersistencePromise.resolve(null);
|
|
9764
9853
|
}
|
|
9765
9854
|
getFieldIndex(transaction, target) {
|
|
9766
9855
|
// Field indices are not supported with memory persistence.
|
|
@@ -9832,6 +9921,7 @@ class MemoryCollectionParentIndex {
|
|
|
9832
9921
|
* limitations under the License.
|
|
9833
9922
|
*/
|
|
9834
9923
|
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
9924
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
9835
9925
|
/**
|
|
9836
9926
|
* A persisted implementation of IndexManager.
|
|
9837
9927
|
*
|
|
@@ -9849,6 +9939,11 @@ class IndexedDbIndexManager {
|
|
|
9849
9939
|
* used to satisfy reads.
|
|
9850
9940
|
*/
|
|
9851
9941
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
9942
|
+
/**
|
|
9943
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
9944
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
9945
|
+
*/
|
|
9946
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
9852
9947
|
this.uid = user.uid || '';
|
|
9853
9948
|
}
|
|
9854
9949
|
/**
|
|
@@ -9917,13 +10012,111 @@ class IndexedDbIndexManager {
|
|
|
9917
10012
|
/*lowerOpen=*/ false,
|
|
9918
10013
|
/*upperOpen=*/ true)));
|
|
9919
10014
|
}
|
|
9920
|
-
getDocumentsMatchingTarget(transaction,
|
|
9921
|
-
|
|
9922
|
-
|
|
10015
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10016
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10017
|
+
let canServeTarget = true;
|
|
10018
|
+
const indexes = new Map();
|
|
10019
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10020
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10021
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10022
|
+
indexes.set(subTarget, index);
|
|
10023
|
+
});
|
|
10024
|
+
}).next(() => {
|
|
10025
|
+
if (!canServeTarget) {
|
|
10026
|
+
return PersistencePromise.resolve(null);
|
|
10027
|
+
}
|
|
10028
|
+
else {
|
|
10029
|
+
let result = documentKeySet();
|
|
10030
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10031
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10032
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10033
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10034
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10035
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10036
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10037
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10038
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10039
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10040
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10041
|
+
return indexEntries
|
|
10042
|
+
.loadFirst(indexRange, target.limit)
|
|
10043
|
+
.next(entries => {
|
|
10044
|
+
entries.forEach(entry => {
|
|
10045
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10046
|
+
});
|
|
10047
|
+
});
|
|
10048
|
+
});
|
|
10049
|
+
}).next(() => result);
|
|
10050
|
+
}
|
|
10051
|
+
});
|
|
10052
|
+
}
|
|
10053
|
+
getSubTargets(target) {
|
|
10054
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10055
|
+
if (subTargets) {
|
|
10056
|
+
return subTargets;
|
|
10057
|
+
}
|
|
10058
|
+
// TODO(orquery): Implement DNF transform
|
|
10059
|
+
subTargets = [target];
|
|
10060
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10061
|
+
return subTargets;
|
|
10062
|
+
}
|
|
10063
|
+
/**
|
|
10064
|
+
* Constructs a key range query on `DbIndexEntryStore` that unions all
|
|
10065
|
+
* bounds.
|
|
10066
|
+
*/
|
|
10067
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10068
|
+
// The number of total index scans we union together. This is similar to a
|
|
10069
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10070
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10071
|
+
// combined with the values from the query bounds.
|
|
10072
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10073
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10074
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10075
|
+
const indexRanges = [];
|
|
10076
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10077
|
+
const arrayValue = arrayValues
|
|
10078
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10079
|
+
: EMPTY_VALUE;
|
|
10080
|
+
const lowerBound = lowerBounds
|
|
10081
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10082
|
+
: this.generateEmptyBound(indexId);
|
|
10083
|
+
const upperBound = upperBounds
|
|
10084
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10085
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10086
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10087
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10088
|
+
/* inclusive= */ true))));
|
|
10089
|
+
}
|
|
10090
|
+
return indexRanges;
|
|
10091
|
+
}
|
|
10092
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10093
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10094
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10095
|
+
return inclusive ? entry : entry.successor();
|
|
10096
|
+
}
|
|
10097
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10098
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10099
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10100
|
+
return inclusive ? entry.successor() : entry;
|
|
10101
|
+
}
|
|
10102
|
+
/**
|
|
10103
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10104
|
+
* and user.
|
|
10105
|
+
*/
|
|
10106
|
+
generateEmptyBound(indexId) {
|
|
10107
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
9923
10108
|
}
|
|
9924
10109
|
getFieldIndex(transaction, target) {
|
|
9925
|
-
|
|
9926
|
-
|
|
10110
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10111
|
+
const collectionGroup = target.collectionGroup != null
|
|
10112
|
+
? target.collectionGroup
|
|
10113
|
+
: target.path.lastSegment();
|
|
10114
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10115
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10116
|
+
// Return the index that matches the most number of segments.
|
|
10117
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10118
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10119
|
+
});
|
|
9927
10120
|
}
|
|
9928
10121
|
/**
|
|
9929
10122
|
* Returns the byte encoded form of the directional values in the field index.
|
|
@@ -9948,11 +10141,79 @@ class IndexedDbIndexManager {
|
|
|
9948
10141
|
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
9949
10142
|
return encoder.encodedBytes();
|
|
9950
10143
|
}
|
|
10144
|
+
/**
|
|
10145
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10146
|
+
* For IN queries, a list of possible values is returned.
|
|
10147
|
+
*/
|
|
10148
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10149
|
+
if (bound === null) {
|
|
10150
|
+
return [];
|
|
10151
|
+
}
|
|
10152
|
+
let encoders = [];
|
|
10153
|
+
encoders.push(new IndexByteEncoder());
|
|
10154
|
+
let boundIdx = 0;
|
|
10155
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10156
|
+
const value = bound[boundIdx++];
|
|
10157
|
+
for (const encoder of encoders) {
|
|
10158
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10159
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10160
|
+
}
|
|
10161
|
+
else {
|
|
10162
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10163
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
return this.getEncodedBytes(encoders);
|
|
10168
|
+
}
|
|
10169
|
+
/**
|
|
10170
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10171
|
+
* queries, a list of possible values is returned.
|
|
10172
|
+
*/
|
|
10173
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10174
|
+
if (bound == null) {
|
|
10175
|
+
return null;
|
|
10176
|
+
}
|
|
10177
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10178
|
+
}
|
|
10179
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10180
|
+
getEncodedBytes(encoders) {
|
|
10181
|
+
const result = [];
|
|
10182
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10183
|
+
result[i] = encoders[i].encodedBytes();
|
|
10184
|
+
}
|
|
10185
|
+
return result;
|
|
10186
|
+
}
|
|
10187
|
+
/**
|
|
10188
|
+
* Creates a separate encoder for each element of an array.
|
|
10189
|
+
*
|
|
10190
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10191
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10192
|
+
* list of new encoders is returned.
|
|
10193
|
+
*/
|
|
10194
|
+
expandIndexValues(encoders, segment, value) {
|
|
10195
|
+
const prefixes = [...encoders];
|
|
10196
|
+
const results = [];
|
|
10197
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10198
|
+
for (const prefix of prefixes) {
|
|
10199
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10200
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10201
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10202
|
+
results.push(clonedEncoder);
|
|
10203
|
+
}
|
|
10204
|
+
}
|
|
10205
|
+
return results;
|
|
10206
|
+
}
|
|
10207
|
+
isInFilter(target, fieldPath) {
|
|
10208
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10209
|
+
f.field.isEqual(fieldPath) &&
|
|
10210
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
10211
|
+
}
|
|
9951
10212
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9952
10213
|
const indexes = indexConfigurationStore(transaction);
|
|
9953
10214
|
const states = indexStateStore(transaction);
|
|
9954
10215
|
return (collectionGroup
|
|
9955
|
-
? indexes.loadAll(
|
|
10216
|
+
? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9956
10217
|
: indexes.loadAll()).next(indexConfigs => {
|
|
9957
10218
|
const result = [];
|
|
9958
10219
|
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
@@ -9969,7 +10230,12 @@ class IndexedDbIndexManager {
|
|
|
9969
10230
|
if (indexes.length === 0) {
|
|
9970
10231
|
return null;
|
|
9971
10232
|
}
|
|
9972
|
-
indexes.sort((l, r) =>
|
|
10233
|
+
indexes.sort((l, r) => {
|
|
10234
|
+
const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
|
|
10235
|
+
return cmp !== 0
|
|
10236
|
+
? cmp
|
|
10237
|
+
: primitiveComparator(l.collectionGroup, r.collectionGroup);
|
|
10238
|
+
});
|
|
9973
10239
|
return indexes[0].collectionGroup;
|
|
9974
10240
|
});
|
|
9975
10241
|
}
|
|
@@ -9977,7 +10243,7 @@ class IndexedDbIndexManager {
|
|
|
9977
10243
|
const indexes = indexConfigurationStore(transaction);
|
|
9978
10244
|
const states = indexStateStore(transaction);
|
|
9979
10245
|
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
9980
|
-
.loadAll(
|
|
10246
|
+
.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9981
10247
|
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
9982
10248
|
}
|
|
9983
10249
|
updateIndexEntries(transaction, documents) {
|
|
@@ -10008,7 +10274,13 @@ class IndexedDbIndexManager {
|
|
|
10008
10274
|
}
|
|
10009
10275
|
addIndexEntry(transaction, document, indexEntry) {
|
|
10010
10276
|
const indexEntries = indexEntriesStore(transaction);
|
|
10011
|
-
return indexEntries.put(
|
|
10277
|
+
return indexEntries.put({
|
|
10278
|
+
indexId: indexEntry.indexId,
|
|
10279
|
+
uid: this.uid,
|
|
10280
|
+
arrayValue: indexEntry.arrayValue,
|
|
10281
|
+
directionalValue: indexEntry.directionalValue,
|
|
10282
|
+
documentKey: encodeResourcePath(document.key.path)
|
|
10283
|
+
});
|
|
10012
10284
|
}
|
|
10013
10285
|
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10014
10286
|
const indexEntries = indexEntriesStore(transaction);
|
|
@@ -10025,7 +10297,7 @@ class IndexedDbIndexManager {
|
|
|
10025
10297
|
let results = new SortedSet(indexEntryComparator);
|
|
10026
10298
|
return indexEntries
|
|
10027
10299
|
.iterate({
|
|
10028
|
-
index:
|
|
10300
|
+
index: DbIndexEntryDocumentKeyIndex,
|
|
10029
10301
|
range: IDBKeyRange.only([
|
|
10030
10302
|
fieldIndex.indexId,
|
|
10031
10303
|
this.uid,
|
|
@@ -10053,7 +10325,7 @@ class IndexedDbIndexManager {
|
|
|
10053
10325
|
}
|
|
10054
10326
|
}
|
|
10055
10327
|
else {
|
|
10056
|
-
results = results.add(new IndexEntry(fieldIndex.indexId, document.key,
|
|
10328
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10057
10329
|
}
|
|
10058
10330
|
return results;
|
|
10059
10331
|
}
|
|
@@ -10079,7 +10351,7 @@ class IndexedDbIndexManager {
|
|
|
10079
10351
|
const states = indexStateStore(transaction);
|
|
10080
10352
|
return states
|
|
10081
10353
|
.iterate({
|
|
10082
|
-
index:
|
|
10354
|
+
index: DbIndexStateSequenceNumberIndex,
|
|
10083
10355
|
reverse: true,
|
|
10084
10356
|
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10085
10357
|
}, (_, state, controller) => {
|
|
@@ -10088,31 +10360,81 @@ class IndexedDbIndexManager {
|
|
|
10088
10360
|
})
|
|
10089
10361
|
.next(() => nextSequenceNumber);
|
|
10090
10362
|
}
|
|
10363
|
+
/**
|
|
10364
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10365
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10366
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10367
|
+
*/
|
|
10368
|
+
createRange(lower, upper, notInValues) {
|
|
10369
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10370
|
+
// sorted set of non-overlapping ranges.
|
|
10371
|
+
notInValues = notInValues
|
|
10372
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10373
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10374
|
+
const bounds = [];
|
|
10375
|
+
bounds.push(lower);
|
|
10376
|
+
for (const notInValue of notInValues) {
|
|
10377
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10378
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10379
|
+
if (cmpToLower === 0) {
|
|
10380
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10381
|
+
// to the next value.
|
|
10382
|
+
bounds[0] = lower.successor();
|
|
10383
|
+
}
|
|
10384
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10385
|
+
// `notInValue` is in the middle of the range
|
|
10386
|
+
bounds.push(notInValue);
|
|
10387
|
+
bounds.push(notInValue.successor());
|
|
10388
|
+
}
|
|
10389
|
+
else if (cmpToUpper > 0) {
|
|
10390
|
+
// `notInValue` (and all following values) are out of the range
|
|
10391
|
+
break;
|
|
10392
|
+
}
|
|
10393
|
+
}
|
|
10394
|
+
bounds.push(upper);
|
|
10395
|
+
const ranges = [];
|
|
10396
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10397
|
+
ranges.push(IDBKeyRange.bound([
|
|
10398
|
+
bounds[i].indexId,
|
|
10399
|
+
this.uid,
|
|
10400
|
+
bounds[i].arrayValue,
|
|
10401
|
+
bounds[i].directionalValue,
|
|
10402
|
+
''
|
|
10403
|
+
], [
|
|
10404
|
+
bounds[i + 1].indexId,
|
|
10405
|
+
this.uid,
|
|
10406
|
+
bounds[i + 1].arrayValue,
|
|
10407
|
+
bounds[i + 1].directionalValue,
|
|
10408
|
+
''
|
|
10409
|
+
]));
|
|
10410
|
+
}
|
|
10411
|
+
return ranges;
|
|
10412
|
+
}
|
|
10091
10413
|
}
|
|
10092
10414
|
/**
|
|
10093
10415
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
10094
10416
|
* document store.
|
|
10095
10417
|
*/
|
|
10096
10418
|
function collectionParentsStore(txn) {
|
|
10097
|
-
return getStore(txn,
|
|
10419
|
+
return getStore(txn, DbCollectionParentStore);
|
|
10098
10420
|
}
|
|
10099
10421
|
/**
|
|
10100
10422
|
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10101
10423
|
*/
|
|
10102
10424
|
function indexEntriesStore(txn) {
|
|
10103
|
-
return getStore(txn,
|
|
10425
|
+
return getStore(txn, DbIndexEntryStore);
|
|
10104
10426
|
}
|
|
10105
10427
|
/**
|
|
10106
10428
|
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10107
10429
|
*/
|
|
10108
10430
|
function indexConfigurationStore(txn) {
|
|
10109
|
-
return getStore(txn,
|
|
10431
|
+
return getStore(txn, DbIndexConfigurationStore);
|
|
10110
10432
|
}
|
|
10111
10433
|
/**
|
|
10112
10434
|
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10113
10435
|
*/
|
|
10114
10436
|
function indexStateStore(txn) {
|
|
10115
|
-
return getStore(txn,
|
|
10437
|
+
return getStore(txn, DbIndexStateStore);
|
|
10116
10438
|
}
|
|
10117
10439
|
|
|
10118
10440
|
/**
|
|
@@ -10136,8 +10458,8 @@ function indexStateStore(txn) {
|
|
|
10136
10458
|
* @returns A PersistencePromise of the document mutations that were removed.
|
|
10137
10459
|
*/
|
|
10138
10460
|
function removeMutationBatch(txn, userId, batch) {
|
|
10139
|
-
const mutationStore = txn.store(
|
|
10140
|
-
const indexTxn = txn.store(
|
|
10461
|
+
const mutationStore = txn.store(DbMutationBatchStore);
|
|
10462
|
+
const indexTxn = txn.store(DbDocumentMutationStore);
|
|
10141
10463
|
const promises = [];
|
|
10142
10464
|
const range = IDBKeyRange.only(batch.batchId);
|
|
10143
10465
|
let numDeleted = 0;
|
|
@@ -10150,7 +10472,7 @@ function removeMutationBatch(txn, userId, batch) {
|
|
|
10150
10472
|
}));
|
|
10151
10473
|
const removedDocuments = [];
|
|
10152
10474
|
for (const mutation of batch.mutations) {
|
|
10153
|
-
const indexKey =
|
|
10475
|
+
const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
|
|
10154
10476
|
promises.push(indexTxn.delete(indexKey));
|
|
10155
10477
|
removedDocuments.push(mutation.key);
|
|
10156
10478
|
}
|
|
@@ -10239,7 +10561,7 @@ class IndexedDbMutationQueue {
|
|
|
10239
10561
|
let empty = true;
|
|
10240
10562
|
const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10241
10563
|
return mutationsStore(transaction)
|
|
10242
|
-
.iterate({ index:
|
|
10564
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
|
|
10243
10565
|
empty = false;
|
|
10244
10566
|
control.done();
|
|
10245
10567
|
})
|
|
@@ -10264,10 +10586,10 @@ class IndexedDbMutationQueue {
|
|
|
10264
10586
|
const promises = [];
|
|
10265
10587
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10266
10588
|
for (const mutation of mutations) {
|
|
10267
|
-
const indexKey =
|
|
10589
|
+
const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
|
|
10268
10590
|
collectionParents = collectionParents.add(mutation.key.path.popLast());
|
|
10269
10591
|
promises.push(mutationStore.put(dbBatch));
|
|
10270
|
-
promises.push(documentStore.put(indexKey,
|
|
10592
|
+
promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
|
|
10271
10593
|
}
|
|
10272
10594
|
collectionParents.forEach(parent => {
|
|
10273
10595
|
promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
|
|
@@ -10318,7 +10640,7 @@ class IndexedDbMutationQueue {
|
|
|
10318
10640
|
const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
|
|
10319
10641
|
let foundBatch = null;
|
|
10320
10642
|
return mutationsStore(transaction)
|
|
10321
|
-
.iterate({ index:
|
|
10643
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
|
|
10322
10644
|
if (dbBatch.userId === this.userId) {
|
|
10323
10645
|
hardAssert(dbBatch.batchId >= nextBatchId);
|
|
10324
10646
|
foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
|
|
@@ -10334,7 +10656,7 @@ class IndexedDbMutationQueue {
|
|
|
10334
10656
|
]);
|
|
10335
10657
|
let batchId = BATCHID_UNKNOWN;
|
|
10336
10658
|
return mutationsStore(transaction)
|
|
10337
|
-
.iterate({ index:
|
|
10659
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
|
|
10338
10660
|
batchId = dbBatch.batchId;
|
|
10339
10661
|
control.done();
|
|
10340
10662
|
})
|
|
@@ -10343,13 +10665,13 @@ class IndexedDbMutationQueue {
|
|
|
10343
10665
|
getAllMutationBatches(transaction) {
|
|
10344
10666
|
const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10345
10667
|
return mutationsStore(transaction)
|
|
10346
|
-
.loadAll(
|
|
10668
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
10347
10669
|
.next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
|
|
10348
10670
|
}
|
|
10349
10671
|
getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
|
|
10350
10672
|
// Scan the document-mutation index starting with a prefix starting with
|
|
10351
10673
|
// the given documentKey.
|
|
10352
|
-
const indexPrefix =
|
|
10674
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10353
10675
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10354
10676
|
const results = [];
|
|
10355
10677
|
return documentMutationsStore(transaction)
|
|
@@ -10384,7 +10706,7 @@ class IndexedDbMutationQueue {
|
|
|
10384
10706
|
let uniqueBatchIDs = new SortedSet(primitiveComparator);
|
|
10385
10707
|
const promises = [];
|
|
10386
10708
|
documentKeys.forEach(documentKey => {
|
|
10387
|
-
const indexStart =
|
|
10709
|
+
const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10388
10710
|
const range = IDBKeyRange.lowerBound(indexStart);
|
|
10389
10711
|
const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
|
|
10390
10712
|
const [userID, encodedPath, batchID] = indexKey;
|
|
@@ -10420,7 +10742,7 @@ class IndexedDbMutationQueue {
|
|
|
10420
10742
|
// Since we don't yet index the actual properties in the mutations, our
|
|
10421
10743
|
// current approach is to just return all mutation batches that affect
|
|
10422
10744
|
// documents in the collection being queried.
|
|
10423
|
-
const indexPrefix =
|
|
10745
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
|
|
10424
10746
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10425
10747
|
// Collect up unique batchIDs encountered during a scan of the index. Use a
|
|
10426
10748
|
// SortedSet to accumulate batch IDs so they can be traversed in order in a
|
|
@@ -10492,7 +10814,7 @@ class IndexedDbMutationQueue {
|
|
|
10492
10814
|
}
|
|
10493
10815
|
// Verify that there are no entries in the documentMutations index if
|
|
10494
10816
|
// the queue is empty.
|
|
10495
|
-
const startRange = IDBKeyRange.lowerBound(
|
|
10817
|
+
const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
|
|
10496
10818
|
const danglingMutationReferences = [];
|
|
10497
10819
|
return documentMutationsStore(txn)
|
|
10498
10820
|
.iterate({ range: startRange }, (key, _, control) => {
|
|
@@ -10520,9 +10842,11 @@ class IndexedDbMutationQueue {
|
|
|
10520
10842
|
return mutationQueuesStore(transaction)
|
|
10521
10843
|
.get(this.userId)
|
|
10522
10844
|
.next((metadata) => {
|
|
10523
|
-
return (metadata ||
|
|
10524
|
-
|
|
10525
|
-
|
|
10845
|
+
return (metadata || {
|
|
10846
|
+
userId: this.userId,
|
|
10847
|
+
lastAcknowledgedBatchId: BATCHID_UNKNOWN,
|
|
10848
|
+
lastStreamToken: ''
|
|
10849
|
+
});
|
|
10526
10850
|
});
|
|
10527
10851
|
}
|
|
10528
10852
|
}
|
|
@@ -10531,7 +10855,7 @@ class IndexedDbMutationQueue {
|
|
|
10531
10855
|
* mutation for the given key.
|
|
10532
10856
|
*/
|
|
10533
10857
|
function mutationQueueContainsKey(txn, userId, key) {
|
|
10534
|
-
const indexKey =
|
|
10858
|
+
const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
|
|
10535
10859
|
const encodedPath = indexKey[1];
|
|
10536
10860
|
const startRange = IDBKeyRange.lowerBound(indexKey);
|
|
10537
10861
|
let containsKey = false;
|
|
@@ -10563,19 +10887,19 @@ function mutationQueuesContainKey(txn, docKey) {
|
|
|
10563
10887
|
* Helper to get a typed SimpleDbStore for the mutations object store.
|
|
10564
10888
|
*/
|
|
10565
10889
|
function mutationsStore(txn) {
|
|
10566
|
-
return getStore(txn,
|
|
10890
|
+
return getStore(txn, DbMutationBatchStore);
|
|
10567
10891
|
}
|
|
10568
10892
|
/**
|
|
10569
10893
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10570
10894
|
*/
|
|
10571
10895
|
function documentMutationsStore(txn) {
|
|
10572
|
-
return getStore(txn,
|
|
10896
|
+
return getStore(txn, DbDocumentMutationStore);
|
|
10573
10897
|
}
|
|
10574
10898
|
/**
|
|
10575
10899
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10576
10900
|
*/
|
|
10577
10901
|
function mutationQueuesStore(txn) {
|
|
10578
|
-
return getStore(txn,
|
|
10902
|
+
return getStore(txn, DbMutationQueueStore);
|
|
10579
10903
|
}
|
|
10580
10904
|
|
|
10581
10905
|
/**
|
|
@@ -10739,14 +11063,14 @@ class IndexedDbTargetCache {
|
|
|
10739
11063
|
}
|
|
10740
11064
|
retrieveMetadata(transaction) {
|
|
10741
11065
|
return globalTargetStore(transaction)
|
|
10742
|
-
.get(
|
|
11066
|
+
.get(DbTargetGlobalKey)
|
|
10743
11067
|
.next(metadata => {
|
|
10744
11068
|
hardAssert(metadata !== null);
|
|
10745
11069
|
return metadata;
|
|
10746
11070
|
});
|
|
10747
11071
|
}
|
|
10748
11072
|
saveMetadata(transaction, metadata) {
|
|
10749
|
-
return globalTargetStore(transaction).put(
|
|
11073
|
+
return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
|
|
10750
11074
|
}
|
|
10751
11075
|
saveTargetData(transaction, targetData) {
|
|
10752
11076
|
return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
|
|
@@ -10779,7 +11103,7 @@ class IndexedDbTargetCache {
|
|
|
10779
11103
|
const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
|
|
10780
11104
|
let result = null;
|
|
10781
11105
|
return targetsStore(transaction)
|
|
10782
|
-
.iterate({ range, index:
|
|
11106
|
+
.iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
|
|
10783
11107
|
const found = fromDbTarget(value);
|
|
10784
11108
|
// After finding a potential match, check that the target is
|
|
10785
11109
|
// actually equal to the requested target.
|
|
@@ -10797,7 +11121,7 @@ class IndexedDbTargetCache {
|
|
|
10797
11121
|
const store = documentTargetStore(txn);
|
|
10798
11122
|
keys.forEach(key => {
|
|
10799
11123
|
const path = encodeResourcePath(key.path);
|
|
10800
|
-
promises.push(store.put(
|
|
11124
|
+
promises.push(store.put({ targetId, path }));
|
|
10801
11125
|
promises.push(this.referenceDelegate.addReference(txn, targetId, key));
|
|
10802
11126
|
});
|
|
10803
11127
|
return PersistencePromise.waitFor(promises);
|
|
@@ -10843,7 +11167,7 @@ class IndexedDbTargetCache {
|
|
|
10843
11167
|
let count = 0;
|
|
10844
11168
|
return documentTargetStore(txn)
|
|
10845
11169
|
.iterate({
|
|
10846
|
-
index:
|
|
11170
|
+
index: DbTargetDocumentDocumentTargetsIndex,
|
|
10847
11171
|
keysOnly: true,
|
|
10848
11172
|
range
|
|
10849
11173
|
}, ([targetId, path], _, control) => {
|
|
@@ -10882,19 +11206,19 @@ class IndexedDbTargetCache {
|
|
|
10882
11206
|
* Helper to get a typed SimpleDbStore for the queries object store.
|
|
10883
11207
|
*/
|
|
10884
11208
|
function targetsStore(txn) {
|
|
10885
|
-
return getStore(txn,
|
|
11209
|
+
return getStore(txn, DbTargetStore);
|
|
10886
11210
|
}
|
|
10887
11211
|
/**
|
|
10888
11212
|
* Helper to get a typed SimpleDbStore for the target globals object store.
|
|
10889
11213
|
*/
|
|
10890
11214
|
function globalTargetStore(txn) {
|
|
10891
|
-
return getStore(txn,
|
|
11215
|
+
return getStore(txn, DbTargetGlobalStore);
|
|
10892
11216
|
}
|
|
10893
11217
|
/**
|
|
10894
11218
|
* Helper to get a typed SimpleDbStore for the document target object store.
|
|
10895
11219
|
*/
|
|
10896
11220
|
function documentTargetStore(txn) {
|
|
10897
|
-
return getStore(txn,
|
|
11221
|
+
return getStore(txn, DbTargetDocumentStore);
|
|
10898
11222
|
}
|
|
10899
11223
|
|
|
10900
11224
|
/**
|
|
@@ -11308,7 +11632,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
11308
11632
|
let nextPath;
|
|
11309
11633
|
return store
|
|
11310
11634
|
.iterate({
|
|
11311
|
-
index:
|
|
11635
|
+
index: DbTargetDocumentDocumentTargetsIndex
|
|
11312
11636
|
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
11313
11637
|
if (targetId === 0) {
|
|
11314
11638
|
// if nextToReport is valid, report it, this is a new key so the
|
|
@@ -11350,113 +11674,12 @@ function sentinelKey$1(key) {
|
|
|
11350
11674
|
* store.
|
|
11351
11675
|
*/
|
|
11352
11676
|
function sentinelRow(key, sequenceNumber) {
|
|
11353
|
-
return
|
|
11677
|
+
return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
|
|
11354
11678
|
}
|
|
11355
11679
|
function writeSentinelKey(txn, key) {
|
|
11356
11680
|
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
11357
11681
|
}
|
|
11358
11682
|
|
|
11359
|
-
/**
|
|
11360
|
-
* @license
|
|
11361
|
-
* Copyright 2017 Google LLC
|
|
11362
|
-
*
|
|
11363
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
11364
|
-
* you may not use this file except in compliance with the License.
|
|
11365
|
-
* You may obtain a copy of the License at
|
|
11366
|
-
*
|
|
11367
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11368
|
-
*
|
|
11369
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11370
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11371
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11372
|
-
* See the License for the specific language governing permissions and
|
|
11373
|
-
* limitations under the License.
|
|
11374
|
-
*/
|
|
11375
|
-
/**
|
|
11376
|
-
* A map implementation that uses objects as keys. Objects must have an
|
|
11377
|
-
* associated equals function and must be immutable. Entries in the map are
|
|
11378
|
-
* stored together with the key being produced from the mapKeyFn. This map
|
|
11379
|
-
* automatically handles collisions of keys.
|
|
11380
|
-
*/
|
|
11381
|
-
class ObjectMap {
|
|
11382
|
-
constructor(mapKeyFn, equalsFn) {
|
|
11383
|
-
this.mapKeyFn = mapKeyFn;
|
|
11384
|
-
this.equalsFn = equalsFn;
|
|
11385
|
-
/**
|
|
11386
|
-
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
11387
|
-
* keep a list of entries that we do a linear search through to find an actual
|
|
11388
|
-
* match. Note that collisions should be rare, so we still expect near
|
|
11389
|
-
* constant time lookups in practice.
|
|
11390
|
-
*/
|
|
11391
|
-
this.inner = {};
|
|
11392
|
-
}
|
|
11393
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
11394
|
-
get(key) {
|
|
11395
|
-
const id = this.mapKeyFn(key);
|
|
11396
|
-
const matches = this.inner[id];
|
|
11397
|
-
if (matches === undefined) {
|
|
11398
|
-
return undefined;
|
|
11399
|
-
}
|
|
11400
|
-
for (const [otherKey, value] of matches) {
|
|
11401
|
-
if (this.equalsFn(otherKey, key)) {
|
|
11402
|
-
return value;
|
|
11403
|
-
}
|
|
11404
|
-
}
|
|
11405
|
-
return undefined;
|
|
11406
|
-
}
|
|
11407
|
-
has(key) {
|
|
11408
|
-
return this.get(key) !== undefined;
|
|
11409
|
-
}
|
|
11410
|
-
/** Put this key and value in the map. */
|
|
11411
|
-
set(key, value) {
|
|
11412
|
-
const id = this.mapKeyFn(key);
|
|
11413
|
-
const matches = this.inner[id];
|
|
11414
|
-
if (matches === undefined) {
|
|
11415
|
-
this.inner[id] = [[key, value]];
|
|
11416
|
-
return;
|
|
11417
|
-
}
|
|
11418
|
-
for (let i = 0; i < matches.length; i++) {
|
|
11419
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
11420
|
-
matches[i] = [key, value];
|
|
11421
|
-
return;
|
|
11422
|
-
}
|
|
11423
|
-
}
|
|
11424
|
-
matches.push([key, value]);
|
|
11425
|
-
}
|
|
11426
|
-
/**
|
|
11427
|
-
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
11428
|
-
*/
|
|
11429
|
-
delete(key) {
|
|
11430
|
-
const id = this.mapKeyFn(key);
|
|
11431
|
-
const matches = this.inner[id];
|
|
11432
|
-
if (matches === undefined) {
|
|
11433
|
-
return false;
|
|
11434
|
-
}
|
|
11435
|
-
for (let i = 0; i < matches.length; i++) {
|
|
11436
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
11437
|
-
if (matches.length === 1) {
|
|
11438
|
-
delete this.inner[id];
|
|
11439
|
-
}
|
|
11440
|
-
else {
|
|
11441
|
-
matches.splice(i, 1);
|
|
11442
|
-
}
|
|
11443
|
-
return true;
|
|
11444
|
-
}
|
|
11445
|
-
}
|
|
11446
|
-
return false;
|
|
11447
|
-
}
|
|
11448
|
-
forEach(fn) {
|
|
11449
|
-
forEach(this.inner, (_, entries) => {
|
|
11450
|
-
for (const [k, v] of entries) {
|
|
11451
|
-
fn(k, v);
|
|
11452
|
-
}
|
|
11453
|
-
});
|
|
11454
|
-
}
|
|
11455
|
-
isEmpty() {
|
|
11456
|
-
return isEmpty(this.inner);
|
|
11457
|
-
}
|
|
11458
|
-
}
|
|
11459
|
-
|
|
11460
11683
|
/**
|
|
11461
11684
|
* @license
|
|
11462
11685
|
* Copyright 2017 Google LLC
|
|
@@ -11725,7 +11948,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11725
11948
|
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
11726
11949
|
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
11727
11950
|
/* open= */ true);
|
|
11728
|
-
iterationOptions.index =
|
|
11951
|
+
iterationOptions.index = DbRemoteDocumentCollectionReadTimeIndex;
|
|
11729
11952
|
}
|
|
11730
11953
|
return remoteDocumentsStore(transaction)
|
|
11731
11954
|
.iterate(iterationOptions, (key, dbRemoteDoc, control) => {
|
|
@@ -11755,14 +11978,14 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11755
11978
|
}
|
|
11756
11979
|
getMetadata(txn) {
|
|
11757
11980
|
return documentGlobalStore(txn)
|
|
11758
|
-
.get(
|
|
11981
|
+
.get(DbRemoteDocumentGlobalKey)
|
|
11759
11982
|
.next(metadata => {
|
|
11760
11983
|
hardAssert(!!metadata);
|
|
11761
11984
|
return metadata;
|
|
11762
11985
|
});
|
|
11763
11986
|
}
|
|
11764
11987
|
setMetadata(txn, metadata) {
|
|
11765
|
-
return documentGlobalStore(txn).put(
|
|
11988
|
+
return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
|
|
11766
11989
|
}
|
|
11767
11990
|
/**
|
|
11768
11991
|
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
@@ -11797,7 +12020,7 @@ function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transacti
|
|
|
11797
12020
|
const documentsStore = remoteDocumentsStore(transaction);
|
|
11798
12021
|
const range = IDBKeyRange.lowerBound(lastReadTime, true);
|
|
11799
12022
|
return documentsStore
|
|
11800
|
-
.iterate({ index:
|
|
12023
|
+
.iterate({ index: DbRemoteDocumentReadTimeIndex, range }, (_, dbRemoteDoc) => {
|
|
11801
12024
|
// Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
|
|
11802
12025
|
// the documents directly since we want to keep sentinel deletes.
|
|
11803
12026
|
const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
|
|
@@ -11821,7 +12044,7 @@ function remoteDocumentCacheGetLastReadTime(transaction) {
|
|
|
11821
12044
|
// If there are no existing entries, we return SnapshotVersion.min().
|
|
11822
12045
|
let readTime = SnapshotVersion.min();
|
|
11823
12046
|
return documentsStore
|
|
11824
|
-
.iterate({ index:
|
|
12047
|
+
.iterate({ index: DbRemoteDocumentReadTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
|
|
11825
12048
|
if (dbRemoteDoc.readTime) {
|
|
11826
12049
|
readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
|
|
11827
12050
|
}
|
|
@@ -11909,17 +12132,39 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11909
12132
|
}
|
|
11910
12133
|
}
|
|
11911
12134
|
function documentGlobalStore(txn) {
|
|
11912
|
-
return getStore(txn,
|
|
12135
|
+
return getStore(txn, DbRemoteDocumentGlobalStore);
|
|
11913
12136
|
}
|
|
11914
12137
|
/**
|
|
11915
12138
|
* Helper to get a typed SimpleDbStore for the remoteDocuments object store.
|
|
11916
12139
|
*/
|
|
11917
12140
|
function remoteDocumentsStore(txn) {
|
|
11918
|
-
return getStore(txn,
|
|
12141
|
+
return getStore(txn, DbRemoteDocumentStore);
|
|
11919
12142
|
}
|
|
11920
12143
|
function dbKey(docKey) {
|
|
11921
12144
|
return docKey.path.toArray();
|
|
11922
12145
|
}
|
|
12146
|
+
/**
|
|
12147
|
+
* Schema Version for the Web client:
|
|
12148
|
+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
12149
|
+
* Document Cache
|
|
12150
|
+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
12151
|
+
* longer required because migration 3 unconditionally clears it.
|
|
12152
|
+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
12153
|
+
* to limbo resolution. Addresses
|
|
12154
|
+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
12155
|
+
* 4. Multi-Tab Support.
|
|
12156
|
+
* 5. Removal of held write acks.
|
|
12157
|
+
* 6. Create document global for tracking document cache size.
|
|
12158
|
+
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
12159
|
+
* 8. Add collection-parent index for Collection Group queries.
|
|
12160
|
+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
12161
|
+
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
12162
|
+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
12163
|
+
* 11. Add bundles and named_queries for bundle support.
|
|
12164
|
+
* 12. Add document overlays.
|
|
12165
|
+
* 13. Add indexing support.
|
|
12166
|
+
*/
|
|
12167
|
+
const SCHEMA_VERSION = 12;
|
|
11923
12168
|
|
|
11924
12169
|
/**
|
|
11925
12170
|
* @license
|
|
@@ -12029,27 +12274,27 @@ class SchemaConverter {
|
|
|
12029
12274
|
return p;
|
|
12030
12275
|
}
|
|
12031
12276
|
addDocumentGlobal(txn) {
|
|
12032
|
-
let
|
|
12277
|
+
let byteSize = 0;
|
|
12033
12278
|
return txn
|
|
12034
|
-
.store(
|
|
12279
|
+
.store(DbRemoteDocumentStore)
|
|
12035
12280
|
.iterate((_, doc) => {
|
|
12036
|
-
|
|
12281
|
+
byteSize += dbDocumentSize(doc);
|
|
12037
12282
|
})
|
|
12038
12283
|
.next(() => {
|
|
12039
|
-
const metadata =
|
|
12284
|
+
const metadata = { byteSize };
|
|
12040
12285
|
return txn
|
|
12041
|
-
.store(
|
|
12042
|
-
.put(
|
|
12286
|
+
.store(DbRemoteDocumentGlobalStore)
|
|
12287
|
+
.put(DbRemoteDocumentGlobalKey, metadata);
|
|
12043
12288
|
});
|
|
12044
12289
|
}
|
|
12045
12290
|
removeAcknowledgedMutations(txn) {
|
|
12046
|
-
const queuesStore = txn.store(
|
|
12047
|
-
const mutationsStore = txn.store(
|
|
12291
|
+
const queuesStore = txn.store(DbMutationQueueStore);
|
|
12292
|
+
const mutationsStore = txn.store(DbMutationBatchStore);
|
|
12048
12293
|
return queuesStore.loadAll().next(queues => {
|
|
12049
12294
|
return PersistencePromise.forEach(queues, (queue) => {
|
|
12050
12295
|
const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
|
|
12051
12296
|
return mutationsStore
|
|
12052
|
-
.loadAll(
|
|
12297
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
12053
12298
|
.next(dbBatches => {
|
|
12054
12299
|
return PersistencePromise.forEach(dbBatches, (dbBatch) => {
|
|
12055
12300
|
hardAssert(dbBatch.userId === queue.userId);
|
|
@@ -12065,12 +12310,16 @@ class SchemaConverter {
|
|
|
12065
12310
|
* with a sequence number. Missing rows are given the most recently used sequence number.
|
|
12066
12311
|
*/
|
|
12067
12312
|
ensureSequenceNumbers(txn) {
|
|
12068
|
-
const documentTargetStore = txn.store(
|
|
12069
|
-
const documentsStore = txn.store(
|
|
12070
|
-
const globalTargetStore = txn.store(
|
|
12071
|
-
return globalTargetStore.get(
|
|
12313
|
+
const documentTargetStore = txn.store(DbTargetDocumentStore);
|
|
12314
|
+
const documentsStore = txn.store(DbRemoteDocumentStore);
|
|
12315
|
+
const globalTargetStore = txn.store(DbTargetGlobalStore);
|
|
12316
|
+
return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
|
|
12072
12317
|
const writeSentinelKey = (path) => {
|
|
12073
|
-
return documentTargetStore.put(
|
|
12318
|
+
return documentTargetStore.put({
|
|
12319
|
+
targetId: 0,
|
|
12320
|
+
path: encodeResourcePath(path),
|
|
12321
|
+
sequenceNumber: metadata.highestListenSequenceNumber
|
|
12322
|
+
});
|
|
12074
12323
|
};
|
|
12075
12324
|
const promises = [];
|
|
12076
12325
|
return documentsStore
|
|
@@ -12091,10 +12340,10 @@ class SchemaConverter {
|
|
|
12091
12340
|
}
|
|
12092
12341
|
createCollectionParentIndex(db, txn) {
|
|
12093
12342
|
// Create the index.
|
|
12094
|
-
db.createObjectStore(
|
|
12095
|
-
keyPath:
|
|
12343
|
+
db.createObjectStore(DbCollectionParentStore, {
|
|
12344
|
+
keyPath: DbCollectionParentKeyPath
|
|
12096
12345
|
});
|
|
12097
|
-
const collectionParentsStore = txn.store(
|
|
12346
|
+
const collectionParentsStore = txn.store(DbCollectionParentStore);
|
|
12098
12347
|
// Helper to add an index entry iff we haven't already written it.
|
|
12099
12348
|
const cache = new MemoryCollectionParentIndex();
|
|
12100
12349
|
const addEntry = (collectionPath) => {
|
|
@@ -12109,7 +12358,7 @@ class SchemaConverter {
|
|
|
12109
12358
|
};
|
|
12110
12359
|
// Index existing remote documents.
|
|
12111
12360
|
return txn
|
|
12112
|
-
.store(
|
|
12361
|
+
.store(DbRemoteDocumentStore)
|
|
12113
12362
|
.iterate({ keysOnly: true }, (pathSegments, _) => {
|
|
12114
12363
|
const path = new ResourcePath(pathSegments);
|
|
12115
12364
|
return addEntry(path.popLast());
|
|
@@ -12117,7 +12366,7 @@ class SchemaConverter {
|
|
|
12117
12366
|
.next(() => {
|
|
12118
12367
|
// Index existing mutations.
|
|
12119
12368
|
return txn
|
|
12120
|
-
.store(
|
|
12369
|
+
.store(DbDocumentMutationStore)
|
|
12121
12370
|
.iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
|
|
12122
12371
|
const path = decodeResourcePath(encodedPath);
|
|
12123
12372
|
return addEntry(path.popLast());
|
|
@@ -12125,7 +12374,7 @@ class SchemaConverter {
|
|
|
12125
12374
|
});
|
|
12126
12375
|
}
|
|
12127
12376
|
rewriteCanonicalIds(txn) {
|
|
12128
|
-
const targetStore = txn.store(
|
|
12377
|
+
const targetStore = txn.store(DbTargetStore);
|
|
12129
12378
|
return targetStore.iterate((key, originalDbTarget) => {
|
|
12130
12379
|
const originalTargetData = fromDbTarget(originalDbTarget);
|
|
12131
12380
|
const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
|
|
@@ -12137,59 +12386,59 @@ function sentinelKey(path) {
|
|
|
12137
12386
|
return [0, encodeResourcePath(path)];
|
|
12138
12387
|
}
|
|
12139
12388
|
function createPrimaryClientStore(db) {
|
|
12140
|
-
db.createObjectStore(
|
|
12389
|
+
db.createObjectStore(DbPrimaryClientStore);
|
|
12141
12390
|
}
|
|
12142
12391
|
function createMutationQueue(db) {
|
|
12143
|
-
db.createObjectStore(
|
|
12144
|
-
keyPath:
|
|
12392
|
+
db.createObjectStore(DbMutationQueueStore, {
|
|
12393
|
+
keyPath: DbMutationQueueKeyPath
|
|
12145
12394
|
});
|
|
12146
|
-
const mutationBatchesStore = db.createObjectStore(
|
|
12147
|
-
keyPath:
|
|
12395
|
+
const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12396
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12148
12397
|
autoIncrement: true
|
|
12149
12398
|
});
|
|
12150
|
-
mutationBatchesStore.createIndex(
|
|
12151
|
-
db.createObjectStore(
|
|
12399
|
+
mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12400
|
+
db.createObjectStore(DbDocumentMutationStore);
|
|
12152
12401
|
}
|
|
12153
12402
|
/**
|
|
12154
12403
|
* Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
|
|
12155
12404
|
* and rewrites all data.
|
|
12156
12405
|
*/
|
|
12157
12406
|
function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
|
|
12158
|
-
const v1MutationsStore = txn.store(
|
|
12407
|
+
const v1MutationsStore = txn.store(DbMutationBatchStore);
|
|
12159
12408
|
return v1MutationsStore.loadAll().next(existingMutations => {
|
|
12160
|
-
db.deleteObjectStore(
|
|
12161
|
-
const mutationsStore = db.createObjectStore(
|
|
12162
|
-
keyPath:
|
|
12409
|
+
db.deleteObjectStore(DbMutationBatchStore);
|
|
12410
|
+
const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12411
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12163
12412
|
autoIncrement: true
|
|
12164
12413
|
});
|
|
12165
|
-
mutationsStore.createIndex(
|
|
12166
|
-
const v3MutationsStore = txn.store(
|
|
12414
|
+
mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12415
|
+
const v3MutationsStore = txn.store(DbMutationBatchStore);
|
|
12167
12416
|
const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
|
|
12168
12417
|
return PersistencePromise.waitFor(writeAll);
|
|
12169
12418
|
});
|
|
12170
12419
|
}
|
|
12171
12420
|
function createRemoteDocumentCache(db) {
|
|
12172
|
-
db.createObjectStore(
|
|
12421
|
+
db.createObjectStore(DbRemoteDocumentStore);
|
|
12173
12422
|
}
|
|
12174
12423
|
function createDocumentGlobalStore(db) {
|
|
12175
|
-
db.createObjectStore(
|
|
12424
|
+
db.createObjectStore(DbRemoteDocumentGlobalStore);
|
|
12176
12425
|
}
|
|
12177
12426
|
function createQueryCache(db) {
|
|
12178
|
-
const targetDocumentsStore = db.createObjectStore(
|
|
12179
|
-
keyPath:
|
|
12427
|
+
const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
|
|
12428
|
+
keyPath: DbTargetDocumentKeyPath
|
|
12180
12429
|
});
|
|
12181
|
-
targetDocumentsStore.createIndex(
|
|
12182
|
-
const targetStore = db.createObjectStore(
|
|
12183
|
-
keyPath:
|
|
12430
|
+
targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
|
|
12431
|
+
const targetStore = db.createObjectStore(DbTargetStore, {
|
|
12432
|
+
keyPath: DbTargetKeyPath
|
|
12184
12433
|
});
|
|
12185
12434
|
// NOTE: This is unique only because the TargetId is the suffix.
|
|
12186
|
-
targetStore.createIndex(
|
|
12187
|
-
db.createObjectStore(
|
|
12435
|
+
targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
|
|
12436
|
+
db.createObjectStore(DbTargetGlobalStore);
|
|
12188
12437
|
}
|
|
12189
12438
|
function dropQueryCache(db) {
|
|
12190
|
-
db.deleteObjectStore(
|
|
12191
|
-
db.deleteObjectStore(
|
|
12192
|
-
db.deleteObjectStore(
|
|
12439
|
+
db.deleteObjectStore(DbTargetDocumentStore);
|
|
12440
|
+
db.deleteObjectStore(DbTargetStore);
|
|
12441
|
+
db.deleteObjectStore(DbTargetGlobalStore);
|
|
12193
12442
|
}
|
|
12194
12443
|
function dropRemoteDocumentChangesStore(db) {
|
|
12195
12444
|
if (db.objectStoreNames.contains('remoteDocumentChanges')) {
|
|
@@ -12202,58 +12451,60 @@ function dropRemoteDocumentChangesStore(db) {
|
|
|
12202
12451
|
* @param txn - The version upgrade transaction for indexeddb
|
|
12203
12452
|
*/
|
|
12204
12453
|
function writeEmptyTargetGlobalEntry(txn) {
|
|
12205
|
-
const globalStore = txn.store(
|
|
12206
|
-
const metadata =
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12454
|
+
const globalStore = txn.store(DbTargetGlobalStore);
|
|
12455
|
+
const metadata = {
|
|
12456
|
+
highestTargetId: 0,
|
|
12457
|
+
highestListenSequenceNumber: 0,
|
|
12458
|
+
lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
|
|
12459
|
+
targetCount: 0
|
|
12460
|
+
};
|
|
12461
|
+
return globalStore.put(DbTargetGlobalKey, metadata);
|
|
12211
12462
|
}
|
|
12212
12463
|
/**
|
|
12213
12464
|
* Creates indices on the RemoteDocuments store used for both multi-tab
|
|
12214
12465
|
* and Index-Free queries.
|
|
12215
12466
|
*/
|
|
12216
12467
|
function createRemoteDocumentReadTimeIndex(txn) {
|
|
12217
|
-
const remoteDocumentStore = txn.objectStore(
|
|
12218
|
-
remoteDocumentStore.createIndex(
|
|
12219
|
-
remoteDocumentStore.createIndex(
|
|
12468
|
+
const remoteDocumentStore = txn.objectStore(DbRemoteDocumentStore);
|
|
12469
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentReadTimeIndex, DbRemoteDocumentReadTimeIndexPath, { unique: false });
|
|
12470
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentCollectionReadTimeIndex, DbRemoteDocumentCollectionReadTimeIndexPath, { unique: false });
|
|
12220
12471
|
}
|
|
12221
12472
|
function createClientMetadataStore(db) {
|
|
12222
|
-
db.createObjectStore(
|
|
12223
|
-
keyPath:
|
|
12473
|
+
db.createObjectStore(DbClientMetadataStore, {
|
|
12474
|
+
keyPath: DbClientMetadataKeyPath
|
|
12224
12475
|
});
|
|
12225
12476
|
}
|
|
12226
12477
|
function createBundlesStore(db) {
|
|
12227
|
-
db.createObjectStore(
|
|
12228
|
-
keyPath:
|
|
12478
|
+
db.createObjectStore(DbBundleStore, {
|
|
12479
|
+
keyPath: DbBundleKeyPath
|
|
12229
12480
|
});
|
|
12230
12481
|
}
|
|
12231
12482
|
function createNamedQueriesStore(db) {
|
|
12232
|
-
db.createObjectStore(
|
|
12233
|
-
keyPath:
|
|
12483
|
+
db.createObjectStore(DbNamedQueryStore, {
|
|
12484
|
+
keyPath: DbNamedQueryKeyPath
|
|
12234
12485
|
});
|
|
12235
12486
|
}
|
|
12236
12487
|
function createFieldIndex(db) {
|
|
12237
|
-
const indexConfiguratioStore = db.createObjectStore(
|
|
12238
|
-
keyPath:
|
|
12488
|
+
const indexConfiguratioStore = db.createObjectStore(DbIndexConfigurationStore, {
|
|
12489
|
+
keyPath: DbIndexConfigurationKeyPath,
|
|
12239
12490
|
autoIncrement: true
|
|
12240
12491
|
});
|
|
12241
|
-
indexConfiguratioStore.createIndex(
|
|
12242
|
-
const indexStateStore = db.createObjectStore(
|
|
12243
|
-
keyPath:
|
|
12492
|
+
indexConfiguratioStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
|
|
12493
|
+
const indexStateStore = db.createObjectStore(DbIndexStateStore, {
|
|
12494
|
+
keyPath: DbIndexStateKeyPath
|
|
12244
12495
|
});
|
|
12245
|
-
indexStateStore.createIndex(
|
|
12246
|
-
const indexEntryStore = db.createObjectStore(
|
|
12247
|
-
keyPath:
|
|
12496
|
+
indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
|
|
12497
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
|
|
12498
|
+
keyPath: DbIndexEntryKeyPath
|
|
12248
12499
|
});
|
|
12249
|
-
indexEntryStore.createIndex(
|
|
12500
|
+
indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
|
|
12250
12501
|
}
|
|
12251
12502
|
function createDocumentOverlayStore(db) {
|
|
12252
|
-
const documentOverlayStore = db.createObjectStore(
|
|
12253
|
-
keyPath:
|
|
12503
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
|
|
12504
|
+
keyPath: DbDocumentOverlayKeyPath
|
|
12254
12505
|
});
|
|
12255
|
-
documentOverlayStore.createIndex(
|
|
12256
|
-
documentOverlayStore.createIndex(
|
|
12506
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
|
|
12507
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
|
|
12257
12508
|
}
|
|
12258
12509
|
|
|
12259
12510
|
/**
|
|
@@ -12501,7 +12752,12 @@ class IndexedDbPersistence {
|
|
|
12501
12752
|
return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
|
|
12502
12753
|
const metadataStore = clientMetadataStore(txn);
|
|
12503
12754
|
return metadataStore
|
|
12504
|
-
.put(
|
|
12755
|
+
.put({
|
|
12756
|
+
clientId: this.clientId,
|
|
12757
|
+
updateTimeMs: Date.now(),
|
|
12758
|
+
networkEnabled: this.networkEnabled,
|
|
12759
|
+
inForeground: this.inForeground
|
|
12760
|
+
})
|
|
12505
12761
|
.next(() => {
|
|
12506
12762
|
if (this.isPrimary) {
|
|
12507
12763
|
return this.verifyPrimaryLease(txn).next(success => {
|
|
@@ -12547,7 +12803,7 @@ class IndexedDbPersistence {
|
|
|
12547
12803
|
}
|
|
12548
12804
|
verifyPrimaryLease(txn) {
|
|
12549
12805
|
const store = primaryClientStore(txn);
|
|
12550
|
-
return store.get(
|
|
12806
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12551
12807
|
return PersistencePromise.resolve(this.isLocalClient(primaryClient));
|
|
12552
12808
|
});
|
|
12553
12809
|
}
|
|
@@ -12565,7 +12821,7 @@ class IndexedDbPersistence {
|
|
|
12565
12821
|
!this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
|
|
12566
12822
|
this.lastGarbageCollectionTime = Date.now();
|
|
12567
12823
|
const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
|
|
12568
|
-
const metadataStore = getStore(txn,
|
|
12824
|
+
const metadataStore = getStore(txn, DbClientMetadataStore);
|
|
12569
12825
|
return metadataStore.loadAll().next(existingClients => {
|
|
12570
12826
|
const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
|
|
12571
12827
|
const inactive = existingClients.filter(client => active.indexOf(client) === -1);
|
|
@@ -12619,7 +12875,7 @@ class IndexedDbPersistence {
|
|
|
12619
12875
|
}
|
|
12620
12876
|
const store = primaryClientStore(txn);
|
|
12621
12877
|
return store
|
|
12622
|
-
.get(
|
|
12878
|
+
.get(DbPrimaryClientKey)
|
|
12623
12879
|
.next(currentPrimary => {
|
|
12624
12880
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12625
12881
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
@@ -12700,7 +12956,7 @@ class IndexedDbPersistence {
|
|
|
12700
12956
|
this.detachWindowUnloadHook();
|
|
12701
12957
|
// Use `SimpleDb.runTransaction` directly to avoid failing if another tab
|
|
12702
12958
|
// has obtained the primary lease.
|
|
12703
|
-
await this.simpleDb.runTransaction('shutdown', 'readwrite', [
|
|
12959
|
+
await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
|
|
12704
12960
|
const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
|
|
12705
12961
|
return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
|
|
12706
12962
|
});
|
|
@@ -12807,7 +13063,7 @@ class IndexedDbPersistence {
|
|
|
12807
13063
|
// be turned off.
|
|
12808
13064
|
verifyAllowTabSynchronization(txn) {
|
|
12809
13065
|
const store = primaryClientStore(txn);
|
|
12810
|
-
return store.get(
|
|
13066
|
+
return store.get(DbPrimaryClientKey).next(currentPrimary => {
|
|
12811
13067
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12812
13068
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
12813
13069
|
!this.isClientZombied(currentPrimary.ownerId);
|
|
@@ -12825,8 +13081,12 @@ class IndexedDbPersistence {
|
|
|
12825
13081
|
* method does not verify that the client is eligible for this lease.
|
|
12826
13082
|
*/
|
|
12827
13083
|
acquireOrExtendPrimaryLease(txn) {
|
|
12828
|
-
const newPrimary =
|
|
12829
|
-
|
|
13084
|
+
const newPrimary = {
|
|
13085
|
+
ownerId: this.clientId,
|
|
13086
|
+
allowTabSynchronization: this.allowTabSynchronization,
|
|
13087
|
+
leaseTimestampMs: Date.now()
|
|
13088
|
+
};
|
|
13089
|
+
return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
|
|
12830
13090
|
}
|
|
12831
13091
|
static isAvailable() {
|
|
12832
13092
|
return SimpleDb.isAvailable();
|
|
@@ -12834,10 +13094,10 @@ class IndexedDbPersistence {
|
|
|
12834
13094
|
/** Checks the primary lease and removes it if we are the current primary. */
|
|
12835
13095
|
releasePrimaryLeaseIfHeld(txn) {
|
|
12836
13096
|
const store = primaryClientStore(txn);
|
|
12837
|
-
return store.get(
|
|
13097
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12838
13098
|
if (this.isLocalClient(primaryClient)) {
|
|
12839
13099
|
logDebug(LOG_TAG$d, 'Releasing primary lease.');
|
|
12840
|
-
return store.delete(
|
|
13100
|
+
return store.delete(DbPrimaryClientKey);
|
|
12841
13101
|
}
|
|
12842
13102
|
else {
|
|
12843
13103
|
return PersistencePromise.resolve();
|
|
@@ -12971,13 +13231,13 @@ class IndexedDbPersistence {
|
|
|
12971
13231
|
* Helper to get a typed SimpleDbStore for the primary client object store.
|
|
12972
13232
|
*/
|
|
12973
13233
|
function primaryClientStore(txn) {
|
|
12974
|
-
return getStore(txn,
|
|
13234
|
+
return getStore(txn, DbPrimaryClientStore);
|
|
12975
13235
|
}
|
|
12976
13236
|
/**
|
|
12977
13237
|
* Helper to get a typed SimpleDbStore for the client metadata object store.
|
|
12978
13238
|
*/
|
|
12979
13239
|
function clientMetadataStore(txn) {
|
|
12980
|
-
return getStore(txn,
|
|
13240
|
+
return getStore(txn, DbClientMetadataStore);
|
|
12981
13241
|
}
|
|
12982
13242
|
/**
|
|
12983
13243
|
* Generates a string used as a prefix when storing data in IndexedDB and
|
|
@@ -14040,7 +14300,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14040
14300
|
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14041
14301
|
}
|
|
14042
14302
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14043
|
-
overlays.forEach(mutation => {
|
|
14303
|
+
overlays.forEach((_, mutation) => {
|
|
14044
14304
|
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14045
14305
|
});
|
|
14046
14306
|
return PersistencePromise.resolve();
|
|
@@ -14054,7 +14314,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14054
14314
|
return PersistencePromise.resolve();
|
|
14055
14315
|
}
|
|
14056
14316
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14057
|
-
const result =
|
|
14317
|
+
const result = newOverlayMap();
|
|
14058
14318
|
const immediateChildrenPathLength = collection.length + 1;
|
|
14059
14319
|
const prefix = new DocumentKey(collection.child(''));
|
|
14060
14320
|
const iter = this.overlays.getIteratorFrom(prefix);
|
|
@@ -14088,19 +14348,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14088
14348
|
if (overlay.largestBatchId > sinceBatchId) {
|
|
14089
14349
|
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14090
14350
|
if (overlaysForBatchId === null) {
|
|
14091
|
-
overlaysForBatchId =
|
|
14351
|
+
overlaysForBatchId = newOverlayMap();
|
|
14092
14352
|
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14093
14353
|
}
|
|
14094
14354
|
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14095
14355
|
}
|
|
14096
14356
|
}
|
|
14097
|
-
const result =
|
|
14357
|
+
const result = newOverlayMap();
|
|
14098
14358
|
const batchIter = batchIdToOverlays.getIterator();
|
|
14099
14359
|
while (batchIter.hasNext()) {
|
|
14100
14360
|
const entry = batchIter.getNext();
|
|
14101
14361
|
const overlays = entry.value;
|
|
14102
|
-
overlays.forEach((
|
|
14103
|
-
if (result.size >= count) {
|
|
14362
|
+
overlays.forEach((key, overlay) => result.set(key, overlay));
|
|
14363
|
+
if (result.size() >= count) {
|
|
14104
14364
|
break;
|
|
14105
14365
|
}
|
|
14106
14366
|
}
|
|
@@ -14113,16 +14373,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14113
14373
|
// Remove the association of the overlay to its batch id.
|
|
14114
14374
|
const existing = this.overlays.get(mutation.key);
|
|
14115
14375
|
if (existing !== null) {
|
|
14116
|
-
this.overlayByBatchId
|
|
14376
|
+
const newSet = this.overlayByBatchId
|
|
14377
|
+
.get(existing.largestBatchId)
|
|
14378
|
+
.delete(mutation.key);
|
|
14379
|
+
this.overlayByBatchId.set(existing.largestBatchId, newSet);
|
|
14117
14380
|
}
|
|
14118
14381
|
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14119
14382
|
// Create the association of this overlay to the given largestBatchId.
|
|
14120
14383
|
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14121
14384
|
if (batch === undefined) {
|
|
14122
|
-
batch =
|
|
14385
|
+
batch = documentKeySet();
|
|
14123
14386
|
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14124
14387
|
}
|
|
14125
|
-
batch.add(mutation.key);
|
|
14388
|
+
this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
|
|
14126
14389
|
}
|
|
14127
14390
|
}
|
|
14128
14391
|
|
|
@@ -15973,9 +16236,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
|
15973
16236
|
metadata.set('X-Firebase-GMPID', appId);
|
|
15974
16237
|
}
|
|
15975
16238
|
metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
|
|
15976
|
-
//
|
|
16239
|
+
// These headers are used to improve routing and project isolation by the
|
|
15977
16240
|
// backend.
|
|
16241
|
+
// TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
|
|
16242
|
+
// released with cl/428820046. Currently blocked because Emulators are now built with Java
|
|
16243
|
+
// 11 from Google3.
|
|
15978
16244
|
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
|
|
16245
|
+
metadata.set('x-goog-request-params', databasePath);
|
|
15979
16246
|
return metadata;
|
|
15980
16247
|
}
|
|
15981
16248
|
/**
|