@firebase/firestore 3.4.6 → 3.4.7-canary.05dc9d6a0

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/firestore/src/core/bundle.d.ts +2 -2
  3. package/dist/firestore/src/core/bundle_impl.d.ts +2 -0
  4. package/dist/firestore/src/core/query.d.ts +7 -0
  5. package/dist/firestore/src/core/snapshot_version.d.ts +1 -0
  6. package/dist/firestore/src/core/sync_engine_impl.d.ts +1 -1
  7. package/dist/firestore/src/local/index_manager.d.ts +3 -2
  8. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  9. package/dist/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  10. package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  11. package/dist/firestore/src/local/indexeddb_schema.d.ts +23 -15
  12. package/dist/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  13. package/dist/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  14. package/dist/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  15. package/dist/firestore/src/local/local_documents_view.d.ts +3 -4
  16. package/dist/firestore/src/local/local_serializer.d.ts +2 -1
  17. package/dist/firestore/src/local/local_store_impl.d.ts +1 -7
  18. package/dist/firestore/src/local/memory_index_manager.d.ts +3 -2
  19. package/dist/firestore/src/local/remote_document_cache.d.ts +13 -4
  20. package/dist/firestore/src/local/shared_client_state.d.ts +6 -3
  21. package/dist/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  22. package/dist/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  23. package/dist/firestore/src/local/simple_db.d.ts +2 -0
  24. package/dist/firestore/src/model/field_index.d.ts +14 -1
  25. package/dist/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  26. package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  27. package/dist/index.esm2017.js +3531 -3400
  28. package/dist/index.esm2017.js.map +1 -1
  29. package/dist/index.esm5.js +4157 -3978
  30. package/dist/index.esm5.js.map +1 -1
  31. package/dist/index.node.cjs.js +589 -362
  32. package/dist/index.node.cjs.js.map +1 -1
  33. package/dist/index.node.mjs +589 -362
  34. package/dist/index.node.mjs.map +1 -1
  35. package/dist/index.rn.js +3643 -3512
  36. package/dist/index.rn.js.map +1 -1
  37. package/dist/internal.d.ts +20 -6
  38. package/dist/lite/firestore/src/core/bundle.d.ts +2 -2
  39. package/dist/lite/firestore/src/core/bundle_impl.d.ts +2 -0
  40. package/dist/lite/firestore/src/core/query.d.ts +7 -0
  41. package/dist/lite/firestore/src/core/snapshot_version.d.ts +1 -0
  42. package/dist/lite/firestore/src/core/sync_engine_impl.d.ts +1 -1
  43. package/dist/lite/firestore/src/local/index_manager.d.ts +3 -2
  44. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  45. package/dist/lite/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  46. package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  47. package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +23 -15
  48. package/dist/lite/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  49. package/dist/lite/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  50. package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  51. package/dist/lite/firestore/src/local/local_documents_view.d.ts +3 -4
  52. package/dist/lite/firestore/src/local/local_serializer.d.ts +2 -1
  53. package/dist/lite/firestore/src/local/local_store_impl.d.ts +1 -7
  54. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +3 -2
  55. package/dist/lite/firestore/src/local/remote_document_cache.d.ts +13 -4
  56. package/dist/lite/firestore/src/local/shared_client_state.d.ts +6 -3
  57. package/dist/lite/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  58. package/dist/lite/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  59. package/dist/lite/firestore/src/local/simple_db.d.ts +2 -0
  60. package/dist/lite/firestore/src/model/field_index.d.ts +14 -1
  61. package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  62. package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  63. package/dist/lite/index.browser.esm2017.js +5 -2
  64. package/dist/lite/index.browser.esm2017.js.map +1 -1
  65. package/dist/lite/index.browser.esm5.js +94 -92
  66. package/dist/lite/index.browser.esm5.js.map +1 -1
  67. package/dist/lite/index.node.cjs.js +5 -2
  68. package/dist/lite/index.node.cjs.js.map +1 -1
  69. package/dist/lite/index.node.mjs +5 -2
  70. package/dist/lite/index.node.mjs.map +1 -1
  71. package/dist/lite/index.rn.esm2017.js +5 -2
  72. package/dist/lite/index.rn.esm2017.js.map +1 -1
  73. package/dist/lite/internal.d.ts +1 -0
  74. package/dist/lite/packages/firestore/src/core/bundle.d.ts +2 -2
  75. package/dist/lite/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  76. package/dist/lite/packages/firestore/src/core/query.d.ts +7 -0
  77. package/dist/lite/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  78. package/dist/lite/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  79. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +3 -2
  80. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  81. package/dist/lite/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  82. package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  83. package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +23 -15
  84. package/dist/lite/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  85. package/dist/lite/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  86. package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  87. package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  88. package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +2 -1
  89. package/dist/lite/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  90. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +3 -2
  91. package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  92. package/dist/lite/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  93. package/dist/lite/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  94. package/dist/lite/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  95. package/dist/lite/packages/firestore/src/local/simple_db.d.ts +2 -0
  96. package/dist/lite/packages/firestore/src/model/field_index.d.ts +14 -1
  97. package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  98. package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  99. package/dist/lite/private.d.ts +1 -0
  100. package/dist/packages/firestore/dist/index.esm2017.d.ts +180 -180
  101. package/dist/packages/firestore/src/core/bundle.d.ts +2 -2
  102. package/dist/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  103. package/dist/packages/firestore/src/core/query.d.ts +7 -0
  104. package/dist/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  105. package/dist/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  106. package/dist/packages/firestore/src/local/index_manager.d.ts +3 -2
  107. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  108. package/dist/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  109. package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  110. package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +23 -15
  111. package/dist/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  112. package/dist/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  113. package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  114. package/dist/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  115. package/dist/packages/firestore/src/local/local_serializer.d.ts +2 -1
  116. package/dist/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  117. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +3 -2
  118. package/dist/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  119. package/dist/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  120. package/dist/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  121. package/dist/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  122. package/dist/packages/firestore/src/local/simple_db.d.ts +2 -0
  123. package/dist/packages/firestore/src/model/field_index.d.ts +14 -1
  124. package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  125. package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  126. package/dist/private.d.ts +20 -6
  127. package/package.json +9 -9
@@ -33,7 +33,7 @@ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
33
33
  var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
34
34
 
35
35
  const name = "@firebase/firestore";
36
- const version$1 = "3.4.6";
36
+ const version$1 = "3.4.7-canary.05dc9d6a0";
37
37
 
38
38
  /**
39
39
  * @license
@@ -86,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
86
86
  User.FIRST_PARTY = new User('first-party-uid');
87
87
  User.MOCK_USER = new User('mock-user');
88
88
 
89
- const version = "9.6.9";
89
+ const version = "9.6.10-canary.05dc9d6a0";
90
90
 
91
91
  /**
92
92
  * @license
@@ -1225,6 +1225,24 @@ function decodeResourcePath(path) {
1225
1225
  return new ResourcePath(segments);
1226
1226
  }
1227
1227
 
1228
+ /**
1229
+ * @license
1230
+ * Copyright 2022 Google LLC
1231
+ *
1232
+ * Licensed under the Apache License, Version 2.0 (the "License");
1233
+ * you may not use this file except in compliance with the License.
1234
+ * You may obtain a copy of the License at
1235
+ *
1236
+ * http://www.apache.org/licenses/LICENSE-2.0
1237
+ *
1238
+ * Unless required by applicable law or agreed to in writing, software
1239
+ * distributed under the License is distributed on an "AS IS" BASIS,
1240
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1241
+ * See the License for the specific language governing permissions and
1242
+ * limitations under the License.
1243
+ */
1244
+ const DbRemoteDocumentStore$1 = 'remoteDocuments';
1245
+
1228
1246
  /**
1229
1247
  * @license
1230
1248
  * Copyright 2022 Google LLC
@@ -1295,26 +1313,36 @@ function newDbDocumentMutationKey(userId, path, batchId) {
1295
1313
  */
1296
1314
  const DbDocumentMutationPlaceholder = {};
1297
1315
  const DbDocumentMutationStore = 'documentMutations';
1298
- const DbRemoteDocumentStore = 'remoteDocuments';
1316
+ const DbRemoteDocumentStore = 'remoteDocumentsV14';
1299
1317
  /**
1300
- * An index that provides access to all entries sorted by read time (which
1301
- * corresponds to the last modification time of each row).
1302
- *
1303
- * This index is used to provide a changelog for Multi-Tab.
1318
+ * The primary key of the remote documents store, which allows for efficient
1319
+ * access by collection path and read time.
1304
1320
  */
1305
- const DbRemoteDocumentReadTimeIndex = 'readTimeIndex';
1306
- const DbRemoteDocumentReadTimeIndexPath = 'readTime';
1321
+ const DbRemoteDocumentKeyPath = [
1322
+ 'prefixPath',
1323
+ 'collectionGroup',
1324
+ 'readTime',
1325
+ 'documentId'
1326
+ ];
1327
+ /** An index that provides access to documents by key. */
1328
+ const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
1329
+ const DbRemoteDocumentDocumentKeyIndexPath = [
1330
+ 'prefixPath',
1331
+ 'collectionGroup',
1332
+ 'documentId'
1333
+ ];
1307
1334
  /**
1308
- * An index that provides access to documents in a collection sorted by read
1335
+ * An index that provides access to documents by collection group and read
1309
1336
  * time.
1310
1337
  *
1311
- * This index is used to allow the RemoteDocumentCache to fetch newly changed
1312
- * documents in a collection.
1338
+ * This index is used by the index backfiller.
1313
1339
  */
