@firebase/firestore 3.4.5 → 3.4.6-canary.3198d58dc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/firestore/src/core/bundle.d.ts +2 -2
- package/dist/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/firestore/src/core/query.d.ts +7 -0
- package/dist/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/firestore/src/model/collections.d.ts +7 -0
- package/dist/firestore/src/model/field_index.d.ts +10 -1
- package/dist/firestore/src/model/type_order.d.ts +2 -1
- package/dist/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/index.esm2017.js +4533 -4502
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4455 -4308
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1606 -1180
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1606 -1180
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4425 -4394
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +31 -10
- package/dist/lite/firestore/src/core/bundle.d.ts +2 -2
- package/dist/lite/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/lite/firestore/src/core/query.d.ts +7 -0
- package/dist/lite/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/lite/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/lite/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/lite/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/lite/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/lite/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/lite/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/lite/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/lite/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/lite/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/lite/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/lite/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/lite/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/lite/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/lite/firestore/src/model/collections.d.ts +7 -0
- package/dist/lite/firestore/src/model/field_index.d.ts +10 -1
- package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/lite/index.browser.esm2017.js +37 -24
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +104 -113
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +24 -9
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +24 -9
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +37 -24
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/internal.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/bundle.d.ts +2 -2
- package/dist/lite/packages/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/lite/packages/firestore/src/core/query.d.ts +7 -0
- package/dist/lite/packages/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/lite/packages/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/lite/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/lite/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/lite/packages/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/lite/packages/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/lite/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/lite/packages/firestore/src/model/collections.d.ts +7 -0
- package/dist/lite/packages/firestore/src/model/field_index.d.ts +10 -1
- package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/packages/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/lite/private.d.ts +1 -0
- package/dist/packages/firestore/dist/index.esm2017.d.ts +197 -197
- package/dist/packages/firestore/src/core/bundle.d.ts +2 -2
- package/dist/packages/firestore/src/core/bundle_impl.d.ts +2 -0
- package/dist/packages/firestore/src/core/query.d.ts +7 -0
- package/dist/packages/firestore/src/core/snapshot_version.d.ts +1 -0
- package/dist/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
- package/dist/packages/firestore/src/index/index_entry.d.ts +6 -0
- package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/index_manager.d.ts +2 -2
- package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
- package/dist/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
- package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
- package/dist/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
- package/dist/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
- package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
- package/dist/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +3 -4
- package/dist/packages/firestore/src/local/local_serializer.d.ts +3 -1
- package/dist/packages/firestore/src/local/local_store_impl.d.ts +1 -7
- package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
- package/dist/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
- package/dist/packages/firestore/src/local/shared_client_state.d.ts +6 -3
- package/dist/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
- package/dist/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
- package/dist/packages/firestore/src/local/simple_db.d.ts +11 -0
- package/dist/packages/firestore/src/model/collections.d.ts +7 -0
- package/dist/packages/firestore/src/model/field_index.d.ts +10 -1
- package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/packages/firestore/src/util/obj_map.d.ts +3 -0
- package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
- package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
- package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
- package/dist/private.d.ts +31 -10
- package/package.json +9 -9
package/dist/index.node.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-canary.3198d58dc";
|
|
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-canary.3198d58dc";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @license
|
|
@@ -1199,55 +1199,41 @@ function decodeResourcePath(path) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
return new ResourcePath(segments);
|
|
1201
1201
|
}
|
|
1202
|
+
|
|
1202
1203
|
/**
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
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.
|
|
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
|
|
1210
|
+
*
|
|
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.
|
|
1226
1218
|
*/
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
this.seconds = seconds;
|
|
1230
|
-
this.nanoseconds = nanoseconds;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1219
|
+
const DbRemoteDocumentStore$1 = 'remoteDocuments';
|
|
1220
|
+
|
|
1233
1221
|
/**
|
|
1234
|
-
*
|
|
1222
|
+
* @license
|
|
1223
|
+
* Copyright 2022 Google LLC
|
|
1235
1224
|
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1225
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1226
|
+
* you may not use this file except in compliance with the License.
|
|
1227
|
+
* You may obtain a copy of the License at
|
|
1228
|
+
*
|
|
1229
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1230
|
+
*
|
|
1231
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1232
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1233
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1234
|
+
* See the License for the specific language governing permissions and
|
|
1235
|
+
* limitations under the License.
|
|
1241
1236
|
*/
|
|
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
1237
|
/**
|
|
1252
1238
|
* Name of the IndexedDb object store.
|
|
1253
1239
|
*
|
|
@@ -1255,550 +1241,128 @@ class DbPrimaryClient {
|
|
|
1255
1241
|
* older clients that only supported single locked access to the persistence
|
|
1256
1242
|
* layer.
|
|
1257
1243
|
*/
|
|
1258
|
-
|
|
1244
|
+
const DbPrimaryClientStore = 'owner';
|
|
1259
1245
|
/**
|
|
1260
1246
|
* The key string used for the single object that exists in the
|
|
1261
1247
|
* DbPrimaryClient store.
|
|
1262
1248
|
*/
|
|
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
|
-
}
|
|
1249
|
+
const DbPrimaryClientKey = 'owner';
|
|
1302
1250
|
/** Name of the IndexedDb object store. */
|
|
1303
|
-
|
|
1251
|
+
const DbMutationQueueStore = 'mutationQueues';
|
|
1304
1252
|
/** 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
|
-
}
|
|
1253
|
+
const DbMutationQueueKeyPath = 'userId';
|
|
1354
1254
|
/** Name of the IndexedDb object store. */
|
|
1355
|
-
|
|
1255
|
+
const DbMutationBatchStore = 'mutations';
|
|
1356
1256
|
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
1357
|
-
|
|
1257
|
+
const DbMutationBatchKeyPath = 'batchId';
|
|
1358
1258
|
/** The index name for lookup of mutations by user. */
|
|
1359
|
-
|
|
1259
|
+
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
1360
1260
|
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
1361
|
-
|
|
1261
|
+
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
1362
1262
|
/**
|
|
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.
|
|
1263
|
+
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1264
|
+
* over all of a user's document mutations.
|
|
1368
1265
|
*/
|
|
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
|
-
}
|
|
1266
|
+
function newDbDocumentMutationPrefixForUser(userId) {
|
|
1267
|
+
return [userId];
|
|
1392
1268
|
}
|
|
1393
|
-
DbDocumentMutation.store = 'documentMutations';
|
|
1394
|
-
/**
|
|
1395
|
-
* Because we store all the useful information for this store in the key,
|
|
1396
|
-
* there is no useful information to store as the value. The raw (unencoded)
|
|
1397
|
-
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1398
|
-
* information.
|
|
1399
|
-
*/
|
|
1400
|
-
DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
|
|
1401
1269
|
/**
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1270
|
+
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1271
|
+
* index to iterate over all at document mutations for a given path or lower.
|
|
1404
1272
|
*/
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
this.path = path;
|
|
1408
|
-
this.readTime = readTime;
|
|
1409
|
-
}
|
|
1273
|
+
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
1274
|
+
return [userId, encodeResourcePath(path)];
|
|
1410
1275
|
}
|
|
1411
1276
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1277
|
+
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1278
|
+
* and deleting into the DbDocumentMutations index.
|
|
1414
1279
|
*/
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
this.path = path;
|
|
1418
|
-
this.version = version;
|
|
1419
|
-
}
|
|
1280
|
+
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
1281
|
+
return [userId, encodeResourcePath(path), batchId];
|
|
1420
1282
|
}
|
|
1421
1283
|
/**
|
|
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.
|
|
1284
|
+
* Because we store all the useful information for this store in the key,
|
|
1285
|
+
* there is no useful information to store as the value. The raw (unencoded)
|
|
1286
|
+
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1287
|
+
* information.
|
|
1433
1288
|
*/
|
|
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';
|
|
1289
|
+
const DbDocumentMutationPlaceholder = {};
|
|
1290
|
+
const DbDocumentMutationStore = 'documentMutations';
|
|
1291
|
+
const DbRemoteDocumentStore = 'remoteDocumentsV14';
|
|
1483
1292
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
* This index is used to provide a changelog for Multi-Tab.
|
|
1293
|
+
* The primary key of the remote documents store, which allows for efficient
|
|
1294
|
+
* access by collection path and read time.
|
|
1488
1295
|
*/
|
|
1489
|
-
|
|
1490
|
-
|
|
1296
|
+
const DbRemoteDocumentKeyPath = [
|
|
1297
|
+
'prefixPath',
|
|
1298
|
+
'collectionGroup',
|
|
1299
|
+
'readTime',
|
|
1300
|
+
'documentId'
|
|
1301
|
+
];
|
|
1302
|
+
/** An index that provides access to documents by key. */
|
|
1303
|
+
const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
|
|
1304
|
+
const DbRemoteDocumentDocumentKeyIndexPath = [
|
|
1305
|
+
'prefixPath',
|
|
1306
|
+
'collectionGroup',
|
|
1307
|
+
'documentId'
|
|
1308
|
+
];
|
|
1491
1309
|
/**
|
|
1492
|
-
* An index that provides access to documents
|
|
1310
|
+
* An index that provides access to documents by collection group and read
|
|
1493
1311
|
* time.
|
|
1494
1312
|
*
|
|
1495
|
-
* This index is used
|
|
1496
|
-
* documents in a collection.
|
|
1497
|
-
*/
|
|
1498
|
-
DbRemoteDocument.collectionReadTimeIndex = 'collectionReadTimeIndex';
|
|
1499
|
-
DbRemoteDocument.collectionReadTimeIndexPath = ['parentPath', 'readTime'];
|
|
1500
|
-
/**
|
|
1501
|
-
* Contains a single entry that has metadata about the remote document cache.
|
|
1313
|
+
* This index is used by the index backfiller.
|
|
1502
1314
|
*/
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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';
|
|
1315
|
+
const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
|
|
1316
|
+
const DbRemoteDocumentCollectionGroupIndexPath = [
|
|
1317
|
+
'collectionGroup',
|
|
1318
|
+
'readTime',
|
|
1319
|
+
'prefixPath',
|
|
1320
|
+
'documentId'
|
|
1321
|
+
];
|
|
1322
|
+
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
1323
|
+
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
1324
|
+
const DbTargetStore = 'targets';
|
|
1601
1325
|
/** Keys are automatically assigned via the targetId property. */
|
|
1602
|
-
|
|
1326
|
+
const DbTargetKeyPath = 'targetId';
|
|
1603
1327
|
/** The name of the queryTargets index. */
|
|
1604
|
-
|
|
1328
|
+
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
1605
1329
|
/**
|
|
1606
1330
|
* The index of all canonicalIds to the targets that they match. This is not
|
|
1607
1331
|
* a unique mapping because canonicalId does not promise a unique name for all
|
|
1608
1332
|
* possible queries, so we append the targetId to make the mapping unique.
|
|
1609
1333
|
*/
|
|
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
|
-
}
|
|
1334
|
+
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
1642
1335
|
/** Name of the IndexedDb object store. */
|
|
1643
|
-
|
|
1336
|
+
const DbTargetDocumentStore = 'targetDocuments';
|
|
1644
1337
|
/** Keys are automatically assigned via the targetId, path properties. */
|
|
1645
|
-
|
|
1338
|
+
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
1646
1339
|
/** The index name for the reverse index. */
|
|
1647
|
-
|
|
1340
|
+
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
1648
1341
|
/** 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
|
-
}
|
|
1342
|
+
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
1689
1343
|
/**
|
|
1690
1344
|
* The key string used for the single object that exists in the
|
|
1691
1345
|
* DbTargetGlobal store.
|
|
1692
1346
|
*/
|
|
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
|
-
}
|
|
1347
|
+
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
1348
|
+
const DbTargetGlobalStore = 'targetGlobal';
|
|
1716
1349
|
/** Name of the IndexedDb object store. */
|
|
1717
|
-
|
|
1350
|
+
const DbCollectionParentStore = 'collectionParents';
|
|
1718
1351
|
/** 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
|
-
}
|
|
1352
|
+
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
1744
1353
|
/** Name of the IndexedDb object store. */
|
|
1745
|
-
|
|
1354
|
+
const DbClientMetadataStore = 'clientMetadata';
|
|
1746
1355
|
/** 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
|
-
}
|
|
1356
|
+
const DbClientMetadataKeyPath = 'clientId';
|
|
1762
1357
|
/** 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
|
-
}
|
|
1358
|
+
const DbBundleStore = 'bundles';
|
|
1359
|
+
const DbBundleKeyPath = 'bundleId';
|
|
1779
1360
|
/** 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
|
-
}
|
|
1361
|
+
const DbNamedQueryStore = 'namedQueries';
|
|
1362
|
+
const DbNamedQueryKeyPath = 'name';
|
|
1799
1363
|
/** Name of the IndexedDb object store. */
|
|
1800
|
-
|
|
1801
|
-
|
|
1364
|
+
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
1365
|
+
const DbIndexConfigurationKeyPath = 'indexId';
|
|
1802
1366
|
/**
|
|
1803
1367
|
* An index that provides access to the index configurations by collection
|
|
1804
1368
|
* group.
|
|
@@ -1807,49 +1371,11 @@ DbIndexConfiguration.keyPath = 'indexId';
|
|
|
1807
1371
|
* not possible here as the Web client supports concurrent access to
|
|
1808
1372
|
* persistence via multi-tab.
|
|
1809
1373
|
*/
|
|
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
|
-
}
|
|
1374
|
+
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
1375
|
+
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
1850
1376
|
/** Name of the IndexedDb object store. */
|
|
1851
|
-
|
|
1852
|
-
|
|
1377
|
+
const DbIndexStateStore = 'indexState';
|
|
1378
|
+
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
1853
1379
|
/**
|
|
1854
1380
|
* An index that provides access to documents in a collection sorted by last
|
|
1855
1381
|
* update time. Used by the backfiller.
|
|
@@ -1858,112 +1384,89 @@ DbIndexState.keyPath = ['indexId', 'uid'];
|
|
|
1858
1384
|
* not possible here as the Web client supports concurrent access to
|
|
1859
1385
|
* persistence via multi-tab.
|
|
1860
1386
|
*/
|
|
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
|
-
}
|
|
1387
|
+
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
1388
|
+
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1883
1389
|
/** Name of the IndexedDb object store. */
|
|
1884
|
-
|
|
1885
|
-
|
|
1390
|
+
const DbIndexEntryStore = 'indexEntries';
|
|
1391
|
+
const DbIndexEntryKeyPath = [
|
|
1886
1392
|
'indexId',
|
|
1887
1393
|
'uid',
|
|
1888
1394
|
'arrayValue',
|
|
1889
1395
|
'directionalValue',
|
|
1890
1396
|
'documentKey'
|
|
1891
1397
|
];
|
|
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
|
-
}
|
|
1398
|
+
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1399
|
+
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1400
|
+
'indexId',
|
|
1401
|
+
'uid',
|
|
1402
|
+
'documentKey'
|
|
1403
|
+
];
|
|
1919
1404
|
/** Name of the IndexedDb object store. */
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1405
|
+
const DbDocumentOverlayStore = 'documentOverlays';
|
|
1406
|
+
const DbDocumentOverlayKeyPath = [
|
|
1407
|
+
'userId',
|
|
1408
|
+
'collectionPath',
|
|
1409
|
+
'documentId'
|
|
1410
|
+
];
|
|
1411
|
+
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1412
|
+
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
1924
1413
|
'userId',
|
|
1925
1414
|
'collectionPath',
|
|
1926
1415
|
'largestBatchId'
|
|
1927
1416
|
];
|
|
1928
|
-
|
|
1929
|
-
|
|
1417
|
+
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1418
|
+
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
1930
1419
|
'userId',
|
|
1931
1420
|
'collectionGroup',
|
|
1932
1421
|
'largestBatchId'
|
|
1933
1422
|
];
|
|
1934
1423
|
// Visible for testing
|
|
1935
1424
|
const V1_STORES = [
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1425
|
+
DbMutationQueueStore,
|
|
1426
|
+
DbMutationBatchStore,
|
|
1427
|
+
DbDocumentMutationStore,
|
|
1428
|
+
DbRemoteDocumentStore$1,
|
|
1429
|
+
DbTargetStore,
|
|
1430
|
+
DbPrimaryClientStore,
|
|
1431
|
+
DbTargetGlobalStore,
|
|
1432
|
+
DbTargetDocumentStore
|
|
1944
1433
|
];
|
|
1945
|
-
// V2 is no longer usable (see comment at top of file)
|
|
1946
1434
|
// Visible for testing
|
|
1947
1435
|
const V3_STORES = V1_STORES;
|
|
1948
1436
|
// 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];
|
|
1437
|
+
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
1438
|
+
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
1439
|
+
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
1440
|
+
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
1441
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
1958
1442
|
const V13_STORES = [
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1443
|
+
DbMutationQueueStore,
|
|
1444
|
+
DbMutationBatchStore,
|
|
1445
|
+
DbDocumentMutationStore,
|
|
1446
|
+
DbRemoteDocumentStore,
|
|
1447
|
+
DbTargetStore,
|
|
1448
|
+
DbPrimaryClientStore,
|
|
1449
|
+
DbTargetGlobalStore,
|
|
1450
|
+
DbTargetDocumentStore,
|
|
1451
|
+
DbClientMetadataStore,
|
|
1452
|
+
DbRemoteDocumentGlobalStore,
|
|
1453
|
+
DbCollectionParentStore,
|
|
1454
|
+
DbBundleStore,
|
|
1455
|
+
DbNamedQueryStore,
|
|
1456
|
+
DbDocumentOverlayStore
|
|
1457
|
+
];
|
|
1458
|
+
const V14_STORES = [
|
|
1459
|
+
...V13_STORES,
|
|
1460
|
+
DbIndexConfigurationStore,
|
|
1461
|
+
DbIndexStateStore,
|
|
1462
|
+
DbIndexEntryStore
|
|
1963
1463
|
];
|
|
1964
1464
|
/** Returns the object stores for the provided schema. */
|
|
1965
1465
|
function getObjectStores(schemaVersion) {
|
|
1966
|
-
if (schemaVersion ===
|
|
1466
|
+
if (schemaVersion === 14) {
|
|
1467
|
+
return V14_STORES;
|
|
1468
|
+
}
|
|
1469
|
+
else if (schemaVersion === 13) {
|
|
1967
1470
|
return V13_STORES;
|
|
1968
1471
|
}
|
|
1969
1472
|
else if (schemaVersion === 12) {
|
|
@@ -2677,6 +2180,21 @@ class SimpleDbStore {
|
|
|
2677
2180
|
});
|
|
2678
2181
|
}
|
|
2679
2182
|
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2185
|
+
* elements if no limit is provided.
|
|
2186
|
+
*/
|
|
2187
|
+
loadFirst(range, count) {
|
|
2188
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
2189
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2190
|
+
request.onerror = (event) => {
|
|
2191
|
+
reject(event.target.error);
|
|
2192
|
+
};
|
|
2193
|
+
request.onsuccess = (event) => {
|
|
2194
|
+
resolve(event.target.result);
|
|
2195
|
+
};
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2680
2198
|
deleteAll(indexOrRange, range) {
|
|
2681
2199
|
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2682
2200
|
const options = this.options(indexOrRange, range);
|
|
@@ -3159,6 +2677,9 @@ class SnapshotVersion {
|
|
|
3159
2677
|
static min() {
|
|
3160
2678
|
return new SnapshotVersion(new Timestamp(0, 0));
|
|
3161
2679
|
}
|
|
2680
|
+
static max() {
|
|
2681
|
+
return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
|
|
2682
|
+
}
|
|
3162
2683
|
compareTo(other) {
|
|
3163
2684
|
return this.timestamp._compareTo(other.timestamp);
|
|
3164
2685
|
}
|
|
@@ -3287,17 +2808,15 @@ class FieldMask {
|
|
|
3287
2808
|
* See the License for the specific language governing permissions and
|
|
3288
2809
|
* limitations under the License.
|
|
3289
2810
|
*/
|
|
2811
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3290
2812
|
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');
|
|
2813
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
2814
|
+
// This was removed to improve the performance of indexing.
|
|
2815
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3297
2816
|
}
|
|
3298
2817
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3299
2818
|
function encodeBase64(raw) {
|
|
3300
|
-
return
|
|
2819
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3301
2820
|
}
|
|
3302
2821
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3303
2822
|
function isBase64Available() {
|
|
@@ -3338,6 +2857,8 @@ class ByteString {
|
|
|
3338
2857
|
return new ByteString(binaryString);
|
|
3339
2858
|
}
|
|
3340
2859
|
static fromUint8Array(array) {
|
|
2860
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
2861
|
+
// is frequently called during indexing.
|
|
3341
2862
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3342
2863
|
return new ByteString(binaryString);
|
|
3343
2864
|
}
|
|
@@ -3756,13 +3277,17 @@ class DocumentKey {
|
|
|
3756
3277
|
* See the License for the specific language governing permissions and
|
|
3757
3278
|
* limitations under the License.
|
|
3758
3279
|
*/
|
|
3280
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3759
3281
|
const MAX_VALUE = {
|
|
3760
3282
|
mapValue: {
|
|
3761
3283
|
fields: {
|
|
3762
|
-
'__type__': { stringValue:
|
|
3284
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3763
3285
|
}
|
|
3764
3286
|
}
|
|
3765
3287
|
};
|
|
3288
|
+
const MIN_VALUE = {
|
|
3289
|
+
nullValue: 'NULL_VALUE'
|
|
3290
|
+
};
|
|
3766
3291
|
/** Extracts the backend's type order for the provided value. */
|
|
3767
3292
|
function typeOrder(value) {
|
|
3768
3293
|
if ('nullValue' in value) {
|
|
@@ -3796,6 +3321,9 @@ function typeOrder(value) {
|
|
|
3796
3321
|
if (isServerTimestamp(value)) {
|
|
3797
3322
|
return 4 /* ServerTimestampValue */;
|
|
3798
3323
|
}
|
|
3324
|
+
else if (isMaxValue(value)) {
|
|
3325
|
+
return 9 /* ArrayValue */;
|
|
3326
|
+
}
|
|
3799
3327
|
return 10 /* ObjectValue */;
|
|
3800
3328
|
}
|
|
3801
3329
|
else {
|
|
@@ -3835,6 +3363,8 @@ function valueEquals(left, right) {
|
|
|
3835
3363
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3836
3364
|
case 10 /* ObjectValue */:
|
|
3837
3365
|
return objectEquals(left, right);
|
|
3366
|
+
case 9007199254740991 /* MaxValue */:
|
|
3367
|
+
return true;
|
|
3838
3368
|
default:
|
|
3839
3369
|
return fail();
|
|
3840
3370
|
}
|
|
@@ -3907,6 +3437,7 @@ function valueCompare(left, right) {
|
|
|
3907
3437
|
}
|
|
3908
3438
|
switch (leftType) {
|
|
3909
3439
|
case 0 /* NullValue */:
|
|
3440
|
+
case 9007199254740991 /* MaxValue */:
|
|
3910
3441
|
return 0;
|
|
3911
3442
|
case 1 /* BooleanValue */:
|
|
3912
3443
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -4175,7 +3706,102 @@ function deepClone(source) {
|
|
|
4175
3706
|
}
|
|
4176
3707
|
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4177
3708
|
function isMaxValue(value) {
|
|
4178
|
-
return
|
|
3709
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
3710
|
+
MAX_VALUE_TYPE);
|
|
3711
|
+
}
|
|
3712
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
3713
|
+
function valuesGetLowerBound(value) {
|
|
3714
|
+
if ('nullValue' in value) {
|
|
3715
|
+
return MIN_VALUE;
|
|
3716
|
+
}
|
|
3717
|
+
else if ('booleanValue' in value) {
|
|
3718
|
+
return { booleanValue: false };
|
|
3719
|
+
}
|
|
3720
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3721
|
+
return { doubleValue: NaN };
|
|
3722
|
+
}
|
|
3723
|
+
else if ('timestampValue' in value) {
|
|
3724
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3725
|
+
}
|
|
3726
|
+
else if ('stringValue' in value) {
|
|
3727
|
+
return { stringValue: '' };
|
|
3728
|
+
}
|
|
3729
|
+
else if ('bytesValue' in value) {
|
|
3730
|
+
return { bytesValue: '' };
|
|
3731
|
+
}
|
|
3732
|
+
else if ('referenceValue' in value) {
|
|
3733
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3734
|
+
}
|
|
3735
|
+
else if ('geoPointValue' in value) {
|
|
3736
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3737
|
+
}
|
|
3738
|
+
else if ('arrayValue' in value) {
|
|
3739
|
+
return { arrayValue: {} };
|
|
3740
|
+
}
|
|
3741
|
+
else if ('mapValue' in value) {
|
|
3742
|
+
return { mapValue: {} };
|
|
3743
|
+
}
|
|
3744
|
+
else {
|
|
3745
|
+
return fail();
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
3749
|
+
function valuesGetUpperBound(value) {
|
|
3750
|
+
if ('nullValue' in value) {
|
|
3751
|
+
return { booleanValue: false };
|
|
3752
|
+
}
|
|
3753
|
+
else if ('booleanValue' in value) {
|
|
3754
|
+
return { doubleValue: NaN };
|
|
3755
|
+
}
|
|
3756
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
3757
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
3758
|
+
}
|
|
3759
|
+
else if ('timestampValue' in value) {
|
|
3760
|
+
return { stringValue: '' };
|
|
3761
|
+
}
|
|
3762
|
+
else if ('stringValue' in value) {
|
|
3763
|
+
return { bytesValue: '' };
|
|
3764
|
+
}
|
|
3765
|
+
else if ('bytesValue' in value) {
|
|
3766
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
3767
|
+
}
|
|
3768
|
+
else if ('referenceValue' in value) {
|
|
3769
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
3770
|
+
}
|
|
3771
|
+
else if ('geoPointValue' in value) {
|
|
3772
|
+
return { arrayValue: {} };
|
|
3773
|
+
}
|
|
3774
|
+
else if ('arrayValue' in value) {
|
|
3775
|
+
return { mapValue: {} };
|
|
3776
|
+
}
|
|
3777
|
+
else if ('mapValue' in value) {
|
|
3778
|
+
return MAX_VALUE;
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
return fail();
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
function valuesMax(left, right) {
|
|
3785
|
+
if (left === undefined) {
|
|
3786
|
+
return right;
|
|
3787
|
+
}
|
|
3788
|
+
else if (right === undefined) {
|
|
3789
|
+
return left;
|
|
3790
|
+
}
|
|
3791
|
+
else {
|
|
3792
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
function valuesMin(left, right) {
|
|
3796
|
+
if (left === undefined) {
|
|
3797
|
+
return right;
|
|
3798
|
+
}
|
|
3799
|
+
else if (right === undefined) {
|
|
3800
|
+
return left;
|
|
3801
|
+
}
|
|
3802
|
+
else {
|
|
3803
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
3804
|
+
}
|
|
4179
3805
|
}
|
|
4180
3806
|
|
|
4181
3807
|
/**
|
|
@@ -4570,6 +4196,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4570
4196
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4571
4197
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4572
4198
|
}
|
|
4199
|
+
/** Returns a debug representation of the field index */
|
|
4200
|
+
function fieldIndexToString(fieldIndex) {
|
|
4201
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4202
|
+
}
|
|
4573
4203
|
/** An index component consisting of field path and index type. */
|
|
4574
4204
|
class IndexSegment {
|
|
4575
4205
|
constructor(
|
|
@@ -4601,6 +4231,28 @@ class IndexState {
|
|
|
4601
4231
|
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
4602
4232
|
}
|
|
4603
4233
|
}
|
|
4234
|
+
/**
|
|
4235
|
+
* Creates an offset that matches all documents with a read time higher than
|
|
4236
|
+
* `readTime`.
|
|
4237
|
+
*/
|
|
4238
|
+
function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
|
|
4239
|
+
// We want to create an offset that matches all documents with a read time
|
|
4240
|
+
// greater than the provided read time. To do so, we technically need to
|
|
4241
|
+
// create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
|
|
4242
|
+
// Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
|
|
4243
|
+
// `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
|
|
4244
|
+
// all valid document IDs.
|
|
4245
|
+
const successorSeconds = readTime.toTimestamp().seconds;
|
|
4246
|
+
const successorNanos = readTime.toTimestamp().nanoseconds + 1;
|
|
4247
|
+
const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
|
|
4248
|
+
? new Timestamp(successorSeconds + 1, 0)
|
|
4249
|
+
: new Timestamp(successorSeconds, successorNanos));
|
|
4250
|
+
return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
|
|
4251
|
+
}
|
|
4252
|
+
/** Creates a new offset based on the provided document. */
|
|
4253
|
+
function newIndexOffsetFromDocument(document) {
|
|
4254
|
+
return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
|
|
4255
|
+
}
|
|
4604
4256
|
/**
|
|
4605
4257
|
* Stores the latest read time, document and batch ID that were processed for an
|
|
4606
4258
|
* index.
|
|
@@ -4625,10 +4277,25 @@ class IndexOffset {
|
|
|
4625
4277
|
this.documentKey = documentKey;
|
|
4626
4278
|
this.largestBatchId = largestBatchId;
|
|
4627
4279
|
}
|
|
4628
|
-
/**
|
|
4280
|
+
/** Returns an offset that sorts before all regular offsets. */
|
|
4629
4281
|
static min() {
|
|
4630
4282
|
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4631
4283
|
}
|
|
4284
|
+
/** Returns an offset that sorts after all regular offsets. */
|
|
4285
|
+
static max() {
|
|
4286
|
+
return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
function indexOffsetComparator(left, right) {
|
|
4290
|
+
let cmp = left.readTime.compareTo(right.readTime);
|
|
4291
|
+
if (cmp !== 0) {
|
|
4292
|
+
return cmp;
|
|
4293
|
+
}
|
|
4294
|
+
cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
4295
|
+
if (cmp !== 0) {
|
|
4296
|
+
return cmp;
|
|
4297
|
+
}
|
|
4298
|
+
return primitiveComparator(left.largestBatchId, right.largestBatchId);
|
|
4632
4299
|
}
|
|
4633
4300
|
|
|
4634
4301
|
/**
|
|
@@ -4741,30 +4408,215 @@ function targetEquals(left, right) {
|
|
|
4741
4408
|
if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
|
|
4742
4409
|
return false;
|
|
4743
4410
|
}
|
|
4744
|
-
}
|
|
4745
|
-
if (left.filters.length !== right.filters.length) {
|
|
4746
|
-
return false;
|
|
4747
|
-
}
|
|
4748
|
-
for (let i = 0; i < left.filters.length; i++) {
|
|
4749
|
-
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
4750
|
-
return false;
|
|
4411
|
+
}
|
|
4412
|
+
if (left.filters.length !== right.filters.length) {
|
|
4413
|
+
return false;
|
|
4414
|
+
}
|
|
4415
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
4416
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
4417
|
+
return false;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
4421
|
+
return false;
|
|
4422
|
+
}
|
|
4423
|
+
if (!left.path.isEqual(right.path)) {
|
|
4424
|
+
return false;
|
|
4425
|
+
}
|
|
4426
|
+
if (!boundEquals(left.startAt, right.startAt)) {
|
|
4427
|
+
return false;
|
|
4428
|
+
}
|
|
4429
|
+
return boundEquals(left.endAt, right.endAt);
|
|
4430
|
+
}
|
|
4431
|
+
function targetIsDocumentTarget(target) {
|
|
4432
|
+
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4433
|
+
target.collectionGroup === null &&
|
|
4434
|
+
target.filters.length === 0);
|
|
4435
|
+
}
|
|
4436
|
+
/** Returns the field filters that target the given field path. */
|
|
4437
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4438
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4439
|
+
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4442
|
+
* filters. Returns `null` if there are no such filters.
|
|
4443
|
+
*/
|
|
4444
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4445
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4446
|
+
if (segment === undefined) {
|
|
4447
|
+
return null;
|
|
4448
|
+
}
|
|
4449
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4450
|
+
switch (fieldFilter.op) {
|
|
4451
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4452
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4453
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4454
|
+
return [fieldFilter.value];
|
|
4455
|
+
// Remaining filters are not array filters.
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
return null;
|
|
4459
|
+
}
|
|
4460
|
+
/**
|
|
4461
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4462
|
+
* `null` if there are no such filters.
|
|
4463
|
+
*/
|
|
4464
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4465
|
+
const values = new Map();
|
|
4466
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4467
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4468
|
+
switch (fieldFilter.op) {
|
|
4469
|
+
case "==" /* EQUAL */:
|
|
4470
|
+
case "in" /* IN */:
|
|
4471
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4472
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4473
|
+
// `value != 'ab'`).
|
|
4474
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4475
|
+
break;
|
|
4476
|
+
case "not-in" /* NOT_IN */:
|
|
4477
|
+
case "!=" /* NOT_EQUAL */:
|
|
4478
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4479
|
+
// segments and hence we can return early here.
|
|
4480
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4481
|
+
return Array.from(values.values());
|
|
4482
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
/**
|
|
4489
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4490
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4491
|
+
* exists.
|
|
4492
|
+
*/
|
|
4493
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4494
|
+
const values = [];
|
|
4495
|
+
let inclusive = true;
|
|
4496
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4497
|
+
// startAt.
|
|
4498
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4499
|
+
let segmentValue = undefined;
|
|
4500
|
+
let segmentInclusive = true;
|
|
4501
|
+
// Process all filters to find a value for the current field segment
|
|
4502
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4503
|
+
let filterValue = undefined;
|
|
4504
|
+
let filterInclusive = true;
|
|
4505
|
+
switch (fieldFilter.op) {
|
|
4506
|
+
case "<" /* LESS_THAN */:
|
|
4507
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4508
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4509
|
+
break;
|
|
4510
|
+
case "==" /* EQUAL */:
|
|
4511
|
+
case "in" /* IN */:
|
|
4512
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4513
|
+
filterValue = fieldFilter.value;
|
|
4514
|
+
break;
|
|
4515
|
+
case ">" /* GREATER_THAN */:
|
|
4516
|
+
filterValue = fieldFilter.value;
|
|
4517
|
+
filterInclusive = false;
|
|
4518
|
+
break;
|
|
4519
|
+
case "!=" /* NOT_EQUAL */:
|
|
4520
|
+
case "not-in" /* NOT_IN */:
|
|
4521
|
+
filterValue = MIN_VALUE;
|
|
4522
|
+
break;
|
|
4523
|
+
// Remaining filters cannot be used as lower bounds.
|
|
4524
|
+
}
|
|
4525
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4526
|
+
segmentValue = filterValue;
|
|
4527
|
+
segmentInclusive = filterInclusive;
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
// If there is a startAt bound, compare the values against the existing
|
|
4531
|
+
// boundary to see if we can narrow the scope.
|
|
4532
|
+
if (target.startAt !== null) {
|
|
4533
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4534
|
+
const orderBy = target.orderBy[i];
|
|
4535
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4536
|
+
const cursorValue = target.startAt.position[i];
|
|
4537
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4538
|
+
segmentValue = cursorValue;
|
|
4539
|
+
segmentInclusive = target.startAt.inclusive;
|
|
4540
|
+
}
|
|
4541
|
+
break;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
if (segmentValue === undefined) {
|
|
4546
|
+
// No lower bound exists
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
values.push(segmentValue);
|
|
4550
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4551
|
+
}
|
|
4552
|
+
return new Bound(values, inclusive);
|
|
4553
|
+
}
|
|
4554
|
+
/**
|
|
4555
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
4556
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
4557
|
+
* upper bound exists.
|
|
4558
|
+
*/
|
|
4559
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
4560
|
+
const values = [];
|
|
4561
|
+
let inclusive = true;
|
|
4562
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4563
|
+
// endAt.
|
|
4564
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4565
|
+
let segmentValue = undefined;
|
|
4566
|
+
let segmentInclusive = true;
|
|
4567
|
+
// Process all filters to find a value for the current field segment
|
|
4568
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4569
|
+
let filterValue = undefined;
|
|
4570
|
+
let filterInclusive = true;
|
|
4571
|
+
switch (fieldFilter.op) {
|
|
4572
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4573
|
+
case ">" /* GREATER_THAN */:
|
|
4574
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4575
|
+
filterInclusive = false;
|
|
4576
|
+
break;
|
|
4577
|
+
case "==" /* EQUAL */:
|
|
4578
|
+
case "in" /* IN */:
|
|
4579
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4580
|
+
filterValue = fieldFilter.value;
|
|
4581
|
+
break;
|
|
4582
|
+
case "<" /* LESS_THAN */:
|
|
4583
|
+
filterValue = fieldFilter.value;
|
|
4584
|
+
filterInclusive = false;
|
|
4585
|
+
break;
|
|
4586
|
+
case "!=" /* NOT_EQUAL */:
|
|
4587
|
+
case "not-in" /* NOT_IN */:
|
|
4588
|
+
filterValue = MAX_VALUE;
|
|
4589
|
+
break;
|
|
4590
|
+
// Remaining filters cannot be used as upper bounds.
|
|
4591
|
+
}
|
|
4592
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4593
|
+
segmentValue = filterValue;
|
|
4594
|
+
segmentInclusive = filterInclusive;
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
// If there is a endAt bound, compare the values against the existing
|
|
4598
|
+
// boundary to see if we can narrow the scope.
|
|
4599
|
+
if (target.endAt !== null) {
|
|
4600
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4601
|
+
const orderBy = target.orderBy[i];
|
|
4602
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4603
|
+
const cursorValue = target.endAt.position[i];
|
|
4604
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4605
|
+
segmentValue = cursorValue;
|
|
4606
|
+
segmentInclusive = target.endAt.inclusive;
|
|
4607
|
+
}
|
|
4608
|
+
break;
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
if (segmentValue === undefined) {
|
|
4613
|
+
// No upper bound exists
|
|
4614
|
+
return null;
|
|
4751
4615
|
}
|
|
4616
|
+
values.push(segmentValue);
|
|
4617
|
+
inclusive && (inclusive = segmentInclusive);
|
|
4752
4618
|
}
|
|
4753
|
-
|
|
4754
|
-
return false;
|
|
4755
|
-
}
|
|
4756
|
-
if (!left.path.isEqual(right.path)) {
|
|
4757
|
-
return false;
|
|
4758
|
-
}
|
|
4759
|
-
if (!boundEquals(left.startAt, right.startAt)) {
|
|
4760
|
-
return false;
|
|
4761
|
-
}
|
|
4762
|
-
return boundEquals(left.endAt, right.endAt);
|
|
4763
|
-
}
|
|
4764
|
-
function targetIsDocumentTarget(target) {
|
|
4765
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4766
|
-
target.collectionGroup === null &&
|
|
4767
|
-
target.filters.length === 0);
|
|
4619
|
+
return new Bound(values, inclusive);
|
|
4768
4620
|
}
|
|
4769
4621
|
class Filter {
|
|
4770
4622
|
}
|
|
@@ -5336,6 +5188,18 @@ function queryMatchesBounds(query, doc) {
|
|
|
5336
5188
|
}
|
|
5337
5189
|
return true;
|
|
5338
5190
|
}
|
|
5191
|
+
/**
|
|
5192
|
+
* Returns the collection group that this query targets.
|
|
5193
|
+
*
|
|
5194
|
+
* PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
|
|
5195
|
+
* synchronization for query results.
|
|
5196
|
+
*/
|
|
5197
|
+
function queryCollectionGroup(query) {
|
|
5198
|
+
return (query.collectionGroup ||
|
|
5199
|
+
(query.path.length % 2 === 1
|
|
5200
|
+
? query.path.lastSegment()
|
|
5201
|
+
: query.path.get(query.path.length - 2)));
|
|
5202
|
+
}
|
|
5339
5203
|
/**
|
|
5340
5204
|
* Returns a new comparator function that can be used to compare two documents
|
|
5341
5205
|
* based on the Query's ordering constraint.
|
|
@@ -5367,6 +5231,116 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5367
5231
|
}
|
|
5368
5232
|
}
|
|
5369
5233
|
|
|
5234
|
+
/**
|
|
5235
|
+
* @license
|
|
5236
|
+
* Copyright 2017 Google LLC
|
|
5237
|
+
*
|
|
5238
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5239
|
+
* you may not use this file except in compliance with the License.
|
|
5240
|
+
* You may obtain a copy of the License at
|
|
5241
|
+
*
|
|
5242
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5243
|
+
*
|
|
5244
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5245
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5246
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5247
|
+
* See the License for the specific language governing permissions and
|
|
5248
|
+
* limitations under the License.
|
|
5249
|
+
*/
|
|
5250
|
+
/**
|
|
5251
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
5252
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
5253
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
5254
|
+
* automatically handles collisions of keys.
|
|
5255
|
+
*/
|
|
5256
|
+
class ObjectMap {
|
|
5257
|
+
constructor(mapKeyFn, equalsFn) {
|
|
5258
|
+
this.mapKeyFn = mapKeyFn;
|
|
5259
|
+
this.equalsFn = equalsFn;
|
|
5260
|
+
/**
|
|
5261
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
5262
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
5263
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
5264
|
+
* constant time lookups in practice.
|
|
5265
|
+
*/
|
|
5266
|
+
this.inner = {};
|
|
5267
|
+
/** The number of entries stored in the map */
|
|
5268
|
+
this.innerSize = 0;
|
|
5269
|
+
}
|
|
5270
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
5271
|
+
get(key) {
|
|
5272
|
+
const id = this.mapKeyFn(key);
|
|
5273
|
+
const matches = this.inner[id];
|
|
5274
|
+
if (matches === undefined) {
|
|
5275
|
+
return undefined;
|
|
5276
|
+
}
|
|
5277
|
+
for (const [otherKey, value] of matches) {
|
|
5278
|
+
if (this.equalsFn(otherKey, key)) {
|
|
5279
|
+
return value;
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
return undefined;
|
|
5283
|
+
}
|
|
5284
|
+
has(key) {
|
|
5285
|
+
return this.get(key) !== undefined;
|
|
5286
|
+
}
|
|
5287
|
+
/** Put this key and value in the map. */
|
|
5288
|
+
set(key, value) {
|
|
5289
|
+
const id = this.mapKeyFn(key);
|
|
5290
|
+
const matches = this.inner[id];
|
|
5291
|
+
if (matches === undefined) {
|
|
5292
|
+
this.inner[id] = [[key, value]];
|
|
5293
|
+
this.innerSize++;
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5297
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5298
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
5299
|
+
matches[i] = [key, value];
|
|
5300
|
+
return;
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
matches.push([key, value]);
|
|
5304
|
+
this.innerSize++;
|
|
5305
|
+
}
|
|
5306
|
+
/**
|
|
5307
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
5308
|
+
*/
|
|
5309
|
+
delete(key) {
|
|
5310
|
+
const id = this.mapKeyFn(key);
|
|
5311
|
+
const matches = this.inner[id];
|
|
5312
|
+
if (matches === undefined) {
|
|
5313
|
+
return false;
|
|
5314
|
+
}
|
|
5315
|
+
for (let i = 0; i < matches.length; i++) {
|
|
5316
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
5317
|
+
if (matches.length === 1) {
|
|
5318
|
+
delete this.inner[id];
|
|
5319
|
+
}
|
|
5320
|
+
else {
|
|
5321
|
+
matches.splice(i, 1);
|
|
5322
|
+
}
|
|
5323
|
+
this.innerSize--;
|
|
5324
|
+
return true;
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
return false;
|
|
5328
|
+
}
|
|
5329
|
+
forEach(fn) {
|
|
5330
|
+
forEach(this.inner, (_, entries) => {
|
|
5331
|
+
for (const [k, v] of entries) {
|
|
5332
|
+
fn(k, v);
|
|
5333
|
+
}
|
|
5334
|
+
});
|
|
5335
|
+
}
|
|
5336
|
+
isEmpty() {
|
|
5337
|
+
return isEmpty(this.inner);
|
|
5338
|
+
}
|
|
5339
|
+
size() {
|
|
5340
|
+
return this.innerSize;
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5370
5344
|
/**
|
|
5371
5345
|
* @license
|
|
5372
5346
|
* Copyright 2017 Google LLC
|
|
@@ -5509,7 +5483,7 @@ class SortedMapIterator {
|
|
|
5509
5483
|
while (!node.isEmpty()) {
|
|
5510
5484
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5511
5485
|
// flip the comparison if we're going in reverse
|
|
5512
|
-
if (isReverse) {
|
|
5486
|
+
if (startKey && isReverse) {
|
|
5513
5487
|
cmp *= -1;
|
|
5514
5488
|
}
|
|
5515
5489
|
if (cmp < 0) {
|
|
@@ -6078,6 +6052,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
|
6078
6052
|
function documentMap() {
|
|
6079
6053
|
return EMPTY_DOCUMENT_MAP;
|
|
6080
6054
|
}
|
|
6055
|
+
function newOverlayMap() {
|
|
6056
|
+
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
6057
|
+
}
|
|
6081
6058
|
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
|
|
6082
6059
|
function documentVersionMap() {
|
|
6083
6060
|
return EMPTY_DOCUMENT_VERSION_MAP;
|
|
@@ -8756,34 +8733,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
|
8756
8733
|
}
|
|
8757
8734
|
/** Encodes a document for storage locally. */
|
|
8758
8735
|
function toDbRemoteDocument(localSerializer, document) {
|
|
8759
|
-
const
|
|
8760
|
-
const
|
|
8736
|
+
const key = document.key;
|
|
8737
|
+
const remoteDoc = {
|
|
8738
|
+
prefixPath: key.getCollectionPath().popLast().toArray(),
|
|
8739
|
+
collectionGroup: key.collectionGroup,
|
|
8740
|
+
documentId: key.path.lastSegment(),
|
|
8741
|
+
readTime: toDbTimestampKey(document.readTime),
|
|
8742
|
+
hasCommittedMutations: document.hasCommittedMutations
|
|
8743
|
+
};
|
|
8761
8744
|
if (document.isFoundDocument()) {
|
|
8762
|
-
|
|
8763
|
-
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8764
|
-
return new DbRemoteDocument(
|
|
8765
|
-
/* unknownDocument= */ null,
|
|
8766
|
-
/* noDocument= */ null, doc, hasCommittedMutations, dbReadTime, parentPath);
|
|
8745
|
+
remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
|
|
8767
8746
|
}
|
|
8768
8747
|
else if (document.isNoDocument()) {
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
/* unknownDocument= */ null, new DbNoDocument(path, version),
|
|
8774
|
-
/* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
|
|
8748
|
+
remoteDoc.noDocument = {
|
|
8749
|
+
path: key.path.toArray(),
|
|
8750
|
+
readTime: toDbTimestamp(document.version)
|
|
8751
|
+
};
|
|
8775
8752
|
}
|
|
8776
8753
|
else if (document.isUnknownDocument()) {
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
/* document= */ null,
|
|
8782
|
-
/* hasCommittedMutations= */ true, dbReadTime, parentPath);
|
|
8754
|
+
remoteDoc.unknownDocument = {
|
|
8755
|
+
path: key.path.toArray(),
|
|
8756
|
+
version: toDbTimestamp(document.version)
|
|
8757
|
+
};
|
|
8783
8758
|
}
|
|
8784
8759
|
else {
|
|
8785
8760
|
return fail();
|
|
8786
8761
|
}
|
|
8762
|
+
return remoteDoc;
|
|
8787
8763
|
}
|
|
8788
8764
|
function toDbTimestampKey(snapshotVersion) {
|
|
8789
8765
|
const timestamp = snapshotVersion.toTimestamp();
|
|
@@ -8795,7 +8771,7 @@ function fromDbTimestampKey(dbTimestampKey) {
|
|
|
8795
8771
|
}
|
|
8796
8772
|
function toDbTimestamp(snapshotVersion) {
|
|
8797
8773
|
const timestamp = snapshotVersion.toTimestamp();
|
|
8798
|
-
return
|
|
8774
|
+
return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
|
|
8799
8775
|
}
|
|
8800
8776
|
function fromDbTimestamp(dbTimestamp) {
|
|
8801
8777
|
const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
|
|
@@ -8805,7 +8781,13 @@ function fromDbTimestamp(dbTimestamp) {
|
|
|
8805
8781
|
function toDbMutationBatch(localSerializer, userId, batch) {
|
|
8806
8782
|
const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8807
8783
|
const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
|
|
8808
|
-
return
|
|
8784
|
+
return {
|
|
8785
|
+
userId,
|
|
8786
|
+
batchId: batch.batchId,
|
|
8787
|
+
localWriteTimeMs: batch.localWriteTime.toMillis(),
|
|
8788
|
+
baseMutations: serializedBaseMutations,
|
|
8789
|
+
mutations: serializedMutations
|
|
8790
|
+
};
|
|
8809
8791
|
}
|
|
8810
8792
|
/** Decodes a DbMutationBatch into a MutationBatch */
|
|
8811
8793
|
function fromDbMutationBatch(localSerializer, dbBatch) {
|
|
@@ -8861,7 +8843,15 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8861
8843
|
// convert it to a base64 string for storage.
|
|
8862
8844
|
const resumeToken = targetData.resumeToken.toBase64();
|
|
8863
8845
|
// lastListenSequenceNumber is always 0 until we do real GC.
|
|
8864
|
-
return
|
|
8846
|
+
return {
|
|
8847
|
+
targetId: targetData.targetId,
|
|
8848
|
+
canonicalId: canonifyTarget(targetData.target),
|
|
8849
|
+
readTime: dbTimestamp,
|
|
8850
|
+
resumeToken,
|
|
8851
|
+
lastListenSequenceNumber: targetData.sequenceNumber,
|
|
8852
|
+
lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
|
|
8853
|
+
query: queryProto
|
|
8854
|
+
};
|
|
8865
8855
|
}
|
|
8866
8856
|
/**
|
|
8867
8857
|
* A helper function for figuring out what kind of query has been stored.
|
|
@@ -8940,7 +8930,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
|
8940
8930
|
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
8941
8931
|
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
8942
8932
|
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
8943
|
-
return
|
|
8933
|
+
return {
|
|
8934
|
+
userId,
|
|
8935
|
+
collectionPath,
|
|
8936
|
+
documentId,
|
|
8937
|
+
collectionGroup: overlay.mutation.key.getCollectionGroup(),
|
|
8938
|
+
largestBatchId: overlay.largestBatchId,
|
|
8939
|
+
overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
|
|
8940
|
+
};
|
|
8944
8941
|
}
|
|
8945
8942
|
/**
|
|
8946
8943
|
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
@@ -8952,7 +8949,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
|
|
|
8952
8949
|
return [userId, collectionPath, docId];
|
|
8953
8950
|
}
|
|
8954
8951
|
function toDbIndexConfiguration(index) {
|
|
8955
|
-
return
|
|
8952
|
+
return {
|
|
8953
|
+
indexId: index.indexId,
|
|
8954
|
+
collectionGroup: index.collectionGroup,
|
|
8955
|
+
fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
|
|
8956
|
+
};
|
|
8956
8957
|
}
|
|
8957
8958
|
function fromDbIndexConfiguration(index, state) {
|
|
8958
8959
|
const decodedState = state
|
|
@@ -8962,7 +8963,14 @@ function fromDbIndexConfiguration(index, state) {
|
|
|
8962
8963
|
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
8963
8964
|
}
|
|
8964
8965
|
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
8965
|
-
return
|
|
8966
|
+
return {
|
|
8967
|
+
indexId,
|
|
8968
|
+
uid: user.uid || '',
|
|
8969
|
+
sequenceNumber,
|
|
8970
|
+
readTime: toDbTimestamp(offset.readTime),
|
|
8971
|
+
documentKey: encodeResourcePath(offset.documentKey.path),
|
|
8972
|
+
largestBatchId: offset.largestBatchId
|
|
8973
|
+
};
|
|
8966
8974
|
}
|
|
8967
8975
|
|
|
8968
8976
|
/**
|
|
@@ -9013,13 +9021,13 @@ class IndexedDbBundleCache {
|
|
|
9013
9021
|
* Helper to get a typed SimpleDbStore for the bundles object store.
|
|
9014
9022
|
*/
|
|
9015
9023
|
function bundlesStore(txn) {
|
|
9016
|
-
return getStore(txn,
|
|
9024
|
+
return getStore(txn, DbBundleStore);
|
|
9017
9025
|
}
|
|
9018
9026
|
/**
|
|
9019
9027
|
* Helper to get a typed SimpleDbStore for the namedQueries object store.
|
|
9020
9028
|
*/
|
|
9021
9029
|
function namedQueriesStore(txn) {
|
|
9022
|
-
return getStore(txn,
|
|
9030
|
+
return getStore(txn, DbNamedQueryStore);
|
|
9023
9031
|
}
|
|
9024
9032
|
|
|
9025
9033
|
/**
|
|
@@ -9066,7 +9074,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9066
9074
|
}
|
|
9067
9075
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9068
9076
|
const promises = [];
|
|
9069
|
-
overlays.forEach(mutation => {
|
|
9077
|
+
overlays.forEach((_, mutation) => {
|
|
9070
9078
|
const overlay = new Overlay(largestBatchId, mutation);
|
|
9071
9079
|
promises.push(this.saveOverlay(transaction, overlay));
|
|
9072
9080
|
});
|
|
@@ -9081,19 +9089,19 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9081
9089
|
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9082
9090
|
/*lowerOpen=*/ false,
|
|
9083
9091
|
/*upperOpen=*/ true);
|
|
9084
|
-
promises.push(documentOverlayStore(transaction).deleteAll(
|
|
9092
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
|
|
9085
9093
|
});
|
|
9086
9094
|
return PersistencePromise.waitFor(promises);
|
|
9087
9095
|
}
|
|
9088
9096
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9089
|
-
const result =
|
|
9097
|
+
const result = newOverlayMap();
|
|
9090
9098
|
const collectionPath = encodeResourcePath(collection);
|
|
9091
9099
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9092
9100
|
// is not inclusive.
|
|
9093
9101
|
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9094
9102
|
/*lowerOpen=*/ true);
|
|
9095
9103
|
return documentOverlayStore(transaction)
|
|
9096
|
-
.loadAll(
|
|
9104
|
+
.loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
|
|
9097
9105
|
.next(dbOverlays => {
|
|
9098
9106
|
for (const dbOverlay of dbOverlays) {
|
|
9099
9107
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
@@ -9103,7 +9111,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9103
9111
|
});
|
|
9104
9112
|
}
|
|
9105
9113
|
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9106
|
-
const result =
|
|
9114
|
+
const result = newOverlayMap();
|
|
9107
9115
|
let currentBatchId = undefined;
|
|
9108
9116
|
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9109
9117
|
// is not inclusive.
|
|
@@ -9111,7 +9119,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9111
9119
|
/*lowerOpen=*/ true);
|
|
9112
9120
|
return documentOverlayStore(transaction)
|
|
9113
9121
|
.iterate({
|
|
9114
|
-
index:
|
|
9122
|
+
index: DbDocumentOverlayCollectionGroupOverlayIndex,
|
|
9115
9123
|
range
|
|
9116
9124
|
}, (_, dbOverlay, control) => {
|
|
9117
9125
|
// We do not want to return partial batch overlays, even if the size
|
|
@@ -9119,7 +9127,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9119
9127
|
// continue to aggregate results even after the result size exceeds
|
|
9120
9128
|
// `count` if there are more overlays from the `currentBatchId`.
|
|
9121
9129
|
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9122
|
-
if (result.size < count ||
|
|
9130
|
+
if (result.size() < count ||
|
|
9123
9131
|
overlay.largestBatchId === currentBatchId) {
|
|
9124
9132
|
result.set(overlay.getKey(), overlay);
|
|
9125
9133
|
currentBatchId = overlay.largestBatchId;
|
|
@@ -9138,7 +9146,7 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9138
9146
|
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9139
9147
|
*/
|
|
9140
9148
|
function documentOverlayStore(txn) {
|
|
9141
|
-
return getStore(txn,
|
|
9149
|
+
return getStore(txn, DbDocumentOverlayStore);
|
|
9142
9150
|
}
|
|
9143
9151
|
|
|
9144
9152
|
/**
|
|
@@ -9694,21 +9702,40 @@ class IndexEntry {
|
|
|
9694
9702
|
this.arrayValue = arrayValue;
|
|
9695
9703
|
this.directionalValue = directionalValue;
|
|
9696
9704
|
}
|
|
9705
|
+
/**
|
|
9706
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
9707
|
+
* directional value.
|
|
9708
|
+
*/
|
|
9709
|
+
successor() {
|
|
9710
|
+
const currentLength = this.directionalValue.length;
|
|
9711
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
9712
|
+
? currentLength + 1
|
|
9713
|
+
: currentLength;
|
|
9714
|
+
const successor = new Uint8Array(newLength);
|
|
9715
|
+
successor.set(this.directionalValue, 0);
|
|
9716
|
+
if (newLength !== currentLength) {
|
|
9717
|
+
successor.set([0], this.directionalValue.length);
|
|
9718
|
+
}
|
|
9719
|
+
else {
|
|
9720
|
+
++successor[successor.length - 1];
|
|
9721
|
+
}
|
|
9722
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
9723
|
+
}
|
|
9697
9724
|
}
|
|
9698
9725
|
function indexEntryComparator(left, right) {
|
|
9699
9726
|
let cmp = left.indexId - right.indexId;
|
|
9700
9727
|
if (cmp !== 0) {
|
|
9701
9728
|
return cmp;
|
|
9702
9729
|
}
|
|
9703
|
-
cmp =
|
|
9730
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
9704
9731
|
if (cmp !== 0) {
|
|
9705
9732
|
return cmp;
|
|
9706
9733
|
}
|
|
9707
|
-
cmp = compareByteArrays(left.
|
|
9734
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
9708
9735
|
if (cmp !== 0) {
|
|
9709
9736
|
return cmp;
|
|
9710
9737
|
}
|
|
9711
|
-
return
|
|
9738
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
9712
9739
|
}
|
|
9713
9740
|
function compareByteArrays(left, right) {
|
|
9714
9741
|
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
@@ -9720,6 +9747,160 @@ function compareByteArrays(left, right) {
|
|
|
9720
9747
|
return left.length - right.length;
|
|
9721
9748
|
}
|
|
9722
9749
|
|
|
9750
|
+
/**
|
|
9751
|
+
* @license
|
|
9752
|
+
* Copyright 2022 Google LLC
|
|
9753
|
+
*
|
|
9754
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9755
|
+
* you may not use this file except in compliance with the License.
|
|
9756
|
+
* You may obtain a copy of the License at
|
|
9757
|
+
*
|
|
9758
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9759
|
+
*
|
|
9760
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9761
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9762
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9763
|
+
* See the License for the specific language governing permissions and
|
|
9764
|
+
* limitations under the License.
|
|
9765
|
+
*/
|
|
9766
|
+
/**
|
|
9767
|
+
* A light query planner for Firestore.
|
|
9768
|
+
*
|
|
9769
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
9770
|
+
* determines whether a given index can be used to serve the specified target.
|
|
9771
|
+
*
|
|
9772
|
+
* The following table showcases some possible index configurations:
|
|
9773
|
+
*
|
|
9774
|
+
* Query | Index
|
|
9775
|
+
* -----------------------------------------------------------------------------
|
|
9776
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
9777
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
9778
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
9779
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
9780
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
9781
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
9782
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
9783
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
9784
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
9785
|
+
*/
|
|
9786
|
+
class TargetIndexMatcher {
|
|
9787
|
+
constructor(target) {
|
|
9788
|
+
this.collectionId =
|
|
9789
|
+
target.collectionGroup != null
|
|
9790
|
+
? target.collectionGroup
|
|
9791
|
+
: target.path.lastSegment();
|
|
9792
|
+
this.orderBys = target.orderBy;
|
|
9793
|
+
this.equalityFilters = [];
|
|
9794
|
+
for (const filter of target.filters) {
|
|
9795
|
+
const fieldFilter = filter;
|
|
9796
|
+
if (fieldFilter.isInequality()) {
|
|
9797
|
+
this.inequalityFilter = fieldFilter;
|
|
9798
|
+
}
|
|
9799
|
+
else {
|
|
9800
|
+
this.equalityFilters.push(fieldFilter);
|
|
9801
|
+
}
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
/**
|
|
9805
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
9806
|
+
* target.
|
|
9807
|
+
*
|
|
9808
|
+
* An index is considered capable of serving the target when:
|
|
9809
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
9810
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
9811
|
+
* fewer.
|
|
9812
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
9813
|
+
* have a corresponding `CONTAINS` segment.
|
|
9814
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
9815
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
9816
|
+
* clauses.
|
|
9817
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
9818
|
+
* - The optional segment for the inequality filter must appear after all
|
|
9819
|
+
* equality segments.
|
|
9820
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
9821
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
9822
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
9823
|
+
* omitted.
|
|
9824
|
+
*/
|
|
9825
|
+
servedByIndex(index) {
|
|
9826
|
+
// If there is an array element, find a matching filter.
|
|
9827
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
9828
|
+
if (arraySegment !== undefined &&
|
|
9829
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
9830
|
+
return false;
|
|
9831
|
+
}
|
|
9832
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
9833
|
+
let segmentIndex = 0;
|
|
9834
|
+
let orderBysIndex = 0;
|
|
9835
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
9836
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9837
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
9838
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
9839
|
+
// Since it is not possible to use the same field path in both an equality
|
|
9840
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
9841
|
+
// that a matching equality segment should instead be used to map to an
|
|
9842
|
+
// inequality filter or orderBy clause.
|
|
9843
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
9844
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
9845
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
9846
|
+
// clauses.
|
|
9847
|
+
break;
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
// If we already have processed all segments, all segments are used to serve
|
|
9851
|
+
// the equality filters and we do not need to map any segments to the
|
|
9852
|
+
// target's inequality and orderBy clauses.
|
|
9853
|
+
if (segmentIndex === segments.length) {
|
|
9854
|
+
return true;
|
|
9855
|
+
}
|
|
9856
|
+
// If there is an inequality filter, the next segment must match both the
|
|
9857
|
+
// filter and the first orderBy clause.
|
|
9858
|
+
if (this.inequalityFilter !== undefined) {
|
|
9859
|
+
const segment = segments[segmentIndex];
|
|
9860
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
9861
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9862
|
+
return false;
|
|
9863
|
+
}
|
|
9864
|
+
++segmentIndex;
|
|
9865
|
+
}
|
|
9866
|
+
// All remaining segments need to represent the prefix of the target's
|
|
9867
|
+
// orderBy.
|
|
9868
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
9869
|
+
const segment = segments[segmentIndex];
|
|
9870
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
9871
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
9872
|
+
return false;
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
return true;
|
|
9876
|
+
}
|
|
9877
|
+
hasMatchingEqualityFilter(segment) {
|
|
9878
|
+
for (const filter of this.equalityFilters) {
|
|
9879
|
+
if (this.matchesFilter(filter, segment)) {
|
|
9880
|
+
return true;
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
return false;
|
|
9884
|
+
}
|
|
9885
|
+
matchesFilter(filter, segment) {
|
|
9886
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
9887
|
+
return false;
|
|
9888
|
+
}
|
|
9889
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
9890
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
9891
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
9892
|
+
}
|
|
9893
|
+
matchesOrderBy(orderBy, segment) {
|
|
9894
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
9895
|
+
return false;
|
|
9896
|
+
}
|
|
9897
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
9898
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
9899
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
9900
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
|
|
9723
9904
|
/**
|
|
9724
9905
|
* @license
|
|
9725
9906
|
* Copyright 2019 Google LLC
|
|
@@ -9758,9 +9939,9 @@ class MemoryIndexManager {
|
|
|
9758
9939
|
// Field indices are not supported with memory persistence.
|
|
9759
9940
|
return PersistencePromise.resolve();
|
|
9760
9941
|
}
|
|
9761
|
-
getDocumentsMatchingTarget(transaction,
|
|
9942
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
9762
9943
|
// Field indices are not supported with memory persistence.
|
|
9763
|
-
return PersistencePromise.resolve(
|
|
9944
|
+
return PersistencePromise.resolve(null);
|
|
9764
9945
|
}
|
|
9765
9946
|
getFieldIndex(transaction, target) {
|
|
9766
9947
|
// Field indices are not supported with memory persistence.
|
|
@@ -9832,6 +10013,7 @@ class MemoryCollectionParentIndex {
|
|
|
9832
10013
|
* limitations under the License.
|
|
9833
10014
|
*/
|
|
9834
10015
|
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
10016
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
9835
10017
|
/**
|
|
9836
10018
|
* A persisted implementation of IndexManager.
|
|
9837
10019
|
*
|
|
@@ -9849,6 +10031,11 @@ class IndexedDbIndexManager {
|
|
|
9849
10031
|
* used to satisfy reads.
|
|
9850
10032
|
*/
|
|
9851
10033
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
10034
|
+
/**
|
|
10035
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
10036
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
10037
|
+
*/
|
|
10038
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
9852
10039
|
this.uid = user.uid || '';
|
|
9853
10040
|
}
|
|
9854
10041
|
/**
|
|
@@ -9917,13 +10104,111 @@ class IndexedDbIndexManager {
|
|
|
9917
10104
|
/*lowerOpen=*/ false,
|
|
9918
10105
|
/*upperOpen=*/ true)));
|
|
9919
10106
|
}
|
|
9920
|
-
getDocumentsMatchingTarget(transaction,
|
|
9921
|
-
|
|
9922
|
-
|
|
10107
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10108
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10109
|
+
let canServeTarget = true;
|
|
10110
|
+
const indexes = new Map();
|
|
10111
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10112
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10113
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10114
|
+
indexes.set(subTarget, index);
|
|
10115
|
+
});
|
|
10116
|
+
}).next(() => {
|
|
10117
|
+
if (!canServeTarget) {
|
|
10118
|
+
return PersistencePromise.resolve(null);
|
|
10119
|
+
}
|
|
10120
|
+
else {
|
|
10121
|
+
let result = documentKeySet();
|
|
10122
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10123
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10124
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10125
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10126
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10127
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10128
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10129
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10130
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10131
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10132
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10133
|
+
return indexEntries
|
|
10134
|
+
.loadFirst(indexRange, target.limit)
|
|
10135
|
+
.next(entries => {
|
|
10136
|
+
entries.forEach(entry => {
|
|
10137
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10138
|
+
});
|
|
10139
|
+
});
|
|
10140
|
+
});
|
|
10141
|
+
}).next(() => result);
|
|
10142
|
+
}
|
|
10143
|
+
});
|
|
10144
|
+
}
|
|
10145
|
+
getSubTargets(target) {
|
|
10146
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10147
|
+
if (subTargets) {
|
|
10148
|
+
return subTargets;
|
|
10149
|
+
}
|
|
10150
|
+
// TODO(orquery): Implement DNF transform
|
|
10151
|
+
subTargets = [target];
|
|
10152
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10153
|
+
return subTargets;
|
|
10154
|
+
}
|
|
10155
|
+
/**
|
|
10156
|
+
* Constructs a key range query on `DbIndexEntryStore` that unions all
|
|
10157
|
+
* bounds.
|
|
10158
|
+
*/
|
|
10159
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10160
|
+
// The number of total index scans we union together. This is similar to a
|
|
10161
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10162
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10163
|
+
// combined with the values from the query bounds.
|
|
10164
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10165
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10166
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10167
|
+
const indexRanges = [];
|
|
10168
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10169
|
+
const arrayValue = arrayValues
|
|
10170
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10171
|
+
: EMPTY_VALUE;
|
|
10172
|
+
const lowerBound = lowerBounds
|
|
10173
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10174
|
+
: this.generateEmptyBound(indexId);
|
|
10175
|
+
const upperBound = upperBounds
|
|
10176
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10177
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10178
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10179
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10180
|
+
/* inclusive= */ true))));
|
|
10181
|
+
}
|
|
10182
|
+
return indexRanges;
|
|
10183
|
+
}
|
|
10184
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10185
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10186
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10187
|
+
return inclusive ? entry : entry.successor();
|
|
10188
|
+
}
|
|
10189
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10190
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10191
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10192
|
+
return inclusive ? entry.successor() : entry;
|
|
10193
|
+
}
|
|
10194
|
+
/**
|
|
10195
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10196
|
+
* and user.
|
|
10197
|
+
*/
|
|
10198
|
+
generateEmptyBound(indexId) {
|
|
10199
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
9923
10200
|
}
|
|
9924
10201
|
getFieldIndex(transaction, target) {
|
|
9925
|
-
|
|
9926
|
-
|
|
10202
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10203
|
+
const collectionGroup = target.collectionGroup != null
|
|
10204
|
+
? target.collectionGroup
|
|
10205
|
+
: target.path.lastSegment();
|
|
10206
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10207
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10208
|
+
// Return the index that matches the most number of segments.
|
|
10209
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10210
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10211
|
+
});
|
|
9927
10212
|
}
|
|
9928
10213
|
/**
|
|
9929
10214
|
* Returns the byte encoded form of the directional values in the field index.
|
|
@@ -9937,22 +10222,90 @@ class IndexedDbIndexManager {
|
|
|
9937
10222
|
if (field == null) {
|
|
9938
10223
|
return null;
|
|
9939
10224
|
}
|
|
9940
|
-
const directionalEncoder = encoder.forKind(segment.kind);
|
|
9941
|
-
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10225
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10226
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10227
|
+
}
|
|
10228
|
+
return encoder.encodedBytes();
|
|
10229
|
+
}
|
|
10230
|
+
/** Encodes a single value to the ascending index format. */
|
|
10231
|
+
encodeSingleElement(value) {
|
|
10232
|
+
const encoder = new IndexByteEncoder();
|
|
10233
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10234
|
+
return encoder.encodedBytes();
|
|
10235
|
+
}
|
|
10236
|
+
/**
|
|
10237
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10238
|
+
* For IN queries, a list of possible values is returned.
|
|
10239
|
+
*/
|
|
10240
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10241
|
+
if (bound === null) {
|
|
10242
|
+
return [];
|
|
10243
|
+
}
|
|
10244
|
+
let encoders = [];
|
|
10245
|
+
encoders.push(new IndexByteEncoder());
|
|
10246
|
+
let boundIdx = 0;
|
|
10247
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10248
|
+
const value = bound[boundIdx++];
|
|
10249
|
+
for (const encoder of encoders) {
|
|
10250
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10251
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10252
|
+
}
|
|
10253
|
+
else {
|
|
10254
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10255
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10256
|
+
}
|
|
10257
|
+
}
|
|
10258
|
+
}
|
|
10259
|
+
return this.getEncodedBytes(encoders);
|
|
10260
|
+
}
|
|
10261
|
+
/**
|
|
10262
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10263
|
+
* queries, a list of possible values is returned.
|
|
10264
|
+
*/
|
|
10265
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10266
|
+
if (bound == null) {
|
|
10267
|
+
return null;
|
|
10268
|
+
}
|
|
10269
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10270
|
+
}
|
|
10271
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10272
|
+
getEncodedBytes(encoders) {
|
|
10273
|
+
const result = [];
|
|
10274
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10275
|
+
result[i] = encoders[i].encodedBytes();
|
|
10276
|
+
}
|
|
10277
|
+
return result;
|
|
10278
|
+
}
|
|
10279
|
+
/**
|
|
10280
|
+
* Creates a separate encoder for each element of an array.
|
|
10281
|
+
*
|
|
10282
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10283
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10284
|
+
* list of new encoders is returned.
|
|
10285
|
+
*/
|
|
10286
|
+
expandIndexValues(encoders, segment, value) {
|
|
10287
|
+
const prefixes = [...encoders];
|
|
10288
|
+
const results = [];
|
|
10289
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10290
|
+
for (const prefix of prefixes) {
|
|
10291
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10292
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10293
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10294
|
+
results.push(clonedEncoder);
|
|
10295
|
+
}
|
|
9942
10296
|
}
|
|
9943
|
-
return
|
|
10297
|
+
return results;
|
|
9944
10298
|
}
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
return encoder.encodedBytes();
|
|
10299
|
+
isInFilter(target, fieldPath) {
|
|
10300
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10301
|
+
f.field.isEqual(fieldPath) &&
|
|
10302
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
9950
10303
|
}
|
|
9951
10304
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9952
10305
|
const indexes = indexConfigurationStore(transaction);
|
|
9953
10306
|
const states = indexStateStore(transaction);
|
|
9954
10307
|
return (collectionGroup
|
|
9955
|
-
? indexes.loadAll(
|
|
10308
|
+
? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9956
10309
|
: indexes.loadAll()).next(indexConfigs => {
|
|
9957
10310
|
const result = [];
|
|
9958
10311
|
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
@@ -9969,7 +10322,12 @@ class IndexedDbIndexManager {
|
|
|
9969
10322
|
if (indexes.length === 0) {
|
|
9970
10323
|
return null;
|
|
9971
10324
|
}
|
|
9972
|
-
indexes.sort((l, r) =>
|
|
10325
|
+
indexes.sort((l, r) => {
|
|
10326
|
+
const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
|
|
10327
|
+
return cmp !== 0
|
|
10328
|
+
? cmp
|
|
10329
|
+
: primitiveComparator(l.collectionGroup, r.collectionGroup);
|
|
10330
|
+
});
|
|
9973
10331
|
return indexes[0].collectionGroup;
|
|
9974
10332
|
});
|
|
9975
10333
|
}
|
|
@@ -9977,7 +10335,7 @@ class IndexedDbIndexManager {
|
|
|
9977
10335
|
const indexes = indexConfigurationStore(transaction);
|
|
9978
10336
|
const states = indexStateStore(transaction);
|
|
9979
10337
|
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
9980
|
-
.loadAll(
|
|
10338
|
+
.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
9981
10339
|
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
9982
10340
|
}
|
|
9983
10341
|
updateIndexEntries(transaction, documents) {
|
|
@@ -10008,7 +10366,13 @@ class IndexedDbIndexManager {
|
|
|
10008
10366
|
}
|
|
10009
10367
|
addIndexEntry(transaction, document, indexEntry) {
|
|
10010
10368
|
const indexEntries = indexEntriesStore(transaction);
|
|
10011
|
-
return indexEntries.put(
|
|
10369
|
+
return indexEntries.put({
|
|
10370
|
+
indexId: indexEntry.indexId,
|
|
10371
|
+
uid: this.uid,
|
|
10372
|
+
arrayValue: indexEntry.arrayValue,
|
|
10373
|
+
directionalValue: indexEntry.directionalValue,
|
|
10374
|
+
documentKey: encodeResourcePath(document.key.path)
|
|
10375
|
+
});
|
|
10012
10376
|
}
|
|
10013
10377
|
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10014
10378
|
const indexEntries = indexEntriesStore(transaction);
|
|
@@ -10025,7 +10389,7 @@ class IndexedDbIndexManager {
|
|
|
10025
10389
|
let results = new SortedSet(indexEntryComparator);
|
|
10026
10390
|
return indexEntries
|
|
10027
10391
|
.iterate({
|
|
10028
|
-
index:
|
|
10392
|
+
index: DbIndexEntryDocumentKeyIndex,
|
|
10029
10393
|
range: IDBKeyRange.only([
|
|
10030
10394
|
fieldIndex.indexId,
|
|
10031
10395
|
this.uid,
|
|
@@ -10053,7 +10417,7 @@ class IndexedDbIndexManager {
|
|
|
10053
10417
|
}
|
|
10054
10418
|
}
|
|
10055
10419
|
else {
|
|
10056
|
-
results = results.add(new IndexEntry(fieldIndex.indexId, document.key,
|
|
10420
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10057
10421
|
}
|
|
10058
10422
|
return results;
|
|
10059
10423
|
}
|
|
@@ -10079,7 +10443,7 @@ class IndexedDbIndexManager {
|
|
|
10079
10443
|
const states = indexStateStore(transaction);
|
|
10080
10444
|
return states
|
|
10081
10445
|
.iterate({
|
|
10082
|
-
index:
|
|
10446
|
+
index: DbIndexStateSequenceNumberIndex,
|
|
10083
10447
|
reverse: true,
|
|
10084
10448
|
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10085
10449
|
}, (_, state, controller) => {
|
|
@@ -10088,31 +10452,81 @@ class IndexedDbIndexManager {
|
|
|
10088
10452
|
})
|
|
10089
10453
|
.next(() => nextSequenceNumber);
|
|
10090
10454
|
}
|
|
10455
|
+
/**
|
|
10456
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10457
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10458
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10459
|
+
*/
|
|
10460
|
+
createRange(lower, upper, notInValues) {
|
|
10461
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10462
|
+
// sorted set of non-overlapping ranges.
|
|
10463
|
+
notInValues = notInValues
|
|
10464
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10465
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10466
|
+
const bounds = [];
|
|
10467
|
+
bounds.push(lower);
|
|
10468
|
+
for (const notInValue of notInValues) {
|
|
10469
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10470
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10471
|
+
if (cmpToLower === 0) {
|
|
10472
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10473
|
+
// to the next value.
|
|
10474
|
+
bounds[0] = lower.successor();
|
|
10475
|
+
}
|
|
10476
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10477
|
+
// `notInValue` is in the middle of the range
|
|
10478
|
+
bounds.push(notInValue);
|
|
10479
|
+
bounds.push(notInValue.successor());
|
|
10480
|
+
}
|
|
10481
|
+
else if (cmpToUpper > 0) {
|
|
10482
|
+
// `notInValue` (and all following values) are out of the range
|
|
10483
|
+
break;
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
bounds.push(upper);
|
|
10487
|
+
const ranges = [];
|
|
10488
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10489
|
+
ranges.push(IDBKeyRange.bound([
|
|
10490
|
+
bounds[i].indexId,
|
|
10491
|
+
this.uid,
|
|
10492
|
+
bounds[i].arrayValue,
|
|
10493
|
+
bounds[i].directionalValue,
|
|
10494
|
+
''
|
|
10495
|
+
], [
|
|
10496
|
+
bounds[i + 1].indexId,
|
|
10497
|
+
this.uid,
|
|
10498
|
+
bounds[i + 1].arrayValue,
|
|
10499
|
+
bounds[i + 1].directionalValue,
|
|
10500
|
+
''
|
|
10501
|
+
]));
|
|
10502
|
+
}
|
|
10503
|
+
return ranges;
|
|
10504
|
+
}
|
|
10091
10505
|
}
|
|
10092
10506
|
/**
|
|
10093
10507
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
10094
10508
|
* document store.
|
|
10095
10509
|
*/
|
|
10096
10510
|
function collectionParentsStore(txn) {
|
|
10097
|
-
return getStore(txn,
|
|
10511
|
+
return getStore(txn, DbCollectionParentStore);
|
|
10098
10512
|
}
|
|
10099
10513
|
/**
|
|
10100
10514
|
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10101
10515
|
*/
|
|
10102
10516
|
function indexEntriesStore(txn) {
|
|
10103
|
-
return getStore(txn,
|
|
10517
|
+
return getStore(txn, DbIndexEntryStore);
|
|
10104
10518
|
}
|
|
10105
10519
|
/**
|
|
10106
10520
|
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10107
10521
|
*/
|
|
10108
10522
|
function indexConfigurationStore(txn) {
|
|
10109
|
-
return getStore(txn,
|
|
10523
|
+
return getStore(txn, DbIndexConfigurationStore);
|
|
10110
10524
|
}
|
|
10111
10525
|
/**
|
|
10112
10526
|
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10113
10527
|
*/
|
|
10114
10528
|
function indexStateStore(txn) {
|
|
10115
|
-
return getStore(txn,
|
|
10529
|
+
return getStore(txn, DbIndexStateStore);
|
|
10116
10530
|
}
|
|
10117
10531
|
|
|
10118
10532
|
/**
|
|
@@ -10136,8 +10550,8 @@ function indexStateStore(txn) {
|
|
|
10136
10550
|
* @returns A PersistencePromise of the document mutations that were removed.
|
|
10137
10551
|
*/
|
|
10138
10552
|
function removeMutationBatch(txn, userId, batch) {
|
|
10139
|
-
const mutationStore = txn.store(
|
|
10140
|
-
const indexTxn = txn.store(
|
|
10553
|
+
const mutationStore = txn.store(DbMutationBatchStore);
|
|
10554
|
+
const indexTxn = txn.store(DbDocumentMutationStore);
|
|
10141
10555
|
const promises = [];
|
|
10142
10556
|
const range = IDBKeyRange.only(batch.batchId);
|
|
10143
10557
|
let numDeleted = 0;
|
|
@@ -10150,7 +10564,7 @@ function removeMutationBatch(txn, userId, batch) {
|
|
|
10150
10564
|
}));
|
|
10151
10565
|
const removedDocuments = [];
|
|
10152
10566
|
for (const mutation of batch.mutations) {
|
|
10153
|
-
const indexKey =
|
|
10567
|
+
const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
|
|
10154
10568
|
promises.push(indexTxn.delete(indexKey));
|
|
10155
10569
|
removedDocuments.push(mutation.key);
|
|
10156
10570
|
}
|
|
@@ -10239,7 +10653,7 @@ class IndexedDbMutationQueue {
|
|
|
10239
10653
|
let empty = true;
|
|
10240
10654
|
const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10241
10655
|
return mutationsStore(transaction)
|
|
10242
|
-
.iterate({ index:
|
|
10656
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
|
|
10243
10657
|
empty = false;
|
|
10244
10658
|
control.done();
|
|
10245
10659
|
})
|
|
@@ -10264,10 +10678,10 @@ class IndexedDbMutationQueue {
|
|
|
10264
10678
|
const promises = [];
|
|
10265
10679
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10266
10680
|
for (const mutation of mutations) {
|
|
10267
|
-
const indexKey =
|
|
10681
|
+
const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
|
|
10268
10682
|
collectionParents = collectionParents.add(mutation.key.path.popLast());
|
|
10269
10683
|
promises.push(mutationStore.put(dbBatch));
|
|
10270
|
-
promises.push(documentStore.put(indexKey,
|
|
10684
|
+
promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
|
|
10271
10685
|
}
|
|
10272
10686
|
collectionParents.forEach(parent => {
|
|
10273
10687
|
promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
|
|
@@ -10318,7 +10732,7 @@ class IndexedDbMutationQueue {
|
|
|
10318
10732
|
const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
|
|
10319
10733
|
let foundBatch = null;
|
|
10320
10734
|
return mutationsStore(transaction)
|
|
10321
|
-
.iterate({ index:
|
|
10735
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
|
|
10322
10736
|
if (dbBatch.userId === this.userId) {
|
|
10323
10737
|
hardAssert(dbBatch.batchId >= nextBatchId);
|
|
10324
10738
|
foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
|
|
@@ -10334,7 +10748,7 @@ class IndexedDbMutationQueue {
|
|
|
10334
10748
|
]);
|
|
10335
10749
|
let batchId = BATCHID_UNKNOWN;
|
|
10336
10750
|
return mutationsStore(transaction)
|
|
10337
|
-
.iterate({ index:
|
|
10751
|
+
.iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
|
|
10338
10752
|
batchId = dbBatch.batchId;
|
|
10339
10753
|
control.done();
|
|
10340
10754
|
})
|
|
@@ -10343,13 +10757,13 @@ class IndexedDbMutationQueue {
|
|
|
10343
10757
|
getAllMutationBatches(transaction) {
|
|
10344
10758
|
const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
|
|
10345
10759
|
return mutationsStore(transaction)
|
|
10346
|
-
.loadAll(
|
|
10760
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
10347
10761
|
.next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
|
|
10348
10762
|
}
|
|
10349
10763
|
getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
|
|
10350
10764
|
// Scan the document-mutation index starting with a prefix starting with
|
|
10351
10765
|
// the given documentKey.
|
|
10352
|
-
const indexPrefix =
|
|
10766
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10353
10767
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10354
10768
|
const results = [];
|
|
10355
10769
|
return documentMutationsStore(transaction)
|
|
@@ -10384,7 +10798,7 @@ class IndexedDbMutationQueue {
|
|
|
10384
10798
|
let uniqueBatchIDs = new SortedSet(primitiveComparator);
|
|
10385
10799
|
const promises = [];
|
|
10386
10800
|
documentKeys.forEach(documentKey => {
|
|
10387
|
-
const indexStart =
|
|
10801
|
+
const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
|
|
10388
10802
|
const range = IDBKeyRange.lowerBound(indexStart);
|
|
10389
10803
|
const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
|
|
10390
10804
|
const [userID, encodedPath, batchID] = indexKey;
|
|
@@ -10420,7 +10834,7 @@ class IndexedDbMutationQueue {
|
|
|
10420
10834
|
// Since we don't yet index the actual properties in the mutations, our
|
|
10421
10835
|
// current approach is to just return all mutation batches that affect
|
|
10422
10836
|
// documents in the collection being queried.
|
|
10423
|
-
const indexPrefix =
|
|
10837
|
+
const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
|
|
10424
10838
|
const indexStart = IDBKeyRange.lowerBound(indexPrefix);
|
|
10425
10839
|
// Collect up unique batchIDs encountered during a scan of the index. Use a
|
|
10426
10840
|
// SortedSet to accumulate batch IDs so they can be traversed in order in a
|
|
@@ -10492,7 +10906,7 @@ class IndexedDbMutationQueue {
|
|
|
10492
10906
|
}
|
|
10493
10907
|
// Verify that there are no entries in the documentMutations index if
|
|
10494
10908
|
// the queue is empty.
|
|
10495
|
-
const startRange = IDBKeyRange.lowerBound(
|
|
10909
|
+
const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
|
|
10496
10910
|
const danglingMutationReferences = [];
|
|
10497
10911
|
return documentMutationsStore(txn)
|
|
10498
10912
|
.iterate({ range: startRange }, (key, _, control) => {
|
|
@@ -10520,9 +10934,11 @@ class IndexedDbMutationQueue {
|
|
|
10520
10934
|
return mutationQueuesStore(transaction)
|
|
10521
10935
|
.get(this.userId)
|
|
10522
10936
|
.next((metadata) => {
|
|
10523
|
-
return (metadata ||
|
|
10524
|
-
|
|
10525
|
-
|
|
10937
|
+
return (metadata || {
|
|
10938
|
+
userId: this.userId,
|
|
10939
|
+
lastAcknowledgedBatchId: BATCHID_UNKNOWN,
|
|
10940
|
+
lastStreamToken: ''
|
|
10941
|
+
});
|
|
10526
10942
|
});
|
|
10527
10943
|
}
|
|
10528
10944
|
}
|
|
@@ -10531,7 +10947,7 @@ class IndexedDbMutationQueue {
|
|
|
10531
10947
|
* mutation for the given key.
|
|
10532
10948
|
*/
|
|
10533
10949
|
function mutationQueueContainsKey(txn, userId, key) {
|
|
10534
|
-
const indexKey =
|
|
10950
|
+
const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
|
|
10535
10951
|
const encodedPath = indexKey[1];
|
|
10536
10952
|
const startRange = IDBKeyRange.lowerBound(indexKey);
|
|
10537
10953
|
let containsKey = false;
|
|
@@ -10563,19 +10979,19 @@ function mutationQueuesContainKey(txn, docKey) {
|
|
|
10563
10979
|
* Helper to get a typed SimpleDbStore for the mutations object store.
|
|
10564
10980
|
*/
|
|
10565
10981
|
function mutationsStore(txn) {
|
|
10566
|
-
return getStore(txn,
|
|
10982
|
+
return getStore(txn, DbMutationBatchStore);
|
|
10567
10983
|
}
|
|
10568
10984
|
/**
|
|
10569
10985
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10570
10986
|
*/
|
|
10571
10987
|
function documentMutationsStore(txn) {
|
|
10572
|
-
return getStore(txn,
|
|
10988
|
+
return getStore(txn, DbDocumentMutationStore);
|
|
10573
10989
|
}
|
|
10574
10990
|
/**
|
|
10575
10991
|
* Helper to get a typed SimpleDbStore for the mutationQueues object store.
|
|
10576
10992
|
*/
|
|
10577
10993
|
function mutationQueuesStore(txn) {
|
|
10578
|
-
return getStore(txn,
|
|
10994
|
+
return getStore(txn, DbMutationQueueStore);
|
|
10579
10995
|
}
|
|
10580
10996
|
|
|
10581
10997
|
/**
|
|
@@ -10739,14 +11155,14 @@ class IndexedDbTargetCache {
|
|
|
10739
11155
|
}
|
|
10740
11156
|
retrieveMetadata(transaction) {
|
|
10741
11157
|
return globalTargetStore(transaction)
|
|
10742
|
-
.get(
|
|
11158
|
+
.get(DbTargetGlobalKey)
|
|
10743
11159
|
.next(metadata => {
|
|
10744
11160
|
hardAssert(metadata !== null);
|
|
10745
11161
|
return metadata;
|
|
10746
11162
|
});
|
|
10747
11163
|
}
|
|
10748
11164
|
saveMetadata(transaction, metadata) {
|
|
10749
|
-
return globalTargetStore(transaction).put(
|
|
11165
|
+
return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
|
|
10750
11166
|
}
|
|
10751
11167
|
saveTargetData(transaction, targetData) {
|
|
10752
11168
|
return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
|
|
@@ -10779,7 +11195,7 @@ class IndexedDbTargetCache {
|
|
|
10779
11195
|
const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
|
|
10780
11196
|
let result = null;
|
|
10781
11197
|
return targetsStore(transaction)
|
|
10782
|
-
.iterate({ range, index:
|
|
11198
|
+
.iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
|
|
10783
11199
|
const found = fromDbTarget(value);
|
|
10784
11200
|
// After finding a potential match, check that the target is
|
|
10785
11201
|
// actually equal to the requested target.
|
|
@@ -10797,7 +11213,7 @@ class IndexedDbTargetCache {
|
|
|
10797
11213
|
const store = documentTargetStore(txn);
|
|
10798
11214
|
keys.forEach(key => {
|
|
10799
11215
|
const path = encodeResourcePath(key.path);
|
|
10800
|
-
promises.push(store.put(
|
|
11216
|
+
promises.push(store.put({ targetId, path }));
|
|
10801
11217
|
promises.push(this.referenceDelegate.addReference(txn, targetId, key));
|
|
10802
11218
|
});
|
|
10803
11219
|
return PersistencePromise.waitFor(promises);
|
|
@@ -10843,7 +11259,7 @@ class IndexedDbTargetCache {
|
|
|
10843
11259
|
let count = 0;
|
|
10844
11260
|
return documentTargetStore(txn)
|
|
10845
11261
|
.iterate({
|
|
10846
|
-
index:
|
|
11262
|
+
index: DbTargetDocumentDocumentTargetsIndex,
|
|
10847
11263
|
keysOnly: true,
|
|
10848
11264
|
range
|
|
10849
11265
|
}, ([targetId, path], _, control) => {
|
|
@@ -10882,19 +11298,19 @@ class IndexedDbTargetCache {
|
|
|
10882
11298
|
* Helper to get a typed SimpleDbStore for the queries object store.
|
|
10883
11299
|
*/
|
|
10884
11300
|
function targetsStore(txn) {
|
|
10885
|
-
return getStore(txn,
|
|
11301
|
+
return getStore(txn, DbTargetStore);
|
|
10886
11302
|
}
|
|
10887
11303
|
/**
|
|
10888
11304
|
* Helper to get a typed SimpleDbStore for the target globals object store.
|
|
10889
11305
|
*/
|
|
10890
11306
|
function globalTargetStore(txn) {
|
|
10891
|
-
return getStore(txn,
|
|
11307
|
+
return getStore(txn, DbTargetGlobalStore);
|
|
10892
11308
|
}
|
|
10893
11309
|
/**
|
|
10894
11310
|
* Helper to get a typed SimpleDbStore for the document target object store.
|
|
10895
11311
|
*/
|
|
10896
11312
|
function documentTargetStore(txn) {
|
|
10897
|
-
return getStore(txn,
|
|
11313
|
+
return getStore(txn, DbTargetDocumentStore);
|
|
10898
11314
|
}
|
|
10899
11315
|
|
|
10900
11316
|
/**
|
|
@@ -11308,7 +11724,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
11308
11724
|
let nextPath;
|
|
11309
11725
|
return store
|
|
11310
11726
|
.iterate({
|
|
11311
|
-
index:
|
|
11727
|
+
index: DbTargetDocumentDocumentTargetsIndex
|
|
11312
11728
|
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
11313
11729
|
if (targetId === 0) {
|
|
11314
11730
|
// if nextToReport is valid, report it, this is a new key so the
|
|
@@ -11350,113 +11766,12 @@ function sentinelKey$1(key) {
|
|
|
11350
11766
|
* store.
|
|
11351
11767
|
*/
|
|
11352
11768
|
function sentinelRow(key, sequenceNumber) {
|
|
11353
|
-
return
|
|
11769
|
+
return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
|
|
11354
11770
|
}
|
|
11355
11771
|
function writeSentinelKey(txn, key) {
|
|
11356
11772
|
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
11357
11773
|
}
|
|
11358
11774
|
|
|
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
11775
|
/**
|
|
11461
11776
|
* @license
|
|
11462
11777
|
* Copyright 2017 Google LLC
|
|
@@ -11596,7 +11911,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11596
11911
|
*/
|
|
11597
11912
|
addEntry(transaction, key, doc) {
|
|
11598
11913
|
const documentStore = remoteDocumentsStore(transaction);
|
|
11599
|
-
return documentStore.put(
|
|
11914
|
+
return documentStore.put(doc);
|
|
11600
11915
|
}
|
|
11601
11916
|
/**
|
|
11602
11917
|
* Removes a document from the cache.
|
|
@@ -11604,10 +11919,9 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11604
11919
|
* All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
|
|
11605
11920
|
* returned by `newChangeBuffer()` to ensure proper accounting of metadata.
|
|
11606
11921
|
*/
|
|
11607
|
-
removeEntry(transaction, documentKey) {
|
|
11922
|
+
removeEntry(transaction, documentKey, readTime) {
|
|
11608
11923
|
const store = remoteDocumentsStore(transaction);
|
|
11609
|
-
|
|
11610
|
-
return store.delete(key);
|
|
11924
|
+
return store.delete(dbReadTimeKey(documentKey, readTime));
|
|
11611
11925
|
}
|
|
11612
11926
|
/**
|
|
11613
11927
|
* Updates the current cache size.
|
|
@@ -11622,11 +11936,15 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11622
11936
|
});
|
|
11623
11937
|
}
|
|
11624
11938
|
getEntry(transaction, documentKey) {
|
|
11939
|
+
let doc = MutableDocument.newInvalidDocument(documentKey);
|
|
11625
11940
|
return remoteDocumentsStore(transaction)
|
|
11626
|
-
.
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
})
|
|
11941
|
+
.iterate({
|
|
11942
|
+
index: DbRemoteDocumentDocumentKeyIndex,
|
|
11943
|
+
range: IDBKeyRange.only(dbKey(documentKey))
|
|
11944
|
+
}, (_, dbRemoteDoc) => {
|
|
11945
|
+
doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
|
|
11946
|
+
})
|
|
11947
|
+
.next(() => doc);
|
|
11630
11948
|
}
|
|
11631
11949
|
/**
|
|
11632
11950
|
* Looks up an entry in the cache.
|
|
@@ -11635,15 +11953,21 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11635
11953
|
* @returns The cached document entry and its size.
|
|
11636
11954
|
*/
|
|
11637
11955
|
getSizedEntry(transaction, documentKey) {
|
|
11956
|
+
let result = {
|
|
11957
|
+
size: 0,
|
|
11958
|
+
document: MutableDocument.newInvalidDocument(documentKey)
|
|
11959
|
+
};
|
|
11638
11960
|
return remoteDocumentsStore(transaction)
|
|
11639
|
-
.
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11961
|
+
.iterate({
|
|
11962
|
+
index: DbRemoteDocumentDocumentKeyIndex,
|
|
11963
|
+
range: IDBKeyRange.only(dbKey(documentKey))
|
|
11964
|
+
}, (_, dbRemoteDoc) => {
|
|
11965
|
+
result = {
|
|
11966
|
+
document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
|
|
11644
11967
|
size: dbDocumentSize(dbRemoteDoc)
|
|
11645
11968
|
};
|
|
11646
|
-
})
|
|
11969
|
+
})
|
|
11970
|
+
.next(() => result);
|
|
11647
11971
|
}
|
|
11648
11972
|
getEntries(transaction, documentKeys) {
|
|
11649
11973
|
let results = mutableDocumentMap();
|
|
@@ -11674,14 +11998,20 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11674
11998
|
if (documentKeys.isEmpty()) {
|
|
11675
11999
|
return PersistencePromise.resolve();
|
|
11676
12000
|
}
|
|
11677
|
-
|
|
11678
|
-
|
|
12001
|
+
let sortedKeys = new SortedSet(dbKeyComparator);
|
|
12002
|
+
documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
|
|
12003
|
+
const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
|
|
12004
|
+
const keyIter = sortedKeys.getIterator();
|
|
11679
12005
|
let nextKey = keyIter.getNext();
|
|
11680
12006
|
return remoteDocumentsStore(transaction)
|
|
11681
|
-
.iterate({ range }, (
|
|
11682
|
-
const potentialKey = DocumentKey.fromSegments(
|
|
12007
|
+
.iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
|
|
12008
|
+
const potentialKey = DocumentKey.fromSegments([
|
|
12009
|
+
...dbRemoteDoc.prefixPath,
|
|
12010
|
+
dbRemoteDoc.collectionGroup,
|
|
12011
|
+
dbRemoteDoc.documentId
|
|
12012
|
+
]);
|
|
11683
12013
|
// Go through keys not found in cache.
|
|
11684
|
-
while (nextKey &&
|
|
12014
|
+
while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
|
|
11685
12015
|
callback(nextKey, null);
|
|
11686
12016
|
nextKey = keyIter.getNext();
|
|
11687
12017
|
}
|
|
@@ -11692,7 +12022,7 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11692
12022
|
}
|
|
11693
12023
|
// Skip to the next key (if there is one).
|
|
11694
12024
|
if (nextKey) {
|
|
11695
|
-
control.skip(nextKey
|
|
12025
|
+
control.skip(dbKey(nextKey));
|
|
11696
12026
|
}
|
|
11697
12027
|
else {
|
|
11698
12028
|
control.done();
|
|
@@ -11707,41 +12037,44 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11707
12037
|
}
|
|
11708
12038
|
});
|
|
11709
12039
|
}
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
11726
|
-
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
11727
|
-
/* open= */ true);
|
|
11728
|
-
iterationOptions.index = DbRemoteDocument.collectionReadTimeIndex;
|
|
11729
|
-
}
|
|
12040
|
+
getAllFromCollection(transaction, collection, offset) {
|
|
12041
|
+
const startKey = [
|
|
12042
|
+
collection.popLast().toArray(),
|
|
12043
|
+
collection.lastSegment(),
|
|
12044
|
+
toDbTimestampKey(offset.readTime),
|
|
12045
|
+
offset.documentKey.path.isEmpty()
|
|
12046
|
+
? ''
|
|
12047
|
+
: offset.documentKey.path.lastSegment()
|
|
12048
|
+
];
|
|
12049
|
+
const endKey = [
|
|
12050
|
+
collection.popLast().toArray(),
|
|
12051
|
+
collection.lastSegment(),
|
|
12052
|
+
[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
12053
|
+
''
|
|
12054
|
+
];
|
|
11730
12055
|
return remoteDocumentsStore(transaction)
|
|
11731
|
-
.
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
// more than one segment longer than the query path.
|
|
11737
|
-
if (key.length !== immediateChildrenPathLength) {
|
|
11738
|
-
return;
|
|
11739
|
-
}
|
|
11740
|
-
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
|
|
11741
|
-
if (collection.isPrefixOf(document.key.path)) {
|
|
12056
|
+
.loadAll(IDBKeyRange.bound(startKey, endKey, true))
|
|
12057
|
+
.next(dbRemoteDocs => {
|
|
12058
|
+
let results = mutableDocumentMap();
|
|
12059
|
+
for (const dbRemoteDoc of dbRemoteDocs) {
|
|
12060
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
|
|
11742
12061
|
results = results.insert(document.key, document);
|
|
11743
12062
|
}
|
|
11744
|
-
|
|
12063
|
+
return results;
|
|
12064
|
+
});
|
|
12065
|
+
}
|
|
12066
|
+
getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
|
|
12067
|
+
let results = mutableDocumentMap();
|
|
12068
|
+
const startKey = dbCollectionGroupKey(collectionGroup, offset);
|
|
12069
|
+
const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
|
|
12070
|
+
return remoteDocumentsStore(transaction)
|
|
12071
|
+
.iterate({
|
|
12072
|
+
index: DbRemoteDocumentCollectionGroupIndex,
|
|
12073
|
+
range: IDBKeyRange.bound(startKey, endKey, true)
|
|
12074
|
+
}, (_, dbRemoteDoc, control) => {
|
|
12075
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
|
|
12076
|
+
results = results.insert(document.key, document);
|
|
12077
|
+
if (results.size === limit) {
|
|
11745
12078
|
control.done();
|
|
11746
12079
|
}
|
|
11747
12080
|
})
|
|
@@ -11755,14 +12088,14 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11755
12088
|
}
|
|
11756
12089
|
getMetadata(txn) {
|
|
11757
12090
|
return documentGlobalStore(txn)
|
|
11758
|
-
.get(
|
|
12091
|
+
.get(DbRemoteDocumentGlobalKey)
|
|
11759
12092
|
.next(metadata => {
|
|
11760
12093
|
hardAssert(!!metadata);
|
|
11761
12094
|
return metadata;
|
|
11762
12095
|
});
|
|
11763
12096
|
}
|
|
11764
12097
|
setMetadata(txn, metadata) {
|
|
11765
|
-
return documentGlobalStore(txn).put(
|
|
12098
|
+
return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
|
|
11766
12099
|
}
|
|
11767
12100
|
/**
|
|
11768
12101
|
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
@@ -11785,50 +12118,6 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
11785
12118
|
function newIndexedDbRemoteDocumentCache(serializer) {
|
|
11786
12119
|
return new IndexedDbRemoteDocumentCacheImpl(serializer);
|
|
11787
12120
|
}
|
|
11788
|
-
/**
|
|
11789
|
-
* Returns the set of documents that have changed since the specified read
|
|
11790
|
-
* time.
|
|
11791
|
-
*/
|
|
11792
|
-
// PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11793
|
-
function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
|
|
11794
|
-
const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
|
|
11795
|
-
let changedDocs = mutableDocumentMap();
|
|
11796
|
-
let lastReadTime = toDbTimestampKey(sinceReadTime);
|
|
11797
|
-
const documentsStore = remoteDocumentsStore(transaction);
|
|
11798
|
-
const range = IDBKeyRange.lowerBound(lastReadTime, true);
|
|
11799
|
-
return documentsStore
|
|
11800
|
-
.iterate({ index: DbRemoteDocument.readTimeIndex, range }, (_, dbRemoteDoc) => {
|
|
11801
|
-
// Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
|
|
11802
|
-
// the documents directly since we want to keep sentinel deletes.
|
|
11803
|
-
const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
|
|
11804
|
-
changedDocs = changedDocs.insert(doc.key, doc);
|
|
11805
|
-
lastReadTime = dbRemoteDoc.readTime;
|
|
11806
|
-
})
|
|
11807
|
-
.next(() => {
|
|
11808
|
-
return {
|
|
11809
|
-
changedDocs,
|
|
11810
|
-
readTime: fromDbTimestampKey(lastReadTime)
|
|
11811
|
-
};
|
|
11812
|
-
});
|
|
11813
|
-
}
|
|
11814
|
-
/**
|
|
11815
|
-
* Returns the read time of the most recently read document in the cache, or
|
|
11816
|
-
* SnapshotVersion.min() if not available.
|
|
11817
|
-
*/
|
|
11818
|
-
// PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11819
|
-
function remoteDocumentCacheGetLastReadTime(transaction) {
|
|
11820
|
-
const documentsStore = remoteDocumentsStore(transaction);
|
|
11821
|
-
// If there are no existing entries, we return SnapshotVersion.min().
|
|
11822
|
-
let readTime = SnapshotVersion.min();
|
|
11823
|
-
return documentsStore
|
|
11824
|
-
.iterate({ index: DbRemoteDocument.readTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
|
|
11825
|
-
if (dbRemoteDoc.readTime) {
|
|
11826
|
-
readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
|
|
11827
|
-
}
|
|
11828
|
-
control.done();
|
|
11829
|
-
})
|
|
11830
|
-
.next(() => readTime);
|
|
11831
|
-
}
|
|
11832
12121
|
/**
|
|
11833
12122
|
* Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
|
|
11834
12123
|
*
|
|
@@ -11846,24 +12135,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11846
12135
|
super();
|
|
11847
12136
|
this.documentCache = documentCache;
|
|
11848
12137
|
this.trackRemovals = trackRemovals;
|
|
11849
|
-
// A map of document sizes prior to applying the changes in
|
|
11850
|
-
this
|
|
12138
|
+
// A map of document sizes and read times prior to applying the changes in
|
|
12139
|
+
// this buffer.
|
|
12140
|
+
this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
11851
12141
|
}
|
|
11852
12142
|
applyChanges(transaction) {
|
|
11853
12143
|
const promises = [];
|
|
11854
12144
|
let sizeDelta = 0;
|
|
11855
12145
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
11856
12146
|
this.changes.forEach((key, documentChange) => {
|
|
11857
|
-
const
|
|
12147
|
+
const previousDoc = this.documentStates.get(key);
|
|
12148
|
+
promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
|
|
11858
12149
|
if (documentChange.isValidDocument()) {
|
|
11859
12150
|
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
|
|
11860
12151
|
collectionParents = collectionParents.add(key.path.popLast());
|
|
11861
12152
|
const size = dbDocumentSize(doc);
|
|
11862
|
-
sizeDelta += size -
|
|
12153
|
+
sizeDelta += size - previousDoc.size;
|
|
11863
12154
|
promises.push(this.documentCache.addEntry(transaction, key, doc));
|
|
11864
12155
|
}
|
|
11865
12156
|
else {
|
|
11866
|
-
sizeDelta -=
|
|
12157
|
+
sizeDelta -= previousDoc.size;
|
|
11867
12158
|
if (this.trackRemovals) {
|
|
11868
12159
|
// In order to track removals, we store a "sentinel delete" in the
|
|
11869
12160
|
// RemoteDocumentCache. This entry is represented by a NoDocument
|
|
@@ -11872,9 +12163,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11872
12163
|
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
|
|
11873
12164
|
promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
|
|
11874
12165
|
}
|
|
11875
|
-
else {
|
|
11876
|
-
promises.push(this.documentCache.removeEntry(transaction, key));
|
|
11877
|
-
}
|
|
11878
12166
|
}
|
|
11879
12167
|
});
|
|
11880
12168
|
collectionParents.forEach(parent => {
|
|
@@ -11888,7 +12176,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11888
12176
|
return this.documentCache
|
|
11889
12177
|
.getSizedEntry(transaction, documentKey)
|
|
11890
12178
|
.next(getResult => {
|
|
11891
|
-
this.
|
|
12179
|
+
this.documentStates.set(documentKey, {
|
|
12180
|
+
size: getResult.size,
|
|
12181
|
+
readTime: getResult.document.readTime
|
|
12182
|
+
});
|
|
11892
12183
|
return getResult.document;
|
|
11893
12184
|
});
|
|
11894
12185
|
}
|
|
@@ -11902,24 +12193,95 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
11902
12193
|
// keys to `DocumentSizeEntry`s. This is to allow returning the
|
|
11903
12194
|
// `MutableDocumentMap` directly, without a conversion.
|
|
11904
12195
|
sizeMap.forEach((documentKey, size) => {
|
|
11905
|
-
this.
|
|
12196
|
+
this.documentStates.set(documentKey, {
|
|
12197
|
+
size,
|
|
12198
|
+
readTime: documents.get(documentKey).readTime
|
|
12199
|
+
});
|
|
11906
12200
|
});
|
|
11907
12201
|
return documents;
|
|
11908
12202
|
});
|
|
11909
12203
|
}
|
|
11910
12204
|
}
|
|
11911
12205
|
function documentGlobalStore(txn) {
|
|
11912
|
-
return getStore(txn,
|
|
12206
|
+
return getStore(txn, DbRemoteDocumentGlobalStore);
|
|
11913
12207
|
}
|
|
11914
12208
|
/**
|
|
11915
12209
|
* Helper to get a typed SimpleDbStore for the remoteDocuments object store.
|
|
11916
12210
|
*/
|
|
11917
12211
|
function remoteDocumentsStore(txn) {
|
|
11918
|
-
return getStore(txn,
|
|
12212
|
+
return getStore(txn, DbRemoteDocumentStore);
|
|
12213
|
+
}
|
|
12214
|
+
/**
|
|
12215
|
+
* Returns a key that can be used for document lookups on the
|
|
12216
|
+
* `DbRemoteDocumentDocumentKeyIndex` index.
|
|
12217
|
+
*/
|
|
12218
|
+
function dbKey(documentKey) {
|
|
12219
|
+
const path = documentKey.path.toArray();
|
|
12220
|
+
return [
|
|
12221
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12222
|
+
/* collection id */ path[path.length - 2],
|
|
12223
|
+
/* document id */ path[path.length - 1]
|
|
12224
|
+
];
|
|
12225
|
+
}
|
|
12226
|
+
/**
|
|
12227
|
+
* Returns a key that can be used for document lookups via the primary key of
|
|
12228
|
+
* the DbRemoteDocument object store.
|
|
12229
|
+
*/
|
|
12230
|
+
function dbReadTimeKey(documentKey, readTime) {
|
|
12231
|
+
const path = documentKey.path.toArray();
|
|
12232
|
+
return [
|
|
12233
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12234
|
+
/* collection id */ path[path.length - 2],
|
|
12235
|
+
toDbTimestampKey(readTime),
|
|
12236
|
+
/* document id */ path[path.length - 1]
|
|
12237
|
+
];
|
|
12238
|
+
}
|
|
12239
|
+
/**
|
|
12240
|
+
* Returns a key that can be used for document lookups on the
|
|
12241
|
+
* `DbRemoteDocumentDocumentCollectionGroupIndex` index.
|
|
12242
|
+
*/
|
|
12243
|
+
function dbCollectionGroupKey(collectionGroup, offset) {
|
|
12244
|
+
const path = offset.documentKey.path.toArray();
|
|
12245
|
+
return [
|
|
12246
|
+
/* collection id */ collectionGroup,
|
|
12247
|
+
toDbTimestampKey(offset.readTime),
|
|
12248
|
+
/* prefix path */ path.slice(0, path.length - 2),
|
|
12249
|
+
/* document id */ path.length > 0 ? path[path.length - 1] : ''
|
|
12250
|
+
];
|
|
11919
12251
|
}
|
|
11920
|
-
|
|
11921
|
-
|
|
12252
|
+
/**
|
|
12253
|
+
* Comparator that compares document keys according to the primary key sorting
|
|
12254
|
+
* used by the `DbRemoteDocumentDocument` store (by collection path and then
|
|
12255
|
+
* document ID).
|
|
12256
|
+
*/
|
|
12257
|
+
function dbKeyComparator(l, r) {
|
|
12258
|
+
const cmp = l.path.length - r.path.length;
|
|
12259
|
+
return cmp !== 0 ? cmp : DocumentKey.comparator(l, r);
|
|
11922
12260
|
}
|
|
12261
|
+
/**
|
|
12262
|
+
* Schema Version for the Web client:
|
|
12263
|
+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
12264
|
+
* Document Cache
|
|
12265
|
+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
12266
|
+
* longer required because migration 3 unconditionally clears it.
|
|
12267
|
+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
12268
|
+
* to limbo resolution. Addresses
|
|
12269
|
+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
12270
|
+
* 4. Multi-Tab Support.
|
|
12271
|
+
* 5. Removal of held write acks.
|
|
12272
|
+
* 6. Create document global for tracking document cache size.
|
|
12273
|
+
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
12274
|
+
* 8. Add collection-parent index for Collection Group queries.
|
|
12275
|
+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
12276
|
+
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
12277
|
+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
12278
|
+
* 11. Add bundles and named_queries for bundle support.
|
|
12279
|
+
* 12. Add document overlays.
|
|
12280
|
+
* 13. Rewrite the keys of the remote document cache to allow for efficient
|
|
12281
|
+
* document lookup via `getAll()`.
|
|
12282
|
+
* 14. Add indexing support.
|
|
12283
|
+
*/
|
|
12284
|
+
const SCHEMA_VERSION = 13;
|
|
11923
12285
|
|
|
11924
12286
|
/**
|
|
11925
12287
|
* @license
|
|
@@ -11955,7 +12317,7 @@ class SchemaConverter {
|
|
|
11955
12317
|
createPrimaryClientStore(db);
|
|
11956
12318
|
createMutationQueue(db);
|
|
11957
12319
|
createQueryCache(db);
|
|
11958
|
-
|
|
12320
|
+
createLegacyRemoteDocumentCache(db);
|
|
11959
12321
|
}
|
|
11960
12322
|
// Migration 2 to populate the targetGlobal object no longer needed since
|
|
11961
12323
|
// migration 3 unconditionally clears it.
|
|
@@ -12004,7 +12366,8 @@ class SchemaConverter {
|
|
|
12004
12366
|
// to the DbRemoteDocument object store itself. Since the previous change
|
|
12005
12367
|
// log only contained transient data, we can drop its object store.
|
|
12006
12368
|
dropRemoteDocumentChangesStore(db);
|
|
12007
|
-
|
|
12369
|
+
// Note: Schema version 9 used to create a read time index for the
|
|
12370
|
+
// RemoteDocumentCache. This is now done with schema version 13.
|
|
12008
12371
|
});
|
|
12009
12372
|
}
|
|
12010
12373
|
if (fromVersion < 10 && toVersion >= 10) {
|
|
@@ -12022,6 +12385,12 @@ class SchemaConverter {
|
|
|
12022
12385
|
});
|
|
12023
12386
|
}
|
|
12024
12387
|
if (fromVersion < 13 && toVersion >= 13) {
|
|
12388
|
+
p = p
|
|
12389
|
+
.next(() => createRemoteDocumentCache(db))
|
|
12390
|
+
.next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
|
|
12391
|
+
.next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
|
|
12392
|
+
}
|
|
12393
|
+
if (fromVersion < 14 && toVersion >= 14) {
|
|
12025
12394
|
p = p.next(() => {
|
|
12026
12395
|
createFieldIndex(db);
|
|
12027
12396
|
});
|
|
@@ -12029,27 +12398,27 @@ class SchemaConverter {
|
|
|
12029
12398
|
return p;
|
|
12030
12399
|
}
|
|
12031
12400
|
addDocumentGlobal(txn) {
|
|
12032
|
-
let
|
|
12401
|
+
let byteSize = 0;
|
|
12033
12402
|
return txn
|
|
12034
|
-
.store(
|
|
12403
|
+
.store(DbRemoteDocumentStore$1)
|
|
12035
12404
|
.iterate((_, doc) => {
|
|
12036
|
-
|
|
12405
|
+
byteSize += dbDocumentSize(doc);
|
|
12037
12406
|
})
|
|
12038
12407
|
.next(() => {
|
|
12039
|
-
const metadata =
|
|
12408
|
+
const metadata = { byteSize };
|
|
12040
12409
|
return txn
|
|
12041
|
-
.store(
|
|
12042
|
-
.put(
|
|
12410
|
+
.store(DbRemoteDocumentGlobalStore)
|
|
12411
|
+
.put(DbRemoteDocumentGlobalKey, metadata);
|
|
12043
12412
|
});
|
|
12044
12413
|
}
|
|
12045
12414
|
removeAcknowledgedMutations(txn) {
|
|
12046
|
-
const queuesStore = txn.store(
|
|
12047
|
-
const mutationsStore = txn.store(
|
|
12415
|
+
const queuesStore = txn.store(DbMutationQueueStore);
|
|
12416
|
+
const mutationsStore = txn.store(DbMutationBatchStore);
|
|
12048
12417
|
return queuesStore.loadAll().next(queues => {
|
|
12049
12418
|
return PersistencePromise.forEach(queues, (queue) => {
|
|
12050
12419
|
const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
|
|
12051
12420
|
return mutationsStore
|
|
12052
|
-
.loadAll(
|
|
12421
|
+
.loadAll(DbMutationBatchUserMutationsIndex, range)
|
|
12053
12422
|
.next(dbBatches => {
|
|
12054
12423
|
return PersistencePromise.forEach(dbBatches, (dbBatch) => {
|
|
12055
12424
|
hardAssert(dbBatch.userId === queue.userId);
|
|
@@ -12065,12 +12434,16 @@ class SchemaConverter {
|
|
|
12065
12434
|
* with a sequence number. Missing rows are given the most recently used sequence number.
|
|
12066
12435
|
*/
|
|
12067
12436
|
ensureSequenceNumbers(txn) {
|
|
12068
|
-
const documentTargetStore = txn.store(
|
|
12069
|
-
const documentsStore = txn.store(
|
|
12070
|
-
const globalTargetStore = txn.store(
|
|
12071
|
-
return globalTargetStore.get(
|
|
12437
|
+
const documentTargetStore = txn.store(DbTargetDocumentStore);
|
|
12438
|
+
const documentsStore = txn.store(DbRemoteDocumentStore$1);
|
|
12439
|
+
const globalTargetStore = txn.store(DbTargetGlobalStore);
|
|
12440
|
+
return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
|
|
12072
12441
|
const writeSentinelKey = (path) => {
|
|
12073
|
-
return documentTargetStore.put(
|
|
12442
|
+
return documentTargetStore.put({
|
|
12443
|
+
targetId: 0,
|
|
12444
|
+
path: encodeResourcePath(path),
|
|
12445
|
+
sequenceNumber: metadata.highestListenSequenceNumber
|
|
12446
|
+
});
|
|
12074
12447
|
};
|
|
12075
12448
|
const promises = [];
|
|
12076
12449
|
return documentsStore
|
|
@@ -12091,10 +12464,10 @@ class SchemaConverter {
|
|
|
12091
12464
|
}
|
|
12092
12465
|
createCollectionParentIndex(db, txn) {
|
|
12093
12466
|
// Create the index.
|
|
12094
|
-
db.createObjectStore(
|
|
12095
|
-
keyPath:
|
|
12467
|
+
db.createObjectStore(DbCollectionParentStore, {
|
|
12468
|
+
keyPath: DbCollectionParentKeyPath
|
|
12096
12469
|
});
|
|
12097
|
-
const collectionParentsStore = txn.store(
|
|
12470
|
+
const collectionParentsStore = txn.store(DbCollectionParentStore);
|
|
12098
12471
|
// Helper to add an index entry iff we haven't already written it.
|
|
12099
12472
|
const cache = new MemoryCollectionParentIndex();
|
|
12100
12473
|
const addEntry = (collectionPath) => {
|
|
@@ -12109,7 +12482,7 @@ class SchemaConverter {
|
|
|
12109
12482
|
};
|
|
12110
12483
|
// Index existing remote documents.
|
|
12111
12484
|
return txn
|
|
12112
|
-
.store(
|
|
12485
|
+
.store(DbRemoteDocumentStore$1)
|
|
12113
12486
|
.iterate({ keysOnly: true }, (pathSegments, _) => {
|
|
12114
12487
|
const path = new ResourcePath(pathSegments);
|
|
12115
12488
|
return addEntry(path.popLast());
|
|
@@ -12117,7 +12490,7 @@ class SchemaConverter {
|
|
|
12117
12490
|
.next(() => {
|
|
12118
12491
|
// Index existing mutations.
|
|
12119
12492
|
return txn
|
|
12120
|
-
.store(
|
|
12493
|
+
.store(DbDocumentMutationStore)
|
|
12121
12494
|
.iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
|
|
12122
12495
|
const path = decodeResourcePath(encodedPath);
|
|
12123
12496
|
return addEntry(path.popLast());
|
|
@@ -12125,71 +12498,99 @@ class SchemaConverter {
|
|
|
12125
12498
|
});
|
|
12126
12499
|
}
|
|
12127
12500
|
rewriteCanonicalIds(txn) {
|
|
12128
|
-
const targetStore = txn.store(
|
|
12501
|
+
const targetStore = txn.store(DbTargetStore);
|
|
12129
12502
|
return targetStore.iterate((key, originalDbTarget) => {
|
|
12130
12503
|
const originalTargetData = fromDbTarget(originalDbTarget);
|
|
12131
12504
|
const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
|
|
12132
12505
|
return targetStore.put(updatedDbTarget);
|
|
12133
12506
|
});
|
|
12134
12507
|
}
|
|
12508
|
+
rewriteRemoteDocumentCache(db, transaction) {
|
|
12509
|
+
const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
|
|
12510
|
+
const writes = [];
|
|
12511
|
+
return legacyRemoteDocumentStore
|
|
12512
|
+
.iterate((_, legacyDocument) => {
|
|
12513
|
+
const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
|
|
12514
|
+
const path = extractKey(legacyDocument).path.toArray();
|
|
12515
|
+
const dbRemoteDocument = {
|
|
12516
|
+
prefixPath: path.slice(0, path.length - 2),
|
|
12517
|
+
collectionGroup: path[path.length - 2],
|
|
12518
|
+
documentId: path[path.length - 1],
|
|
12519
|
+
readTime: legacyDocument.readTime || [0, 0],
|
|
12520
|
+
unknownDocument: legacyDocument.unknownDocument,
|
|
12521
|
+
noDocument: legacyDocument.noDocument,
|
|
12522
|
+
document: legacyDocument.document,
|
|
12523
|
+
hasCommittedMutations: !!legacyDocument.hasCommittedMutations
|
|
12524
|
+
};
|
|
12525
|
+
writes.push(remoteDocumentStore.put(dbRemoteDocument));
|
|
12526
|
+
})
|
|
12527
|
+
.next(() => PersistencePromise.waitFor(writes));
|
|
12528
|
+
}
|
|
12135
12529
|
}
|
|
12136
12530
|
function sentinelKey(path) {
|
|
12137
12531
|
return [0, encodeResourcePath(path)];
|
|
12138
12532
|
}
|
|
12139
12533
|
function createPrimaryClientStore(db) {
|
|
12140
|
-
db.createObjectStore(
|
|
12534
|
+
db.createObjectStore(DbPrimaryClientStore);
|
|
12141
12535
|
}
|
|
12142
12536
|
function createMutationQueue(db) {
|
|
12143
|
-
db.createObjectStore(
|
|
12144
|
-
keyPath:
|
|
12537
|
+
db.createObjectStore(DbMutationQueueStore, {
|
|
12538
|
+
keyPath: DbMutationQueueKeyPath
|
|
12145
12539
|
});
|
|
12146
|
-
const mutationBatchesStore = db.createObjectStore(
|
|
12147
|
-
keyPath:
|
|
12540
|
+
const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12541
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12148
12542
|
autoIncrement: true
|
|
12149
12543
|
});
|
|
12150
|
-
mutationBatchesStore.createIndex(
|
|
12151
|
-
db.createObjectStore(
|
|
12544
|
+
mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12545
|
+
db.createObjectStore(DbDocumentMutationStore);
|
|
12152
12546
|
}
|
|
12153
12547
|
/**
|
|
12154
12548
|
* Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
|
|
12155
12549
|
* and rewrites all data.
|
|
12156
12550
|
*/
|
|
12157
12551
|
function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
|
|
12158
|
-
const v1MutationsStore = txn.store(
|
|
12552
|
+
const v1MutationsStore = txn.store(DbMutationBatchStore);
|
|
12159
12553
|
return v1MutationsStore.loadAll().next(existingMutations => {
|
|
12160
|
-
db.deleteObjectStore(
|
|
12161
|
-
const mutationsStore = db.createObjectStore(
|
|
12162
|
-
keyPath:
|
|
12554
|
+
db.deleteObjectStore(DbMutationBatchStore);
|
|
12555
|
+
const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
|
|
12556
|
+
keyPath: DbMutationBatchKeyPath,
|
|
12163
12557
|
autoIncrement: true
|
|
12164
12558
|
});
|
|
12165
|
-
mutationsStore.createIndex(
|
|
12166
|
-
const v3MutationsStore = txn.store(
|
|
12559
|
+
mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
|
|
12560
|
+
const v3MutationsStore = txn.store(DbMutationBatchStore);
|
|
12167
12561
|
const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
|
|
12168
12562
|
return PersistencePromise.waitFor(writeAll);
|
|
12169
12563
|
});
|
|
12170
12564
|
}
|
|
12565
|
+
function createLegacyRemoteDocumentCache(db) {
|
|
12566
|
+
db.createObjectStore(DbRemoteDocumentStore$1);
|
|
12567
|
+
}
|
|
12171
12568
|
function createRemoteDocumentCache(db) {
|
|
12172
|
-
db.createObjectStore(
|
|
12569
|
+
const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
|
|
12570
|
+
keyPath: DbRemoteDocumentKeyPath
|
|
12571
|
+
});
|
|
12572
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
|
|
12573
|
+
remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
|
|
12173
12574
|
}
|
|
12174
12575
|
function createDocumentGlobalStore(db) {
|
|
12175
|
-
db.createObjectStore(
|
|
12576
|
+
db.createObjectStore(DbRemoteDocumentGlobalStore);
|
|
12176
12577
|
}
|
|
12177
12578
|
function createQueryCache(db) {
|
|
12178
|
-
const targetDocumentsStore = db.createObjectStore(
|
|
12179
|
-
keyPath:
|
|
12579
|
+
const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
|
|
12580
|
+
keyPath: DbTargetDocumentKeyPath
|
|
12180
12581
|
});
|
|
12181
|
-
targetDocumentsStore.createIndex(
|
|
12182
|
-
const targetStore = db.createObjectStore(
|
|
12183
|
-
keyPath:
|
|
12582
|
+
targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
|
|
12583
|
+
const targetStore = db.createObjectStore(DbTargetStore, {
|
|
12584
|
+
keyPath: DbTargetKeyPath
|
|
12184
12585
|
});
|
|
12185
12586
|
// NOTE: This is unique only because the TargetId is the suffix.
|
|
12186
|
-
targetStore.createIndex(
|
|
12187
|
-
db.createObjectStore(
|
|
12587
|
+
targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
|
|
12588
|
+
db.createObjectStore(DbTargetGlobalStore);
|
|
12188
12589
|
}
|
|
12189
12590
|
function dropQueryCache(db) {
|
|
12190
|
-
db.deleteObjectStore(
|
|
12191
|
-
db.deleteObjectStore(
|
|
12192
|
-
db.deleteObjectStore(
|
|
12591
|
+
db.deleteObjectStore(DbTargetDocumentStore);
|
|
12592
|
+
db.deleteObjectStore(DbTargetStore);
|
|
12593
|
+
db.deleteObjectStore(DbTargetGlobalStore);
|
|
12193
12594
|
}
|
|
12194
12595
|
function dropRemoteDocumentChangesStore(db) {
|
|
12195
12596
|
if (db.objectStoreNames.contains('remoteDocumentChanges')) {
|
|
@@ -12202,58 +12603,65 @@ function dropRemoteDocumentChangesStore(db) {
|
|
|
12202
12603
|
* @param txn - The version upgrade transaction for indexeddb
|
|
12203
12604
|
*/
|
|
12204
12605
|
function writeEmptyTargetGlobalEntry(txn) {
|
|
12205
|
-
const globalStore = txn.store(
|
|
12206
|
-
const metadata =
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
}
|
|
12212
|
-
|
|
12213
|
-
* Creates indices on the RemoteDocuments store used for both multi-tab
|
|
12214
|
-
* and Index-Free queries.
|
|
12215
|
-
*/
|
|
12216
|
-
function createRemoteDocumentReadTimeIndex(txn) {
|
|
12217
|
-
const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
|
|
12218
|
-
remoteDocumentStore.createIndex(DbRemoteDocument.readTimeIndex, DbRemoteDocument.readTimeIndexPath, { unique: false });
|
|
12219
|
-
remoteDocumentStore.createIndex(DbRemoteDocument.collectionReadTimeIndex, DbRemoteDocument.collectionReadTimeIndexPath, { unique: false });
|
|
12606
|
+
const globalStore = txn.store(DbTargetGlobalStore);
|
|
12607
|
+
const metadata = {
|
|
12608
|
+
highestTargetId: 0,
|
|
12609
|
+
highestListenSequenceNumber: 0,
|
|
12610
|
+
lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
|
|
12611
|
+
targetCount: 0
|
|
12612
|
+
};
|
|
12613
|
+
return globalStore.put(DbTargetGlobalKey, metadata);
|
|
12220
12614
|
}
|
|
12221
12615
|
function createClientMetadataStore(db) {
|
|
12222
|
-
db.createObjectStore(
|
|
12223
|
-
keyPath:
|
|
12616
|
+
db.createObjectStore(DbClientMetadataStore, {
|
|
12617
|
+
keyPath: DbClientMetadataKeyPath
|
|
12224
12618
|
});
|
|
12225
12619
|
}
|
|
12226
12620
|
function createBundlesStore(db) {
|
|
12227
|
-
db.createObjectStore(
|
|
12228
|
-
keyPath:
|
|
12621
|
+
db.createObjectStore(DbBundleStore, {
|
|
12622
|
+
keyPath: DbBundleKeyPath
|
|
12229
12623
|
});
|
|
12230
12624
|
}
|
|
12231
12625
|
function createNamedQueriesStore(db) {
|
|
12232
|
-
db.createObjectStore(
|
|
12233
|
-
keyPath:
|
|
12626
|
+
db.createObjectStore(DbNamedQueryStore, {
|
|
12627
|
+
keyPath: DbNamedQueryKeyPath
|
|
12234
12628
|
});
|
|
12235
12629
|
}
|
|
12236
12630
|
function createFieldIndex(db) {
|
|
12237
|
-
const
|
|
12238
|
-
keyPath:
|
|
12631
|
+
const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
|
|
12632
|
+
keyPath: DbIndexConfigurationKeyPath,
|
|
12239
12633
|
autoIncrement: true
|
|
12240
12634
|
});
|
|
12241
|
-
|
|
12242
|
-
const indexStateStore = db.createObjectStore(
|
|
12243
|
-
keyPath:
|
|
12635
|
+
indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
|
|
12636
|
+
const indexStateStore = db.createObjectStore(DbIndexStateStore, {
|
|
12637
|
+
keyPath: DbIndexStateKeyPath
|
|
12244
12638
|
});
|
|
12245
|
-
indexStateStore.createIndex(
|
|
12246
|
-
const indexEntryStore = db.createObjectStore(
|
|
12247
|
-
keyPath:
|
|
12639
|
+
indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
|
|
12640
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
|
|
12641
|
+
keyPath: DbIndexEntryKeyPath
|
|
12248
12642
|
});
|
|
12249
|
-
indexEntryStore.createIndex(
|
|
12643
|
+
indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
|
|
12250
12644
|
}
|
|
12251
12645
|
function createDocumentOverlayStore(db) {
|
|
12252
|
-
const documentOverlayStore = db.createObjectStore(
|
|
12253
|
-
keyPath:
|
|
12646
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
|
|
12647
|
+
keyPath: DbDocumentOverlayKeyPath
|
|
12254
12648
|
});
|
|
12255
|
-
documentOverlayStore.createIndex(
|
|
12256
|
-
documentOverlayStore.createIndex(
|
|
12649
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
|
|
12650
|
+
documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
|
|
12651
|
+
}
|
|
12652
|
+
function extractKey(remoteDoc) {
|
|
12653
|
+
if (remoteDoc.document) {
|
|
12654
|
+
return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
|
|
12655
|
+
}
|
|
12656
|
+
else if (remoteDoc.noDocument) {
|
|
12657
|
+
return DocumentKey.fromSegments(remoteDoc.noDocument.path);
|
|
12658
|
+
}
|
|
12659
|
+
else if (remoteDoc.unknownDocument) {
|
|
12660
|
+
return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
|
|
12661
|
+
}
|
|
12662
|
+
else {
|
|
12663
|
+
return fail();
|
|
12664
|
+
}
|
|
12257
12665
|
}
|
|
12258
12666
|
|
|
12259
12667
|
/**
|
|
@@ -12501,7 +12909,12 @@ class IndexedDbPersistence {
|
|
|
12501
12909
|
return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
|
|
12502
12910
|
const metadataStore = clientMetadataStore(txn);
|
|
12503
12911
|
return metadataStore
|
|
12504
|
-
.put(
|
|
12912
|
+
.put({
|
|
12913
|
+
clientId: this.clientId,
|
|
12914
|
+
updateTimeMs: Date.now(),
|
|
12915
|
+
networkEnabled: this.networkEnabled,
|
|
12916
|
+
inForeground: this.inForeground
|
|
12917
|
+
})
|
|
12505
12918
|
.next(() => {
|
|
12506
12919
|
if (this.isPrimary) {
|
|
12507
12920
|
return this.verifyPrimaryLease(txn).next(success => {
|
|
@@ -12547,7 +12960,7 @@ class IndexedDbPersistence {
|
|
|
12547
12960
|
}
|
|
12548
12961
|
verifyPrimaryLease(txn) {
|
|
12549
12962
|
const store = primaryClientStore(txn);
|
|
12550
|
-
return store.get(
|
|
12963
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12551
12964
|
return PersistencePromise.resolve(this.isLocalClient(primaryClient));
|
|
12552
12965
|
});
|
|
12553
12966
|
}
|
|
@@ -12565,7 +12978,7 @@ class IndexedDbPersistence {
|
|
|
12565
12978
|
!this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
|
|
12566
12979
|
this.lastGarbageCollectionTime = Date.now();
|
|
12567
12980
|
const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
|
|
12568
|
-
const metadataStore = getStore(txn,
|
|
12981
|
+
const metadataStore = getStore(txn, DbClientMetadataStore);
|
|
12569
12982
|
return metadataStore.loadAll().next(existingClients => {
|
|
12570
12983
|
const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
|
|
12571
12984
|
const inactive = existingClients.filter(client => active.indexOf(client) === -1);
|
|
@@ -12619,7 +13032,7 @@ class IndexedDbPersistence {
|
|
|
12619
13032
|
}
|
|
12620
13033
|
const store = primaryClientStore(txn);
|
|
12621
13034
|
return store
|
|
12622
|
-
.get(
|
|
13035
|
+
.get(DbPrimaryClientKey)
|
|
12623
13036
|
.next(currentPrimary => {
|
|
12624
13037
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12625
13038
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
@@ -12700,7 +13113,7 @@ class IndexedDbPersistence {
|
|
|
12700
13113
|
this.detachWindowUnloadHook();
|
|
12701
13114
|
// Use `SimpleDb.runTransaction` directly to avoid failing if another tab
|
|
12702
13115
|
// has obtained the primary lease.
|
|
12703
|
-
await this.simpleDb.runTransaction('shutdown', 'readwrite', [
|
|
13116
|
+
await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
|
|
12704
13117
|
const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
|
|
12705
13118
|
return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
|
|
12706
13119
|
});
|
|
@@ -12807,7 +13220,7 @@ class IndexedDbPersistence {
|
|
|
12807
13220
|
// be turned off.
|
|
12808
13221
|
verifyAllowTabSynchronization(txn) {
|
|
12809
13222
|
const store = primaryClientStore(txn);
|
|
12810
|
-
return store.get(
|
|
13223
|
+
return store.get(DbPrimaryClientKey).next(currentPrimary => {
|
|
12811
13224
|
const currentLeaseIsValid = currentPrimary !== null &&
|
|
12812
13225
|
this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
|
|
12813
13226
|
!this.isClientZombied(currentPrimary.ownerId);
|
|
@@ -12825,8 +13238,12 @@ class IndexedDbPersistence {
|
|
|
12825
13238
|
* method does not verify that the client is eligible for this lease.
|
|
12826
13239
|
*/
|
|
12827
13240
|
acquireOrExtendPrimaryLease(txn) {
|
|
12828
|
-
const newPrimary =
|
|
12829
|
-
|
|
13241
|
+
const newPrimary = {
|
|
13242
|
+
ownerId: this.clientId,
|
|
13243
|
+
allowTabSynchronization: this.allowTabSynchronization,
|
|
13244
|
+
leaseTimestampMs: Date.now()
|
|
13245
|
+
};
|
|
13246
|
+
return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
|
|
12830
13247
|
}
|
|
12831
13248
|
static isAvailable() {
|
|
12832
13249
|
return SimpleDb.isAvailable();
|
|
@@ -12834,10 +13251,10 @@ class IndexedDbPersistence {
|
|
|
12834
13251
|
/** Checks the primary lease and removes it if we are the current primary. */
|
|
12835
13252
|
releasePrimaryLeaseIfHeld(txn) {
|
|
12836
13253
|
const store = primaryClientStore(txn);
|
|
12837
|
-
return store.get(
|
|
13254
|
+
return store.get(DbPrimaryClientKey).next(primaryClient => {
|
|
12838
13255
|
if (this.isLocalClient(primaryClient)) {
|
|
12839
13256
|
logDebug(LOG_TAG$d, 'Releasing primary lease.');
|
|
12840
|
-
return store.delete(
|
|
13257
|
+
return store.delete(DbPrimaryClientKey);
|
|
12841
13258
|
}
|
|
12842
13259
|
else {
|
|
12843
13260
|
return PersistencePromise.resolve();
|
|
@@ -12971,13 +13388,13 @@ class IndexedDbPersistence {
|
|
|
12971
13388
|
* Helper to get a typed SimpleDbStore for the primary client object store.
|
|
12972
13389
|
*/
|
|
12973
13390
|
function primaryClientStore(txn) {
|
|
12974
|
-
return getStore(txn,
|
|
13391
|
+
return getStore(txn, DbPrimaryClientStore);
|
|
12975
13392
|
}
|
|
12976
13393
|
/**
|
|
12977
13394
|
* Helper to get a typed SimpleDbStore for the client metadata object store.
|
|
12978
13395
|
*/
|
|
12979
13396
|
function clientMetadataStore(txn) {
|
|
12980
|
-
return getStore(txn,
|
|
13397
|
+
return getStore(txn, DbClientMetadataStore);
|
|
12981
13398
|
}
|
|
12982
13399
|
/**
|
|
12983
13400
|
* Generates a string used as a prefix when storing data in IndexedDB and
|
|
@@ -13087,18 +13504,17 @@ class LocalDocumentsView {
|
|
|
13087
13504
|
*
|
|
13088
13505
|
* @param transaction - The persistence transaction.
|
|
13089
13506
|
* @param query - The query to match documents against.
|
|
13090
|
-
* @param
|
|
13091
|
-
* documents that have been read since this snapshot version (exclusive).
|
|
13507
|
+
* @param offset - Read time and key to start scanning by (exclusive).
|
|
13092
13508
|
*/
|
|
13093
|
-
getDocumentsMatchingQuery(transaction, query,
|
|
13509
|
+
getDocumentsMatchingQuery(transaction, query, offset) {
|
|
13094
13510
|
if (isDocumentQuery$1(query)) {
|
|
13095
13511
|
return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
|
|
13096
13512
|
}
|
|
13097
13513
|
else if (isCollectionGroupQuery(query)) {
|
|
13098
|
-
return this.getDocumentsMatchingCollectionGroupQuery(transaction, query,
|
|
13514
|
+
return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
|
|
13099
13515
|
}
|
|
13100
13516
|
else {
|
|
13101
|
-
return this.getDocumentsMatchingCollectionQuery(transaction, query,
|
|
13517
|
+
return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
|
|
13102
13518
|
}
|
|
13103
13519
|
}
|
|
13104
13520
|
getDocumentsMatchingDocumentQuery(transaction, docPath) {
|
|
@@ -13111,7 +13527,7 @@ class LocalDocumentsView {
|
|
|
13111
13527
|
return result;
|
|
13112
13528
|
});
|
|
13113
13529
|
}
|
|
13114
|
-
getDocumentsMatchingCollectionGroupQuery(transaction, query,
|
|
13530
|
+
getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
|
|
13115
13531
|
const collectionId = query.collectionGroup;
|
|
13116
13532
|
let results = documentMap();
|
|
13117
13533
|
return this.indexManager
|
|
@@ -13121,7 +13537,7 @@ class LocalDocumentsView {
|
|
|
13121
13537
|
// collectionId and aggregate the results.
|
|
13122
13538
|
return PersistencePromise.forEach(parents, (parent) => {
|
|
13123
13539
|
const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
|
|
13124
|
-
return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery,
|
|
13540
|
+
return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
|
|
13125
13541
|
r.forEach((key, doc) => {
|
|
13126
13542
|
results = results.insert(key, doc);
|
|
13127
13543
|
});
|
|
@@ -13129,11 +13545,11 @@ class LocalDocumentsView {
|
|
|
13129
13545
|
}).next(() => results);
|
|
13130
13546
|
});
|
|
13131
13547
|
}
|
|
13132
|
-
getDocumentsMatchingCollectionQuery(transaction, query,
|
|
13548
|
+
getDocumentsMatchingCollectionQuery(transaction, query, offset) {
|
|
13133
13549
|
// Query the remote documents and overlay mutations.
|
|
13134
13550
|
let results;
|
|
13135
13551
|
return this.remoteDocumentCache
|
|
13136
|
-
.
|
|
13552
|
+
.getAllFromCollection(transaction, query.path, offset)
|
|
13137
13553
|
.next(queryResults => {
|
|
13138
13554
|
results = queryResults;
|
|
13139
13555
|
return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
|
|
@@ -13219,11 +13635,12 @@ class LocalStoreImpl {
|
|
|
13219
13635
|
// TODO(wuandy): Evaluate if TargetId can be part of Target.
|
|
13220
13636
|
this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
|
|
13221
13637
|
/**
|
|
13222
|
-
*
|
|
13638
|
+
* A per collection group index of the last read time processed by
|
|
13639
|
+
* `getNewDocumentChanges()`.
|
|
13223
13640
|
*
|
|
13224
13641
|
* PORTING NOTE: This is only used for multi-tab synchronization.
|
|
13225
13642
|
*/
|
|
13226
|
-
this.
|
|
13643
|
+
this.collectionGroupReadTime = new Map();
|
|
13227
13644
|
this.remoteDocuments = persistence.getRemoteDocumentCache();
|
|
13228
13645
|
this.targetCache = persistence.getTargetCache();
|
|
13229
13646
|
this.bundleCache = persistence.getBundleCache();
|
|
@@ -13758,6 +14175,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
|
|
|
13758
14175
|
? lastLimboFreeSnapshotVersion
|
|
13759
14176
|
: SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
|
|
13760
14177
|
.next(documents => {
|
|
14178
|
+
setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
|
|
13761
14179
|
return { documents, remoteKeys };
|
|
13762
14180
|
});
|
|
13763
14181
|
});
|
|
@@ -13835,28 +14253,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
|
|
|
13835
14253
|
* since the prior call.
|
|
13836
14254
|
*/
|
|
13837
14255
|
// PORTING NOTE: Multi-Tab only.
|
|
13838
|
-
function localStoreGetNewDocumentChanges(localStore) {
|
|
14256
|
+
function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
|
|
13839
14257
|
const localStoreImpl = debugCast(localStore);
|
|
14258
|
+
// Get the current maximum read time for the collection. This should always
|
|
14259
|
+
// exist, but to reduce the chance for regressions we default to
|
|
14260
|
+
// SnapshotVersion.Min()
|
|
14261
|
+
// TODO(indexing): Consider removing the default value.
|
|
14262
|
+
const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
|
|
14263
|
+
SnapshotVersion.min();
|
|
13840
14264
|
return localStoreImpl.persistence
|
|
13841
|
-
.runTransaction('Get new document changes', 'readonly', txn =>
|
|
13842
|
-
|
|
13843
|
-
|
|
14265
|
+
.runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
|
|
14266
|
+
/* limit= */ Number.MAX_SAFE_INTEGER))
|
|
14267
|
+
.then(changedDocs => {
|
|
14268
|
+
setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
|
|
13844
14269
|
return changedDocs;
|
|
13845
14270
|
});
|
|
13846
14271
|
}
|
|
13847
|
-
/**
|
|
13848
|
-
* Reads the newest document change from persistence and moves the internal
|
|
13849
|
-
* synchronization marker forward so that calls to `getNewDocumentChanges()`
|
|
13850
|
-
* only return changes that happened after client initialization.
|
|
13851
|
-
*/
|
|
14272
|
+
/** Sets the collection group's maximum read time from the given documents. */
|
|
13852
14273
|
// PORTING NOTE: Multi-Tab only.
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
.
|
|
13857
|
-
|
|
13858
|
-
|
|
14274
|
+
function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
|
|
14275
|
+
let readTime = SnapshotVersion.min();
|
|
14276
|
+
changedDocs.forEach((_, doc) => {
|
|
14277
|
+
if (doc.readTime.compareTo(readTime) > 0) {
|
|
14278
|
+
readTime = doc.readTime;
|
|
14279
|
+
}
|
|
13859
14280
|
});
|
|
14281
|
+
localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
|
|
13860
14282
|
}
|
|
13861
14283
|
/**
|
|
13862
14284
|
* Creates a new target using the given bundle name, which will be used to
|
|
@@ -14040,7 +14462,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14040
14462
|
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14041
14463
|
}
|
|
14042
14464
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14043
|
-
overlays.forEach(mutation => {
|
|
14465
|
+
overlays.forEach((_, mutation) => {
|
|
14044
14466
|
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14045
14467
|
});
|
|
14046
14468
|
return PersistencePromise.resolve();
|
|
@@ -14054,7 +14476,7 @@ class MemoryDocumentOverlayCache {
|
|
|
14054
14476
|
return PersistencePromise.resolve();
|
|
14055
14477
|
}
|
|
14056
14478
|
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14057
|
-
const result =
|
|
14479
|
+
const result = newOverlayMap();
|
|
14058
14480
|
const immediateChildrenPathLength = collection.length + 1;
|
|
14059
14481
|
const prefix = new DocumentKey(collection.child(''));
|
|
14060
14482
|
const iter = this.overlays.getIteratorFrom(prefix);
|
|
@@ -14088,19 +14510,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14088
14510
|
if (overlay.largestBatchId > sinceBatchId) {
|
|
14089
14511
|
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14090
14512
|
if (overlaysForBatchId === null) {
|
|
14091
|
-
overlaysForBatchId =
|
|
14513
|
+
overlaysForBatchId = newOverlayMap();
|
|
14092
14514
|
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14093
14515
|
}
|
|
14094
14516
|
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14095
14517
|
}
|
|
14096
14518
|
}
|
|
14097
|
-
const result =
|
|
14519
|
+
const result = newOverlayMap();
|
|
14098
14520
|
const batchIter = batchIdToOverlays.getIterator();
|
|
14099
14521
|
while (batchIter.hasNext()) {
|
|
14100
14522
|
const entry = batchIter.getNext();
|
|
14101
14523
|
const overlays = entry.value;
|
|
14102
|
-
overlays.forEach((
|
|
14103
|
-
if (result.size >= count) {
|
|
14524
|
+
overlays.forEach((key, overlay) => result.set(key, overlay));
|
|
14525
|
+
if (result.size() >= count) {
|
|
14104
14526
|
break;
|
|
14105
14527
|
}
|
|
14106
14528
|
}
|
|
@@ -14113,16 +14535,19 @@ class MemoryDocumentOverlayCache {
|
|
|
14113
14535
|
// Remove the association of the overlay to its batch id.
|
|
14114
14536
|
const existing = this.overlays.get(mutation.key);
|
|
14115
14537
|
if (existing !== null) {
|
|
14116
|
-
this.overlayByBatchId
|
|
14538
|
+
const newSet = this.overlayByBatchId
|
|
14539
|
+
.get(existing.largestBatchId)
|
|
14540
|
+
.delete(mutation.key);
|
|
14541
|
+
this.overlayByBatchId.set(existing.largestBatchId, newSet);
|
|
14117
14542
|
}
|
|
14118
14543
|
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14119
14544
|
// Create the association of this overlay to the given largestBatchId.
|
|
14120
14545
|
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14121
14546
|
if (batch === undefined) {
|
|
14122
|
-
batch =
|
|
14547
|
+
batch = documentKeySet();
|
|
14123
14548
|
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14124
14549
|
}
|
|
14125
|
-
batch.add(mutation.key);
|
|
14550
|
+
this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
|
|
14126
14551
|
}
|
|
14127
14552
|
}
|
|
14128
14553
|
|
|
@@ -14536,7 +14961,7 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
14536
14961
|
});
|
|
14537
14962
|
return PersistencePromise.resolve(results);
|
|
14538
14963
|
}
|
|
14539
|
-
|
|
14964
|
+
getAllFromCollection(transaction, collectionPath, offset) {
|
|
14540
14965
|
let results = mutableDocumentMap();
|
|
14541
14966
|
// Documents are ordered by key, so we can use a prefix scan to narrow down
|
|
14542
14967
|
// the documents we need to match the query against.
|
|
@@ -14551,13 +14976,19 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
14551
14976
|
// Exclude entries from subcollections.
|
|
14552
14977
|
continue;
|
|
14553
14978
|
}
|
|
14554
|
-
if (document
|
|
14979
|
+
if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
|
|
14980
|
+
// The document sorts before the offset.
|
|
14555
14981
|
continue;
|
|
14556
14982
|
}
|
|
14557
14983
|
results = results.insert(document.key, document.mutableCopy());
|
|
14558
14984
|
}
|
|
14559
14985
|
return PersistencePromise.resolve(results);
|
|
14560
14986
|
}
|
|
14987
|
+
getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
|
|
14988
|
+
// This method should only be called from the IndexBackfiller if persistence
|
|
14989
|
+
// is enabled.
|
|
14990
|
+
fail();
|
|
14991
|
+
}
|
|
14561
14992
|
forEachDocumentKey(transaction, f) {
|
|
14562
14993
|
return PersistencePromise.forEach(this.docs, (key) => f(key));
|
|
14563
14994
|
}
|
|
@@ -15010,7 +15441,7 @@ class QueryEngine {
|
|
|
15010
15441
|
}
|
|
15011
15442
|
// Retrieve all results for documents that were updated since the last
|
|
15012
15443
|
// limbo-document free remote snapshot.
|
|
15013
|
-
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
|
|
15444
|
+
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
|
|
15014
15445
|
// We merge `previousResults` into `updateResults`, since
|
|
15015
15446
|
// `updateResults` is already a DocumentMap. If a document is
|
|
15016
15447
|
// contained in both lists, then its contents are the same.
|
|
@@ -15072,7 +15503,7 @@ class QueryEngine {
|
|
|
15072
15503
|
if (getLogLevel() <= LogLevel.DEBUG) {
|
|
15073
15504
|
logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
|
|
15074
15505
|
}
|
|
15075
|
-
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query,
|
|
15506
|
+
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15076
15507
|
}
|
|
15077
15508
|
}
|
|
15078
15509
|
|
|
@@ -15133,8 +15564,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
|
|
|
15133
15564
|
// The WebStorage prefix that plays as a event to indicate the remote documents
|
|
15134
15565
|
// might have changed due to some secondary tabs loading a bundle.
|
|
15135
15566
|
// format of the key is:
|
|
15136
|
-
//
|
|
15137
|
-
|
|
15567
|
+
// firestore_bundle_loaded_v2_<persistenceKey>
|
|
15568
|
+
// The version ending with "v2" stores the list of modified collection groups.
|
|
15569
|
+
const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
|
|
15138
15570
|
function createBundleLoadedKey(persistenceKey) {
|
|
15139
15571
|
return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
|
|
15140
15572
|
}
|
|
@@ -15512,8 +15944,8 @@ class WebStorageSharedClientState {
|
|
|
15512
15944
|
setOnlineState(onlineState) {
|
|
15513
15945
|
this.persistOnlineState(onlineState);
|
|
15514
15946
|
}
|
|
15515
|
-
notifyBundleLoaded() {
|
|
15516
|
-
this.persistBundleLoadedState();
|
|
15947
|
+
notifyBundleLoaded(collectionGroups) {
|
|
15948
|
+
this.persistBundleLoadedState(collectionGroups);
|
|
15517
15949
|
}
|
|
15518
15950
|
shutdown() {
|
|
15519
15951
|
if (this.started) {
|
|
@@ -15597,7 +16029,8 @@ class WebStorageSharedClientState {
|
|
|
15597
16029
|
}
|
|
15598
16030
|
}
|
|
15599
16031
|
else if (storageEvent.key === this.bundleLoadedKey) {
|
|
15600
|
-
|
|
16032
|
+
const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
|
|
16033
|
+
await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
|
|
15601
16034
|
}
|
|
15602
16035
|
});
|
|
15603
16036
|
}
|
|
@@ -15629,8 +16062,9 @@ class WebStorageSharedClientState {
|
|
|
15629
16062
|
const targetMetadata = new QueryTargetMetadata(targetId, state, error);
|
|
15630
16063
|
this.setItem(targetKey, targetMetadata.toWebStorageJSON());
|
|
15631
16064
|
}
|
|
15632
|
-
persistBundleLoadedState() {
|
|
15633
|
-
|
|
16065
|
+
persistBundleLoadedState(collectionGroups) {
|
|
16066
|
+
const json = JSON.stringify(Array.from(collectionGroups));
|
|
16067
|
+
this.setItem(this.bundleLoadedKey, json);
|
|
15634
16068
|
}
|
|
15635
16069
|
/**
|
|
15636
16070
|
* Parses a client state key in WebStorage. Returns null if the key does not
|
|
@@ -15674,6 +16108,9 @@ class WebStorageSharedClientState {
|
|
|
15674
16108
|
fromWebStorageOnlineState(value) {
|
|
15675
16109
|
return SharedOnlineState.fromWebStorageEntry(value);
|
|
15676
16110
|
}
|
|
16111
|
+
fromWebStoreBundleLoadedState(value) {
|
|
16112
|
+
return JSON.parse(value);
|
|
16113
|
+
}
|
|
15677
16114
|
async handleMutationBatchEvent(mutationBatch) {
|
|
15678
16115
|
if (mutationBatch.user.uid !== this.currentUser.uid) {
|
|
15679
16116
|
logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
|
|
@@ -15790,7 +16227,7 @@ class MemorySharedClientState {
|
|
|
15790
16227
|
}
|
|
15791
16228
|
shutdown() { }
|
|
15792
16229
|
writeSequenceNumber(sequenceNumber) { }
|
|
15793
|
-
notifyBundleLoaded() {
|
|
16230
|
+
notifyBundleLoaded(collectionGroups) {
|
|
15794
16231
|
// No op.
|
|
15795
16232
|
}
|
|
15796
16233
|
}
|
|
@@ -15973,9 +16410,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
|
15973
16410
|
metadata.set('X-Firebase-GMPID', appId);
|
|
15974
16411
|
}
|
|
15975
16412
|
metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
|
|
15976
|
-
//
|
|
16413
|
+
// These headers are used to improve routing and project isolation by the
|
|
15977
16414
|
// backend.
|
|
16415
|
+
// TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
|
|
16416
|
+
// released with cl/428820046. Currently blocked because Emulators are now built with Java
|
|
16417
|
+
// 11 from Google3.
|
|
15978
16418
|
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
|
|
16419
|
+
metadata.set('x-goog-request-params', databasePath);
|
|
15979
16420
|
return metadata;
|
|
15980
16421
|
}
|
|
15981
16422
|
/**
|
|
@@ -21086,29 +21527,6 @@ class LocalViewChanges {
|
|
|
21086
21527
|
}
|
|
21087
21528
|
}
|
|
21088
21529
|
|
|
21089
|
-
/**
|
|
21090
|
-
* @license
|
|
21091
|
-
* Copyright 2020 Google LLC
|
|
21092
|
-
*
|
|
21093
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
21094
|
-
* you may not use this file except in compliance with the License.
|
|
21095
|
-
* You may obtain a copy of the License at
|
|
21096
|
-
*
|
|
21097
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
21098
|
-
*
|
|
21099
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
21100
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
21101
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21102
|
-
* See the License for the specific language governing permissions and
|
|
21103
|
-
* limitations under the License.
|
|
21104
|
-
*/
|
|
21105
|
-
class BundleLoadResult {
|
|
21106
|
-
constructor(progress, changedDocs) {
|
|
21107
|
-
this.progress = progress;
|
|
21108
|
-
this.changedDocs = changedDocs;
|
|
21109
|
-
}
|
|
21110
|
-
}
|
|
21111
|
-
|
|
21112
21530
|
/**
|
|
21113
21531
|
* @license
|
|
21114
21532
|
* Copyright 2020 Google LLC
|
|
@@ -21163,6 +21581,8 @@ class BundleLoader {
|
|
|
21163
21581
|
this.queries = [];
|
|
21164
21582
|
/** Batched documents to be saved into storage */
|
|
21165
21583
|
this.documents = [];
|
|
21584
|
+
/** The collection groups affected by this bundle. */
|
|
21585
|
+
this.collectionGroups = new Set();
|
|
21166
21586
|
this.progress = bundleInitialProgress(bundleMetadata);
|
|
21167
21587
|
}
|
|
21168
21588
|
/**
|
|
@@ -21182,6 +21602,8 @@ class BundleLoader {
|
|
|
21182
21602
|
if (!element.payload.documentMetadata.exists) {
|
|
21183
21603
|
++documentsLoaded;
|
|
21184
21604
|
}
|
|
21605
|
+
const path = ResourcePath.fromString(element.payload.documentMetadata.name);
|
|
21606
|
+
this.collectionGroups.add(path.get(path.length - 2));
|
|
21185
21607
|
}
|
|
21186
21608
|
else if (element.payload.document) {
|
|
21187
21609
|
this.documents[this.documents.length - 1].document =
|
|
@@ -21212,13 +21634,17 @@ class BundleLoader {
|
|
|
21212
21634
|
* Update the progress to 'Success' and return the updated progress.
|
|
21213
21635
|
*/
|
|
21214
21636
|
async complete() {
|
|
21215
|
-
const
|
|
21637
|
+
const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
|
|
21216
21638
|
const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
|
|
21217
21639
|
for (const q of this.queries) {
|
|
21218
21640
|
await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
|
|
21219
21641
|
}
|
|
21220
21642
|
this.progress.taskState = 'Success';
|
|
21221
|
-
return
|
|
21643
|
+
return {
|
|
21644
|
+
progress: this.progress,
|
|
21645
|
+
changedCollectionGroups: this.collectionGroups,
|
|
21646
|
+
changedDocs
|
|
21647
|
+
};
|
|
21222
21648
|
}
|
|
21223
21649
|
}
|
|
21224
21650
|
/**
|
|
@@ -22297,9 +22723,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
|
|
|
22297
22723
|
* snapshots if needed.
|
|
22298
22724
|
*/
|
|
22299
22725
|
// PORTING NOTE: Multi-Tab only.
|
|
22300
|
-
async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
|
|
22726
|
+
async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
|
|
22301
22727
|
const syncEngineImpl = debugCast(syncEngine);
|
|
22302
|
-
return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
|
|
22728
|
+
return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
|
|
22303
22729
|
}
|
|
22304
22730
|
/** Applies a mutation state to an existing batch. */
|
|
22305
22731
|
// PORTING NOTE: Multi-Tab only.
|
|
@@ -22464,11 +22890,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
|
|
|
22464
22890
|
logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
|
|
22465
22891
|
return;
|
|
22466
22892
|
}
|
|
22467
|
-
|
|
22893
|
+
const query = syncEngineImpl.queriesByTarget.get(targetId);
|
|
22894
|
+
if (query && query.length > 0) {
|
|
22468
22895
|
switch (state) {
|
|
22469
22896
|
case 'current':
|
|
22470
22897
|
case 'not-current': {
|
|
22471
|
-
const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
|
|
22898
|
+
const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
|
|
22472
22899
|
const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
|
|
22473
22900
|
await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
|
|
22474
22901
|
break;
|
|
@@ -22551,10 +22978,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
|
|
|
22551
22978
|
function syncEngineLoadBundle(syncEngine, bundleReader, task) {
|
|
22552
22979
|
const syncEngineImpl = debugCast(syncEngine);
|
|
22553
22980
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
22554
|
-
loadBundleImpl(syncEngineImpl, bundleReader, task).then(
|
|
22555
|
-
syncEngineImpl.sharedClientState.notifyBundleLoaded();
|
|
22981
|
+
loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
|
|
22982
|
+
syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
|
|
22556
22983
|
});
|
|
22557
22984
|
}
|
|
22985
|
+
/** Loads a bundle and returns the list of affected collection groups. */
|
|
22558
22986
|
async function loadBundleImpl(syncEngine, reader, task) {
|
|
22559
22987
|
try {
|
|
22560
22988
|
const metadata = await reader.getMetadata();
|
|
@@ -22562,7 +22990,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
|
|
|
22562
22990
|
if (skip) {
|
|
22563
22991
|
await reader.close();
|
|
22564
22992
|
task._completeWith(bundleSuccessProgress(metadata));
|
|
22565
|
-
return;
|
|
22993
|
+
return Promise.resolve(new Set());
|
|
22566
22994
|
}
|
|
22567
22995
|
task._updateProgress(bundleInitialProgress(metadata));
|
|
22568
22996
|
const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
|
|
@@ -22576,18 +23004,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
|
|
|
22576
23004
|
element = await reader.nextElement();
|
|
22577
23005
|
}
|
|
22578
23006
|
const result = await loader.complete();
|
|
22579
|
-
// TODO(b/160876443): This currently raises snapshots with
|
|
22580
|
-
// `fromCache=false` if users already listen to some queries and bundles
|
|
22581
|
-
// has newer version.
|
|
22582
23007
|
await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
|
|
22583
23008
|
/* remoteEvent */ undefined);
|
|
22584
23009
|
// Save metadata, so loading the same bundle will skip.
|
|
22585
23010
|
await localStoreSaveBundle(syncEngine.localStore, metadata);
|
|
22586
23011
|
task._completeWith(result.progress);
|
|
23012
|
+
return Promise.resolve(result.changedCollectionGroups);
|
|
22587
23013
|
}
|
|
22588
23014
|
catch (e) {
|
|
22589
23015
|
logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
|
|
22590
23016
|
task._failWith(e);
|
|
23017
|
+
return Promise.resolve(new Set());
|
|
22591
23018
|
}
|
|
22592
23019
|
}
|
|
22593
23020
|
|
|
@@ -22656,7 +23083,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
|
|
|
22656
23083
|
}
|
|
22657
23084
|
async initialize(cfg) {
|
|
22658
23085
|
await super.initialize(cfg);
|
|
22659
|
-
await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
|
|
22660
23086
|
await this.onlineComponentProvider.initialize(this, cfg);
|
|
22661
23087
|
// Enqueue writes from a previous session
|
|
22662
23088
|
await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);
|