1314
- const DbRemoteDocumentCollectionReadTimeIndex = 'collectionReadTimeIndex';
1315
- const DbRemoteDocumentCollectionReadTimeIndexPath = [
1316
- 'parentPath',
1317
- 'readTime'
1340
+ const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
1341
+ const DbRemoteDocumentCollectionGroupIndexPath = [
1342
+ 'collectionGroup',
1343
+ 'readTime',
1344
+ 'prefixPath',
1345
+ 'documentId'
1318
1346
  ];
1319
1347
  const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1320
1348
  const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
@@ -1390,13 +1418,14 @@ const DbIndexEntryKeyPath = [
1390
1418
  'uid',
1391
1419
  'arrayValue',
1392
1420
  'directionalValue',
1421
+ 'orderedDocumentKey',
1393
1422
  'documentKey'
1394
1423
  ];
1395
1424
  const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1396
1425
  const DbIndexEntryDocumentKeyIndexPath = [
1397
1426
  'indexId',
1398
1427
  'uid',
1399
- 'documentKey'
1428
+ 'orderedDocumentKey'
1400
1429
  ];
1401
1430
  /** Name of the IndexedDb object store. */
1402
1431
  const DbDocumentOverlayStore = 'documentOverlays';
@@ -1422,7 +1451,7 @@ const V1_STORES = [
1422
1451
  DbMutationQueueStore,
1423
1452
  DbMutationBatchStore,
1424
1453
  DbDocumentMutationStore,
1425
- DbRemoteDocumentStore,
1454
+ DbRemoteDocumentStore$1,
1426
1455
  DbTargetStore,
1427
1456
  DbPrimaryClientStore,
1428
1457
  DbTargetGlobalStore,
@@ -1437,14 +1466,33 @@ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1437
1466
  const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1438
1467
  const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1439
1468
  const V13_STORES = [
1440
- ...V12_STORES,
1469
+ DbMutationQueueStore,
1470
+ DbMutationBatchStore,
1471
+ DbDocumentMutationStore,
1472
+ DbRemoteDocumentStore,
1473
+ DbTargetStore,
1474
+ DbPrimaryClientStore,
1475
+ DbTargetGlobalStore,
1476
+ DbTargetDocumentStore,
1477
+ DbClientMetadataStore,
1478
+ DbRemoteDocumentGlobalStore,
1479
+ DbCollectionParentStore,
1480
+ DbBundleStore,
1481
+ DbNamedQueryStore,
1482
+ DbDocumentOverlayStore
1483
+ ];
1484
+ const V14_STORES = [
1485
+ ...V13_STORES,
1441
1486
  DbIndexConfigurationStore,
1442
1487
  DbIndexStateStore,
1443
1488
  DbIndexEntryStore
1444
1489
  ];
1445
1490
  /** Returns the object stores for the provided schema. */
1446
1491
  function getObjectStores(schemaVersion) {
1447
- if (schemaVersion === 13) {
1492
+ if (schemaVersion === 14) {
1493
+ return V14_STORES;
1494
+ }
1495
+ else if (schemaVersion === 13) {
1448
1496
  return V13_STORES;
1449
1497
  }
1450
1498
  else if (schemaVersion === 12) {
@@ -2263,9 +2311,7 @@ class SimpleDbStore {
2263
2311
  cursor.continue(controller.skipToKey);
2264
2312
  }
2265
2313
  };
2266
- }).next(() => {
2267
- return PersistencePromise.waitFor(results);
2268
- });
2314
+ }).next(() => PersistencePromise.waitFor(results));
2269
2315
  }
2270
2316
  options(indexOrRange, range) {
2271
2317
  let indexName = undefined;
@@ -2655,6 +2701,9 @@ class SnapshotVersion {
2655
2701
  static min() {
2656
2702
  return new SnapshotVersion(new Timestamp(0, 0));
2657
2703
  }
2704
+ static max() {
2705
+ return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
2706
+ }
2658
2707
  compareTo(other) {
2659
2708
  return this.timestamp._compareTo(other.timestamp);
2660
2709
  }
@@ -4171,6 +4220,17 @@ function fieldIndexGetArraySegment(fieldIndex) {
4171
4220
  function fieldIndexGetDirectionalSegments(fieldIndex) {
4172
4221
  return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4173
4222
  }
4223
+ /**
4224
+ * Returns the order of the document key component for the given index.
4225
+ *
4226
+ * PORTING NOTE: This is only used in the Web IndexedDb implementation.
4227
+ */
4228
+ function fieldIndexGetKeyOrder(fieldIndex) {
4229
+ const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
4230
+ return directionalSegments.length === 0
4231
+ ? 0 /* ASCENDING */
4232
+ : directionalSegments[directionalSegments.length - 1].kind;
4233
+ }
4174
4234
  /** Returns a debug representation of the field index */
4175
4235
  function fieldIndexToString(fieldIndex) {
4176
4236
  return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
@@ -4206,6 +4266,28 @@ class IndexState {
4206
4266
  return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
4207
4267
  }
4208
4268
  }
4269
+ /**
4270
+ * Creates an offset that matches all documents with a read time higher than
4271
+ * `readTime`.
4272
+ */
4273
+ function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
4274
+ // We want to create an offset that matches all documents with a read time
4275
+ // greater than the provided read time. To do so, we technically need to
4276
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
4277
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
4278
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
4279
+ // all valid document IDs.
4280
+ const successorSeconds = readTime.toTimestamp().seconds;
4281
+ const successorNanos = readTime.toTimestamp().nanoseconds + 1;
4282
+ const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
4283
+ ? new Timestamp(successorSeconds + 1, 0)
4284
+ : new Timestamp(successorSeconds, successorNanos));
4285
+ return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
4286
+ }
4287
+ /** Creates a new offset based on the provided document. */
4288
+ function newIndexOffsetFromDocument(document) {
4289
+ return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
4290
+ }
4209
4291
  /**
4210
4292
  * Stores the latest read time, document and batch ID that were processed for an
4211
4293
  * index.
@@ -4230,10 +4312,25 @@ class IndexOffset {
4230
4312
  this.documentKey = documentKey;
4231
4313
  this.largestBatchId = largestBatchId;
4232
4314
  }
4233
- /** The state of an index that has not yet been backfilled. */
4315
+ /** Returns an offset that sorts before all regular offsets. */
4234
4316
  static min() {
4235
4317
  return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4236
4318
  }
4319
+ /** Returns an offset that sorts after all regular offsets. */
4320
+ static max() {
4321
+ return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4322
+ }
4323
+ }
4324
+ function indexOffsetComparator(left, right) {
4325
+ let cmp = left.readTime.compareTo(right.readTime);
4326
+ if (cmp !== 0) {
4327
+ return cmp;
4328
+ }
4329
+ cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
4330
+ if (cmp !== 0) {
4331
+ return cmp;
4332
+ }
4333
+ return primitiveComparator(left.largestBatchId, right.largestBatchId);
4237
4334
  }
4238
4335
 
4239
4336
  /**
@@ -4434,58 +4531,15 @@ function targetGetLowerBound(target, fieldIndex) {
4434
4531
  // For each segment, retrieve a lower bound if there is a suitable filter or
4435
4532
  // startAt.
4436
4533
  for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4437
- let segmentValue = undefined;
4438
- let segmentInclusive = true;
4439
- // Process all filters to find a value for the current field segment
4440
- for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4441
- let filterValue = undefined;
4442
- let filterInclusive = true;
4443
- switch (fieldFilter.op) {
4444
- case "<" /* LESS_THAN */:
4445
- case "<=" /* LESS_THAN_OR_EQUAL */:
4446
- filterValue = valuesGetLowerBound(fieldFilter.value);
4447
- break;
4448
- case "==" /* EQUAL */:
4449
- case "in" /* IN */:
4450
- case ">=" /* GREATER_THAN_OR_EQUAL */:
4451
- filterValue = fieldFilter.value;
4452
- break;
4453
- case ">" /* GREATER_THAN */:
4454
- filterValue = fieldFilter.value;
4455
- filterInclusive = false;
4456
- break;
4457
- case "!=" /* NOT_EQUAL */:
4458
- case "not-in" /* NOT_IN */:
4459
- filterValue = MIN_VALUE;
4460
- break;
4461
- // Remaining filters cannot be used as lower bounds.
4462
- }
4463
- if (valuesMax(segmentValue, filterValue) === filterValue) {
4464
- segmentValue = filterValue;
4465
- segmentInclusive = filterInclusive;
4466
- }
4467
- }
4468
- // If there is a startAt bound, compare the values against the existing
4469
- // boundary to see if we can narrow the scope.
4470
- if (target.startAt !== null) {
4471
- for (let i = 0; i < target.orderBy.length; ++i) {
4472
- const orderBy = target.orderBy[i];
4473
- if (orderBy.field.isEqual(segment.fieldPath)) {
4474
- const cursorValue = target.startAt.position[i];
4475
- if (valuesMax(segmentValue, cursorValue) === cursorValue) {
4476
- segmentValue = cursorValue;
4477
- segmentInclusive = target.startAt.inclusive;
4478
- }
4479
- break;
4480
- }
4481
- }
4482
- }
4483
- if (segmentValue === undefined) {
4534
+ const segmentBound = segment.kind === 0 /* ASCENDING */
4535
+ ? targetGetLowerBoundForField(target, segment.fieldPath, target.startAt)
4536
+ : targetGetUpperBoundForField(target, segment.fieldPath, target.startAt);
4537
+ if (!segmentBound.value) {
4484
4538
  // No lower bound exists
4485
4539
  return null;
4486
4540
  }
4487
- values.push(segmentValue);
4488
- inclusive && (inclusive = segmentInclusive);
4541
+ values.push(segmentBound.value);
4542
+ inclusive && (inclusive = segmentBound.inclusive);
4489
4543
  }
4490
4544
  return new Bound(values, inclusive);
4491
4545
  }
@@ -4500,62 +4554,125 @@ function targetGetUpperBound(target, fieldIndex) {
4500
4554
  // For each segment, retrieve an upper bound if there is a suitable filter or
4501
4555
  // endAt.
4502
4556
  for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4503
- let segmentValue = undefined;
4504
- let segmentInclusive = true;
4505
- // Process all filters to find a value for the current field segment
4506
- for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4507
- let filterValue = undefined;
4508
- let filterInclusive = true;
4509
- switch (fieldFilter.op) {
4510
- case ">=" /* GREATER_THAN_OR_EQUAL */:
4511
- case ">" /* GREATER_THAN */:
4512
- filterValue = valuesGetUpperBound(fieldFilter.value);
4513
- filterInclusive = false;
4514
- break;
4515
- case "==" /* EQUAL */:
4516
- case "in" /* IN */:
4517
- case "<=" /* LESS_THAN_OR_EQUAL */:
4518
- filterValue = fieldFilter.value;
4519
- break;
4520
- case "<" /* LESS_THAN */:
4521
- filterValue = fieldFilter.value;
4522
- filterInclusive = false;
4523
- break;
4524
- case "!=" /* NOT_EQUAL */:
4525
- case "not-in" /* NOT_IN */:
4526
- filterValue = MAX_VALUE;
4527
- break;
4528
- // Remaining filters cannot be used as upper bounds.
4529
- }
4530
- if (valuesMin(segmentValue, filterValue) === filterValue) {
4531
- segmentValue = filterValue;
4532
- segmentInclusive = filterInclusive;
4533
- }
4534
- }
4535
- // If there is a endAt bound, compare the values against the existing
4536
- // boundary to see if we can narrow the scope.
4537
- if (target.endAt !== null) {
4538
- for (let i = 0; i < target.orderBy.length; ++i) {
4539
- const orderBy = target.orderBy[i];
4540
- if (orderBy.field.isEqual(segment.fieldPath)) {
4541
- const cursorValue = target.endAt.position[i];
4542
- if (valuesMin(segmentValue, cursorValue) === cursorValue) {
4543
- segmentValue = cursorValue;
4544
- segmentInclusive = target.endAt.inclusive;
4545
- }
4546
- break;
4547
- }
4548
- }
4549
- }
4550
- if (segmentValue === undefined) {
4557
+ const segmentBound = segment.kind === 0 /* ASCENDING */
4558
+ ? targetGetUpperBoundForField(target, segment.fieldPath, target.endAt)
4559
+ : targetGetLowerBoundForField(target, segment.fieldPath, target.endAt);
4560
+ if (!segmentBound.value) {
4551
4561
  // No upper bound exists
4552
4562
  return null;
4553
4563
  }
4554
- values.push(segmentValue);
4555
- inclusive && (inclusive = segmentInclusive);
4564
+ values.push(segmentBound.value);
4565
+ inclusive && (inclusive = segmentBound.inclusive);
4556
4566
  }
4557
4567
  return new Bound(values, inclusive);
4558
4568
  }
4569
+ /**
4570
+ * Returns the value to use as the lower bound for ascending index segment at
4571
+ * the provided `fieldPath` (or the upper bound for an descending segment).
4572
+ */
4573
+ function targetGetLowerBoundForField(target, fieldPath, bound) {
4574
+ let value = undefined;
4575
+ let inclusive = true;
4576
+ // Process all filters to find a value for the current field segment
4577
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
4578
+ let filterValue = undefined;
4579
+ let filterInclusive = true;
4580
+ switch (fieldFilter.op) {
4581
+ case "<" /* LESS_THAN */:
4582
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4583
+ filterValue = valuesGetLowerBound(fieldFilter.value);
4584
+ break;
4585
+ case "==" /* EQUAL */:
4586
+ case "in" /* IN */:
4587
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4588
+ filterValue = fieldFilter.value;
4589
+ break;
4590
+ case ">" /* GREATER_THAN */:
4591
+ filterValue = fieldFilter.value;
4592
+ filterInclusive = false;
4593
+ break;
4594
+ case "!=" /* NOT_EQUAL */:
4595
+ case "not-in" /* NOT_IN */:
4596
+ filterValue = MIN_VALUE;
4597
+ break;
4598
+ // Remaining filters cannot be used as lower bounds.
4599
+ }
4600
+ if (valuesMax(value, filterValue) === filterValue) {
4601
+ value = filterValue;
4602
+ inclusive = filterInclusive;
4603
+ }
4604
+ }
4605
+ // If there is an additional bound, compare the values against the existing
4606
+ // range to see if we can narrow the scope.
4607
+ if (bound !== null) {
4608
+ for (let i = 0; i < target.orderBy.length; ++i) {
4609
+ const orderBy = target.orderBy[i];
4610
+ if (orderBy.field.isEqual(fieldPath)) {
4611
+ const cursorValue = bound.position[i];
4612
+ if (valuesMax(value, cursorValue) === cursorValue) {
4613
+ value = cursorValue;
4614
+ inclusive = bound.inclusive;
4615
+ }
4616
+ break;
4617
+ }
4618
+ }
4619
+ }
4620
+ return { value, inclusive };
4621
+ }
4622
+ /**
4623
+ * Returns the value to use as the upper bound for ascending index segment at
4624
+ * the provided `fieldPath` (or the lower bound for an descending segment).
4625
+ */
4626
+ function targetGetUpperBoundForField(target, fieldPath, bound) {
4627
+ let value = undefined;
4628
+ let inclusive = true;
4629
+ // Process all filters to find a value for the current field segment
4630
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
4631
+ let filterValue = undefined;
4632
+ let filterInclusive = true;
4633
+ switch (fieldFilter.op) {
4634
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4635
+ case ">" /* GREATER_THAN */:
4636
+ filterValue = valuesGetUpperBound(fieldFilter.value);
4637
+ filterInclusive = false;
4638
+ break;
4639
+ case "==" /* EQUAL */:
4640
+ case "in" /* IN */:
4641
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4642
+ filterValue = fieldFilter.value;
4643
+ break;
4644
+ case "<" /* LESS_THAN */:
4645
+ filterValue = fieldFilter.value;
4646
+ filterInclusive = false;
4647
+ break;
4648
+ case "!=" /* NOT_EQUAL */:
4649
+ case "not-in" /* NOT_IN */:
4650
+ filterValue = MAX_VALUE;
4651
+ break;
4652
+ // Remaining filters cannot be used as upper bounds.
4653
+ }
4654
+ if (valuesMin(value, filterValue) === filterValue) {
4655
+ value = filterValue;
4656
+ inclusive = filterInclusive;
4657
+ }
4658
+ }
4659
+ // If there is an additional bound, compare the values against the existing
4660
+ // range to see if we can narrow the scope.
4661
+ if (bound !== null) {
4662
+ for (let i = 0; i < target.orderBy.length; ++i) {
4663
+ const orderBy = target.orderBy[i];
4664
+ if (orderBy.field.isEqual(fieldPath)) {
4665
+ const cursorValue = bound.position[i];
4666
+ if (valuesMin(value, cursorValue) === cursorValue) {
4667
+ value = cursorValue;
4668
+ inclusive = bound.inclusive;
4669
+ }
4670
+ break;
4671
+ }
4672
+ }
4673
+ }
4674
+ return { value, inclusive };
4675
+ }
4559
4676
  class Filter {
4560
4677
  }
4561
4678
  class FieldFilter extends Filter {
@@ -5126,6 +5243,18 @@ function queryMatchesBounds(query, doc) {
5126
5243
  }
5127
5244
  return true;
5128
5245
  }
5246
+ /**
5247
+ * Returns the collection group that this query targets.
5248
+ *
5249
+ * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
5250
+ * synchronization for query results.
5251
+ */
5252
+ function queryCollectionGroup(query) {
5253
+ return (query.collectionGroup ||
5254
+ (query.path.length % 2 === 1
5255
+ ? query.path.lastSegment()
5256
+ : query.path.get(query.path.length - 2)));
5257
+ }
5129
5258
  /**
5130
5259
  * Returns a new comparator function that can be used to compare two documents
5131
5260
  * based on the Query's ordering constraint.
@@ -8659,40 +8788,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
8659
8788
  }
8660
8789
  /** Encodes a document for storage locally. */
8661
8790
  function toDbRemoteDocument(localSerializer, document) {
8662
- const parentPath = document.key.path.popLast().toArray();
8663
- const readTime = toDbTimestampKey(document.readTime);
8791
+ const key = document.key;
8792
+ const remoteDoc = {
8793
+ prefixPath: key.getCollectionPath().popLast().toArray(),
8794
+ collectionGroup: key.collectionGroup,
8795
+ documentId: key.path.lastSegment(),
8796
+ readTime: toDbTimestampKey(document.readTime),
8797
+ hasCommittedMutations: document.hasCommittedMutations
8798
+ };
8664
8799
  if (document.isFoundDocument()) {
8665
- const doc = toDocument(localSerializer.remoteSerializer, document);
8666
- const hasCommittedMutations = document.hasCommittedMutations;
8667
- return {
8668
- document: doc,
8669
- hasCommittedMutations,
8670
- readTime,
8671
- parentPath
8672
- };
8800
+ remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
8673
8801
  }
8674
8802
  else if (document.isNoDocument()) {
8675
- const path = document.key.path.toArray();
8676
- const hasCommittedMutations = document.hasCommittedMutations;
8677
- return {
8678
- noDocument: { path, readTime: toDbTimestamp(document.version) },
8679
- hasCommittedMutations,
8680
- readTime,
8681
- parentPath
8803
+ remoteDoc.noDocument = {
8804
+ path: key.path.toArray(),
8805
+ readTime: toDbTimestamp(document.version)
8682
8806
  };
8683
8807
  }
8684
8808
  else if (document.isUnknownDocument()) {
8685
- const path = document.key.path.toArray();
8686
- return {
8687
- unknownDocument: { path, version: toDbTimestamp(document.version) },
8688
- hasCommittedMutations: true,
8689
- readTime,
8690
- parentPath
8809
+ remoteDoc.unknownDocument = {
8810
+ path: key.path.toArray(),
8811
+ version: toDbTimestamp(document.version)
8691
8812
  };
8692
8813
  }
8693
8814
  else {
8694
8815
  return fail();
8695
8816
  }
8817
+ return remoteDoc;
8696
8818
  }
8697
8819
  function toDbTimestampKey(snapshotVersion) {
8698
8820
  const timestamp = snapshotVersion.toTimestamp();
@@ -9954,8 +10076,9 @@ const EMPTY_VALUE = new Uint8Array(0);
9954
10076
  * data as it supports multi-tab access.
9955
10077
  */
9956
10078
  class IndexedDbIndexManager {
9957
- constructor(user) {
10079
+ constructor(user, databaseId) {
9958
10080
  this.user = user;
10081
+ this.databaseId = databaseId;
9959
10082
  /**
9960
10083
  * An in-memory copy of the index entries we've already written since the SDK
9961
10084
  * launched. Used to avoid re-writing the same entry repeatedly.
@@ -10051,7 +10174,8 @@ class IndexedDbIndexManager {
10051
10174
  return PersistencePromise.resolve(null);
10052
10175
  }
10053
10176
  else {
10054
- let result = documentKeySet();
10177
+ let existingKeys = documentKeySet();
10178
+ const result = [];
10055
10179
  return PersistencePromise.forEach(indexes, (index, subTarget) => {
10056
10180
  logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10057
10181
  const arrayValues = targetGetArrayValues(subTarget, index);
@@ -10067,7 +10191,11 @@ class IndexedDbIndexManager {
10067
10191
  .loadFirst(indexRange, target.limit)
10068
10192
  .next(entries => {
10069
10193
  entries.forEach(entry => {
10070
- result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10194
+ const documentKey = DocumentKey.fromSegments(entry.documentKey);
10195
+ if (!existingKeys.has(documentKey)) {
10196
+ existingKeys = existingKeys.add(documentKey);
10197
+ result.push(documentKey);
10198
+ }
10071
10199
  });
10072
10200
  });
10073
10201
  });
@@ -10166,6 +10294,15 @@ class IndexedDbIndexManager {
10166
10294
  FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
10167
10295
  return encoder.encodedBytes();
10168
10296
  }
10297
+ /**
10298
+ * Returns an encoded form of the document key that sorts based on the key
10299
+ * ordering of the field index.
10300
+ */
10301
+ encodeDirectionalKey(fieldIndex, documentKey) {
10302
+ const encoder = new IndexByteEncoder();
10303
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
10304
+ return encoder.encodedBytes();
10305
+ }
10169
10306
  /**
10170
10307
  * Encodes the given field values according to the specification in `target`.
10171
10308
  * For IN queries, a list of possible values is returned.
@@ -10289,7 +10426,7 @@ class IndexedDbIndexManager {
10289
10426
  return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
10290
10427
  const newEntries = this.computeIndexEntries(doc, fieldIndex);
10291
10428
  if (!existingEntries.isEqual(newEntries)) {
10292
- return this.updateEntries(transaction, doc, existingEntries, newEntries);
10429
+ return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
10293
10430
  }
10294
10431
  return PersistencePromise.resolve();
10295
10432
  });
@@ -10297,24 +10434,26 @@ class IndexedDbIndexManager {
10297
10434
  });
10298
10435
  });
10299
10436
  }
10300
- addIndexEntry(transaction, document, indexEntry) {
10437
+ addIndexEntry(transaction, document, fieldIndex, indexEntry) {
10301
10438
  const indexEntries = indexEntriesStore(transaction);
10302
10439
  return indexEntries.put({
10303
10440
  indexId: indexEntry.indexId,
10304
10441
  uid: this.uid,
10305
10442
  arrayValue: indexEntry.arrayValue,
10306
10443
  directionalValue: indexEntry.directionalValue,
10307
- documentKey: encodeResourcePath(document.key.path)
10444
+ orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
10445
+ documentKey: document.key.path.toArray()
10308
10446
  });
10309
10447
  }
10310
- deleteIndexEntry(transaction, document, indexEntry) {
10448
+ deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
10311
10449
  const indexEntries = indexEntriesStore(transaction);
10312
10450
  return indexEntries.delete([
10313
10451
  indexEntry.indexId,
10314
10452
  this.uid,
10315
10453
  indexEntry.arrayValue,
10316
10454
  indexEntry.directionalValue,
10317
- encodeResourcePath(document.key.path)
10455
+ this.encodeDirectionalKey(fieldIndex, document.key),
10456
+ document.key.path.toArray()
10318
10457
  ]);
10319
10458
  }
10320
10459
  getExistingIndexEntries(transaction, documentKey, fieldIndex) {
@@ -10326,7 +10465,7 @@ class IndexedDbIndexManager {
10326
10465
  range: IDBKeyRange.only([
10327
10466
  fieldIndex.indexId,
10328
10467
  this.uid,
10329
- encodeResourcePath(documentKey.path)
10468
+ this.encodeDirectionalKey(fieldIndex, documentKey)
10330
10469
  ])
10331
10470
  }, (_, entry) => {
10332
10471
  results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
@@ -10359,15 +10498,15 @@ class IndexedDbIndexManager {
10359
10498
  * that are no longer referenced in `newEntries` and adding all newly added
10360
10499
  * entries.
10361
10500
  */
10362
- updateEntries(transaction, document, existingEntries, newEntries) {
10501
+ updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
10363
10502
  logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
10364
10503
  const promises = [];
10365
10504
  diffSortedSets(existingEntries, newEntries, indexEntryComparator,
10366
10505
  /* onAdd= */ entry => {
10367
- promises.push(this.addIndexEntry(transaction, document, entry));
10506
+ promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
10368
10507
  },
10369
10508
  /* onRemove= */ entry => {
10370
- promises.push(this.deleteIndexEntry(transaction, document, entry));
10509
+ promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
10371
10510
  });
10372
10511
  return PersistencePromise.waitFor(promises);
10373
10512
  }
@@ -10424,13 +10563,15 @@ class IndexedDbIndexManager {
10424
10563
  this.uid,
10425
10564
  bounds[i].arrayValue,
10426
10565
  bounds[i].directionalValue,
10427
- ''
10566
+ EMPTY_VALUE,
10567
+ []
10428
10568
  ], [
10429
10569
  bounds[i + 1].indexId,
10430
10570
  this.uid,
10431
10571
  bounds[i + 1].arrayValue,
10432
10572
  bounds[i + 1].directionalValue,
10433
- ''
10573
+ EMPTY_VALUE,
10574
+ []
10434
10575
  ]));
10435
10576
  }
10436
10577
  return ranges;
@@ -11844,7 +11985,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11844
11985
  */
11845
11986
  addEntry(transaction, key, doc) {
11846
11987
  const documentStore = remoteDocumentsStore(transaction);
11847
- return documentStore.put(dbKey(key), doc);
11988
+ return documentStore.put(doc);
11848
11989
  }
11849
11990
  /**
11850
11991
  * Removes a document from the cache.
@@ -11852,10 +11993,9 @@ class IndexedDbRemoteDocumentCacheImpl {
11852
11993
  * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
11853
11994
  * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
11854
11995
  */
11855
- removeEntry(transaction, documentKey) {
11996
+ removeEntry(transaction, documentKey, readTime) {
11856
11997
  const store = remoteDocumentsStore(transaction);
11857
- const key = dbKey(documentKey);
11858
- return store.delete(key);
11998
+ return store.delete(dbReadTimeKey(documentKey, readTime));
11859
11999
  }
11860
12000
  /**
11861
12001
  * Updates the current cache size.
@@ -11870,11 +12010,15 @@ class IndexedDbRemoteDocumentCacheImpl {
11870
12010
  });
11871
12011
  }
11872
12012
  getEntry(transaction, documentKey) {
12013
+ let doc = MutableDocument.newInvalidDocument(documentKey);
11873
12014
  return remoteDocumentsStore(transaction)
11874
- .get(dbKey(documentKey))
11875
- .next(dbRemoteDoc => {
11876
- return this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11877
- });
12015
+ .iterate({
12016
+ index: DbRemoteDocumentDocumentKeyIndex,
12017
+ range: IDBKeyRange.only(dbKey(documentKey))
12018
+ }, (_, dbRemoteDoc) => {
12019
+ doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
12020
+ })
12021
+ .next(() => doc);
11878
12022
  }
11879
12023
  /**
11880
12024
  * Looks up an entry in the cache.
@@ -11883,15 +12027,21 @@ class IndexedDbRemoteDocumentCacheImpl {
11883
12027
  * @returns The cached document entry and its size.
11884
12028
  */
11885
12029
  getSizedEntry(transaction, documentKey) {
12030
+ let result = {
12031
+ size: 0,
12032
+ document: MutableDocument.newInvalidDocument(documentKey)
12033
+ };
11886
12034
  return remoteDocumentsStore(transaction)
11887
- .get(dbKey(documentKey))
11888
- .next(dbRemoteDoc => {
11889
- const doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11890
- return {
11891
- document: doc,
12035
+ .iterate({
12036
+ index: DbRemoteDocumentDocumentKeyIndex,
12037
+ range: IDBKeyRange.only(dbKey(documentKey))
12038
+ }, (_, dbRemoteDoc) => {
12039
+ result = {
12040
+ document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
11892
12041
  size: dbDocumentSize(dbRemoteDoc)
11893
12042
  };
11894
- });
12043
+ })
12044
+ .next(() => result);
11895
12045
  }
11896
12046
  getEntries(transaction, documentKeys) {
11897
12047
  let results = mutableDocumentMap();
@@ -11922,14 +12072,20 @@ class IndexedDbRemoteDocumentCacheImpl {
11922
12072
  if (documentKeys.isEmpty()) {
11923
12073
  return PersistencePromise.resolve();
11924
12074
  }
11925
- const range = IDBKeyRange.bound(documentKeys.first().path.toArray(), documentKeys.last().path.toArray());
11926
- const keyIter = documentKeys.getIterator();
12075
+ let sortedKeys = new SortedSet(dbKeyComparator);
12076
+ documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
12077
+ const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
12078
+ const keyIter = sortedKeys.getIterator();
11927
12079
  let nextKey = keyIter.getNext();
11928
12080
  return remoteDocumentsStore(transaction)
11929
- .iterate({ range }, (potentialKeyRaw, dbRemoteDoc, control) => {
11930
- const potentialKey = DocumentKey.fromSegments(potentialKeyRaw);
12081
+ .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
12082
+ const potentialKey = DocumentKey.fromSegments([
12083
+ ...dbRemoteDoc.prefixPath,
12084
+ dbRemoteDoc.collectionGroup,
12085
+ dbRemoteDoc.documentId
12086
+ ]);
11931
12087
  // Go through keys not found in cache.
11932
- while (nextKey && DocumentKey.comparator(nextKey, potentialKey) < 0) {
12088
+ while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
11933
12089
  callback(nextKey, null);
11934
12090
  nextKey = keyIter.getNext();
11935
12091
  }
@@ -11940,7 +12096,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11940
12096
  }
11941
12097
  // Skip to the next key (if there is one).
11942
12098
  if (nextKey) {
11943
- control.skip(nextKey.path.toArray());
12099
+ control.skip(dbKey(nextKey));
11944
12100
  }
11945
12101
  else {
11946
12102
  control.done();
@@ -11955,41 +12111,44 @@ class IndexedDbRemoteDocumentCacheImpl {
11955
12111
  }
11956
12112
  });
11957
12113
  }
11958
- getAll(transaction, collection, sinceReadTime) {
11959
- let results = mutableDocumentMap();
11960
- const immediateChildrenPathLength = collection.length + 1;
11961
- const iterationOptions = {};
11962
- if (sinceReadTime.isEqual(SnapshotVersion.min())) {
11963
- // Documents are ordered by key, so we can use a prefix scan to narrow
11964
- // down the documents we need to match the query against.
11965
- const startKey = collection.toArray();
11966
- iterationOptions.range = IDBKeyRange.lowerBound(startKey);
11967
- }
11968
- else {
11969
- // Execute an index-free query and filter by read time. This is safe
11970
- // since all document changes to queries that have a
11971
- // lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
11972
- const collectionKey = collection.toArray();
11973
- const readTimeKey = toDbTimestampKey(sinceReadTime);
11974
- iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
11975
- /* open= */ true);
11976
- iterationOptions.index = DbRemoteDocumentCollectionReadTimeIndex;
11977
- }
12114
+ getAllFromCollection(transaction, collection, offset) {
12115
+ const startKey = [
12116
+ collection.popLast().toArray(),
12117
+ collection.lastSegment(),
12118
+ toDbTimestampKey(offset.readTime),
12119
+ offset.documentKey.path.isEmpty()
12120
+ ? ''
12121
+ : offset.documentKey.path.lastSegment()
12122
+ ];
12123
+ const endKey = [
12124
+ collection.popLast().toArray(),
12125
+ collection.lastSegment(),
12126
+ [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
12127
+ ''
12128
+ ];
11978
12129
  return remoteDocumentsStore(transaction)
11979
- .iterate(iterationOptions, (key, dbRemoteDoc, control) => {
11980
- // The query is actually returning any path that starts with the query
11981
- // path prefix which may include documents in subcollections. For
11982
- // example, a query on 'rooms' will return rooms/abc/messages/xyx but we
11983
- // shouldn't match it. Fix this by discarding rows with document keys
11984
- // more than one segment longer than the query path.
11985
- if (key.length !== immediateChildrenPathLength) {
11986
- return;
11987
- }
11988
- const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
11989
- if (collection.isPrefixOf(document.key.path)) {
12130
+ .loadAll(IDBKeyRange.bound(startKey, endKey, true))
12131
+ .next(dbRemoteDocs => {
12132
+ let results = mutableDocumentMap();
12133
+ for (const dbRemoteDoc of dbRemoteDocs) {
12134
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
11990
12135
  results = results.insert(document.key, document);
11991
12136
  }
11992
- else {
12137
+ return results;
12138
+ });
12139
+ }
12140
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
12141
+ let results = mutableDocumentMap();
12142
+ const startKey = dbCollectionGroupKey(collectionGroup, offset);
12143
+ const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
12144
+ return remoteDocumentsStore(transaction)
12145
+ .iterate({
12146
+ index: DbRemoteDocumentCollectionGroupIndex,
12147
+ range: IDBKeyRange.bound(startKey, endKey, true)
12148
+ }, (_, dbRemoteDoc, control) => {
12149
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
12150
+ results = results.insert(document.key, document);
12151
+ if (results.size === limit) {
11993
12152
  control.done();
11994
12153
  }
11995
12154
  })
@@ -12033,50 +12192,6 @@ class IndexedDbRemoteDocumentCacheImpl {
12033
12192
  function newIndexedDbRemoteDocumentCache(serializer) {
12034
12193
  return new IndexedDbRemoteDocumentCacheImpl(serializer);
12035
12194
  }
12036
- /**
12037
- * Returns the set of documents that have changed since the specified read
12038
- * time.
12039
- */
12040
- // PORTING NOTE: This is only used for multi-tab synchronization.
12041
- function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
12042
- const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
12043
- let changedDocs = mutableDocumentMap();
12044
- let lastReadTime = toDbTimestampKey(sinceReadTime);
12045
- const documentsStore = remoteDocumentsStore(transaction);
12046
- const range = IDBKeyRange.lowerBound(lastReadTime, true);
12047
- return documentsStore
12048
- .iterate({ index: DbRemoteDocumentReadTimeIndex, range }, (_, dbRemoteDoc) => {
12049
- // Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
12050
- // the documents directly since we want to keep sentinel deletes.
12051
- const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
12052
- changedDocs = changedDocs.insert(doc.key, doc);
12053
- lastReadTime = dbRemoteDoc.readTime;
12054
- })
12055
- .next(() => {
12056
- return {
12057
- changedDocs,
12058
- readTime: fromDbTimestampKey(lastReadTime)
12059
- };
12060
- });
12061
- }
12062
- /**
12063
- * Returns the read time of the most recently read document in the cache, or
12064
- * SnapshotVersion.min() if not available.
12065
- */
12066
- // PORTING NOTE: This is only used for multi-tab synchronization.
12067
- function remoteDocumentCacheGetLastReadTime(transaction) {
12068
- const documentsStore = remoteDocumentsStore(transaction);
12069
- // If there are no existing entries, we return SnapshotVersion.min().
12070
- let readTime = SnapshotVersion.min();
12071
- return documentsStore
12072
- .iterate({ index: DbRemoteDocumentReadTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
12073
- if (dbRemoteDoc.readTime) {
12074
- readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
12075
- }
12076
- control.done();
12077
- })
12078
- .next(() => readTime);
12079
- }
12080
12195
  /**
12081
12196
  * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
12082
12197
  *
@@ -12094,24 +12209,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12094
12209
  super();
12095
12210
  this.documentCache = documentCache;
12096
12211
  this.trackRemovals = trackRemovals;
12097
- // A map of document sizes prior to applying the changes in this buffer.
12098
- this.documentSizes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12212
+ // A map of document sizes and read times prior to applying the changes in
12213
+ // this buffer.
12214
+ this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12099
12215
  }
12100
12216
  applyChanges(transaction) {
12101
12217
  const promises = [];
12102
12218
  let sizeDelta = 0;
12103
12219
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
12104
12220
  this.changes.forEach((key, documentChange) => {
12105
- const previousSize = this.documentSizes.get(key);
12221
+ const previousDoc = this.documentStates.get(key);
12222
+ promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
12106
12223
  if (documentChange.isValidDocument()) {
12107
12224
  const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
12108
12225
  collectionParents = collectionParents.add(key.path.popLast());
12109
12226
  const size = dbDocumentSize(doc);
12110
- sizeDelta += size - previousSize;
12227
+ sizeDelta += size - previousDoc.size;
12111
12228
  promises.push(this.documentCache.addEntry(transaction, key, doc));
12112
12229
  }
12113
12230
  else {
12114
- sizeDelta -= previousSize;
12231
+ sizeDelta -= previousDoc.size;
12115
12232
  if (this.trackRemovals) {
12116
12233
  // In order to track removals, we store a "sentinel delete" in the
12117
12234
  // RemoteDocumentCache. This entry is represented by a NoDocument
@@ -12120,9 +12237,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12120
12237
  const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
12121
12238
  promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
12122
12239
  }
12123
- else {
12124
- promises.push(this.documentCache.removeEntry(transaction, key));
12125
- }
12126
12240
  }
12127
12241
  });
12128
12242
  collectionParents.forEach(parent => {
@@ -12136,7 +12250,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12136
12250
  return this.documentCache
12137
12251
  .getSizedEntry(transaction, documentKey)
12138
12252
  .next(getResult => {
12139
- this.documentSizes.set(documentKey, getResult.size);
12253
+ this.documentStates.set(documentKey, {
12254
+ size: getResult.size,
12255
+ readTime: getResult.document.readTime
12256
+ });
12140
12257
  return getResult.document;
12141
12258
  });
12142
12259
  }
@@ -12150,7 +12267,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12150
12267
  // keys to `DocumentSizeEntry`s. This is to allow returning the
12151
12268
  // `MutableDocumentMap` directly, without a conversion.
12152
12269
  sizeMap.forEach((documentKey, size) => {
12153
- this.documentSizes.set(documentKey, size);
12270
+ this.documentStates.set(documentKey, {
12271
+ size,
12272
+ readTime: documents.get(documentKey).readTime
12273
+ });
12154
12274
  });
12155
12275
  return documents;
12156
12276
  });
@@ -12165,8 +12285,71 @@ function documentGlobalStore(txn) {
12165
12285
  function remoteDocumentsStore(txn) {
12166
12286
  return getStore(txn, DbRemoteDocumentStore);
12167
12287
  }
12168
- function dbKey(docKey) {
12169
- return docKey.path.toArray();
12288
+ /**
12289
+ * Returns a key that can be used for document lookups on the
12290
+ * `DbRemoteDocumentDocumentKeyIndex` index.
12291
+ */
12292
+ function dbKey(documentKey) {
12293
+ const path = documentKey.path.toArray();
12294
+ return [
12295
+ /* prefix path */ path.slice(0, path.length - 2),
12296
+ /* collection id */ path[path.length - 2],
12297
+ /* document id */ path[path.length - 1]
12298
+ ];
12299
+ }
12300
+ /**
12301
+ * Returns a key that can be used for document lookups via the primary key of
12302
+ * the DbRemoteDocument object store.
12303
+ */
12304
+ function dbReadTimeKey(documentKey, readTime) {
12305
+ const path = documentKey.path.toArray();
12306
+ return [
12307
+ /* prefix path */ path.slice(0, path.length - 2),
12308
+ /* collection id */ path[path.length - 2],
12309
+ toDbTimestampKey(readTime),
12310
+ /* document id */ path[path.length - 1]
12311
+ ];
12312
+ }
12313
+ /**
12314
+ * Returns a key that can be used for document lookups on the
12315
+ * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
12316
+ */
12317
+ function dbCollectionGroupKey(collectionGroup, offset) {
12318
+ const path = offset.documentKey.path.toArray();
12319
+ return [
12320
+ /* collection id */ collectionGroup,
12321
+ toDbTimestampKey(offset.readTime),
12322
+ /* prefix path */ path.slice(0, path.length - 2),
12323
+ /* document id */ path.length > 0 ? path[path.length - 1] : ''
12324
+ ];
12325
+ }
12326
+ /**
12327
+ * Comparator that compares document keys according to the primary key sorting
12328
+ * used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
12329
+ * and then document ID).
12330
+ *
12331
+ * Visible for testing.
12332
+ */
12333
+ function dbKeyComparator(l, r) {
12334
+ const left = l.path.toArray();
12335
+ const right = r.path.toArray();
12336
+ // The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
12337
+ let cmp = 0;
12338
+ for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
12339
+ cmp = primitiveComparator(left[i], right[i]);
12340
+ if (cmp) {
12341
+ return cmp;
12342
+ }
12343
+ }
12344
+ cmp = primitiveComparator(left.length, right.length);
12345
+ if (cmp) {
12346
+ return cmp;
12347
+ }
12348
+ cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
12349
+ if (cmp) {
12350
+ return cmp;
12351
+ }
12352
+ return primitiveComparator(left[left.length - 1], right[right.length - 1]);
12170
12353
  }
12171
12354
  /**
12172
12355
  * Schema Version for the Web client:
@@ -12187,9 +12370,11 @@ function dbKey(docKey) {
12187
12370
  * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12188
12371
  * 11. Add bundles and named_queries for bundle support.
12189
12372
  * 12. Add document overlays.
12190
- * 13. Add indexing support.
12373
+ * 13. Rewrite the keys of the remote document cache to allow for efficient
12374
+ * document lookup via `getAll()`.
12375
+ * 14. Add indexing support.
12191
12376
  */
12192
- const SCHEMA_VERSION = 12;
12377
+ const SCHEMA_VERSION = 13;
12193
12378
 
12194
12379
  /**
12195
12380
  * @license
@@ -12225,7 +12410,7 @@ class SchemaConverter {
12225
12410
  createPrimaryClientStore(db);
12226
12411
  createMutationQueue(db);
12227
12412
  createQueryCache(db);
12228
- createRemoteDocumentCache(db);
12413
+ createLegacyRemoteDocumentCache(db);
12229
12414
  }
12230
12415
  // Migration 2 to populate the targetGlobal object no longer needed since
12231
12416
  // migration 3 unconditionally clears it.
@@ -12274,7 +12459,8 @@ class SchemaConverter {
12274
12459
  // to the DbRemoteDocument object store itself. Since the previous change
12275
12460
  // log only contained transient data, we can drop its object store.
12276
12461
  dropRemoteDocumentChangesStore(db);
12277
- createRemoteDocumentReadTimeIndex(txn);
12462
+ // Note: Schema version 9 used to create a read time index for the
12463
+ // RemoteDocumentCache. This is now done with schema version 13.
12278
12464
  });
12279
12465
  }
12280
12466
  if (fromVersion < 10 && toVersion >= 10) {
@@ -12292,6 +12478,12 @@ class SchemaConverter {
12292
12478
  });
12293
12479
  }
12294
12480
  if (fromVersion < 13 && toVersion >= 13) {
12481
+ p = p
12482
+ .next(() => createRemoteDocumentCache(db))
12483
+ .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
12484
+ .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
12485
+ }
12486
+ if (fromVersion < 14 && toVersion >= 14) {
12295
12487
  p = p.next(() => {
12296
12488
  createFieldIndex(db);
12297
12489
  });
@@ -12301,7 +12493,7 @@ class SchemaConverter {
12301
12493
  addDocumentGlobal(txn) {
12302
12494
  let byteSize = 0;
12303
12495
  return txn
12304
- .store(DbRemoteDocumentStore)
12496
+ .store(DbRemoteDocumentStore$1)
12305
12497
  .iterate((_, doc) => {
12306
12498
  byteSize += dbDocumentSize(doc);
12307
12499
  })
@@ -12336,7 +12528,7 @@ class SchemaConverter {
12336
12528
  */
12337
12529
  ensureSequenceNumbers(txn) {
12338
12530
  const documentTargetStore = txn.store(DbTargetDocumentStore);
12339
- const documentsStore = txn.store(DbRemoteDocumentStore);
12531
+ const documentsStore = txn.store(DbRemoteDocumentStore$1);
12340
12532
  const globalTargetStore = txn.store(DbTargetGlobalStore);
12341
12533
  return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
12342
12534
  const writeSentinelKey = (path) => {
@@ -12383,7 +12575,7 @@ class SchemaConverter {
12383
12575
  };
12384
12576
  // Index existing remote documents.
12385
12577
  return txn
12386
- .store(DbRemoteDocumentStore)
12578
+ .store(DbRemoteDocumentStore$1)
12387
12579
  .iterate({ keysOnly: true }, (pathSegments, _) => {
12388
12580
  const path = new ResourcePath(pathSegments);
12389
12581
  return addEntry(path.popLast());
@@ -12406,6 +12598,27 @@ class SchemaConverter {
12406
12598
  return targetStore.put(updatedDbTarget);
12407
12599
  });
12408
12600
  }
12601
+ rewriteRemoteDocumentCache(db, transaction) {
12602
+ const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
12603
+ const writes = [];
12604
+ return legacyRemoteDocumentStore
12605
+ .iterate((_, legacyDocument) => {
12606
+ const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
12607
+ const path = extractKey(legacyDocument).path.toArray();
12608
+ const dbRemoteDocument = {
12609
+ prefixPath: path.slice(0, path.length - 2),
12610
+ collectionGroup: path[path.length - 2],
12611
+ documentId: path[path.length - 1],
12612
+ readTime: legacyDocument.readTime || [0, 0],
12613
+ unknownDocument: legacyDocument.unknownDocument,
12614
+ noDocument: legacyDocument.noDocument,
12615
+ document: legacyDocument.document,
12616
+ hasCommittedMutations: !!legacyDocument.hasCommittedMutations
12617
+ };
12618
+ writes.push(remoteDocumentStore.put(dbRemoteDocument));
12619
+ })
12620
+ .next(() => PersistencePromise.waitFor(writes));
12621
+ }
12409
12622
  }
12410
12623
  function sentinelKey(path) {
12411
12624
  return [0, encodeResourcePath(path)];
@@ -12442,8 +12655,15 @@ function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
12442
12655
  return PersistencePromise.waitFor(writeAll);
12443
12656
  });
12444
12657
  }
12658
+ function createLegacyRemoteDocumentCache(db) {
12659
+ db.createObjectStore(DbRemoteDocumentStore$1);
12660
+ }
12445
12661
  function createRemoteDocumentCache(db) {
12446
- db.createObjectStore(DbRemoteDocumentStore);
12662
+ const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
12663
+ keyPath: DbRemoteDocumentKeyPath
12664
+ });
12665
+ remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
12666
+ remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
12447
12667
  }
12448
12668
  function createDocumentGlobalStore(db) {
12449
12669
  db.createObjectStore(DbRemoteDocumentGlobalStore);
@@ -12485,15 +12705,6 @@ function writeEmptyTargetGlobalEntry(txn) {
12485
12705
  };
12486
12706
  return globalStore.put(DbTargetGlobalKey, metadata);
12487
12707
  }
12488
- /**
12489
- * Creates indices on the RemoteDocuments store used for both multi-tab
12490
- * and Index-Free queries.
12491
- */
12492
- function createRemoteDocumentReadTimeIndex(txn) {
12493
- const remoteDocumentStore = txn.objectStore(DbRemoteDocumentStore);
12494
- remoteDocumentStore.createIndex(DbRemoteDocumentReadTimeIndex, DbRemoteDocumentReadTimeIndexPath, { unique: false });
12495
- remoteDocumentStore.createIndex(DbRemoteDocumentCollectionReadTimeIndex, DbRemoteDocumentCollectionReadTimeIndexPath, { unique: false });
12496
- }
12497
12708
  function createClientMetadataStore(db) {
12498
12709
  db.createObjectStore(DbClientMetadataStore, {
12499
12710
  keyPath: DbClientMetadataKeyPath
@@ -12510,11 +12721,11 @@ function createNamedQueriesStore(db) {
12510
12721
  });
12511
12722
  }
12512
12723
  function createFieldIndex(db) {
12513
- const indexConfiguratioStore = db.createObjectStore(DbIndexConfigurationStore, {
12724
+ const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
12514
12725
  keyPath: DbIndexConfigurationKeyPath,
12515
12726
  autoIncrement: true
12516
12727
  });
12517
- indexConfiguratioStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12728
+ indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12518
12729
  const indexStateStore = db.createObjectStore(DbIndexStateStore, {
12519
12730
  keyPath: DbIndexStateKeyPath
12520
12731
  });
@@ -12530,6 +12741,20 @@ function createDocumentOverlayStore(db) {
12530
12741
  });
12531
12742
  documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
12532
12743
  documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
12744
+ }
12745
+ function extractKey(remoteDoc) {
12746
+ if (remoteDoc.document) {
12747
+ return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
12748
+ }
12749
+ else if (remoteDoc.noDocument) {
12750
+ return DocumentKey.fromSegments(remoteDoc.noDocument.path);
12751
+ }
12752
+ else if (remoteDoc.unknownDocument) {
12753
+ return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
12754
+ }
12755
+ else {
12756
+ return fail();
12757
+ }
12533
12758
  }
12534
12759
 
12535
12760
  /**
@@ -13025,7 +13250,7 @@ class IndexedDbPersistence {
13025
13250
  return this.remoteDocumentCache;
13026
13251
  }
13027
13252
  getIndexManager(user) {
13028
- return new IndexedDbIndexManager(user);
13253
+ return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
13029
13254
  }
13030
13255
  getDocumentOverlayCache(user) {
13031
13256
  return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
@@ -13372,18 +13597,17 @@ class LocalDocumentsView {
13372
13597
  *
13373
13598
  * @param transaction - The persistence transaction.
13374
13599
  * @param query - The query to match documents against.
13375
- * @param sinceReadTime - If not set to SnapshotVersion.min(), return only
13376
- * documents that have been read since this snapshot version (exclusive).
13600
+ * @param offset - Read time and key to start scanning by (exclusive).
13377
13601
  */
13378
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
13602
+ getDocumentsMatchingQuery(transaction, query, offset) {
13379
13603
  if (isDocumentQuery$1(query)) {
13380
13604
  return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
13381
13605
  }
13382
13606
  else if (isCollectionGroupQuery(query)) {
13383
- return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime);
13607
+ return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
13384
13608
  }
13385
13609
  else {
13386
- return this.getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime);
13610
+ return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
13387
13611
  }
13388
13612
  }
13389
13613
  getDocumentsMatchingDocumentQuery(transaction, docPath) {
@@ -13396,7 +13620,7 @@ class LocalDocumentsView {
13396
13620
  return result;
13397
13621
  });
13398
13622
  }
13399
- getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime) {
13623
+ getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
13400
13624
  const collectionId = query.collectionGroup;
13401
13625
  let results = documentMap();
13402
13626
  return this.indexManager
@@ -13406,7 +13630,7 @@ class LocalDocumentsView {
13406
13630
  // collectionId and aggregate the results.
13407
13631
  return PersistencePromise.forEach(parents, (parent) => {
13408
13632
  const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
13409
- return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, sinceReadTime).next(r => {
13633
+ return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
13410
13634
  r.forEach((key, doc) => {
13411
13635
  results = results.insert(key, doc);
13412
13636
  });
@@ -13414,11 +13638,11 @@ class LocalDocumentsView {
13414
13638
  }).next(() => results);
13415
13639
  });
13416
13640
  }
13417
- getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
13641
+ getDocumentsMatchingCollectionQuery(transaction, query, offset) {
13418
13642
  // Query the remote documents and overlay mutations.
13419
13643
  let results;
13420
13644
  return this.remoteDocumentCache
13421
- .getAll(transaction, query.path, sinceReadTime)
13645
+ .getAllFromCollection(transaction, query.path, offset)
13422
13646
  .next(queryResults => {
13423
13647
  results = queryResults;
13424
13648
  return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
@@ -13504,11 +13728,12 @@ class LocalStoreImpl {
13504
13728
  // TODO(wuandy): Evaluate if TargetId can be part of Target.
13505
13729
  this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
13506
13730
  /**
13507
- * The read time of the last entry processed by `getNewDocumentChanges()`.
13731
+ * A per collection group index of the last read time processed by
13732
+ * `getNewDocumentChanges()`.
13508
13733
  *
13509
13734
  * PORTING NOTE: This is only used for multi-tab synchronization.
13510
13735
  */
13511
- this.lastDocumentChangeReadTime = SnapshotVersion.min();
13736
+ this.collectionGroupReadTime = new Map();
13512
13737
  this.remoteDocuments = persistence.getRemoteDocumentCache();
13513
13738
  this.targetCache = persistence.getTargetCache();
13514
13739
  this.bundleCache = persistence.getBundleCache();
@@ -14043,6 +14268,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
14043
14268
  ? lastLimboFreeSnapshotVersion
14044
14269
  : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
14045
14270
  .next(documents => {
14271
+ setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
14046
14272
  return { documents, remoteKeys };
14047
14273
  });
14048
14274
  });
@@ -14120,28 +14346,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
14120
14346
  * since the prior call.
14121
14347
  */
14122
14348
  // PORTING NOTE: Multi-Tab only.
14123
- function localStoreGetNewDocumentChanges(localStore) {
14349
+ function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
14124
14350
  const localStoreImpl = debugCast(localStore);
14351
+ // Get the current maximum read time for the collection. This should always
14352
+ // exist, but to reduce the chance for regressions we default to
14353
+ // SnapshotVersion.Min()
14354
+ // TODO(indexing): Consider removing the default value.
14355
+ const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
14356
+ SnapshotVersion.min();
14125
14357
  return localStoreImpl.persistence
14126
- .runTransaction('Get new document changes', 'readonly', txn => remoteDocumentCacheGetNewDocumentChanges(localStoreImpl.remoteDocuments, txn, localStoreImpl.lastDocumentChangeReadTime))
14127
- .then(({ changedDocs, readTime }) => {
14128
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14358
+ .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
14359
+ /* limit= */ Number.MAX_SAFE_INTEGER))
14360
+ .then(changedDocs => {
14361
+ setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
14129
14362
  return changedDocs;
14130
14363
  });
14131
14364
  }
14132
- /**
14133
- * Reads the newest document change from persistence and moves the internal
14134
- * synchronization marker forward so that calls to `getNewDocumentChanges()`
14135
- * only return changes that happened after client initialization.
14136
- */
14365
+ /** Sets the collection group's maximum read time from the given documents. */
14137
14366
  // PORTING NOTE: Multi-Tab only.
14138
- async function localStoreSynchronizeLastDocumentChangeReadTime(localStore) {
14139
- const localStoreImpl = debugCast(localStore);
14140
- return localStoreImpl.persistence
14141
- .runTransaction('Synchronize last document change read time', 'readonly', txn => remoteDocumentCacheGetLastReadTime(txn))
14142
- .then(readTime => {
14143
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14367
+ function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
14368
+ let readTime = SnapshotVersion.min();
14369
+ changedDocs.forEach((_, doc) => {
14370
+ if (doc.readTime.compareTo(readTime) > 0) {
14371
+ readTime = doc.readTime;
14372
+ }
14144
14373
  });
14374
+ localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
14145
14375
  }
14146
14376
  /**
14147
14377
  * Creates a new target using the given bundle name, which will be used to
@@ -14824,7 +15054,7 @@ class MemoryRemoteDocumentCacheImpl {
14824
15054
  });
14825
15055
  return PersistencePromise.resolve(results);
14826
15056
  }
14827
- getAll(transaction, collectionPath, sinceReadTime) {
15057
+ getAllFromCollection(transaction, collectionPath, offset) {
14828
15058
  let results = mutableDocumentMap();
14829
15059
  // Documents are ordered by key, so we can use a prefix scan to narrow down
14830
15060
  // the documents we need to match the query against.
@@ -14839,13 +15069,19 @@ class MemoryRemoteDocumentCacheImpl {
14839
15069
  // Exclude entries from subcollections.
14840
15070
  continue;
14841
15071
  }
14842
- if (document.readTime.compareTo(sinceReadTime) <= 0) {
15072
+ if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
15073
+ // The document sorts before the offset.
14843
15074
  continue;
14844
15075
  }
14845
15076
  results = results.insert(document.key, document.mutableCopy());
14846
15077
  }
14847
15078
  return PersistencePromise.resolve(results);
14848
15079
  }
15080
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
15081
+ // This method should only be called from the IndexBackfiller if persistence
15082
+ // is enabled.
15083
+ fail();
15084
+ }
14849
15085
  forEachDocumentKey(transaction, f) {
14850
15086
  return PersistencePromise.forEach(this.docs, (key) => f(key));
14851
15087
  }
@@ -15298,7 +15534,7 @@ class QueryEngine {
15298
15534
  }
15299
15535
  // Retrieve all results for documents that were updated since the last
15300
15536
  // limbo-document free remote snapshot.
15301
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
15537
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
15302
15538
  // We merge `previousResults` into `updateResults`, since
15303
15539
  // `updateResults` is already a DocumentMap. If a document is
15304
15540
  // contained in both lists, then its contents are the same.
@@ -15360,7 +15596,7 @@ class QueryEngine {
15360
15596
  if (getLogLevel() <= logger.LogLevel.DEBUG) {
15361
15597
  logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
15362
15598
  }
15363
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, SnapshotVersion.min());
15599
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
15364
15600
  }
15365
15601
  }
15366
15602
 
@@ -15421,8 +15657,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
15421
15657
  // The WebStorage prefix that plays as a event to indicate the remote documents
15422
15658
  // might have changed due to some secondary tabs loading a bundle.
15423
15659
  // format of the key is:
15424
- // firestore_bundle_loaded_<persistenceKey>
15425
- const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded';
15660
+ // firestore_bundle_loaded_v2_<persistenceKey>
15661
+ // The version ending with "v2" stores the list of modified collection groups.
15662
+ const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
15426
15663
  function createBundleLoadedKey(persistenceKey) {
15427
15664
  return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
15428
15665
  }
@@ -15800,8 +16037,8 @@ class WebStorageSharedClientState {
15800
16037
  setOnlineState(onlineState) {
15801
16038
  this.persistOnlineState(onlineState);
15802
16039
  }
15803
- notifyBundleLoaded() {
15804
- this.persistBundleLoadedState();
16040
+ notifyBundleLoaded(collectionGroups) {
16041
+ this.persistBundleLoadedState(collectionGroups);
15805
16042
  }
15806
16043
  shutdown() {
15807
16044
  if (this.started) {
@@ -15885,7 +16122,8 @@ class WebStorageSharedClientState {
15885
16122
  }
15886
16123
  }
15887
16124
  else if (storageEvent.key === this.bundleLoadedKey) {
15888
- return this.syncEngine.synchronizeWithChangedDocuments();
16125
+ const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
16126
+ await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
15889
16127
  }
15890
16128
  });
15891
16129
  }
@@ -15917,8 +16155,9 @@ class WebStorageSharedClientState {
15917
16155
  const targetMetadata = new QueryTargetMetadata(targetId, state, error);
15918
16156
  this.setItem(targetKey, targetMetadata.toWebStorageJSON());
15919
16157
  }
15920
- persistBundleLoadedState() {
15921
- this.setItem(this.bundleLoadedKey, 'value-not-used');
16158
+ persistBundleLoadedState(collectionGroups) {
16159
+ const json = JSON.stringify(Array.from(collectionGroups));
16160
+ this.setItem(this.bundleLoadedKey, json);
15922
16161
  }
15923
16162
  /**
15924
16163
  * Parses a client state key in WebStorage. Returns null if the key does not
@@ -15962,6 +16201,9 @@ class WebStorageSharedClientState {
15962
16201
  fromWebStorageOnlineState(value) {
15963
16202
  return SharedOnlineState.fromWebStorageEntry(value);
15964
16203
  }
16204
+ fromWebStoreBundleLoadedState(value) {
16205
+ return JSON.parse(value);
16206
+ }
15965
16207
  async handleMutationBatchEvent(mutationBatch) {
15966
16208
  if (mutationBatch.user.uid !== this.currentUser.uid) {
15967
16209
  logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
@@ -16078,7 +16320,7 @@ class MemorySharedClientState {
16078
16320
  }
16079
16321
  shutdown() { }
16080
16322
  writeSequenceNumber(sequenceNumber) { }
16081
- notifyBundleLoaded() {
16323
+ notifyBundleLoaded(collectionGroups) {
16082
16324
  // No op.
16083
16325
  }
16084
16326
  }
@@ -21378,29 +21620,6 @@ class LocalViewChanges {
21378
21620
  }
21379
21621
  }
21380
21622
 
21381
- /**
21382
- * @license
21383
- * Copyright 2020 Google LLC
21384
- *
21385
- * Licensed under the Apache License, Version 2.0 (the "License");
21386
- * you may not use this file except in compliance with the License.
21387
- * You may obtain a copy of the License at
21388
- *
21389
- * http://www.apache.org/licenses/LICENSE-2.0
21390
- *
21391
- * Unless required by applicable law or agreed to in writing, software
21392
- * distributed under the License is distributed on an "AS IS" BASIS,
21393
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21394
- * See the License for the specific language governing permissions and
21395
- * limitations under the License.
21396
- */
21397
- class BundleLoadResult {
21398
- constructor(progress, changedDocs) {
21399
- this.progress = progress;
21400
- this.changedDocs = changedDocs;
21401
- }
21402
- }
21403
-
21404
21623
  /**
21405
21624
  * @license
21406
21625
  * Copyright 2020 Google LLC
@@ -21455,6 +21674,8 @@ class BundleLoader {
21455
21674
  this.queries = [];
21456
21675
  /** Batched documents to be saved into storage */
21457
21676
  this.documents = [];
21677
+ /** The collection groups affected by this bundle. */
21678
+ this.collectionGroups = new Set();
21458
21679
  this.progress = bundleInitialProgress(bundleMetadata);
21459
21680
  }
21460
21681
  /**
@@ -21474,6 +21695,8 @@ class BundleLoader {
21474
21695
  if (!element.payload.documentMetadata.exists) {
21475
21696
  ++documentsLoaded;
21476
21697
  }
21698
+ const path = ResourcePath.fromString(element.payload.documentMetadata.name);
21699
+ this.collectionGroups.add(path.get(path.length - 2));
21477
21700
  }
21478
21701
  else if (element.payload.document) {
21479
21702
  this.documents[this.documents.length - 1].document =
@@ -21504,13 +21727,17 @@ class BundleLoader {
21504
21727
  * Update the progress to 'Success' and return the updated progress.
21505
21728
  */
21506
21729
  async complete() {
21507
- const changedDocuments = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21730
+ const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21508
21731
  const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
21509
21732
  for (const q of this.queries) {
21510
21733
  await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
21511
21734
  }
21512
21735
  this.progress.taskState = 'Success';
21513
- return new BundleLoadResult(Object.assign({}, this.progress), changedDocuments);
21736
+ return {
21737
+ progress: this.progress,
21738
+ changedCollectionGroups: this.collectionGroups,
21739
+ changedDocs
21740
+ };
21514
21741
  }
21515
21742
  }
21516
21743
  /**
@@ -22589,9 +22816,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
22589
22816
  * snapshots if needed.
22590
22817
  */
22591
22818
  // PORTING NOTE: Multi-Tab only.
22592
- async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
22819
+ async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
22593
22820
  const syncEngineImpl = debugCast(syncEngine);
22594
- return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22821
+ return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22595
22822
  }
22596
22823
  /** Applies a mutation state to an existing batch. */
22597
22824
  // PORTING NOTE: Multi-Tab only.
@@ -22756,11 +22983,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
22756
22983
  logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
22757
22984
  return;
22758
22985
  }
22759
- if (syncEngineImpl.queriesByTarget.has(targetId)) {
22986
+ const query = syncEngineImpl.queriesByTarget.get(targetId);
22987
+ if (query && query.length > 0) {
22760
22988
  switch (state) {
22761
22989
  case 'current':
22762
22990
  case 'not-current': {
22763
- const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
22991
+ const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
22764
22992
  const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
22765
22993
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
22766
22994
  break;
@@ -22843,10 +23071,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
22843
23071
  function syncEngineLoadBundle(syncEngine, bundleReader, task) {
22844
23072
  const syncEngineImpl = debugCast(syncEngine);
22845
23073
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
22846
- loadBundleImpl(syncEngineImpl, bundleReader, task).then(() => {
22847
- syncEngineImpl.sharedClientState.notifyBundleLoaded();
23074
+ loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
23075
+ syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
22848
23076
  });
22849
23077
  }
23078
+ /** Loads a bundle and returns the list of affected collection groups. */
22850
23079
  async function loadBundleImpl(syncEngine, reader, task) {
22851
23080
  try {
22852
23081
  const metadata = await reader.getMetadata();
@@ -22854,7 +23083,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
22854
23083
  if (skip) {
22855
23084
  await reader.close();
22856
23085
  task._completeWith(bundleSuccessProgress(metadata));
22857
- return;
23086
+ return Promise.resolve(new Set());
22858
23087
  }
22859
23088
  task._updateProgress(bundleInitialProgress(metadata));
22860
23089
  const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
@@ -22868,18 +23097,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
22868
23097
  element = await reader.nextElement();
22869
23098
  }
22870
23099
  const result = await loader.complete();
22871
- // TODO(b/160876443): This currently raises snapshots with
22872
- // `fromCache=false` if users already listen to some queries and bundles
22873
- // has newer version.
22874
23100
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
22875
23101
  /* remoteEvent */ undefined);
22876
23102
  // Save metadata, so loading the same bundle will skip.
22877
23103
  await localStoreSaveBundle(syncEngine.localStore, metadata);
22878
23104
  task._completeWith(result.progress);
23105
+ return Promise.resolve(result.changedCollectionGroups);
22879
23106
  }
22880
23107
  catch (e) {
22881
23108
  logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
22882
23109
  task._failWith(e);
23110
+ return Promise.resolve(new Set());
22883
23111
  }
22884
23112
  }
22885
23113
 
@@ -22948,7 +23176,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
22948
23176
  }
22949
23177
  async initialize(cfg) {
22950
23178
  await super.initialize(cfg);
22951
- await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
22952
23179
  await this.onlineComponentProvider.initialize(this, cfg);
22953
23180
  // Enqueue writes from a previous session
22954
23181
  await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);