@firebase/firestore 3.4.4 → 3.4.5-canary.8ac8fb099
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 +12 -0
- package/dist/firestore/src/api/credentials.d.ts +1 -0
- package/dist/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
- package/dist/firestore/src/api/snapshot.d.ts +1 -2
- package/dist/firestore/src/api.d.ts +1 -1
- package/dist/firestore/src/core/database_info.d.ts +1 -0
- package/dist/firestore/src/core/target.d.ts +34 -5
- package/dist/firestore/src/index/index_byte_encoder.d.ts +18 -1
- package/dist/firestore/src/index/index_entry.d.ts +32 -0
- package/dist/firestore/src/lite-api/query.d.ts +2 -2
- package/dist/firestore/src/local/document_overlay_cache.d.ts +72 -0
- package/dist/firestore/src/local/index_manager.d.ts +52 -0
- package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
- package/dist/firestore/src/local/indexeddb_persistence.d.ts +6 -4
- package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
- package/dist/firestore/src/local/indexeddb_schema.d.ts +195 -7
- package/dist/firestore/src/local/local_documents_view.d.ts +0 -1
- package/dist/firestore/src/local/local_serializer.d.ts +19 -3
- package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
- package/dist/firestore/src/local/memory_index_manager.d.ts +11 -0
- package/dist/firestore/src/local/memory_persistence.d.ts +6 -2
- package/dist/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
- package/dist/firestore/src/local/persistence.d.ts +8 -2
- package/dist/firestore/src/local/remote_document_cache.d.ts +7 -9
- package/dist/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
- package/dist/firestore/src/local/simple_db.d.ts +12 -0
- package/dist/firestore/src/model/document.d.ts +7 -0
- package/dist/firestore/src/model/document_key.d.ts +6 -0
- package/dist/firestore/src/model/field_index.d.ts +89 -7
- package/dist/firestore/src/model/overlay.d.ts +32 -0
- package/dist/firestore/src/model/target_index_matcher.d.ts +70 -0
- package/dist/firestore/src/model/type_order.d.ts +2 -1
- package/dist/firestore/src/model/values.d.ts +7 -0
- package/dist/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/firestore/src/platform/node/grpc_connection.d.ts +2 -2
- package/dist/firestore/src/platform/node/load_protos.d.ts +2 -2
- package/dist/firestore/src/util/sorted_set.d.ts +18 -0
- package/dist/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
- package/dist/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
- package/dist/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
- package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
- package/dist/firestore/test/unit/local/test_index_manager.d.ts +11 -0
- package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
- package/dist/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
- package/dist/firestore/test/unit/model/field_index.test.d.ts +17 -0
- package/dist/firestore/test/unit/model/target.test.d.ts +17 -0
- package/dist/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
- package/dist/firestore/test/util/helpers.d.ts +8 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.esm2017.js +5415 -3839
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +5039 -3613
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +2908 -592
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +2888 -593
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +5396 -3862
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +265 -26
- package/dist/lite/firestore/src/api/credentials.d.ts +1 -0
- package/dist/lite/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
- package/dist/lite/firestore/src/api/snapshot.d.ts +1 -2
- package/dist/lite/firestore/src/api.d.ts +1 -1
- package/dist/lite/firestore/src/core/database_info.d.ts +1 -0
- package/dist/lite/firestore/src/core/target.d.ts +34 -5
- package/dist/lite/firestore/src/index/index_byte_encoder.d.ts +18 -1
- package/dist/lite/firestore/src/index/index_entry.d.ts +32 -0
- package/dist/lite/firestore/src/lite-api/query.d.ts +2 -2
- package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +72 -0
- package/dist/lite/firestore/src/local/index_manager.d.ts +52 -0
- package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
- package/dist/lite/firestore/src/local/indexeddb_persistence.d.ts +6 -4
- package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +195 -7
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +0 -1
- package/dist/lite/firestore/src/local/local_serializer.d.ts +19 -3
- package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +11 -0
- package/dist/lite/firestore/src/local/memory_persistence.d.ts +6 -2
- package/dist/lite/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
- package/dist/lite/firestore/src/local/persistence.d.ts +8 -2
- package/dist/lite/firestore/src/local/remote_document_cache.d.ts +7 -9
- package/dist/lite/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
- package/dist/lite/firestore/src/local/simple_db.d.ts +12 -0
- package/dist/lite/firestore/src/model/document.d.ts +7 -0
- package/dist/lite/firestore/src/model/document_key.d.ts +6 -0
- package/dist/lite/firestore/src/model/field_index.d.ts +89 -7
- package/dist/lite/firestore/src/model/overlay.d.ts +32 -0
- package/dist/lite/firestore/src/model/target_index_matcher.d.ts +70 -0
- package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/firestore/src/model/values.d.ts +7 -0
- package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/firestore/src/platform/node/grpc_connection.d.ts +2 -2
- package/dist/lite/firestore/src/platform/node/load_protos.d.ts +2 -2
- package/dist/lite/firestore/src/util/sorted_set.d.ts +18 -0
- package/dist/lite/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
- package/dist/lite/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
- package/dist/lite/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
- package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
- package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +11 -0
- package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
- package/dist/lite/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
- package/dist/lite/firestore/test/unit/model/field_index.test.d.ts +17 -0
- package/dist/lite/firestore/test/unit/model/target.test.d.ts +17 -0
- package/dist/lite/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
- package/dist/lite/firestore/test/util/helpers.d.ts +8 -2
- package/dist/lite/index.browser.esm2017.js +1207 -1174
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +370 -361
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +83 -41
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +83 -41
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +375 -342
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/internal.d.ts +16 -2
- package/dist/lite/packages/firestore/dist/lite/index.browser.esm2017.d.ts +125 -124
- package/dist/lite/packages/firestore/src/api/credentials.d.ts +1 -0
- package/dist/lite/packages/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
- package/dist/lite/packages/firestore/src/api/snapshot.d.ts +1 -2
- package/dist/lite/packages/firestore/src/api.d.ts +1 -1
- package/dist/lite/packages/firestore/src/core/database_info.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/target.d.ts +34 -5
- package/dist/lite/packages/firestore/src/index/index_byte_encoder.d.ts +18 -1
- package/dist/lite/packages/firestore/src/index/index_entry.d.ts +32 -0
- package/dist/lite/packages/firestore/src/lite-api/query.d.ts +2 -2
- package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +72 -0
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +52 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
- package/dist/lite/packages/firestore/src/local/indexeddb_persistence.d.ts +6 -4
- package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +195 -7
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +0 -1
- package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +19 -3
- package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +11 -0
- package/dist/lite/packages/firestore/src/local/memory_persistence.d.ts +6 -2
- package/dist/lite/packages/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
- package/dist/lite/packages/firestore/src/local/persistence.d.ts +8 -2
- package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +7 -9
- package/dist/lite/packages/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
- package/dist/lite/packages/firestore/src/local/simple_db.d.ts +12 -0
- package/dist/lite/packages/firestore/src/model/document.d.ts +7 -0
- package/dist/lite/packages/firestore/src/model/document_key.d.ts +6 -0
- package/dist/lite/packages/firestore/src/model/field_index.d.ts +89 -7
- package/dist/lite/packages/firestore/src/model/overlay.d.ts +32 -0
- package/dist/lite/packages/firestore/src/model/target_index_matcher.d.ts +70 -0
- package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/lite/packages/firestore/src/model/values.d.ts +7 -0
- package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/lite/packages/firestore/src/platform/node/grpc_connection.d.ts +2 -2
- package/dist/lite/packages/firestore/src/platform/node/load_protos.d.ts +2 -2
- package/dist/lite/packages/firestore/src/util/sorted_set.d.ts +18 -0
- package/dist/lite/packages/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
- package/dist/lite/packages/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
- package/dist/lite/packages/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
- package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
- package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +11 -0
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
- package/dist/lite/packages/firestore/test/unit/model/field_index.test.d.ts +17 -0
- package/dist/lite/packages/firestore/test/unit/model/target.test.d.ts +17 -0
- package/dist/lite/packages/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
- package/dist/lite/packages/firestore/test/util/helpers.d.ts +8 -2
- package/dist/lite/private.d.ts +9 -2
- package/dist/packages/firestore/dist/index.esm2017.d.ts +199 -194
- package/dist/packages/firestore/src/api/credentials.d.ts +1 -0
- package/dist/packages/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
- package/dist/packages/firestore/src/api/snapshot.d.ts +1 -2
- package/dist/packages/firestore/src/api.d.ts +1 -1
- package/dist/packages/firestore/src/core/database_info.d.ts +1 -0
- package/dist/packages/firestore/src/core/target.d.ts +34 -5
- package/dist/packages/firestore/src/index/index_byte_encoder.d.ts +18 -1
- package/dist/packages/firestore/src/index/index_entry.d.ts +32 -0
- package/dist/packages/firestore/src/lite-api/query.d.ts +2 -2
- package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +72 -0
- package/dist/packages/firestore/src/local/index_manager.d.ts +52 -0
- package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
- package/dist/packages/firestore/src/local/indexeddb_persistence.d.ts +6 -4
- package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +195 -7
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +0 -1
- package/dist/packages/firestore/src/local/local_serializer.d.ts +19 -3
- package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +11 -0
- package/dist/packages/firestore/src/local/memory_persistence.d.ts +6 -2
- package/dist/packages/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
- package/dist/packages/firestore/src/local/persistence.d.ts +8 -2
- package/dist/packages/firestore/src/local/remote_document_cache.d.ts +7 -9
- package/dist/packages/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
- package/dist/packages/firestore/src/local/simple_db.d.ts +12 -0
- package/dist/packages/firestore/src/model/document.d.ts +7 -0
- package/dist/packages/firestore/src/model/document_key.d.ts +6 -0
- package/dist/packages/firestore/src/model/field_index.d.ts +89 -7
- package/dist/packages/firestore/src/model/overlay.d.ts +32 -0
- package/dist/packages/firestore/src/model/target_index_matcher.d.ts +70 -0
- package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
- package/dist/packages/firestore/src/model/values.d.ts +7 -0
- package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
- package/dist/packages/firestore/src/platform/node/grpc_connection.d.ts +2 -2
- package/dist/packages/firestore/src/platform/node/load_protos.d.ts +2 -2
- package/dist/packages/firestore/src/util/sorted_set.d.ts +18 -0
- package/dist/packages/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
- package/dist/packages/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
- package/dist/packages/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
- package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
- package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +11 -0
- package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
- package/dist/packages/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
- package/dist/packages/firestore/test/unit/model/field_index.test.d.ts +17 -0
- package/dist/packages/firestore/test/unit/model/target.test.d.ts +17 -0
- package/dist/packages/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
- package/dist/packages/firestore/test/util/helpers.d.ts +8 -2
- package/dist/private.d.ts +258 -26
- package/package.json +9 -9
package/dist/index.node.cjs.js
CHANGED
|
@@ -8,11 +8,32 @@ var logger = require('@firebase/logger');
|
|
|
8
8
|
var util = require('util');
|
|
9
9
|
var util$1 = require('@firebase/util');
|
|
10
10
|
var crypto = require('crypto');
|
|
11
|
-
var
|
|
11
|
+
var grpc = require('@grpc/grpc-js');
|
|
12
12
|
var protoLoader = require('@grpc/proto-loader');
|
|
13
13
|
|
|
14
|
+
function _interopNamespace(e) {
|
|
15
|
+
if (e && e.__esModule) return e;
|
|
16
|
+
var n = Object.create(null);
|
|
17
|
+
if (e) {
|
|
18
|
+
Object.keys(e).forEach(function (k) {
|
|
19
|
+
if (k !== 'default') {
|
|
20
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
21
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get: function () { return e[k]; }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
n["default"] = e;
|
|
29
|
+
return Object.freeze(n);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
|
|
33
|
+
var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
|
|
34
|
+
|
|
14
35
|
const name = "@firebase/firestore";
|
|
15
|
-
const version$1 = "3.4.
|
|
36
|
+
const version$1 = "3.4.5-canary.8ac8fb099";
|
|
16
37
|
|
|
17
38
|
/**
|
|
18
39
|
* @license
|
|
@@ -65,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
65
86
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
66
87
|
User.MOCK_USER = new User('mock-user');
|
|
67
88
|
|
|
68
|
-
const version = "9.6.
|
|
89
|
+
const version = "9.6.7-canary.8ac8fb099";
|
|
69
90
|
|
|
70
91
|
/**
|
|
71
92
|
* @license
|
|
@@ -670,13 +691,19 @@ class FirebaseAppCheckTokenProvider {
|
|
|
670
691
|
this.appCheckProvider = appCheckProvider;
|
|
671
692
|
this.forceRefresh = false;
|
|
672
693
|
this.appCheck = null;
|
|
694
|
+
this.latestAppCheckToken = null;
|
|
673
695
|
}
|
|
674
696
|
start(asyncQueue, changeListener) {
|
|
675
697
|
const onTokenChanged = tokenResult => {
|
|
676
698
|
if (tokenResult.error != null) {
|
|
677
699
|
logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
|
|
678
700
|
}
|
|
679
|
-
|
|
701
|
+
const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
|
|
702
|
+
this.latestAppCheckToken = tokenResult.token;
|
|
703
|
+
logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
|
|
704
|
+
return tokenUpdated
|
|
705
|
+
? changeListener(tokenResult.token)
|
|
706
|
+
: Promise.resolve();
|
|
680
707
|
};
|
|
681
708
|
this.tokenListener = (tokenResult) => {
|
|
682
709
|
asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
|
|
@@ -711,6 +738,7 @@ class FirebaseAppCheckTokenProvider {
|
|
|
711
738
|
return this.appCheck.getToken(forceRefresh).then(tokenResult => {
|
|
712
739
|
if (tokenResult) {
|
|
713
740
|
hardAssert(typeof tokenResult.token === 'string');
|
|
741
|
+
this.latestAppCheckToken = tokenResult.token;
|
|
714
742
|
return new AppCheckToken(tokenResult.token);
|
|
715
743
|
}
|
|
716
744
|
else {
|
|
@@ -1196,23 +1224,6 @@ function decodeResourcePath(path) {
|
|
|
1196
1224
|
}
|
|
1197
1225
|
return new ResourcePath(segments);
|
|
1198
1226
|
}
|
|
1199
|
-
|
|
1200
|
-
/**
|
|
1201
|
-
* @license
|
|
1202
|
-
* Copyright 2017 Google LLC
|
|
1203
|
-
*
|
|
1204
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1205
|
-
* you may not use this file except in compliance with the License.
|
|
1206
|
-
* You may obtain a copy of the License at
|
|
1207
|
-
*
|
|
1208
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1209
|
-
*
|
|
1210
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1211
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1212
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1213
|
-
* See the License for the specific language governing permissions and
|
|
1214
|
-
* limitations under the License.
|
|
1215
|
-
*/
|
|
1216
1227
|
/**
|
|
1217
1228
|
* Schema Version for the Web client:
|
|
1218
1229
|
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
@@ -1231,8 +1242,10 @@ function decodeResourcePath(path) {
|
|
|
1231
1242
|
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
1232
1243
|
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
1233
1244
|
* 11. Add bundles and named_queries for bundle support.
|
|
1245
|
+
* 12. Add document overlays.
|
|
1246
|
+
* 13. Add indexing support.
|
|
1234
1247
|
*/
|
|
1235
|
-
const SCHEMA_VERSION =
|
|
1248
|
+
const SCHEMA_VERSION = 12;
|
|
1236
1249
|
/**
|
|
1237
1250
|
* Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
|
|
1238
1251
|
*/
|
|
@@ -1757,9 +1770,7 @@ class DbClientMetadata {
|
|
|
1757
1770
|
DbClientMetadata.store = 'clientMetadata';
|
|
1758
1771
|
/** Keys are automatically assigned via the clientId properties. */
|
|
1759
1772
|
DbClientMetadata.keyPath = 'clientId';
|
|
1760
|
-
/**
|
|
1761
|
-
* A object representing a bundle loaded by the SDK.
|
|
1762
|
-
*/
|
|
1773
|
+
/** An object representing a bundle loaded by the SDK. */
|
|
1763
1774
|
class DbBundle {
|
|
1764
1775
|
constructor(
|
|
1765
1776
|
/** The ID of the loaded bundle. */
|
|
@@ -1776,9 +1787,7 @@ class DbBundle {
|
|
|
1776
1787
|
/** Name of the IndexedDb object store. */
|
|
1777
1788
|
DbBundle.store = 'bundles';
|
|
1778
1789
|
DbBundle.keyPath = 'bundleId';
|
|
1779
|
-
/**
|
|
1780
|
-
* A object representing a named query loaded by the SDK via a bundle.
|
|
1781
|
-
*/
|
|
1790
|
+
/** An object representing a named query loaded by the SDK via a bundle. */
|
|
1782
1791
|
class DbNamedQuery {
|
|
1783
1792
|
constructor(
|
|
1784
1793
|
/** The name of the query. */
|
|
@@ -1795,6 +1804,158 @@ class DbNamedQuery {
|
|
|
1795
1804
|
/** Name of the IndexedDb object store. */
|
|
1796
1805
|
DbNamedQuery.store = 'namedQueries';
|
|
1797
1806
|
DbNamedQuery.keyPath = 'name';
|
|
1807
|
+
/** An object representing the global configuration for a field index. */
|
|
1808
|
+
class DbIndexConfiguration {
|
|
1809
|
+
constructor(
|
|
1810
|
+
/**
|
|
1811
|
+
* The index id for this entry. Undefined for indexes that are not yet
|
|
1812
|
+
* persisted.
|
|
1813
|
+
*/
|
|
1814
|
+
indexId,
|
|
1815
|
+
/** The collection group this index belongs to. */
|
|
1816
|
+
collectionGroup,
|
|
1817
|
+
/** The fields to index for this index. */
|
|
1818
|
+
fields) {
|
|
1819
|
+
this.indexId = indexId;
|
|
1820
|
+
this.collectionGroup = collectionGroup;
|
|
1821
|
+
this.fields = fields;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
/** Name of the IndexedDb object store. */
|
|
1825
|
+
DbIndexConfiguration.store = 'indexConfiguration';
|
|
1826
|
+
DbIndexConfiguration.keyPath = 'indexId';
|
|
1827
|
+
/**
|
|
1828
|
+
* An index that provides access to the index configurations by collection
|
|
1829
|
+
* group.
|
|
1830
|
+
*
|
|
1831
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1832
|
+
* not possible here as the Web client supports concurrent access to
|
|
1833
|
+
* persistence via multi-tab.
|
|
1834
|
+
*/
|
|
1835
|
+
DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
|
|
1836
|
+
DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
|
|
1837
|
+
/**
|
|
1838
|
+
* An object describing how up-to-date the index backfill is for each user and
|
|
1839
|
+
* index.
|
|
1840
|
+
*/
|
|
1841
|
+
class DbIndexState {
|
|
1842
|
+
constructor(
|
|
1843
|
+
/** The index id for this entry. */
|
|
1844
|
+
indexId,
|
|
1845
|
+
/** The user id for this entry. */
|
|
1846
|
+
uid,
|
|
1847
|
+
/**
|
|
1848
|
+
* A number that indicates when the index was last updated (relative to
|
|
1849
|
+
* other indexes).
|
|
1850
|
+
*/
|
|
1851
|
+
sequenceNumber,
|
|
1852
|
+
/**
|
|
1853
|
+
* The latest read time that has been indexed by Firestore for this field
|
|
1854
|
+
* index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
|
|
1855
|
+
*/
|
|
1856
|
+
readTime,
|
|
1857
|
+
/**
|
|
1858
|
+
* The last document that has been indexed for this field index. Empty if
|
|
1859
|
+
* no documents have been indexed.
|
|
1860
|
+
*/
|
|
1861
|
+
documentKey,
|
|
1862
|
+
/**
|
|
1863
|
+
* The largest mutation batch id that has been processed for this index. -1
|
|
1864
|
+
* if no mutations have been indexed.
|
|
1865
|
+
*/
|
|
1866
|
+
largestBatchId) {
|
|
1867
|
+
this.indexId = indexId;
|
|
1868
|
+
this.uid = uid;
|
|
1869
|
+
this.sequenceNumber = sequenceNumber;
|
|
1870
|
+
this.readTime = readTime;
|
|
1871
|
+
this.documentKey = documentKey;
|
|
1872
|
+
this.largestBatchId = largestBatchId;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
/** Name of the IndexedDb object store. */
|
|
1876
|
+
DbIndexState.store = 'indexState';
|
|
1877
|
+
DbIndexState.keyPath = ['indexId', 'uid'];
|
|
1878
|
+
/**
|
|
1879
|
+
* An index that provides access to documents in a collection sorted by last
|
|
1880
|
+
* update time. Used by the backfiller.
|
|
1881
|
+
*
|
|
1882
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1883
|
+
* not possible here as the Web client supports concurrent access to
|
|
1884
|
+
* persistence via multi-tab.
|
|
1885
|
+
*/
|
|
1886
|
+
DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
|
|
1887
|
+
DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1888
|
+
/** An object that stores the encoded entries for all documents and fields. */
|
|
1889
|
+
class DbIndexEntry {
|
|
1890
|
+
constructor(
|
|
1891
|
+
/** The index id for this entry. */
|
|
1892
|
+
indexId,
|
|
1893
|
+
/** The user id for this entry. */
|
|
1894
|
+
uid,
|
|
1895
|
+
/** The encoded array index value for this entry. */
|
|
1896
|
+
arrayValue,
|
|
1897
|
+
/** The encoded directional value for equality and inequality filters. */
|
|
1898
|
+
directionalValue,
|
|
1899
|
+
/** The document key this entry points to. */
|
|
1900
|
+
documentKey) {
|
|
1901
|
+
this.indexId = indexId;
|
|
1902
|
+
this.uid = uid;
|
|
1903
|
+
this.arrayValue = arrayValue;
|
|
1904
|
+
this.directionalValue = directionalValue;
|
|
1905
|
+
this.documentKey = documentKey;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
/** Name of the IndexedDb object store. */
|
|
1909
|
+
DbIndexEntry.store = 'indexEntries';
|
|
1910
|
+
DbIndexEntry.keyPath = [
|
|
1911
|
+
'indexId',
|
|
1912
|
+
'uid',
|
|
1913
|
+
'arrayValue',
|
|
1914
|
+
'directionalValue',
|
|
1915
|
+
'documentKey'
|
|
1916
|
+
];
|
|
1917
|
+
DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
|
|
1918
|
+
DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
|
|
1919
|
+
/**
|
|
1920
|
+
* An object representing a document overlay.
|
|
1921
|
+
*/
|
|
1922
|
+
class DbDocumentOverlay {
|
|
1923
|
+
constructor(
|
|
1924
|
+
/** The user ID to whom this overlay belongs. */
|
|
1925
|
+
userId,
|
|
1926
|
+
/** The path to the collection that contains the document. */
|
|
1927
|
+
collectionPath,
|
|
1928
|
+
/** The ID (key) of the document within the collection. */
|
|
1929
|
+
documentId,
|
|
1930
|
+
/** The collection group to which the document belongs. */
|
|
1931
|
+
collectionGroup,
|
|
1932
|
+
/** The largest batch ID that's been applied for this overlay. */
|
|
1933
|
+
largestBatchId,
|
|
1934
|
+
/** The overlay mutation. */
|
|
1935
|
+
overlayMutation) {
|
|
1936
|
+
this.userId = userId;
|
|
1937
|
+
this.collectionPath = collectionPath;
|
|
1938
|
+
this.documentId = documentId;
|
|
1939
|
+
this.collectionGroup = collectionGroup;
|
|
1940
|
+
this.largestBatchId = largestBatchId;
|
|
1941
|
+
this.overlayMutation = overlayMutation;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
/** Name of the IndexedDb object store. */
|
|
1945
|
+
DbDocumentOverlay.store = 'documentOverlays';
|
|
1946
|
+
DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
|
|
1947
|
+
DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1948
|
+
DbDocumentOverlay.collectionPathOverlayIndexPath = [
|
|
1949
|
+
'userId',
|
|
1950
|
+
'collectionPath',
|
|
1951
|
+
'largestBatchId'
|
|
1952
|
+
];
|
|
1953
|
+
DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1954
|
+
DbDocumentOverlay.collectionGroupOverlayIndexPath = [
|
|
1955
|
+
'userId',
|
|
1956
|
+
'collectionGroup',
|
|
1957
|
+
'largestBatchId'
|
|
1958
|
+
];
|
|
1798
1959
|
// Visible for testing
|
|
1799
1960
|
const V1_STORES = [
|
|
1800
1961
|
DbMutationQueue.store,
|
|
@@ -1809,7 +1970,6 @@ const V1_STORES = [
|
|
|
1809
1970
|
// V2 is no longer usable (see comment at top of file)
|
|
1810
1971
|
// Visible for testing
|
|
1811
1972
|
const V3_STORES = V1_STORES;
|
|
1812
|
-
// Visible for testing
|
|
1813
1973
|
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
1814
1974
|
const V4_STORES = [...V3_STORES, DbClientMetadata.store];
|
|
1815
1975
|
// V5 does not change the set of stores.
|
|
@@ -1819,12 +1979,28 @@ const V8_STORES = [...V6_STORES, DbCollectionParent.store];
|
|
|
1819
1979
|
// V9 does not change the set of stores.
|
|
1820
1980
|
// V10 does not change the set of stores.
|
|
1821
1981
|
const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1982
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
|
|
1983
|
+
const V13_STORES = [
|
|
1984
|
+
...V12_STORES,
|
|
1985
|
+
DbIndexConfiguration.store,
|
|
1986
|
+
DbIndexState.store,
|
|
1987
|
+
DbIndexEntry.store
|
|
1988
|
+
];
|
|
1989
|
+
/** Returns the object stores for the provided schema. */
|
|
1990
|
+
function getObjectStores(schemaVersion) {
|
|
1991
|
+
if (schemaVersion === 13) {
|
|
1992
|
+
return V13_STORES;
|
|
1993
|
+
}
|
|
1994
|
+
else if (schemaVersion === 12) {
|
|
1995
|
+
return V12_STORES;
|
|
1996
|
+
}
|
|
1997
|
+
else if (schemaVersion === 11) {
|
|
1998
|
+
return V11_STORES;
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
fail();
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
1828
2004
|
|
|
1829
2005
|
/**
|
|
1830
2006
|
* @license
|
|
@@ -2065,7 +2241,7 @@ class PersistencePromise {
|
|
|
2065
2241
|
*/
|
|
2066
2242
|
// References to `window` are guarded by SimpleDb.isAvailable()
|
|
2067
2243
|
/* eslint-disable no-restricted-globals */
|
|
2068
|
-
const LOG_TAG$
|
|
2244
|
+
const LOG_TAG$h = 'SimpleDb';
|
|
2069
2245
|
/**
|
|
2070
2246
|
* The maximum number of retry attempts for an IndexedDb transaction that fails
|
|
2071
2247
|
* with a DOMException.
|
|
@@ -2116,11 +2292,20 @@ class SimpleDbTransaction {
|
|
|
2116
2292
|
this.completionDeferred.reject(error);
|
|
2117
2293
|
}
|
|
2118
2294
|
if (!this.aborted) {
|
|
2119
|
-
logDebug(LOG_TAG$
|
|
2295
|
+
logDebug(LOG_TAG$h, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
|
|
2120
2296
|
this.aborted = true;
|
|
2121
2297
|
this.transaction.abort();
|
|
2122
2298
|
}
|
|
2123
2299
|
}
|
|
2300
|
+
maybeCommit() {
|
|
2301
|
+
// If the browser supports V3 IndexedDB, we invoke commit() explicitly to
|
|
2302
|
+
// speed up index DB processing if the event loop remains blocks.
|
|
2303
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2304
|
+
const maybeV3IndexedDb = this.transaction;
|
|
2305
|
+
if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
|
|
2306
|
+
maybeV3IndexedDb.commit();
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2124
2309
|
/**
|
|
2125
2310
|
* Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
|
|
2126
2311
|
* operations performed on the SimpleDbStore happen within the context of this
|
|
@@ -2170,7 +2355,7 @@ class SimpleDb {
|
|
|
2170
2355
|
}
|
|
2171
2356
|
/** Deletes the specified database. */
|
|
2172
2357
|
static delete(name) {
|
|
2173
|
-
logDebug(LOG_TAG$
|
|
2358
|
+
logDebug(LOG_TAG$h, 'Removing database:', name);
|
|
2174
2359
|
return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
|
|
2175
2360
|
}
|
|
2176
2361
|
/** Returns true if IndexedDB is available in the current environment. */
|
|
@@ -2249,7 +2434,7 @@ class SimpleDb {
|
|
|
2249
2434
|
*/
|
|
2250
2435
|
async ensureDb(action) {
|
|
2251
2436
|
if (!this.db) {
|
|
2252
|
-
logDebug(LOG_TAG$
|
|
2437
|
+
logDebug(LOG_TAG$h, 'Opening database:', this.name);
|
|
2253
2438
|
this.db = await new Promise((resolve, reject) => {
|
|
2254
2439
|
// TODO(mikelehen): Investigate browser compatibility.
|
|
2255
2440
|
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
|
|
@@ -2285,12 +2470,12 @@ class SimpleDb {
|
|
|
2285
2470
|
}
|
|
2286
2471
|
};
|
|
2287
2472
|
request.onupgradeneeded = (event) => {
|
|
2288
|
-
logDebug(LOG_TAG$
|
|
2473
|
+
logDebug(LOG_TAG$h, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
|
|
2289
2474
|
const db = event.target.result;
|
|
2290
2475
|
this.schemaConverter
|
|
2291
2476
|
.createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
|
|
2292
2477
|
.next(() => {
|
|
2293
|
-
logDebug(LOG_TAG$
|
|
2478
|
+
logDebug(LOG_TAG$h, 'Database upgrade to version ' + this.version + ' complete');
|
|
2294
2479
|
});
|
|
2295
2480
|
};
|
|
2296
2481
|
});
|
|
@@ -2317,6 +2502,10 @@ class SimpleDb {
|
|
|
2317
2502
|
this.db = await this.ensureDb(action);
|
|
2318
2503
|
const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
|
|
2319
2504
|
const transactionFnResult = transactionFn(transaction)
|
|
2505
|
+
.next(result => {
|
|
2506
|
+
transaction.maybeCommit();
|
|
2507
|
+
return result;
|
|
2508
|
+
})
|
|
2320
2509
|
.catch(error => {
|
|
2321
2510
|
// Abort the transaction if there was an error.
|
|
2322
2511
|
transaction.abort(error);
|
|
@@ -2344,7 +2533,7 @@ class SimpleDb {
|
|
|
2344
2533
|
// exception is wrapped in a generic error by our async/await handling.
|
|
2345
2534
|
const retryable = error.name !== 'FirebaseError' &&
|
|
2346
2535
|
attemptNumber < TRANSACTION_RETRY_COUNT;
|
|
2347
|
-
logDebug(LOG_TAG$
|
|
2536
|
+
logDebug(LOG_TAG$h, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
|
|
2348
2537
|
this.close();
|
|
2349
2538
|
if (!retryable) {
|
|
2350
2539
|
return Promise.reject(error);
|
|
@@ -2431,11 +2620,11 @@ class SimpleDbStore {
|
|
|
2431
2620
|
put(keyOrValue, value) {
|
|
2432
2621
|
let request;
|
|
2433
2622
|
if (value !== undefined) {
|
|
2434
|
-
logDebug(LOG_TAG$
|
|
2623
|
+
logDebug(LOG_TAG$h, 'PUT', this.store.name, keyOrValue, value);
|
|
2435
2624
|
request = this.store.put(value, keyOrValue);
|
|
2436
2625
|
}
|
|
2437
2626
|
else {
|
|
2438
|
-
logDebug(LOG_TAG$
|
|
2627
|
+
logDebug(LOG_TAG$h, 'PUT', this.store.name, '<auto-key>', keyOrValue);
|
|
2439
2628
|
request = this.store.put(keyOrValue);
|
|
2440
2629
|
}
|
|
2441
2630
|
return wrapRequest(request);
|
|
@@ -2448,7 +2637,7 @@ class SimpleDbStore {
|
|
|
2448
2637
|
* @returns The key of the value to add.
|
|
2449
2638
|
*/
|
|
2450
2639
|
add(value) {
|
|
2451
|
-
logDebug(LOG_TAG$
|
|
2640
|
+
logDebug(LOG_TAG$h, 'ADD', this.store.name, value, value);
|
|
2452
2641
|
const request = this.store.add(value);
|
|
2453
2642
|
return wrapRequest(request);
|
|
2454
2643
|
}
|
|
@@ -2468,12 +2657,12 @@ class SimpleDbStore {
|
|
|
2468
2657
|
if (result === undefined) {
|
|
2469
2658
|
result = null;
|
|
2470
2659
|
}
|
|
2471
|
-
logDebug(LOG_TAG$
|
|
2660
|
+
logDebug(LOG_TAG$h, 'GET', this.store.name, key, result);
|
|
2472
2661
|
return result;
|
|
2473
2662
|
});
|
|
2474
2663
|
}
|
|
2475
2664
|
delete(key) {
|
|
2476
|
-
logDebug(LOG_TAG$
|
|
2665
|
+
logDebug(LOG_TAG$h, 'DELETE', this.store.name, key);
|
|
2477
2666
|
const request = this.store.delete(key);
|
|
2478
2667
|
return wrapRequest(request);
|
|
2479
2668
|
}
|
|
@@ -2484,21 +2673,52 @@ class SimpleDbStore {
|
|
|
2484
2673
|
* Returns the number of rows in the store.
|
|
2485
2674
|
*/
|
|
2486
2675
|
count() {
|
|
2487
|
-
logDebug(LOG_TAG$
|
|
2676
|
+
logDebug(LOG_TAG$h, 'COUNT', this.store.name);
|
|
2488
2677
|
const request = this.store.count();
|
|
2489
2678
|
return wrapRequest(request);
|
|
2490
2679
|
}
|
|
2491
2680
|
loadAll(indexOrRange, range) {
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
return
|
|
2681
|
+
const iterateOptions = this.options(indexOrRange, range);
|
|
2682
|
+
// Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
|
|
2683
|
+
// 20% faster. Unfortunately, getAll() does not support custom indices.
|
|
2684
|
+
if (!iterateOptions.index && typeof this.store.getAll === 'function') {
|
|
2685
|
+
const request = this.store.getAll(iterateOptions.range);
|
|
2686
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2687
|
+
request.onerror = (event) => {
|
|
2688
|
+
reject(event.target.error);
|
|
2689
|
+
};
|
|
2690
|
+
request.onsuccess = (event) => {
|
|
2691
|
+
resolve(event.target.result);
|
|
2692
|
+
};
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
else {
|
|
2696
|
+
const cursor = this.cursor(iterateOptions);
|
|
2697
|
+
const results = [];
|
|
2698
|
+
return this.iterateCursor(cursor, (key, value) => {
|
|
2699
|
+
results.push(value);
|
|
2700
|
+
}).next(() => {
|
|
2701
|
+
return results;
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2707
|
+
* elements if no limit is provided.
|
|
2708
|
+
*/
|
|
2709
|
+
loadFirst(range, count) {
|
|
2710
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
2711
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2712
|
+
request.onerror = (event) => {
|
|
2713
|
+
reject(event.target.error);
|
|
2714
|
+
};
|
|
2715
|
+
request.onsuccess = (event) => {
|
|
2716
|
+
resolve(event.target.result);
|
|
2717
|
+
};
|
|
2498
2718
|
});
|
|
2499
2719
|
}
|
|
2500
2720
|
deleteAll(indexOrRange, range) {
|
|
2501
|
-
logDebug(LOG_TAG$
|
|
2721
|
+
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2502
2722
|
const options = this.options(indexOrRange, range);
|
|
2503
2723
|
options.keysOnly = false;
|
|
2504
2724
|
const cursor = this.cursor(options);
|
|
@@ -3107,17 +3327,15 @@ class FieldMask {
|
|
|
3107
3327
|
* See the License for the specific language governing permissions and
|
|
3108
3328
|
* limitations under the License.
|
|
3109
3329
|
*/
|
|
3330
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3110
3331
|
function decodeBase64(encoded) {
|
|
3111
|
-
//
|
|
3112
|
-
//
|
|
3113
|
-
|
|
3114
|
-
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
|
|
3115
|
-
}
|
|
3116
|
-
return new Buffer(encoded, 'base64').toString('binary');
|
|
3332
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
3333
|
+
// This was removed to improve the performance of indexing.
|
|
3334
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3117
3335
|
}
|
|
3118
3336
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3119
3337
|
function encodeBase64(raw) {
|
|
3120
|
-
return
|
|
3338
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3121
3339
|
}
|
|
3122
3340
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3123
3341
|
function isBase64Available() {
|
|
@@ -3158,6 +3376,8 @@ class ByteString {
|
|
|
3158
3376
|
return new ByteString(binaryString);
|
|
3159
3377
|
}
|
|
3160
3378
|
static fromUint8Array(array) {
|
|
3379
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
3380
|
+
// is frequently called during indexing.
|
|
3161
3381
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3162
3382
|
return new ByteString(binaryString);
|
|
3163
3383
|
}
|
|
@@ -3378,6 +3598,75 @@ function getLocalWriteTime(value) {
|
|
|
3378
3598
|
return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
|
|
3379
3599
|
}
|
|
3380
3600
|
|
|
3601
|
+
/**
|
|
3602
|
+
* @license
|
|
3603
|
+
* Copyright 2017 Google LLC
|
|
3604
|
+
*
|
|
3605
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3606
|
+
* you may not use this file except in compliance with the License.
|
|
3607
|
+
* You may obtain a copy of the License at
|
|
3608
|
+
*
|
|
3609
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
3610
|
+
*
|
|
3611
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
3612
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
3613
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3614
|
+
* See the License for the specific language governing permissions and
|
|
3615
|
+
* limitations under the License.
|
|
3616
|
+
*/
|
|
3617
|
+
class DatabaseInfo {
|
|
3618
|
+
/**
|
|
3619
|
+
* Constructs a DatabaseInfo using the provided host, databaseId and
|
|
3620
|
+
* persistenceKey.
|
|
3621
|
+
*
|
|
3622
|
+
* @param databaseId - The database to use.
|
|
3623
|
+
* @param appId - The Firebase App Id.
|
|
3624
|
+
* @param persistenceKey - A unique identifier for this Firestore's local
|
|
3625
|
+
* storage (used in conjunction with the databaseId).
|
|
3626
|
+
* @param host - The Firestore backend host to connect to.
|
|
3627
|
+
* @param ssl - Whether to use SSL when connecting.
|
|
3628
|
+
* @param forceLongPolling - Whether to use the forceLongPolling option
|
|
3629
|
+
* when using WebChannel as the network transport.
|
|
3630
|
+
* @param autoDetectLongPolling - Whether to use the detectBufferingProxy
|
|
3631
|
+
* option when using WebChannel as the network transport.
|
|
3632
|
+
* @param useFetchStreams Whether to use the Fetch API instead of
|
|
3633
|
+
* XMLHTTPRequest
|
|
3634
|
+
*/
|
|
3635
|
+
constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
|
|
3636
|
+
this.databaseId = databaseId;
|
|
3637
|
+
this.appId = appId;
|
|
3638
|
+
this.persistenceKey = persistenceKey;
|
|
3639
|
+
this.host = host;
|
|
3640
|
+
this.ssl = ssl;
|
|
3641
|
+
this.forceLongPolling = forceLongPolling;
|
|
3642
|
+
this.autoDetectLongPolling = autoDetectLongPolling;
|
|
3643
|
+
this.useFetchStreams = useFetchStreams;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
/** The default database name for a project. */
|
|
3647
|
+
const DEFAULT_DATABASE_NAME = '(default)';
|
|
3648
|
+
/**
|
|
3649
|
+
* Represents the database ID a Firestore client is associated with.
|
|
3650
|
+
* @internal
|
|
3651
|
+
*/
|
|
3652
|
+
class DatabaseId {
|
|
3653
|
+
constructor(projectId, database) {
|
|
3654
|
+
this.projectId = projectId;
|
|
3655
|
+
this.database = database ? database : DEFAULT_DATABASE_NAME;
|
|
3656
|
+
}
|
|
3657
|
+
static empty() {
|
|
3658
|
+
return new DatabaseId('', '');
|
|
3659
|
+
}
|
|
3660
|
+
get isDefaultDatabase() {
|
|
3661
|
+
return this.database === DEFAULT_DATABASE_NAME;
|
|
3662
|
+
}
|
|
3663
|
+
isEqual(other) {
|
|
3664
|
+
return (other instanceof DatabaseId &&
|
|
3665
|
+
other.projectId === this.projectId &&
|
|
3666
|
+
other.database === this.database);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3381
3670
|
/**
|
|
3382
3671
|
* @license
|
|
3383
3672
|
* Copyright 2017 Google LLC
|
|
@@ -3449,11 +3738,25 @@ class DocumentKey {
|
|
|
3449
3738
|
static fromName(name) {
|
|
3450
3739
|
return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
|
|
3451
3740
|
}
|
|
3741
|
+
static empty() {
|
|
3742
|
+
return new DocumentKey(ResourcePath.emptyPath());
|
|
3743
|
+
}
|
|
3744
|
+
get collectionGroup() {
|
|
3745
|
+
return this.path.popLast().lastSegment();
|
|
3746
|
+
}
|
|
3452
3747
|
/** Returns true if the document is in the specified collectionId. */
|
|
3453
3748
|
hasCollectionId(collectionId) {
|
|
3454
3749
|
return (this.path.length >= 2 &&
|
|
3455
3750
|
this.path.get(this.path.length - 2) === collectionId);
|
|
3456
3751
|
}
|
|
3752
|
+
/** Returns the collection group (i.e. the name of the parent collection) for this key. */
|
|
3753
|
+
getCollectionGroup() {
|
|
3754
|
+
return this.path.get(this.path.length - 2);
|
|
3755
|
+
}
|
|
3756
|
+
/** Returns the fully qualified path to the parent collection. */
|
|
3757
|
+
getCollectionPath() {
|
|
3758
|
+
return this.path.popLast();
|
|
3759
|
+
}
|
|
3457
3760
|
isEqual(other) {
|
|
3458
3761
|
return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
|
|
3459
3762
|
}
|
|
@@ -3493,6 +3796,17 @@ class DocumentKey {
|
|
|
3493
3796
|
* See the License for the specific language governing permissions and
|
|
3494
3797
|
* limitations under the License.
|
|
3495
3798
|
*/
|
|
3799
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3800
|
+
const MAX_VALUE = {
|
|
3801
|
+
mapValue: {
|
|
3802
|
+
fields: {
|
|
3803
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
const MIN_VALUE = {
|
|
3808
|
+
nullValue: 'NULL_VALUE'
|
|
3809
|
+
};
|
|
3496
3810
|
/** Extracts the backend's type order for the provided value. */
|
|
3497
3811
|
function typeOrder(value) {
|
|
3498
3812
|
if ('nullValue' in value) {
|
|
@@ -3526,6 +3840,9 @@ function typeOrder(value) {
|
|
|
3526
3840
|
if (isServerTimestamp(value)) {
|
|
3527
3841
|
return 4 /* ServerTimestampValue */;
|
|
3528
3842
|
}
|
|
3843
|
+
else if (isMaxValue(value)) {
|
|
3844
|
+
return 9 /* ArrayValue */;
|
|
3845
|
+
}
|
|
3529
3846
|
return 10 /* ObjectValue */;
|
|
3530
3847
|
}
|
|
3531
3848
|
else {
|
|
@@ -3565,6 +3882,8 @@ function valueEquals(left, right) {
|
|
|
3565
3882
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3566
3883
|
case 10 /* ObjectValue */:
|
|
3567
3884
|
return objectEquals(left, right);
|
|
3885
|
+
case 9007199254740991 /* MaxValue */:
|
|
3886
|
+
return true;
|
|
3568
3887
|
default:
|
|
3569
3888
|
return fail();
|
|
3570
3889
|
}
|
|
@@ -3637,6 +3956,7 @@ function valueCompare(left, right) {
|
|
|
3637
3956
|
}
|
|
3638
3957
|
switch (leftType) {
|
|
3639
3958
|
case 0 /* NullValue */:
|
|
3959
|
+
case 9007199254740991 /* MaxValue */:
|
|
3640
3960
|
return 0;
|
|
3641
3961
|
case 1 /* BooleanValue */:
|
|
3642
3962
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -3902,6 +4222,105 @@ function deepClone(source) {
|
|
|
3902
4222
|
else {
|
|
3903
4223
|
return Object.assign({}, source);
|
|
3904
4224
|
}
|
|
4225
|
+
}
|
|
4226
|
+
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4227
|
+
function isMaxValue(value) {
|
|
4228
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
4229
|
+
MAX_VALUE_TYPE);
|
|
4230
|
+
}
|
|
4231
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
4232
|
+
function valuesGetLowerBound(value) {
|
|
4233
|
+
if ('nullValue' in value) {
|
|
4234
|
+
return MIN_VALUE;
|
|
4235
|
+
}
|
|
4236
|
+
else if ('booleanValue' in value) {
|
|
4237
|
+
return { booleanValue: false };
|
|
4238
|
+
}
|
|
4239
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
4240
|
+
return { doubleValue: NaN };
|
|
4241
|
+
}
|
|
4242
|
+
else if ('timestampValue' in value) {
|
|
4243
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
4244
|
+
}
|
|
4245
|
+
else if ('stringValue' in value) {
|
|
4246
|
+
return { stringValue: '' };
|
|
4247
|
+
}
|
|
4248
|
+
else if ('bytesValue' in value) {
|
|
4249
|
+
return { bytesValue: '' };
|
|
4250
|
+
}
|
|
4251
|
+
else if ('referenceValue' in value) {
|
|
4252
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
4253
|
+
}
|
|
4254
|
+
else if ('geoPointValue' in value) {
|
|
4255
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
4256
|
+
}
|
|
4257
|
+
else if ('arrayValue' in value) {
|
|
4258
|
+
return { arrayValue: {} };
|
|
4259
|
+
}
|
|
4260
|
+
else if ('mapValue' in value) {
|
|
4261
|
+
return { mapValue: {} };
|
|
4262
|
+
}
|
|
4263
|
+
else {
|
|
4264
|
+
return fail();
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
4268
|
+
function valuesGetUpperBound(value) {
|
|
4269
|
+
if ('nullValue' in value) {
|
|
4270
|
+
return { booleanValue: false };
|
|
4271
|
+
}
|
|
4272
|
+
else if ('booleanValue' in value) {
|
|
4273
|
+
return { doubleValue: NaN };
|
|
4274
|
+
}
|
|
4275
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
4276
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
4277
|
+
}
|
|
4278
|
+
else if ('timestampValue' in value) {
|
|
4279
|
+
return { stringValue: '' };
|
|
4280
|
+
}
|
|
4281
|
+
else if ('stringValue' in value) {
|
|
4282
|
+
return { bytesValue: '' };
|
|
4283
|
+
}
|
|
4284
|
+
else if ('bytesValue' in value) {
|
|
4285
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
4286
|
+
}
|
|
4287
|
+
else if ('referenceValue' in value) {
|
|
4288
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
4289
|
+
}
|
|
4290
|
+
else if ('geoPointValue' in value) {
|
|
4291
|
+
return { arrayValue: {} };
|
|
4292
|
+
}
|
|
4293
|
+
else if ('arrayValue' in value) {
|
|
4294
|
+
return { mapValue: {} };
|
|
4295
|
+
}
|
|
4296
|
+
else if ('mapValue' in value) {
|
|
4297
|
+
return MAX_VALUE;
|
|
4298
|
+
}
|
|
4299
|
+
else {
|
|
4300
|
+
return fail();
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
function valuesMax(left, right) {
|
|
4304
|
+
if (left === undefined) {
|
|
4305
|
+
return right;
|
|
4306
|
+
}
|
|
4307
|
+
else if (right === undefined) {
|
|
4308
|
+
return left;
|
|
4309
|
+
}
|
|
4310
|
+
else {
|
|
4311
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
function valuesMin(left, right) {
|
|
4315
|
+
if (left === undefined) {
|
|
4316
|
+
return right;
|
|
4317
|
+
}
|
|
4318
|
+
else if (right === undefined) {
|
|
4319
|
+
return left;
|
|
4320
|
+
}
|
|
4321
|
+
else {
|
|
4322
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
4323
|
+
}
|
|
3905
4324
|
}
|
|
3906
4325
|
|
|
3907
4326
|
/**
|
|
@@ -4097,10 +4516,11 @@ function extractFieldMask(value) {
|
|
|
4097
4516
|
* from all views.
|
|
4098
4517
|
*/
|
|
4099
4518
|
class MutableDocument {
|
|
4100
|
-
constructor(key, documentType, version, data, documentState) {
|
|
4519
|
+
constructor(key, documentType, version, readTime, data, documentState) {
|
|
4101
4520
|
this.key = key;
|
|
4102
4521
|
this.documentType = documentType;
|
|
4103
4522
|
this.version = version;
|
|
4523
|
+
this.readTime = readTime;
|
|
4104
4524
|
this.data = data;
|
|
4105
4525
|
this.documentState = documentState;
|
|
4106
4526
|
}
|
|
@@ -4109,18 +4529,18 @@ class MutableDocument {
|
|
|
4109
4529
|
* base document for mutations.
|
|
4110
4530
|
*/
|
|
4111
4531
|
static newInvalidDocument(documentKey) {
|
|
4112
|
-
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4532
|
+
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4113
4533
|
}
|
|
4114
4534
|
/**
|
|
4115
4535
|
* Creates a new document that is known to exist with the given data at the
|
|
4116
4536
|
* given version.
|
|
4117
4537
|
*/
|
|
4118
4538
|
static newFoundDocument(documentKey, version, value) {
|
|
4119
|
-
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, value, 0 /* SYNCED */);
|
|
4539
|
+
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
|
|
4120
4540
|
}
|
|
4121
4541
|
/** Creates a new document that is known to not exist at the given version. */
|
|
4122
4542
|
static newNoDocument(documentKey, version) {
|
|
4123
|
-
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, ObjectValue.empty(), 0 /* SYNCED */);
|
|
4543
|
+
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4124
4544
|
}
|
|
4125
4545
|
/**
|
|
4126
4546
|
* Creates a new document that is known to exist at the given version but
|
|
@@ -4128,7 +4548,7 @@ class MutableDocument {
|
|
|
4128
4548
|
* base document).
|
|
4129
4549
|
*/
|
|
4130
4550
|
static newUnknownDocument(documentKey, version) {
|
|
4131
|
-
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
4551
|
+
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
4132
4552
|
}
|
|
4133
4553
|
/**
|
|
4134
4554
|
* Changes the document type to indicate that it exists and that its version
|
|
@@ -4172,6 +4592,10 @@ class MutableDocument {
|
|
|
4172
4592
|
this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4173
4593
|
return this;
|
|
4174
4594
|
}
|
|
4595
|
+
setReadTime(readTime) {
|
|
4596
|
+
this.readTime = readTime;
|
|
4597
|
+
return this;
|
|
4598
|
+
}
|
|
4175
4599
|
get hasLocalMutations() {
|
|
4176
4600
|
return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4177
4601
|
}
|
|
@@ -4202,7 +4626,7 @@ class MutableDocument {
|
|
|
4202
4626
|
this.data.isEqual(other.data));
|
|
4203
4627
|
}
|
|
4204
4628
|
mutableCopy() {
|
|
4205
|
-
return new MutableDocument(this.key, this.documentType, this.version, this.data.clone(), this.documentState);
|
|
4629
|
+
return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
|
|
4206
4630
|
}
|
|
4207
4631
|
toString() {
|
|
4208
4632
|
return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
|
|
@@ -4227,7 +4651,138 @@ function compareDocumentsByField(field, d1, d2) {
|
|
|
4227
4651
|
|
|
4228
4652
|
/**
|
|
4229
4653
|
* @license
|
|
4230
|
-
* Copyright
|
|
4654
|
+
* Copyright 2021 Google LLC
|
|
4655
|
+
*
|
|
4656
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4657
|
+
* you may not use this file except in compliance with the License.
|
|
4658
|
+
* You may obtain a copy of the License at
|
|
4659
|
+
*
|
|
4660
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
4661
|
+
*
|
|
4662
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4663
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
4664
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4665
|
+
* See the License for the specific language governing permissions and
|
|
4666
|
+
* limitations under the License.
|
|
4667
|
+
*/
|
|
4668
|
+
/**
|
|
4669
|
+
* The initial mutation batch id for each index. Gets updated during index
|
|
4670
|
+
* backfill.
|
|
4671
|
+
*/
|
|
4672
|
+
const INITIAL_LARGEST_BATCH_ID = -1;
|
|
4673
|
+
/**
|
|
4674
|
+
* The initial sequence number for each index. Gets updated during index
|
|
4675
|
+
* backfill.
|
|
4676
|
+
*/
|
|
4677
|
+
const INITIAL_SEQUENCE_NUMBER = 0;
|
|
4678
|
+
/**
|
|
4679
|
+
* An index definition for field indexes in Firestore.
|
|
4680
|
+
*
|
|
4681
|
+
* Every index is associated with a collection. The definition contains a list
|
|
4682
|
+
* of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
|
|
4683
|
+
* `CONTAINS` for ArrayContains/ArrayContainsAny queries).
|
|
4684
|
+
*
|
|
4685
|
+
* Unlike the backend, the SDK does not differentiate between collection or
|
|
4686
|
+
* collection group-scoped indices. Every index can be used for both single
|
|
4687
|
+
* collection and collection group queries.
|
|
4688
|
+
*/
|
|
4689
|
+
class FieldIndex {
|
|
4690
|
+
constructor(
|
|
4691
|
+
/**
|
|
4692
|
+
* The index ID. Returns -1 if the index ID is not available (e.g. the index
|
|
4693
|
+
* has not yet been persisted).
|
|
4694
|
+
*/
|
|
4695
|
+
indexId,
|
|
4696
|
+
/** The collection ID this index applies to. */
|
|
4697
|
+
collectionGroup,
|
|
4698
|
+
/** The field segments for this index. */
|
|
4699
|
+
fields,
|
|
4700
|
+
/** Shows how up-to-date the index is for the current user. */
|
|
4701
|
+
indexState) {
|
|
4702
|
+
this.indexId = indexId;
|
|
4703
|
+
this.collectionGroup = collectionGroup;
|
|
4704
|
+
this.fields = fields;
|
|
4705
|
+
this.indexState = indexState;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
/** An ID for an index that has not yet been added to persistence. */
|
|
4709
|
+
FieldIndex.UNKNOWN_ID = -1;
|
|
4710
|
+
/** Returns the ArrayContains/ArrayContainsAny segment for this index. */
|
|
4711
|
+
function fieldIndexGetArraySegment(fieldIndex) {
|
|
4712
|
+
return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
|
|
4713
|
+
}
|
|
4714
|
+
/** Returns all directional (ascending/descending) segments for this index. */
|
|
4715
|
+
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4716
|
+
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4717
|
+
}
|
|
4718
|
+
/** Returns a debug representation of the field index */
|
|
4719
|
+
function fieldIndexToString(fieldIndex) {
|
|
4720
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4721
|
+
}
|
|
4722
|
+
/** An index component consisting of field path and index type. */
|
|
4723
|
+
class IndexSegment {
|
|
4724
|
+
constructor(
|
|
4725
|
+
/** The field path of the component. */
|
|
4726
|
+
fieldPath,
|
|
4727
|
+
/** The fields sorting order. */
|
|
4728
|
+
kind) {
|
|
4729
|
+
this.fieldPath = fieldPath;
|
|
4730
|
+
this.kind = kind;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
/**
|
|
4734
|
+
* Stores the "high water mark" that indicates how updated the Index is for the
|
|
4735
|
+
* current user.
|
|
4736
|
+
*/
|
|
4737
|
+
class IndexState {
|
|
4738
|
+
constructor(
|
|
4739
|
+
/**
|
|
4740
|
+
* Indicates when the index was last updated (relative to other indexes).
|
|
4741
|
+
*/
|
|
4742
|
+
sequenceNumber,
|
|
4743
|
+
/** The the latest indexed read time, document and batch id. */
|
|
4744
|
+
offset) {
|
|
4745
|
+
this.sequenceNumber = sequenceNumber;
|
|
4746
|
+
this.offset = offset;
|
|
4747
|
+
}
|
|
4748
|
+
/** The state of an index that has not yet been backfilled. */
|
|
4749
|
+
static empty() {
|
|
4750
|
+
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
/**
|
|
4754
|
+
* Stores the latest read time, document and batch ID that were processed for an
|
|
4755
|
+
* index.
|
|
4756
|
+
*/
|
|
4757
|
+
class IndexOffset {
|
|
4758
|
+
constructor(
|
|
4759
|
+
/**
|
|
4760
|
+
* The latest read time version that has been indexed by Firestore for this
|
|
4761
|
+
* field index.
|
|
4762
|
+
*/
|
|
4763
|
+
readTime,
|
|
4764
|
+
/**
|
|
4765
|
+
* The key of the last document that was indexed for this query. Use
|
|
4766
|
+
* `DocumentKey.empty()` if no document has been indexed.
|
|
4767
|
+
*/
|
|
4768
|
+
documentKey,
|
|
4769
|
+
/*
|
|
4770
|
+
* The largest mutation batch id that's been processed by Firestore.
|
|
4771
|
+
*/
|
|
4772
|
+
largestBatchId) {
|
|
4773
|
+
this.readTime = readTime;
|
|
4774
|
+
this.documentKey = documentKey;
|
|
4775
|
+
this.largestBatchId = largestBatchId;
|
|
4776
|
+
}
|
|
4777
|
+
/** The state of an index that has not yet been backfilled. */
|
|
4778
|
+
static min() {
|
|
4779
|
+
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
/**
|
|
4784
|
+
* @license
|
|
4785
|
+
* Copyright 2019 Google LLC
|
|
4231
4786
|
*
|
|
4232
4787
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4233
4788
|
* you may not use this file except in compliance with the License.
|
|
@@ -4268,27 +4823,29 @@ function newTarget(path, collectionGroup = null, orderBy = [], filters = [], lim
|
|
|
4268
4823
|
function canonifyTarget(target) {
|
|
4269
4824
|
const targetImpl = debugCast(target);
|
|
4270
4825
|
if (targetImpl.memoizedCanonicalId === null) {
|
|
4271
|
-
let
|
|
4826
|
+
let str = targetImpl.path.canonicalString();
|
|
4272
4827
|
if (targetImpl.collectionGroup !== null) {
|
|
4273
|
-
|
|
4828
|
+
str += '|cg:' + targetImpl.collectionGroup;
|
|
4274
4829
|
}
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4830
|
+
str += '|f:';
|
|
4831
|
+
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4832
|
+
str += '|ob:';
|
|
4833
|
+
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4279
4834
|
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4835
|
+
str += '|l:';
|
|
4836
|
+
str += targetImpl.limit;
|
|
4282
4837
|
}
|
|
4283
4838
|
if (targetImpl.startAt) {
|
|
4284
|
-
|
|
4285
|
-
|
|
4839
|
+
str += '|lb:';
|
|
4840
|
+
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4841
|
+
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4286
4842
|
}
|
|
4287
4843
|
if (targetImpl.endAt) {
|
|
4288
|
-
|
|
4289
|
-
|
|
4844
|
+
str += '|ub:';
|
|
4845
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4846
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4290
4847
|
}
|
|
4291
|
-
targetImpl.memoizedCanonicalId =
|
|
4848
|
+
targetImpl.memoizedCanonicalId = str;
|
|
4292
4849
|
}
|
|
4293
4850
|
return targetImpl.memoizedCanonicalId;
|
|
4294
4851
|
}
|
|
@@ -4311,10 +4868,14 @@ function stringifyTarget(target) {
|
|
|
4311
4868
|
.join(', ')}]`;
|
|
4312
4869
|
}
|
|
4313
4870
|
if (target.startAt) {
|
|
4314
|
-
str += ', startAt: '
|
|
4871
|
+
str += ', startAt: ';
|
|
4872
|
+
str += target.startAt.inclusive ? 'b:' : 'a:';
|
|
4873
|
+
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4315
4874
|
}
|
|
4316
4875
|
if (target.endAt) {
|
|
4317
|
-
str += ', endAt: '
|
|
4876
|
+
str += ', endAt: ';
|
|
4877
|
+
str += target.endAt.inclusive ? 'a:' : 'b:';
|
|
4878
|
+
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4318
4879
|
}
|
|
4319
4880
|
return `Target(${str})`;
|
|
4320
4881
|
}
|
|
@@ -4349,11 +4910,196 @@ function targetEquals(left, right) {
|
|
|
4349
4910
|
}
|
|
4350
4911
|
return boundEquals(left.endAt, right.endAt);
|
|
4351
4912
|
}
|
|
4352
|
-
function
|
|
4913
|
+
function targetIsDocumentTarget(target) {
|
|
4353
4914
|
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4354
4915
|
target.collectionGroup === null &&
|
|
4355
4916
|
target.filters.length === 0);
|
|
4356
4917
|
}
|
|
4918
|
+
/** Returns the field filters that target the given field path. */
|
|
4919
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4920
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4921
|
+
}
|
|
4922
|
+
/**
|
|
4923
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4924
|
+
* filters. Returns `null` if there are no such filters.
|
|
4925
|
+
*/
|
|
4926
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4927
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4928
|
+
if (segment === undefined) {
|
|
4929
|
+
return null;
|
|
4930
|
+
}
|
|
4931
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4932
|
+
switch (fieldFilter.op) {
|
|
4933
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4934
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4935
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4936
|
+
return [fieldFilter.value];
|
|
4937
|
+
// Remaining filters are not array filters.
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
return null;
|
|
4941
|
+
}
|
|
4942
|
+
/**
|
|
4943
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4944
|
+
* `null` if there are no such filters.
|
|
4945
|
+
*/
|
|
4946
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4947
|
+
const values = new Map();
|
|
4948
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4949
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4950
|
+
switch (fieldFilter.op) {
|
|
4951
|
+
case "==" /* EQUAL */:
|
|
4952
|
+
case "in" /* IN */:
|
|
4953
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4954
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4955
|
+
// `value != 'ab'`).
|
|
4956
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4957
|
+
break;
|
|
4958
|
+
case "not-in" /* NOT_IN */:
|
|
4959
|
+
case "!=" /* NOT_EQUAL */:
|
|
4960
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4961
|
+
// segments and hence we can return early here.
|
|
4962
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4963
|
+
return Array.from(values.values());
|
|
4964
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
return null;
|
|
4969
|
+
}
|
|
4970
|
+
/**
|
|
4971
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4972
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4973
|
+
* exists.
|
|
4974
|
+
*/
|
|
4975
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4976
|
+
const values = [];
|
|
4977
|
+
let inclusive = true;
|
|
4978
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4979
|
+
// startAt.
|
|
4980
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4981
|
+
let segmentValue = undefined;
|
|
4982
|
+
let segmentInclusive = true;
|
|
4983
|
+
// Process all filters to find a value for the current field segment
|
|
4984
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4985
|
+
let filterValue = undefined;
|
|
4986
|
+
let filterInclusive = true;
|
|
4987
|
+
switch (fieldFilter.op) {
|
|
4988
|
+
case "<" /* LESS_THAN */:
|
|
4989
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4990
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4991
|
+
break;
|
|
4992
|
+
case "==" /* EQUAL */:
|
|
4993
|
+
case "in" /* IN */:
|
|
4994
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4995
|
+
filterValue = fieldFilter.value;
|
|
4996
|
+
break;
|
|
4997
|
+
case ">" /* GREATER_THAN */:
|
|
4998
|
+
filterValue = fieldFilter.value;
|
|
4999
|
+
filterInclusive = false;
|
|
5000
|
+
break;
|
|
5001
|
+
case "!=" /* NOT_EQUAL */:
|
|
5002
|
+
case "not-in" /* NOT_IN */:
|
|
5003
|
+
filterValue = MIN_VALUE;
|
|
5004
|
+
break;
|
|
5005
|
+
// Remaining filters cannot be used as lower bounds.
|
|
5006
|
+
}
|
|
5007
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
5008
|
+
segmentValue = filterValue;
|
|
5009
|
+
segmentInclusive = filterInclusive;
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
// If there is a startAt bound, compare the values against the existing
|
|
5013
|
+
// boundary to see if we can narrow the scope.
|
|
5014
|
+
if (target.startAt !== null) {
|
|
5015
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5016
|
+
const orderBy = target.orderBy[i];
|
|
5017
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
5018
|
+
const cursorValue = target.startAt.position[i];
|
|
5019
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
5020
|
+
segmentValue = cursorValue;
|
|
5021
|
+
segmentInclusive = target.startAt.inclusive;
|
|
5022
|
+
}
|
|
5023
|
+
break;
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
if (segmentValue === undefined) {
|
|
5028
|
+
// No lower bound exists
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
values.push(segmentValue);
|
|
5032
|
+
inclusive && (inclusive = segmentInclusive);
|
|
5033
|
+
}
|
|
5034
|
+
return new Bound(values, inclusive);
|
|
5035
|
+
}
|
|
5036
|
+
/**
|
|
5037
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
5038
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
5039
|
+
* upper bound exists.
|
|
5040
|
+
*/
|
|
5041
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
5042
|
+
const values = [];
|
|
5043
|
+
let inclusive = true;
|
|
5044
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
5045
|
+
// endAt.
|
|
5046
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5047
|
+
let segmentValue = undefined;
|
|
5048
|
+
let segmentInclusive = true;
|
|
5049
|
+
// Process all filters to find a value for the current field segment
|
|
5050
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5051
|
+
let filterValue = undefined;
|
|
5052
|
+
let filterInclusive = true;
|
|
5053
|
+
switch (fieldFilter.op) {
|
|
5054
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5055
|
+
case ">" /* GREATER_THAN */:
|
|
5056
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
5057
|
+
filterInclusive = false;
|
|
5058
|
+
break;
|
|
5059
|
+
case "==" /* EQUAL */:
|
|
5060
|
+
case "in" /* IN */:
|
|
5061
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5062
|
+
filterValue = fieldFilter.value;
|
|
5063
|
+
break;
|
|
5064
|
+
case "<" /* LESS_THAN */:
|
|
5065
|
+
filterValue = fieldFilter.value;
|
|
5066
|
+
filterInclusive = false;
|
|
5067
|
+
break;
|
|
5068
|
+
case "!=" /* NOT_EQUAL */:
|
|
5069
|
+
case "not-in" /* NOT_IN */:
|
|
5070
|
+
filterValue = MAX_VALUE;
|
|
5071
|
+
break;
|
|
5072
|
+
// Remaining filters cannot be used as upper bounds.
|
|
5073
|
+
}
|
|
5074
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
5075
|
+
segmentValue = filterValue;
|
|
5076
|
+
segmentInclusive = filterInclusive;
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
// If there is a endAt bound, compare the values against the existing
|
|
5080
|
+
// boundary to see if we can narrow the scope.
|
|
5081
|
+
if (target.endAt !== null) {
|
|
5082
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5083
|
+
const orderBy = target.orderBy[i];
|
|
5084
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
5085
|
+
const cursorValue = target.endAt.position[i];
|
|
5086
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
5087
|
+
segmentValue = cursorValue;
|
|
5088
|
+
segmentInclusive = target.endAt.inclusive;
|
|
5089
|
+
}
|
|
5090
|
+
break;
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
if (segmentValue === undefined) {
|
|
5095
|
+
// No upper bound exists
|
|
5096
|
+
return null;
|
|
5097
|
+
}
|
|
5098
|
+
values.push(segmentValue);
|
|
5099
|
+
inclusive && (inclusive = segmentInclusive);
|
|
5100
|
+
}
|
|
5101
|
+
return new Bound(values, inclusive);
|
|
5102
|
+
}
|
|
4357
5103
|
class Filter {
|
|
4358
5104
|
}
|
|
4359
5105
|
class FieldFilter extends Filter {
|
|
@@ -4552,17 +5298,11 @@ class ArrayContainsAnyFilter extends FieldFilter {
|
|
|
4552
5298
|
* just after the provided values.
|
|
4553
5299
|
*/
|
|
4554
5300
|
class Bound {
|
|
4555
|
-
constructor(position,
|
|
5301
|
+
constructor(position, inclusive) {
|
|
4556
5302
|
this.position = position;
|
|
4557
|
-
this.
|
|
5303
|
+
this.inclusive = inclusive;
|
|
4558
5304
|
}
|
|
4559
5305
|
}
|
|
4560
|
-
function canonifyBound(bound) {
|
|
4561
|
-
// TODO(b/29183165): Make this collision robust.
|
|
4562
|
-
return `${bound.before ? 'b' : 'a'}:${bound.position
|
|
4563
|
-
.map(p => canonicalId(p))
|
|
4564
|
-
.join(',')}`;
|
|
4565
|
-
}
|
|
4566
5306
|
/**
|
|
4567
5307
|
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
4568
5308
|
*/
|
|
@@ -4582,11 +5322,7 @@ function stringifyOrderBy(orderBy) {
|
|
|
4582
5322
|
function orderByEquals(left, right) {
|
|
4583
5323
|
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4584
5324
|
}
|
|
4585
|
-
|
|
4586
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4587
|
-
* order.
|
|
4588
|
-
*/
|
|
4589
|
-
function sortsBeforeDocument(bound, orderBy, doc) {
|
|
5325
|
+
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4590
5326
|
let comparison = 0;
|
|
4591
5327
|
for (let i = 0; i < bound.position.length; i++) {
|
|
4592
5328
|
const orderByComponent = orderBy[i];
|
|
@@ -4605,7 +5341,23 @@ function sortsBeforeDocument(bound, orderBy, doc) {
|
|
|
4605
5341
|
break;
|
|
4606
5342
|
}
|
|
4607
5343
|
}
|
|
4608
|
-
return
|
|
5344
|
+
return comparison;
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Returns true if a document sorts after a bound using the provided sort
|
|
5348
|
+
* order.
|
|
5349
|
+
*/
|
|
5350
|
+
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
5351
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5352
|
+
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Returns true if a document sorts before a bound using the provided sort
|
|
5356
|
+
* order.
|
|
5357
|
+
*/
|
|
5358
|
+
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
5359
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5360
|
+
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4609
5361
|
}
|
|
4610
5362
|
function boundEquals(left, right) {
|
|
4611
5363
|
if (left === null) {
|
|
@@ -4614,7 +5366,7 @@ function boundEquals(left, right) {
|
|
|
4614
5366
|
else if (right === null) {
|
|
4615
5367
|
return false;
|
|
4616
5368
|
}
|
|
4617
|
-
if (left.
|
|
5369
|
+
if (left.inclusive !== right.inclusive ||
|
|
4618
5370
|
left.position.length !== right.position.length) {
|
|
4619
5371
|
return false;
|
|
4620
5372
|
}
|
|
@@ -4818,10 +5570,10 @@ function queryToTarget(query) {
|
|
|
4818
5570
|
}
|
|
4819
5571
|
// We need to swap the cursors to match the now-flipped query ordering.
|
|
4820
5572
|
const startAt = queryImpl.endAt
|
|
4821
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.
|
|
5573
|
+
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
4822
5574
|
: null;
|
|
4823
5575
|
const endAt = queryImpl.startAt
|
|
4824
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.
|
|
5576
|
+
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
4825
5577
|
: null;
|
|
4826
5578
|
// Now return as a LimitType.First query.
|
|
4827
5579
|
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
@@ -4909,11 +5661,11 @@ function queryMatchesFilters(query, doc) {
|
|
|
4909
5661
|
/** Makes sure a document is within the bounds, if provided. */
|
|
4910
5662
|
function queryMatchesBounds(query, doc) {
|
|
4911
5663
|
if (query.startAt &&
|
|
4912
|
-
!
|
|
5664
|
+
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
4913
5665
|
return false;
|
|
4914
5666
|
}
|
|
4915
5667
|
if (query.endAt &&
|
|
4916
|
-
|
|
5668
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
4917
5669
|
return false;
|
|
4918
5670
|
}
|
|
4919
5671
|
return true;
|
|
@@ -5091,7 +5843,7 @@ class SortedMapIterator {
|
|
|
5091
5843
|
while (!node.isEmpty()) {
|
|
5092
5844
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5093
5845
|
// flip the comparison if we're going in reverse
|
|
5094
|
-
if (isReverse) {
|
|
5846
|
+
if (startKey && isReverse) {
|
|
5095
5847
|
cmp *= -1;
|
|
5096
5848
|
}
|
|
5097
5849
|
if (cmp < 0) {
|
|
@@ -5568,6 +6320,72 @@ class SortedSetIterator {
|
|
|
5568
6320
|
hasNext() {
|
|
5569
6321
|
return this.iter.hasNext();
|
|
5570
6322
|
}
|
|
6323
|
+
}
|
|
6324
|
+
/**
|
|
6325
|
+
* Compares two sorted sets for equality using their natural ordering. The
|
|
6326
|
+
* method computes the intersection and invokes `onAdd` for every element that
|
|
6327
|
+
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
6328
|
+
* `before` but missing from `after`.
|
|
6329
|
+
*
|
|
6330
|
+
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
6331
|
+
* n), where n is the size of the two lists.
|
|
6332
|
+
*
|
|
6333
|
+
* @param before - The elements that exist in the original set.
|
|
6334
|
+
* @param after - The elements to diff against the original set.
|
|
6335
|
+
* @param comparator - The comparator for the elements in before and after.
|
|
6336
|
+
* @param onAdd - A function to invoke for every element that is part of `
|
|
6337
|
+
* after` but not `before`.
|
|
6338
|
+
* @param onRemove - A function to invoke for every element that is part of
|
|
6339
|
+
* `before` but not `after`.
|
|
6340
|
+
*/
|
|
6341
|
+
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
6342
|
+
const beforeIt = before.getIterator();
|
|
6343
|
+
const afterIt = after.getIterator();
|
|
6344
|
+
let beforeValue = advanceIterator(beforeIt);
|
|
6345
|
+
let afterValue = advanceIterator(afterIt);
|
|
6346
|
+
// Walk through the two sets at the same time, using the ordering defined by
|
|
6347
|
+
// `comparator`.
|
|
6348
|
+
while (beforeValue || afterValue) {
|
|
6349
|
+
let added = false;
|
|
6350
|
+
let removed = false;
|
|
6351
|
+
if (beforeValue && afterValue) {
|
|
6352
|
+
const cmp = comparator(beforeValue, afterValue);
|
|
6353
|
+
if (cmp < 0) {
|
|
6354
|
+
// The element was removed if the next element in our ordered
|
|
6355
|
+
// walkthrough is only in `before`.
|
|
6356
|
+
removed = true;
|
|
6357
|
+
}
|
|
6358
|
+
else if (cmp > 0) {
|
|
6359
|
+
// The element was added if the next element in our ordered walkthrough
|
|
6360
|
+
// is only in `after`.
|
|
6361
|
+
added = true;
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
else if (beforeValue != null) {
|
|
6365
|
+
removed = true;
|
|
6366
|
+
}
|
|
6367
|
+
else {
|
|
6368
|
+
added = true;
|
|
6369
|
+
}
|
|
6370
|
+
if (added) {
|
|
6371
|
+
onAdd(afterValue);
|
|
6372
|
+
afterValue = advanceIterator(afterIt);
|
|
6373
|
+
}
|
|
6374
|
+
else if (removed) {
|
|
6375
|
+
onRemove(beforeValue);
|
|
6376
|
+
beforeValue = advanceIterator(beforeIt);
|
|
6377
|
+
}
|
|
6378
|
+
else {
|
|
6379
|
+
beforeValue = advanceIterator(beforeIt);
|
|
6380
|
+
afterValue = advanceIterator(afterIt);
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
/**
|
|
6385
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
6386
|
+
*/
|
|
6387
|
+
function advanceIterator(it) {
|
|
6388
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5571
6389
|
}
|
|
5572
6390
|
|
|
5573
6391
|
/**
|
|
@@ -6406,6 +7224,47 @@ class MutationBatchResult {
|
|
|
6406
7224
|
}
|
|
6407
7225
|
}
|
|
6408
7226
|
|
|
7227
|
+
/**
|
|
7228
|
+
* @license
|
|
7229
|
+
* Copyright 2022 Google LLC
|
|
7230
|
+
*
|
|
7231
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7232
|
+
* you may not use this file except in compliance with the License.
|
|
7233
|
+
* You may obtain a copy of the License at
|
|
7234
|
+
*
|
|
7235
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
7236
|
+
*
|
|
7237
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7238
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
7239
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
7240
|
+
* See the License for the specific language governing permissions and
|
|
7241
|
+
* limitations under the License.
|
|
7242
|
+
*/
|
|
7243
|
+
/**
|
|
7244
|
+
* Representation of an overlay computed by Firestore.
|
|
7245
|
+
*
|
|
7246
|
+
* Holds information about a mutation and the largest batch id in Firestore when
|
|
7247
|
+
* the mutation was created.
|
|
7248
|
+
*/
|
|
7249
|
+
class Overlay {
|
|
7250
|
+
constructor(largestBatchId, mutation) {
|
|
7251
|
+
this.largestBatchId = largestBatchId;
|
|
7252
|
+
this.mutation = mutation;
|
|
7253
|
+
}
|
|
7254
|
+
getKey() {
|
|
7255
|
+
return this.mutation.key;
|
|
7256
|
+
}
|
|
7257
|
+
isEqual(other) {
|
|
7258
|
+
return other !== null && this.mutation === other.mutation;
|
|
7259
|
+
}
|
|
7260
|
+
toString() {
|
|
7261
|
+
return `Overlay{
|
|
7262
|
+
largestBatchId: ${this.largestBatchId},
|
|
7263
|
+
mutation: ${this.mutation.toString()}
|
|
7264
|
+
}`;
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
|
|
6409
7268
|
/**
|
|
6410
7269
|
* @license
|
|
6411
7270
|
* Copyright 2017 Google LLC
|
|
@@ -6878,7 +7737,7 @@ class TargetState {
|
|
|
6878
7737
|
this._current = true;
|
|
6879
7738
|
}
|
|
6880
7739
|
}
|
|
6881
|
-
const LOG_TAG$
|
|
7740
|
+
const LOG_TAG$g = 'WatchChangeAggregator';
|
|
6882
7741
|
/**
|
|
6883
7742
|
* A helper class to accumulate watch changes into a RemoteEvent.
|
|
6884
7743
|
*/
|
|
@@ -6994,7 +7853,7 @@ class WatchChangeAggregator {
|
|
|
6994
7853
|
const targetData = this.targetDataForActiveTarget(targetId);
|
|
6995
7854
|
if (targetData) {
|
|
6996
7855
|
const target = targetData.target;
|
|
6997
|
-
if (
|
|
7856
|
+
if (targetIsDocumentTarget(target)) {
|
|
6998
7857
|
if (expectedCount === 0) {
|
|
6999
7858
|
// The existence filter told us the document does not exist. We deduce
|
|
7000
7859
|
// that this document does not exist and apply a deleted document to
|
|
@@ -7029,7 +7888,7 @@ class WatchChangeAggregator {
|
|
|
7029
7888
|
this.targetStates.forEach((targetState, targetId) => {
|
|
7030
7889
|
const targetData = this.targetDataForActiveTarget(targetId);
|
|
7031
7890
|
if (targetData) {
|
|
7032
|
-
if (targetState.current &&
|
|
7891
|
+
if (targetState.current && targetIsDocumentTarget(targetData.target)) {
|
|
7033
7892
|
// Document queries for document that don't exist can produce an empty
|
|
7034
7893
|
// result set. To update our local cache, we synthesize a document
|
|
7035
7894
|
// delete if we have not previously received the document. This
|
|
@@ -7072,6 +7931,7 @@ class WatchChangeAggregator {
|
|
|
7072
7931
|
resolvedLimboDocuments = resolvedLimboDocuments.add(key);
|
|
7073
7932
|
}
|
|
7074
7933
|
});
|
|
7934
|
+
this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
|
|
7075
7935
|
const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
|
|
7076
7936
|
this.pendingDocumentUpdates = mutableDocumentMap();
|
|
7077
7937
|
this.pendingDocumentTargetMapping = documentTargetMap();
|
|
@@ -7172,7 +8032,7 @@ class WatchChangeAggregator {
|
|
|
7172
8032
|
isActiveTarget(targetId) {
|
|
7173
8033
|
const targetActive = this.targetDataForActiveTarget(targetId) !== null;
|
|
7174
8034
|
if (!targetActive) {
|
|
7175
|
-
logDebug(LOG_TAG$
|
|
8035
|
+
logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
|
|
7176
8036
|
}
|
|
7177
8037
|
return targetActive;
|
|
7178
8038
|
}
|
|
@@ -7791,10 +8651,10 @@ function toQueryTarget(serializer, target) {
|
|
|
7791
8651
|
result.structuredQuery.limit = limit;
|
|
7792
8652
|
}
|
|
7793
8653
|
if (target.startAt) {
|
|
7794
|
-
result.structuredQuery.startAt =
|
|
8654
|
+
result.structuredQuery.startAt = toStartAtCursor(target.startAt);
|
|
7795
8655
|
}
|
|
7796
8656
|
if (target.endAt) {
|
|
7797
|
-
result.structuredQuery.endAt =
|
|
8657
|
+
result.structuredQuery.endAt = toEndAtCursor(target.endAt);
|
|
7798
8658
|
}
|
|
7799
8659
|
return result;
|
|
7800
8660
|
}
|
|
@@ -7827,11 +8687,11 @@ function convertQueryTargetToQuery(target) {
|
|
|
7827
8687
|
}
|
|
7828
8688
|
let startAt = null;
|
|
7829
8689
|
if (query.startAt) {
|
|
7830
|
-
startAt =
|
|
8690
|
+
startAt = fromStartAtCursor(query.startAt);
|
|
7831
8691
|
}
|
|
7832
8692
|
let endAt = null;
|
|
7833
8693
|
if (query.endAt) {
|
|
7834
|
-
endAt =
|
|
8694
|
+
endAt = fromEndAtCursor(query.endAt);
|
|
7835
8695
|
}
|
|
7836
8696
|
return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* First */, startAt, endAt);
|
|
7837
8697
|
}
|
|
@@ -7864,7 +8724,7 @@ function toLabel(serializer, purpose) {
|
|
|
7864
8724
|
function toTarget(serializer, targetData) {
|
|
7865
8725
|
let result;
|
|
7866
8726
|
const target = targetData.target;
|
|
7867
|
-
if (
|
|
8727
|
+
if (targetIsDocumentTarget(target)) {
|
|
7868
8728
|
result = { documents: toDocumentsTarget(serializer, target) };
|
|
7869
8729
|
}
|
|
7870
8730
|
else {
|
|
@@ -7922,16 +8782,27 @@ function toOrder(orderBys) {
|
|
|
7922
8782
|
function fromOrder(orderBys) {
|
|
7923
8783
|
return orderBys.map(order => fromPropertyOrder(order));
|
|
7924
8784
|
}
|
|
7925
|
-
function
|
|
8785
|
+
function toStartAtCursor(cursor) {
|
|
8786
|
+
return {
|
|
8787
|
+
before: cursor.inclusive,
|
|
8788
|
+
values: cursor.position
|
|
8789
|
+
};
|
|
8790
|
+
}
|
|
8791
|
+
function toEndAtCursor(cursor) {
|
|
7926
8792
|
return {
|
|
7927
|
-
before: cursor.
|
|
8793
|
+
before: !cursor.inclusive,
|
|
7928
8794
|
values: cursor.position
|
|
7929
8795
|
};
|
|
7930
8796
|
}
|
|
7931
|
-
function
|
|
7932
|
-
const
|
|
8797
|
+
function fromStartAtCursor(cursor) {
|
|
8798
|
+
const inclusive = !!cursor.before;
|
|
8799
|
+
const position = cursor.values || [];
|
|
8800
|
+
return new Bound(position, inclusive);
|
|
8801
|
+
}
|
|
8802
|
+
function fromEndAtCursor(cursor) {
|
|
8803
|
+
const inclusive = !cursor.before;
|
|
7933
8804
|
const position = cursor.values || [];
|
|
7934
|
-
return new Bound(position,
|
|
8805
|
+
return new Bound(position, inclusive);
|
|
7935
8806
|
}
|
|
7936
8807
|
// visible for testing
|
|
7937
8808
|
function toDirection(dir) {
|
|
@@ -8192,29 +9063,34 @@ class LocalSerializer {
|
|
|
8192
9063
|
}
|
|
8193
9064
|
/** Decodes a remote document from storage locally to a Document. */
|
|
8194
9065
|
function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
9066
|
+
let doc;
|
|
8195
9067
|
if (remoteDoc.document) {
|
|
8196
|
-
|
|
9068
|
+
doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
|
|
8197
9069
|
}
|
|
8198
9070
|
else if (remoteDoc.noDocument) {
|
|
8199
9071
|
const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
|
|
8200
9072
|
const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
9073
|
+
doc = MutableDocument.newNoDocument(key, version);
|
|
9074
|
+
if (remoteDoc.hasCommittedMutations) {
|
|
9075
|
+
doc.setHasCommittedMutations();
|
|
9076
|
+
}
|
|
8205
9077
|
}
|
|
8206
9078
|
else if (remoteDoc.unknownDocument) {
|
|
8207
9079
|
const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
|
|
8208
9080
|
const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
|
|
8209
|
-
|
|
9081
|
+
doc = MutableDocument.newUnknownDocument(key, version);
|
|
8210
9082
|
}
|
|
8211
9083
|
else {
|
|
8212
9084
|
return fail();
|
|
8213
9085
|
}
|
|
9086
|
+
if (remoteDoc.readTime) {
|
|
9087
|
+
doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
|
|
9088
|
+
}
|
|
9089
|
+
return doc;
|
|
8214
9090
|
}
|
|
8215
9091
|
/** Encodes a document for storage locally. */
|
|
8216
|
-
function toDbRemoteDocument(localSerializer, document
|
|
8217
|
-
const dbReadTime = toDbTimestampKey(readTime);
|
|
9092
|
+
function toDbRemoteDocument(localSerializer, document) {
|
|
9093
|
+
const dbReadTime = toDbTimestampKey(document.readTime);
|
|
8218
9094
|
const parentPath = document.key.path.popLast().toArray();
|
|
8219
9095
|
if (document.isFoundDocument()) {
|
|
8220
9096
|
const doc = toDocument(localSerializer.remoteSerializer, document);
|
|
@@ -8225,10 +9101,10 @@ function toDbRemoteDocument(localSerializer, document, readTime) {
|
|
|
8225
9101
|
}
|
|
8226
9102
|
else if (document.isNoDocument()) {
|
|
8227
9103
|
const path = document.key.path.toArray();
|
|
8228
|
-
const
|
|
9104
|
+
const version = toDbTimestamp(document.version);
|
|
8229
9105
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8230
9106
|
return new DbRemoteDocument(
|
|
8231
|
-
/* unknownDocument= */ null, new DbNoDocument(path,
|
|
9107
|
+
/* unknownDocument= */ null, new DbNoDocument(path, version),
|
|
8232
9108
|
/* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
|
|
8233
9109
|
}
|
|
8234
9110
|
else if (document.isUnknownDocument()) {
|
|
@@ -8309,7 +9185,7 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8309
9185
|
const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
|
|
8310
9186
|
const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
|
|
8311
9187
|
let queryProto;
|
|
8312
|
-
if (
|
|
9188
|
+
if (targetIsDocumentTarget(targetData.target)) {
|
|
8313
9189
|
queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
|
|
8314
9190
|
}
|
|
8315
9191
|
else {
|
|
@@ -8390,6 +9266,37 @@ function fromBundleMetadata(metadata) {
|
|
|
8390
9266
|
version: metadata.version,
|
|
8391
9267
|
createTime: fromVersion(metadata.createTime)
|
|
8392
9268
|
};
|
|
9269
|
+
}
|
|
9270
|
+
/** Encodes a DbDocumentOverlay object to an Overlay model object. */
|
|
9271
|
+
function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
9272
|
+
return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
|
|
9273
|
+
}
|
|
9274
|
+
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
9275
|
+
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
9276
|
+
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
9277
|
+
return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
|
|
9278
|
+
}
|
|
9279
|
+
/**
|
|
9280
|
+
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
9281
|
+
* document key.
|
|
9282
|
+
*/
|
|
9283
|
+
function toDbDocumentOverlayKey(userId, docKey) {
|
|
9284
|
+
const docId = docKey.path.lastSegment();
|
|
9285
|
+
const collectionPath = encodeResourcePath(docKey.path.popLast());
|
|
9286
|
+
return [userId, collectionPath, docId];
|
|
9287
|
+
}
|
|
9288
|
+
function toDbIndexConfiguration(index) {
|
|
9289
|
+
return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
|
|
9290
|
+
}
|
|
9291
|
+
function fromDbIndexConfiguration(index, state) {
|
|
9292
|
+
const decodedState = state
|
|
9293
|
+
? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
|
|
9294
|
+
: IndexState.empty();
|
|
9295
|
+
const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
|
|
9296
|
+
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
9297
|
+
}
|
|
9298
|
+
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
9299
|
+
return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
|
|
8393
9300
|
}
|
|
8394
9301
|
|
|
8395
9302
|
/**
|
|
@@ -8451,7 +9358,7 @@ function namedQueriesStore(txn) {
|
|
|
8451
9358
|
|
|
8452
9359
|
/**
|
|
8453
9360
|
* @license
|
|
8454
|
-
* Copyright
|
|
9361
|
+
* Copyright 2022 Google LLC
|
|
8455
9362
|
*
|
|
8456
9363
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8457
9364
|
* you may not use this file except in compliance with the License.
|
|
@@ -8466,56 +9373,1060 @@ function namedQueriesStore(txn) {
|
|
|
8466
9373
|
* limitations under the License.
|
|
8467
9374
|
*/
|
|
8468
9375
|
/**
|
|
8469
|
-
*
|
|
9376
|
+
* Implementation of DocumentOverlayCache using IndexedDb.
|
|
8470
9377
|
*/
|
|
8471
|
-
class
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
9378
|
+
class IndexedDbDocumentOverlayCache {
|
|
9379
|
+
/**
|
|
9380
|
+
* @param serializer - The document serializer.
|
|
9381
|
+
* @param userId - The userId for which we are accessing overlays.
|
|
9382
|
+
*/
|
|
9383
|
+
constructor(serializer, userId) {
|
|
9384
|
+
this.serializer = serializer;
|
|
9385
|
+
this.userId = userId;
|
|
8478
9386
|
}
|
|
8479
|
-
|
|
8480
|
-
|
|
9387
|
+
static forUser(serializer, user) {
|
|
9388
|
+
const userId = user.uid || '';
|
|
9389
|
+
return new IndexedDbDocumentOverlayCache(serializer, userId);
|
|
8481
9390
|
}
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
9391
|
+
getOverlay(transaction, key) {
|
|
9392
|
+
return documentOverlayStore(transaction)
|
|
9393
|
+
.get(toDbDocumentOverlayKey(this.userId, key))
|
|
9394
|
+
.next(dbOverlay => {
|
|
9395
|
+
if (dbOverlay) {
|
|
9396
|
+
return fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9397
|
+
}
|
|
9398
|
+
return null;
|
|
9399
|
+
});
|
|
8491
9400
|
}
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
this.index[collectionId] = existingParents.add(parentPath);
|
|
8500
|
-
return added;
|
|
9401
|
+
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9402
|
+
const promises = [];
|
|
9403
|
+
overlays.forEach(mutation => {
|
|
9404
|
+
const overlay = new Overlay(largestBatchId, mutation);
|
|
9405
|
+
promises.push(this.saveOverlay(transaction, overlay));
|
|
9406
|
+
});
|
|
9407
|
+
return PersistencePromise.waitFor(promises);
|
|
8501
9408
|
}
|
|
8502
|
-
|
|
8503
|
-
const
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
9409
|
+
removeOverlaysForBatchId(transaction, documentKeys, batchId) {
|
|
9410
|
+
const collectionPaths = new Set();
|
|
9411
|
+
// Get the set of unique collection paths.
|
|
9412
|
+
documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
|
|
9413
|
+
const promises = [];
|
|
9414
|
+
collectionPaths.forEach(collectionPath => {
|
|
9415
|
+
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9416
|
+
/*lowerOpen=*/ false,
|
|
9417
|
+
/*upperOpen=*/ true);
|
|
9418
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
|
|
9419
|
+
});
|
|
9420
|
+
return PersistencePromise.waitFor(promises);
|
|
8507
9421
|
}
|
|
8508
|
-
|
|
8509
|
-
const
|
|
8510
|
-
|
|
8511
|
-
|
|
9422
|
+
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9423
|
+
const result = new Map();
|
|
9424
|
+
const collectionPath = encodeResourcePath(collection);
|
|
9425
|
+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9426
|
+
// is not inclusive.
|
|
9427
|
+
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9428
|
+
/*lowerOpen=*/ true);
|
|
9429
|
+
return documentOverlayStore(transaction)
|
|
9430
|
+
.loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
|
|
9431
|
+
.next(dbOverlays => {
|
|
9432
|
+
for (const dbOverlay of dbOverlays) {
|
|
9433
|
+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9434
|
+
result.set(overlay.getKey(), overlay);
|
|
9435
|
+
}
|
|
9436
|
+
return result;
|
|
9437
|
+
});
|
|
8512
9438
|
}
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
9439
|
+
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9440
|
+
const result = new Map();
|
|
9441
|
+
let currentBatchId = undefined;
|
|
9442
|
+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9443
|
+
// is not inclusive.
|
|
9444
|
+
const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
|
|
9445
|
+
/*lowerOpen=*/ true);
|
|
9446
|
+
return documentOverlayStore(transaction)
|
|
9447
|
+
.iterate({
|
|
9448
|
+
index: DbDocumentOverlay.collectionGroupOverlayIndex,
|
|
9449
|
+
range
|
|
9450
|
+
}, (_, dbOverlay, control) => {
|
|
9451
|
+
// We do not want to return partial batch overlays, even if the size
|
|
9452
|
+
// of the result set exceeds the given `count` argument. Therefore, we
|
|
9453
|
+
// continue to aggregate results even after the result size exceeds
|
|
9454
|
+
// `count` if there are more overlays from the `currentBatchId`.
|
|
9455
|
+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9456
|
+
if (result.size < count ||
|
|
9457
|
+
overlay.largestBatchId === currentBatchId) {
|
|
9458
|
+
result.set(overlay.getKey(), overlay);
|
|
9459
|
+
currentBatchId = overlay.largestBatchId;
|
|
9460
|
+
}
|
|
9461
|
+
else {
|
|
9462
|
+
control.done();
|
|
9463
|
+
}
|
|
9464
|
+
})
|
|
9465
|
+
.next(() => result);
|
|
9466
|
+
}
|
|
9467
|
+
saveOverlay(transaction, overlay) {
|
|
9468
|
+
return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
/**
|
|
9472
|
+
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9473
|
+
*/
|
|
9474
|
+
function documentOverlayStore(txn) {
|
|
9475
|
+
return getStore(txn, DbDocumentOverlay.store);
|
|
9476
|
+
}
|
|
9477
|
+
|
|
9478
|
+
/**
|
|
9479
|
+
* @license
|
|
9480
|
+
* Copyright 2021 Google LLC
|
|
9481
|
+
*
|
|
9482
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9483
|
+
* you may not use this file except in compliance with the License.
|
|
9484
|
+
* You may obtain a copy of the License at
|
|
9485
|
+
*
|
|
9486
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9487
|
+
*
|
|
9488
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9489
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9490
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9491
|
+
* See the License for the specific language governing permissions and
|
|
9492
|
+
* limitations under the License.
|
|
9493
|
+
*/
|
|
9494
|
+
// Note: This code is copied from the backend. Code that is not used by
|
|
9495
|
+
// Firestore was removed.
|
|
9496
|
+
const INDEX_TYPE_NULL = 5;
|
|
9497
|
+
const INDEX_TYPE_BOOLEAN = 10;
|
|
9498
|
+
const INDEX_TYPE_NAN = 13;
|
|
9499
|
+
const INDEX_TYPE_NUMBER = 15;
|
|
9500
|
+
const INDEX_TYPE_TIMESTAMP = 20;
|
|
9501
|
+
const INDEX_TYPE_STRING = 25;
|
|
9502
|
+
const INDEX_TYPE_BLOB = 30;
|
|
9503
|
+
const INDEX_TYPE_REFERENCE = 37;
|
|
9504
|
+
const INDEX_TYPE_GEOPOINT = 45;
|
|
9505
|
+
const INDEX_TYPE_ARRAY = 50;
|
|
9506
|
+
const INDEX_TYPE_MAP = 55;
|
|
9507
|
+
const INDEX_TYPE_REFERENCE_SEGMENT = 60;
|
|
9508
|
+
// A terminator that indicates that a truncatable value was not truncated.
|
|
9509
|
+
// This must be smaller than all other type labels.
|
|
9510
|
+
const NOT_TRUNCATED = 2;
|
|
9511
|
+
/** Firestore index value writer. */
|
|
9512
|
+
class FirestoreIndexValueWriter {
|
|
9513
|
+
constructor() { }
|
|
9514
|
+
// The write methods below short-circuit writing terminators for values
|
|
9515
|
+
// containing a (terminating) truncated value.
|
|
9516
|
+
//
|
|
9517
|
+
// As an example, consider the resulting encoding for:
|
|
9518
|
+
//
|
|
9519
|
+
// ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
|
|
9520
|
+
// ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
|
|
9521
|
+
// ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
|
|
9522
|
+
/** Writes an index value. */
|
|
9523
|
+
writeIndexValue(value, encoder) {
|
|
9524
|
+
this.writeIndexValueAux(value, encoder);
|
|
9525
|
+
// Write separator to split index values
|
|
9526
|
+
// (see go/firestore-storage-format#encodings).
|
|
9527
|
+
encoder.writeInfinity();
|
|
9528
|
+
}
|
|
9529
|
+
writeIndexValueAux(indexValue, encoder) {
|
|
9530
|
+
if ('nullValue' in indexValue) {
|
|
9531
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
|
|
9532
|
+
}
|
|
9533
|
+
else if ('booleanValue' in indexValue) {
|
|
9534
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
|
|
9535
|
+
encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
|
|
9536
|
+
}
|
|
9537
|
+
else if ('integerValue' in indexValue) {
|
|
9538
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
|
|
9539
|
+
encoder.writeNumber(normalizeNumber(indexValue.integerValue));
|
|
9540
|
+
}
|
|
9541
|
+
else if ('doubleValue' in indexValue) {
|
|
9542
|
+
const n = normalizeNumber(indexValue.doubleValue);
|
|
9543
|
+
if (isNaN(n)) {
|
|
9544
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
|
|
9545
|
+
}
|
|
9546
|
+
else {
|
|
9547
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
|
|
9548
|
+
if (isNegativeZero(n)) {
|
|
9549
|
+
// -0.0, 0 and 0.0 are all considered the same
|
|
9550
|
+
encoder.writeNumber(0.0);
|
|
9551
|
+
}
|
|
9552
|
+
else {
|
|
9553
|
+
encoder.writeNumber(n);
|
|
9554
|
+
}
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
else if ('timestampValue' in indexValue) {
|
|
9558
|
+
const timestamp = indexValue.timestampValue;
|
|
9559
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
|
|
9560
|
+
if (typeof timestamp === 'string') {
|
|
9561
|
+
encoder.writeString(timestamp);
|
|
9562
|
+
}
|
|
9563
|
+
else {
|
|
9564
|
+
encoder.writeString(`${timestamp.seconds || ''}`);
|
|
9565
|
+
encoder.writeNumber(timestamp.nanos || 0);
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
else if ('stringValue' in indexValue) {
|
|
9569
|
+
this.writeIndexString(indexValue.stringValue, encoder);
|
|
9570
|
+
this.writeTruncationMarker(encoder);
|
|
9571
|
+
}
|
|
9572
|
+
else if ('bytesValue' in indexValue) {
|
|
9573
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
|
|
9574
|
+
encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
|
|
9575
|
+
this.writeTruncationMarker(encoder);
|
|
9576
|
+
}
|
|
9577
|
+
else if ('referenceValue' in indexValue) {
|
|
9578
|
+
this.writeIndexEntityRef(indexValue.referenceValue, encoder);
|
|
9579
|
+
}
|
|
9580
|
+
else if ('geoPointValue' in indexValue) {
|
|
9581
|
+
const geoPoint = indexValue.geoPointValue;
|
|
9582
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
|
|
9583
|
+
encoder.writeNumber(geoPoint.latitude || 0);
|
|
9584
|
+
encoder.writeNumber(geoPoint.longitude || 0);
|
|
9585
|
+
}
|
|
9586
|
+
else if ('mapValue' in indexValue) {
|
|
9587
|
+
if (isMaxValue(indexValue)) {
|
|
9588
|
+
this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
|
|
9589
|
+
}
|
|
9590
|
+
else {
|
|
9591
|
+
this.writeIndexMap(indexValue.mapValue, encoder);
|
|
9592
|
+
this.writeTruncationMarker(encoder);
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
else if ('arrayValue' in indexValue) {
|
|
9596
|
+
this.writeIndexArray(indexValue.arrayValue, encoder);
|
|
9597
|
+
this.writeTruncationMarker(encoder);
|
|
9598
|
+
}
|
|
9599
|
+
else {
|
|
9600
|
+
fail();
|
|
9601
|
+
}
|
|
9602
|
+
}
|
|
9603
|
+
writeIndexString(stringIndexValue, encoder) {
|
|
9604
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
|
|
9605
|
+
this.writeUnlabeledIndexString(stringIndexValue, encoder);
|
|
9606
|
+
}
|
|
9607
|
+
writeUnlabeledIndexString(stringIndexValue, encoder) {
|
|
9608
|
+
encoder.writeString(stringIndexValue);
|
|
9609
|
+
}
|
|
9610
|
+
writeIndexMap(mapIndexValue, encoder) {
|
|
9611
|
+
const map = mapIndexValue.fields || {};
|
|
9612
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
|
|
9613
|
+
for (const key of Object.keys(map)) {
|
|
9614
|
+
this.writeIndexString(key, encoder);
|
|
9615
|
+
this.writeIndexValueAux(map[key], encoder);
|
|
9616
|
+
}
|
|
9617
|
+
}
|
|
9618
|
+
writeIndexArray(arrayIndexValue, encoder) {
|
|
9619
|
+
const values = arrayIndexValue.values || [];
|
|
9620
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
|
|
9621
|
+
for (const element of values) {
|
|
9622
|
+
this.writeIndexValueAux(element, encoder);
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
writeIndexEntityRef(referenceValue, encoder) {
|
|
9626
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
|
|
9627
|
+
const path = DocumentKey.fromName(referenceValue).path;
|
|
9628
|
+
path.forEach(segment => {
|
|
9629
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
|
|
9630
|
+
this.writeUnlabeledIndexString(segment, encoder);
|
|
9631
|
+
});
|
|
9632
|
+
}
|
|
9633
|
+
writeValueTypeLabel(encoder, typeOrder) {
|
|
9634
|
+
encoder.writeNumber(typeOrder);
|
|
9635
|
+
}
|
|
9636
|
+
writeTruncationMarker(encoder) {
|
|
9637
|
+
// While the SDK does not implement truncation, the truncation marker is
|
|
9638
|
+
// used to terminate all variable length values (which are strings, bytes,
|
|
9639
|
+
// references, arrays and maps).
|
|
9640
|
+
encoder.writeNumber(NOT_TRUNCATED);
|
|
9641
|
+
}
|
|
9642
|
+
}
|
|
9643
|
+
FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
|
|
9644
|
+
|
|
9645
|
+
/**
|
|
9646
|
+
* @license
|
|
9647
|
+
* Copyright 2021 Google LLC
|
|
9648
|
+
*
|
|
9649
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9650
|
+
* you may not use this file except in compliance with the License.
|
|
9651
|
+
* You may obtain a copy of the License at
|
|
9652
|
+
*
|
|
9653
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9654
|
+
*
|
|
9655
|
+
* Unless required by applicable law | agreed to in writing, software
|
|
9656
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9657
|
+
* WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
|
|
9658
|
+
* See the License for the specific language governing permissions and
|
|
9659
|
+
* limitations under the License.
|
|
9660
|
+
*/
|
|
9661
|
+
/** These constants are taken from the backend. */
|
|
9662
|
+
const MIN_SURROGATE = '\uD800';
|
|
9663
|
+
const MAX_SURROGATE = '\uDBFF';
|
|
9664
|
+
const ESCAPE1 = 0x00;
|
|
9665
|
+
const NULL_BYTE = 0xff; // Combined with ESCAPE1
|
|
9666
|
+
const SEPARATOR = 0x01; // Combined with ESCAPE1
|
|
9667
|
+
const ESCAPE2 = 0xff;
|
|
9668
|
+
const INFINITY = 0xff; // Combined with ESCAPE2
|
|
9669
|
+
const FF_BYTE = 0x00; // Combined with ESCAPE2
|
|
9670
|
+
const LONG_SIZE = 64;
|
|
9671
|
+
const BYTE_SIZE = 8;
|
|
9672
|
+
/**
|
|
9673
|
+
* The default size of the buffer. This is arbitrary, but likely larger than
|
|
9674
|
+
* most index values so that less copies of the underlying buffer will be made.
|
|
9675
|
+
* For large values, a single copy will made to double the buffer length.
|
|
9676
|
+
*/
|
|
9677
|
+
const DEFAULT_BUFFER_SIZE = 1024;
|
|
9678
|
+
/** Converts a JavaScript number to a byte array (using big endian encoding). */
|
|
9679
|
+
function doubleToLongBits(value) {
|
|
9680
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
9681
|
+
dv.setFloat64(0, value, /* littleEndian= */ false);
|
|
9682
|
+
return new Uint8Array(dv.buffer);
|
|
9683
|
+
}
|
|
9684
|
+
/**
|
|
9685
|
+
* Counts the number of zeros in a byte.
|
|
9686
|
+
*
|
|
9687
|
+
* Visible for testing.
|
|
9688
|
+
*/
|
|
9689
|
+
function numberOfLeadingZerosInByte(x) {
|
|
9690
|
+
if (x === 0) {
|
|
9691
|
+
return 8;
|
|
9692
|
+
}
|
|
9693
|
+
let zeros = 0;
|
|
9694
|
+
if (x >> 4 === 0) {
|
|
9695
|
+
// Test if the first four bits are zero.
|
|
9696
|
+
zeros += 4;
|
|
9697
|
+
x = x << 4;
|
|
9698
|
+
}
|
|
9699
|
+
if (x >> 6 === 0) {
|
|
9700
|
+
// Test if the first two (or next two) bits are zero.
|
|
9701
|
+
zeros += 2;
|
|
9702
|
+
x = x << 2;
|
|
9703
|
+
}
|
|
9704
|
+
if (x >> 7 === 0) {
|
|
9705
|
+
// Test if the remaining bit is zero.
|
|
9706
|
+
zeros += 1;
|
|
9707
|
+
}
|
|
9708
|
+
return zeros;
|
|
9709
|
+
}
|
|
9710
|
+
/** Counts the number of leading zeros in the given byte array. */
|
|
9711
|
+
function numberOfLeadingZeros(bytes) {
|
|
9712
|
+
let leadingZeros = 0;
|
|
9713
|
+
for (let i = 0; i < 8; ++i) {
|
|
9714
|
+
const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
|
|
9715
|
+
leadingZeros += zeros;
|
|
9716
|
+
if (zeros !== 8) {
|
|
9717
|
+
break;
|
|
9718
|
+
}
|
|
9719
|
+
}
|
|
9720
|
+
return leadingZeros;
|
|
9721
|
+
}
|
|
9722
|
+
/**
|
|
9723
|
+
* Returns the number of bytes required to store "value". Leading zero bytes
|
|
9724
|
+
* are skipped.
|
|
9725
|
+
*/
|
|
9726
|
+
function unsignedNumLength(value) {
|
|
9727
|
+
// This is just the number of bytes for the unsigned representation of the number.
|
|
9728
|
+
const numBits = LONG_SIZE - numberOfLeadingZeros(value);
|
|
9729
|
+
return Math.ceil(numBits / BYTE_SIZE);
|
|
9730
|
+
}
|
|
9731
|
+
/**
|
|
9732
|
+
* OrderedCodeWriter is a minimal-allocation implementation of the writing
|
|
9733
|
+
* behavior defined by the backend.
|
|
9734
|
+
*
|
|
9735
|
+
* The code is ported from its Java counterpart.
|
|
9736
|
+
*/
|
|
9737
|
+
class OrderedCodeWriter {
|
|
9738
|
+
constructor() {
|
|
9739
|
+
this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
|
|
9740
|
+
this.position = 0;
|
|
9741
|
+
}
|
|
9742
|
+
writeBytesAscending(value) {
|
|
9743
|
+
const it = value[Symbol.iterator]();
|
|
9744
|
+
let byte = it.next();
|
|
9745
|
+
while (!byte.done) {
|
|
9746
|
+
this.writeByteAscending(byte.value);
|
|
9747
|
+
byte = it.next();
|
|
9748
|
+
}
|
|
9749
|
+
this.writeSeparatorAscending();
|
|
9750
|
+
}
|
|
9751
|
+
writeBytesDescending(value) {
|
|
9752
|
+
const it = value[Symbol.iterator]();
|
|
9753
|
+
let byte = it.next();
|
|
9754
|
+
while (!byte.done) {
|
|
9755
|
+
this.writeByteDescending(byte.value);
|
|
9756
|
+
byte = it.next();
|
|
9757
|
+
}
|
|
9758
|
+
this.writeSeparatorDescending();
|
|
9759
|
+
}
|
|
9760
|
+
/** Writes utf8 bytes into this byte sequence, ascending. */
|
|
9761
|
+
writeUtf8Ascending(sequence) {
|
|
9762
|
+
for (const c of sequence) {
|
|
9763
|
+
const charCode = c.charCodeAt(0);
|
|
9764
|
+
if (charCode < 0x80) {
|
|
9765
|
+
this.writeByteAscending(charCode);
|
|
9766
|
+
}
|
|
9767
|
+
else if (charCode < 0x800) {
|
|
9768
|
+
this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
|
|
9769
|
+
this.writeByteAscending(0x80 | (0x3f & charCode));
|
|
9770
|
+
}
|
|
9771
|
+
else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
|
|
9772
|
+
this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
|
|
9773
|
+
this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
|
|
9774
|
+
this.writeByteAscending(0x80 | (0x3f & charCode));
|
|
9775
|
+
}
|
|
9776
|
+
else {
|
|
9777
|
+
const codePoint = c.codePointAt(0);
|
|
9778
|
+
this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
|
|
9779
|
+
this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
|
|
9780
|
+
this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
|
|
9781
|
+
this.writeByteAscending(0x80 | (0x3f & codePoint));
|
|
9782
|
+
}
|
|
9783
|
+
}
|
|
9784
|
+
this.writeSeparatorAscending();
|
|
9785
|
+
}
|
|
9786
|
+
/** Writes utf8 bytes into this byte sequence, descending */
|
|
9787
|
+
writeUtf8Descending(sequence) {
|
|
9788
|
+
for (const c of sequence) {
|
|
9789
|
+
const charCode = c.charCodeAt(0);
|
|
9790
|
+
if (charCode < 0x80) {
|
|
9791
|
+
this.writeByteDescending(charCode);
|
|
9792
|
+
}
|
|
9793
|
+
else if (charCode < 0x800) {
|
|
9794
|
+
this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
|
|
9795
|
+
this.writeByteDescending(0x80 | (0x3f & charCode));
|
|
9796
|
+
}
|
|
9797
|
+
else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
|
|
9798
|
+
this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
|
|
9799
|
+
this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
|
|
9800
|
+
this.writeByteDescending(0x80 | (0x3f & charCode));
|
|
9801
|
+
}
|
|
9802
|
+
else {
|
|
9803
|
+
const codePoint = c.codePointAt(0);
|
|
9804
|
+
this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
|
|
9805
|
+
this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
|
|
9806
|
+
this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
|
|
9807
|
+
this.writeByteDescending(0x80 | (0x3f & codePoint));
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
this.writeSeparatorDescending();
|
|
9811
|
+
}
|
|
9812
|
+
writeNumberAscending(val) {
|
|
9813
|
+
// Values are encoded with a single byte length prefix, followed by the
|
|
9814
|
+
// actual value in big-endian format with leading 0 bytes dropped.
|
|
9815
|
+
const value = this.toOrderedBits(val);
|
|
9816
|
+
const len = unsignedNumLength(value);
|
|
9817
|
+
this.ensureAvailable(1 + len);
|
|
9818
|
+
this.buffer[this.position++] = len & 0xff; // Write the length
|
|
9819
|
+
for (let i = value.length - len; i < value.length; ++i) {
|
|
9820
|
+
this.buffer[this.position++] = value[i] & 0xff;
|
|
9821
|
+
}
|
|
9822
|
+
}
|
|
9823
|
+
writeNumberDescending(val) {
|
|
9824
|
+
// Values are encoded with a single byte length prefix, followed by the
|
|
9825
|
+
// inverted value in big-endian format with leading 0 bytes dropped.
|
|
9826
|
+
const value = this.toOrderedBits(val);
|
|
9827
|
+
const len = unsignedNumLength(value);
|
|
9828
|
+
this.ensureAvailable(1 + len);
|
|
9829
|
+
this.buffer[this.position++] = ~(len & 0xff); // Write the length
|
|
9830
|
+
for (let i = value.length - len; i < value.length; ++i) {
|
|
9831
|
+
this.buffer[this.position++] = ~(value[i] & 0xff);
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9834
|
+
/**
|
|
9835
|
+
* Writes the "infinity" byte sequence that sorts after all other byte
|
|
9836
|
+
* sequences written in ascending order.
|
|
9837
|
+
*/
|
|
9838
|
+
writeInfinityAscending() {
|
|
9839
|
+
this.writeEscapedByteAscending(ESCAPE2);
|
|
9840
|
+
this.writeEscapedByteAscending(INFINITY);
|
|
9841
|
+
}
|
|
9842
|
+
/**
|
|
9843
|
+
* Writes the "infinity" byte sequence that sorts before all other byte
|
|
9844
|
+
* sequences written in descending order.
|
|
9845
|
+
*/
|
|
9846
|
+
writeInfinityDescending() {
|
|
9847
|
+
this.writeEscapedByteDescending(ESCAPE2);
|
|
9848
|
+
this.writeEscapedByteDescending(INFINITY);
|
|
9849
|
+
}
|
|
9850
|
+
/**
|
|
9851
|
+
* Resets the buffer such that it is the same as when it was newly
|
|
9852
|
+
* constructed.
|
|
9853
|
+
*/
|
|
9854
|
+
reset() {
|
|
9855
|
+
this.position = 0;
|
|
9856
|
+
}
|
|
9857
|
+
seed(encodedBytes) {
|
|
9858
|
+
this.ensureAvailable(encodedBytes.length);
|
|
9859
|
+
this.buffer.set(encodedBytes, this.position);
|
|
9860
|
+
this.position += encodedBytes.length;
|
|
9861
|
+
}
|
|
9862
|
+
/** Makes a copy of the encoded bytes in this buffer. */
|
|
9863
|
+
encodedBytes() {
|
|
9864
|
+
return this.buffer.slice(0, this.position);
|
|
9865
|
+
}
|
|
9866
|
+
/**
|
|
9867
|
+
* Encodes `val` into an encoding so that the order matches the IEEE 754
|
|
9868
|
+
* floating-point comparison results with the following exceptions:
|
|
9869
|
+
* -0.0 < 0.0
|
|
9870
|
+
* all non-NaN < NaN
|
|
9871
|
+
* NaN = NaN
|
|
9872
|
+
*/
|
|
9873
|
+
toOrderedBits(val) {
|
|
9874
|
+
const value = doubleToLongBits(val);
|
|
9875
|
+
// Check if the first bit is set. We use a bit mask since value[0] is
|
|
9876
|
+
// encoded as a number from 0 to 255.
|
|
9877
|
+
const isNegative = (value[0] & 0x80) !== 0;
|
|
9878
|
+
// Revert the two complement to get natural ordering
|
|
9879
|
+
value[0] ^= isNegative ? 0xff : 0x80;
|
|
9880
|
+
for (let i = 1; i < value.length; ++i) {
|
|
9881
|
+
value[i] ^= isNegative ? 0xff : 0x00;
|
|
9882
|
+
}
|
|
9883
|
+
return value;
|
|
9884
|
+
}
|
|
9885
|
+
/** Writes a single byte ascending to the buffer. */
|
|
9886
|
+
writeByteAscending(b) {
|
|
9887
|
+
const masked = b & 0xff;
|
|
9888
|
+
if (masked === ESCAPE1) {
|
|
9889
|
+
this.writeEscapedByteAscending(ESCAPE1);
|
|
9890
|
+
this.writeEscapedByteAscending(NULL_BYTE);
|
|
9891
|
+
}
|
|
9892
|
+
else if (masked === ESCAPE2) {
|
|
9893
|
+
this.writeEscapedByteAscending(ESCAPE2);
|
|
9894
|
+
this.writeEscapedByteAscending(FF_BYTE);
|
|
9895
|
+
}
|
|
9896
|
+
else {
|
|
9897
|
+
this.writeEscapedByteAscending(masked);
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
/** Writes a single byte descending to the buffer. */
|
|
9901
|
+
writeByteDescending(b) {
|
|
9902
|
+
const masked = b & 0xff;
|
|
9903
|
+
if (masked === ESCAPE1) {
|
|
9904
|
+
this.writeEscapedByteDescending(ESCAPE1);
|
|
9905
|
+
this.writeEscapedByteDescending(NULL_BYTE);
|
|
9906
|
+
}
|
|
9907
|
+
else if (masked === ESCAPE2) {
|
|
9908
|
+
this.writeEscapedByteDescending(ESCAPE2);
|
|
9909
|
+
this.writeEscapedByteDescending(FF_BYTE);
|
|
9910
|
+
}
|
|
9911
|
+
else {
|
|
9912
|
+
this.writeEscapedByteDescending(b);
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
writeSeparatorAscending() {
|
|
9916
|
+
this.writeEscapedByteAscending(ESCAPE1);
|
|
9917
|
+
this.writeEscapedByteAscending(SEPARATOR);
|
|
9918
|
+
}
|
|
9919
|
+
writeSeparatorDescending() {
|
|
9920
|
+
this.writeEscapedByteDescending(ESCAPE1);
|
|
9921
|
+
this.writeEscapedByteDescending(SEPARATOR);
|
|
9922
|
+
}
|
|
9923
|
+
writeEscapedByteAscending(b) {
|
|
9924
|
+
this.ensureAvailable(1);
|
|
9925
|
+
this.buffer[this.position++] = b;
|
|
9926
|
+
}
|
|
9927
|
+
writeEscapedByteDescending(b) {
|
|
9928
|
+
this.ensureAvailable(1);
|
|
9929
|
+
this.buffer[this.position++] = ~b;
|
|
9930
|
+
}
|
|
9931
|
+
ensureAvailable(bytes) {
|
|
9932
|
+
const minCapacity = bytes + this.position;
|
|
9933
|
+
if (minCapacity <= this.buffer.length) {
|
|
9934
|
+
return;
|
|
9935
|
+
}
|
|
9936
|
+
// Try doubling.
|
|
9937
|
+
let newLength = this.buffer.length * 2;
|
|
9938
|
+
// Still not big enough? Just allocate the right size.
|
|
9939
|
+
if (newLength < minCapacity) {
|
|
9940
|
+
newLength = minCapacity;
|
|
9941
|
+
}
|
|
9942
|
+
// Create the new buffer.
|
|
9943
|
+
const newBuffer = new Uint8Array(newLength);
|
|
9944
|
+
newBuffer.set(this.buffer); // copy old data
|
|
9945
|
+
this.buffer = newBuffer;
|
|
9946
|
+
}
|
|
9947
|
+
}
|
|
9948
|
+
|
|
9949
|
+
class AscendingIndexByteEncoder {
|
|
9950
|
+
constructor(orderedCode) {
|
|
9951
|
+
this.orderedCode = orderedCode;
|
|
9952
|
+
}
|
|
9953
|
+
writeBytes(value) {
|
|
9954
|
+
this.orderedCode.writeBytesAscending(value);
|
|
9955
|
+
}
|
|
9956
|
+
writeString(value) {
|
|
9957
|
+
this.orderedCode.writeUtf8Ascending(value);
|
|
9958
|
+
}
|
|
9959
|
+
writeNumber(value) {
|
|
9960
|
+
this.orderedCode.writeNumberAscending(value);
|
|
9961
|
+
}
|
|
9962
|
+
writeInfinity() {
|
|
9963
|
+
this.orderedCode.writeInfinityAscending();
|
|
9964
|
+
}
|
|
9965
|
+
}
|
|
9966
|
+
class DescendingIndexByteEncoder {
|
|
9967
|
+
constructor(orderedCode) {
|
|
9968
|
+
this.orderedCode = orderedCode;
|
|
9969
|
+
}
|
|
9970
|
+
writeBytes(value) {
|
|
9971
|
+
this.orderedCode.writeBytesDescending(value);
|
|
9972
|
+
}
|
|
9973
|
+
writeString(value) {
|
|
9974
|
+
this.orderedCode.writeUtf8Descending(value);
|
|
9975
|
+
}
|
|
9976
|
+
writeNumber(value) {
|
|
9977
|
+
this.orderedCode.writeNumberDescending(value);
|
|
9978
|
+
}
|
|
9979
|
+
writeInfinity() {
|
|
9980
|
+
this.orderedCode.writeInfinityDescending();
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
/**
|
|
9984
|
+
* Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
|
|
9985
|
+
* actual encoding.
|
|
9986
|
+
*/
|
|
9987
|
+
class IndexByteEncoder {
|
|
9988
|
+
constructor() {
|
|
9989
|
+
this.orderedCode = new OrderedCodeWriter();
|
|
9990
|
+
this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
|
|
9991
|
+
this.descending = new DescendingIndexByteEncoder(this.orderedCode);
|
|
9992
|
+
}
|
|
9993
|
+
seed(encodedBytes) {
|
|
9994
|
+
this.orderedCode.seed(encodedBytes);
|
|
9995
|
+
}
|
|
9996
|
+
forKind(kind) {
|
|
9997
|
+
return kind === 0 /* ASCENDING */ ? this.ascending : this.descending;
|
|
9998
|
+
}
|
|
9999
|
+
encodedBytes() {
|
|
10000
|
+
return this.orderedCode.encodedBytes();
|
|
10001
|
+
}
|
|
10002
|
+
reset() {
|
|
10003
|
+
this.orderedCode.reset();
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
|
|
10007
|
+
/**
|
|
10008
|
+
* @license
|
|
10009
|
+
* Copyright 2022 Google LLC
|
|
10010
|
+
*
|
|
10011
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10012
|
+
* you may not use this file except in compliance with the License.
|
|
10013
|
+
* You may obtain a copy of the License at
|
|
10014
|
+
*
|
|
10015
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10016
|
+
*
|
|
10017
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10018
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10019
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10020
|
+
* See the License for the specific language governing permissions and
|
|
10021
|
+
* limitations under the License.
|
|
10022
|
+
*/
|
|
10023
|
+
/** Represents an index entry saved by the SDK in persisted storage. */
|
|
10024
|
+
class IndexEntry {
|
|
10025
|
+
constructor(indexId, documentKey, arrayValue, directionalValue) {
|
|
10026
|
+
this.indexId = indexId;
|
|
10027
|
+
this.documentKey = documentKey;
|
|
10028
|
+
this.arrayValue = arrayValue;
|
|
10029
|
+
this.directionalValue = directionalValue;
|
|
10030
|
+
}
|
|
10031
|
+
/**
|
|
10032
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
10033
|
+
* directional value.
|
|
10034
|
+
*/
|
|
10035
|
+
successor() {
|
|
10036
|
+
const currentLength = this.directionalValue.length;
|
|
10037
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
10038
|
+
? currentLength + 1
|
|
10039
|
+
: currentLength;
|
|
10040
|
+
const successor = new Uint8Array(newLength);
|
|
10041
|
+
successor.set(this.directionalValue, 0);
|
|
10042
|
+
if (newLength !== currentLength) {
|
|
10043
|
+
successor.set([0], this.directionalValue.length);
|
|
10044
|
+
}
|
|
10045
|
+
else {
|
|
10046
|
+
++successor[successor.length - 1];
|
|
10047
|
+
}
|
|
10048
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
10049
|
+
}
|
|
10050
|
+
}
|
|
10051
|
+
function indexEntryComparator(left, right) {
|
|
10052
|
+
let cmp = left.indexId - right.indexId;
|
|
10053
|
+
if (cmp !== 0) {
|
|
10054
|
+
return cmp;
|
|
10055
|
+
}
|
|
10056
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
10057
|
+
if (cmp !== 0) {
|
|
10058
|
+
return cmp;
|
|
10059
|
+
}
|
|
10060
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
10061
|
+
if (cmp !== 0) {
|
|
10062
|
+
return cmp;
|
|
10063
|
+
}
|
|
10064
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
10065
|
+
}
|
|
10066
|
+
function compareByteArrays(left, right) {
|
|
10067
|
+
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
10068
|
+
const compare = left[i] - right[i];
|
|
10069
|
+
if (compare !== 0) {
|
|
10070
|
+
return compare;
|
|
10071
|
+
}
|
|
10072
|
+
}
|
|
10073
|
+
return left.length - right.length;
|
|
10074
|
+
}
|
|
10075
|
+
|
|
10076
|
+
/**
|
|
10077
|
+
* @license
|
|
10078
|
+
* Copyright 2022 Google LLC
|
|
10079
|
+
*
|
|
10080
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10081
|
+
* you may not use this file except in compliance with the License.
|
|
10082
|
+
* You may obtain a copy of the License at
|
|
10083
|
+
*
|
|
10084
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10085
|
+
*
|
|
10086
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10087
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10088
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10089
|
+
* See the License for the specific language governing permissions and
|
|
10090
|
+
* limitations under the License.
|
|
10091
|
+
*/
|
|
10092
|
+
/**
|
|
10093
|
+
* A light query planner for Firestore.
|
|
10094
|
+
*
|
|
10095
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
10096
|
+
* determines whether a given index can be used to serve the specified target.
|
|
10097
|
+
*
|
|
10098
|
+
* The following table showcases some possible index configurations:
|
|
10099
|
+
*
|
|
10100
|
+
* Query | Index
|
|
10101
|
+
* -----------------------------------------------------------------------------
|
|
10102
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
10103
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
10104
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
10105
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
10106
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
10107
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
10108
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
10109
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
10110
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
10111
|
+
*/
|
|
10112
|
+
class TargetIndexMatcher {
|
|
10113
|
+
constructor(target) {
|
|
10114
|
+
this.collectionId =
|
|
10115
|
+
target.collectionGroup != null
|
|
10116
|
+
? target.collectionGroup
|
|
10117
|
+
: target.path.lastSegment();
|
|
10118
|
+
this.orderBys = target.orderBy;
|
|
10119
|
+
this.equalityFilters = [];
|
|
10120
|
+
for (const filter of target.filters) {
|
|
10121
|
+
const fieldFilter = filter;
|
|
10122
|
+
if (fieldFilter.isInequality()) {
|
|
10123
|
+
this.inequalityFilter = fieldFilter;
|
|
10124
|
+
}
|
|
10125
|
+
else {
|
|
10126
|
+
this.equalityFilters.push(fieldFilter);
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
}
|
|
10130
|
+
/**
|
|
10131
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
10132
|
+
* target.
|
|
10133
|
+
*
|
|
10134
|
+
* An index is considered capable of serving the target when:
|
|
10135
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
10136
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
10137
|
+
* fewer.
|
|
10138
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
10139
|
+
* have a corresponding `CONTAINS` segment.
|
|
10140
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
10141
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
10142
|
+
* clauses.
|
|
10143
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
10144
|
+
* - The optional segment for the inequality filter must appear after all
|
|
10145
|
+
* equality segments.
|
|
10146
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
10147
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
10148
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
10149
|
+
* omitted.
|
|
10150
|
+
*/
|
|
10151
|
+
servedByIndex(index) {
|
|
10152
|
+
// If there is an array element, find a matching filter.
|
|
10153
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
10154
|
+
if (arraySegment !== undefined &&
|
|
10155
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
10156
|
+
return false;
|
|
10157
|
+
}
|
|
10158
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
10159
|
+
let segmentIndex = 0;
|
|
10160
|
+
let orderBysIndex = 0;
|
|
10161
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
10162
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
10163
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
10164
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
10165
|
+
// Since it is not possible to use the same field path in both an equality
|
|
10166
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
10167
|
+
// that a matching equality segment should instead be used to map to an
|
|
10168
|
+
// inequality filter or orderBy clause.
|
|
10169
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
10170
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
10171
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
10172
|
+
// clauses.
|
|
10173
|
+
break;
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
// If we already have processed all segments, all segments are used to serve
|
|
10177
|
+
// the equality filters and we do not need to map any segments to the
|
|
10178
|
+
// target's inequality and orderBy clauses.
|
|
10179
|
+
if (segmentIndex === segments.length) {
|
|
10180
|
+
return true;
|
|
10181
|
+
}
|
|
10182
|
+
// If there is an inequality filter, the next segment must match both the
|
|
10183
|
+
// filter and the first orderBy clause.
|
|
10184
|
+
if (this.inequalityFilter !== undefined) {
|
|
10185
|
+
const segment = segments[segmentIndex];
|
|
10186
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
10187
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
10188
|
+
return false;
|
|
10189
|
+
}
|
|
10190
|
+
++segmentIndex;
|
|
10191
|
+
}
|
|
10192
|
+
// All remaining segments need to represent the prefix of the target's
|
|
10193
|
+
// orderBy.
|
|
10194
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
10195
|
+
const segment = segments[segmentIndex];
|
|
10196
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
10197
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
10198
|
+
return false;
|
|
10199
|
+
}
|
|
10200
|
+
}
|
|
10201
|
+
return true;
|
|
10202
|
+
}
|
|
10203
|
+
hasMatchingEqualityFilter(segment) {
|
|
10204
|
+
for (const filter of this.equalityFilters) {
|
|
10205
|
+
if (this.matchesFilter(filter, segment)) {
|
|
10206
|
+
return true;
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
10209
|
+
return false;
|
|
10210
|
+
}
|
|
10211
|
+
matchesFilter(filter, segment) {
|
|
10212
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
10213
|
+
return false;
|
|
10214
|
+
}
|
|
10215
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
10216
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
10217
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
10218
|
+
}
|
|
10219
|
+
matchesOrderBy(orderBy, segment) {
|
|
10220
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
10221
|
+
return false;
|
|
10222
|
+
}
|
|
10223
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
10224
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
10225
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
10226
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
10227
|
+
}
|
|
10228
|
+
}
|
|
10229
|
+
|
|
10230
|
+
/**
|
|
10231
|
+
* @license
|
|
10232
|
+
* Copyright 2017 Google LLC
|
|
10233
|
+
*
|
|
10234
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10235
|
+
* you may not use this file except in compliance with the License.
|
|
10236
|
+
* You may obtain a copy of the License at
|
|
10237
|
+
*
|
|
10238
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10239
|
+
*
|
|
10240
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10241
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10242
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10243
|
+
* See the License for the specific language governing permissions and
|
|
10244
|
+
* limitations under the License.
|
|
10245
|
+
*/
|
|
10246
|
+
/**
|
|
10247
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
10248
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
10249
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
10250
|
+
* automatically handles collisions of keys.
|
|
10251
|
+
*/
|
|
10252
|
+
class ObjectMap {
|
|
10253
|
+
constructor(mapKeyFn, equalsFn) {
|
|
10254
|
+
this.mapKeyFn = mapKeyFn;
|
|
10255
|
+
this.equalsFn = equalsFn;
|
|
10256
|
+
/**
|
|
10257
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
10258
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
10259
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
10260
|
+
* constant time lookups in practice.
|
|
10261
|
+
*/
|
|
10262
|
+
this.inner = {};
|
|
10263
|
+
}
|
|
10264
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
10265
|
+
get(key) {
|
|
10266
|
+
const id = this.mapKeyFn(key);
|
|
10267
|
+
const matches = this.inner[id];
|
|
10268
|
+
if (matches === undefined) {
|
|
10269
|
+
return undefined;
|
|
10270
|
+
}
|
|
10271
|
+
for (const [otherKey, value] of matches) {
|
|
10272
|
+
if (this.equalsFn(otherKey, key)) {
|
|
10273
|
+
return value;
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
return undefined;
|
|
10277
|
+
}
|
|
10278
|
+
has(key) {
|
|
10279
|
+
return this.get(key) !== undefined;
|
|
10280
|
+
}
|
|
10281
|
+
/** Put this key and value in the map. */
|
|
10282
|
+
set(key, value) {
|
|
10283
|
+
const id = this.mapKeyFn(key);
|
|
10284
|
+
const matches = this.inner[id];
|
|
10285
|
+
if (matches === undefined) {
|
|
10286
|
+
this.inner[id] = [[key, value]];
|
|
10287
|
+
return;
|
|
10288
|
+
}
|
|
10289
|
+
for (let i = 0; i < matches.length; i++) {
|
|
10290
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
10291
|
+
matches[i] = [key, value];
|
|
10292
|
+
return;
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
matches.push([key, value]);
|
|
10296
|
+
}
|
|
10297
|
+
/**
|
|
10298
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
10299
|
+
*/
|
|
10300
|
+
delete(key) {
|
|
10301
|
+
const id = this.mapKeyFn(key);
|
|
10302
|
+
const matches = this.inner[id];
|
|
10303
|
+
if (matches === undefined) {
|
|
10304
|
+
return false;
|
|
10305
|
+
}
|
|
10306
|
+
for (let i = 0; i < matches.length; i++) {
|
|
10307
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
10308
|
+
if (matches.length === 1) {
|
|
10309
|
+
delete this.inner[id];
|
|
10310
|
+
}
|
|
10311
|
+
else {
|
|
10312
|
+
matches.splice(i, 1);
|
|
10313
|
+
}
|
|
10314
|
+
return true;
|
|
10315
|
+
}
|
|
10316
|
+
}
|
|
10317
|
+
return false;
|
|
10318
|
+
}
|
|
10319
|
+
forEach(fn) {
|
|
10320
|
+
forEach(this.inner, (_, entries) => {
|
|
10321
|
+
for (const [k, v] of entries) {
|
|
10322
|
+
fn(k, v);
|
|
10323
|
+
}
|
|
10324
|
+
});
|
|
10325
|
+
}
|
|
10326
|
+
isEmpty() {
|
|
10327
|
+
return isEmpty(this.inner);
|
|
10328
|
+
}
|
|
10329
|
+
}
|
|
10330
|
+
|
|
10331
|
+
/**
|
|
10332
|
+
* @license
|
|
10333
|
+
* Copyright 2019 Google LLC
|
|
10334
|
+
*
|
|
10335
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10336
|
+
* you may not use this file except in compliance with the License.
|
|
10337
|
+
* You may obtain a copy of the License at
|
|
10338
|
+
*
|
|
10339
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10340
|
+
*
|
|
10341
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10342
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10343
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10344
|
+
* See the License for the specific language governing permissions and
|
|
10345
|
+
* limitations under the License.
|
|
10346
|
+
*/
|
|
10347
|
+
/**
|
|
10348
|
+
* An in-memory implementation of IndexManager.
|
|
10349
|
+
*/
|
|
10350
|
+
class MemoryIndexManager {
|
|
10351
|
+
constructor() {
|
|
10352
|
+
this.collectionParentIndex = new MemoryCollectionParentIndex();
|
|
10353
|
+
}
|
|
10354
|
+
addToCollectionParentIndex(transaction, collectionPath) {
|
|
10355
|
+
this.collectionParentIndex.add(collectionPath);
|
|
10356
|
+
return PersistencePromise.resolve();
|
|
10357
|
+
}
|
|
10358
|
+
getCollectionParents(transaction, collectionId) {
|
|
10359
|
+
return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
|
|
10360
|
+
}
|
|
10361
|
+
addFieldIndex(transaction, index) {
|
|
10362
|
+
// Field indices are not supported with memory persistence.
|
|
10363
|
+
return PersistencePromise.resolve();
|
|
10364
|
+
}
|
|
10365
|
+
deleteFieldIndex(transaction, index) {
|
|
10366
|
+
// Field indices are not supported with memory persistence.
|
|
10367
|
+
return PersistencePromise.resolve();
|
|
10368
|
+
}
|
|
10369
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10370
|
+
// Field indices are not supported with memory persistence.
|
|
10371
|
+
return PersistencePromise.resolve(null);
|
|
10372
|
+
}
|
|
10373
|
+
getFieldIndex(transaction, target) {
|
|
10374
|
+
// Field indices are not supported with memory persistence.
|
|
10375
|
+
return PersistencePromise.resolve(null);
|
|
10376
|
+
}
|
|
10377
|
+
getFieldIndexes(transaction, collectionGroup) {
|
|
10378
|
+
// Field indices are not supported with memory persistence.
|
|
10379
|
+
return PersistencePromise.resolve([]);
|
|
10380
|
+
}
|
|
10381
|
+
getNextCollectionGroupToUpdate(transaction) {
|
|
10382
|
+
// Field indices are not supported with memory persistence.
|
|
10383
|
+
return PersistencePromise.resolve(null);
|
|
10384
|
+
}
|
|
10385
|
+
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10386
|
+
// Field indices are not supported with memory persistence.
|
|
10387
|
+
return PersistencePromise.resolve();
|
|
10388
|
+
}
|
|
10389
|
+
updateIndexEntries(transaction, documents) {
|
|
10390
|
+
// Field indices are not supported with memory persistence.
|
|
10391
|
+
return PersistencePromise.resolve();
|
|
10392
|
+
}
|
|
10393
|
+
}
|
|
10394
|
+
/**
|
|
10395
|
+
* Internal implementation of the collection-parent index exposed by MemoryIndexManager.
|
|
10396
|
+
* Also used for in-memory caching by IndexedDbIndexManager and initial index population
|
|
10397
|
+
* in indexeddb_schema.ts
|
|
10398
|
+
*/
|
|
10399
|
+
class MemoryCollectionParentIndex {
|
|
10400
|
+
constructor() {
|
|
10401
|
+
this.index = {};
|
|
10402
|
+
}
|
|
10403
|
+
// Returns false if the entry already existed.
|
|
10404
|
+
add(collectionPath) {
|
|
10405
|
+
const collectionId = collectionPath.lastSegment();
|
|
10406
|
+
const parentPath = collectionPath.popLast();
|
|
10407
|
+
const existingParents = this.index[collectionId] ||
|
|
10408
|
+
new SortedSet(ResourcePath.comparator);
|
|
10409
|
+
const added = !existingParents.has(parentPath);
|
|
10410
|
+
this.index[collectionId] = existingParents.add(parentPath);
|
|
10411
|
+
return added;
|
|
10412
|
+
}
|
|
10413
|
+
has(collectionPath) {
|
|
10414
|
+
const collectionId = collectionPath.lastSegment();
|
|
10415
|
+
const parentPath = collectionPath.popLast();
|
|
10416
|
+
const existingParents = this.index[collectionId];
|
|
10417
|
+
return existingParents && existingParents.has(parentPath);
|
|
10418
|
+
}
|
|
10419
|
+
getEntries(collectionId) {
|
|
10420
|
+
const parentPaths = this.index[collectionId] ||
|
|
10421
|
+
new SortedSet(ResourcePath.comparator);
|
|
10422
|
+
return parentPaths.toArray();
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
|
|
10426
|
+
/**
|
|
10427
|
+
* @license
|
|
10428
|
+
* Copyright 2019 Google LLC
|
|
10429
|
+
*
|
|
8519
10430
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8520
10431
|
* you may not use this file except in compliance with the License.
|
|
8521
10432
|
* You may obtain a copy of the License at
|
|
@@ -8528,19 +10439,31 @@ class MemoryCollectionParentIndex {
|
|
|
8528
10439
|
* See the License for the specific language governing permissions and
|
|
8529
10440
|
* limitations under the License.
|
|
8530
10441
|
*/
|
|
10442
|
+
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
10443
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
8531
10444
|
/**
|
|
8532
10445
|
* A persisted implementation of IndexManager.
|
|
10446
|
+
*
|
|
10447
|
+
* PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
|
|
10448
|
+
* data as it supports multi-tab access.
|
|
8533
10449
|
*/
|
|
8534
10450
|
class IndexedDbIndexManager {
|
|
8535
|
-
constructor() {
|
|
10451
|
+
constructor(user) {
|
|
10452
|
+
this.user = user;
|
|
8536
10453
|
/**
|
|
8537
10454
|
* An in-memory copy of the index entries we've already written since the SDK
|
|
8538
10455
|
* launched. Used to avoid re-writing the same entry repeatedly.
|
|
8539
10456
|
*
|
|
8540
|
-
* This is *NOT* a complete cache of what's in persistence and so can never be
|
|
8541
|
-
* satisfy reads.
|
|
10457
|
+
* This is *NOT* a complete cache of what's in persistence and so can never be
|
|
10458
|
+
* used to satisfy reads.
|
|
8542
10459
|
*/
|
|
8543
10460
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
10461
|
+
/**
|
|
10462
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
10463
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
10464
|
+
*/
|
|
10465
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
10466
|
+
this.uid = user.uid || '';
|
|
8544
10467
|
}
|
|
8545
10468
|
/**
|
|
8546
10469
|
* Adds a new entry to the collection parent index.
|
|
@@ -8587,6 +10510,414 @@ class IndexedDbIndexManager {
|
|
|
8587
10510
|
return parentPaths;
|
|
8588
10511
|
});
|
|
8589
10512
|
}
|
|
10513
|
+
addFieldIndex(transaction, index) {
|
|
10514
|
+
// TODO(indexing): Verify that the auto-incrementing index ID works in
|
|
10515
|
+
// Safari & Firefox.
|
|
10516
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10517
|
+
const dbIndex = toDbIndexConfiguration(index);
|
|
10518
|
+
delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
|
|
10519
|
+
return indexes.add(dbIndex).next();
|
|
10520
|
+
}
|
|
10521
|
+
deleteFieldIndex(transaction, index) {
|
|
10522
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10523
|
+
const states = indexStateStore(transaction);
|
|
10524
|
+
const entries = indexEntriesStore(transaction);
|
|
10525
|
+
return indexes
|
|
10526
|
+
.delete(index.indexId)
|
|
10527
|
+
.next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
|
|
10528
|
+
/*lowerOpen=*/ false,
|
|
10529
|
+
/*upperOpen=*/ true)))
|
|
10530
|
+
.next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
|
|
10531
|
+
/*lowerOpen=*/ false,
|
|
10532
|
+
/*upperOpen=*/ true)));
|
|
10533
|
+
}
|
|
10534
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10535
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10536
|
+
let canServeTarget = true;
|
|
10537
|
+
const indexes = new Map();
|
|
10538
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10539
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10540
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10541
|
+
indexes.set(subTarget, index);
|
|
10542
|
+
});
|
|
10543
|
+
}).next(() => {
|
|
10544
|
+
if (!canServeTarget) {
|
|
10545
|
+
return PersistencePromise.resolve(null);
|
|
10546
|
+
}
|
|
10547
|
+
else {
|
|
10548
|
+
let result = documentKeySet();
|
|
10549
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10550
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10551
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10552
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10553
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10554
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10555
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10556
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10557
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10558
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10559
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10560
|
+
return indexEntries
|
|
10561
|
+
.loadFirst(indexRange, target.limit)
|
|
10562
|
+
.next(entries => {
|
|
10563
|
+
entries.forEach(entry => {
|
|
10564
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10565
|
+
});
|
|
10566
|
+
});
|
|
10567
|
+
});
|
|
10568
|
+
}).next(() => result);
|
|
10569
|
+
}
|
|
10570
|
+
});
|
|
10571
|
+
}
|
|
10572
|
+
getSubTargets(target) {
|
|
10573
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10574
|
+
if (subTargets) {
|
|
10575
|
+
return subTargets;
|
|
10576
|
+
}
|
|
10577
|
+
// TODO(orquery): Implement DNF transform
|
|
10578
|
+
subTargets = [target];
|
|
10579
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10580
|
+
return subTargets;
|
|
10581
|
+
}
|
|
10582
|
+
/**
|
|
10583
|
+
* Constructs a key range query on `DbIndexEntry.store` that unions all
|
|
10584
|
+
* bounds.
|
|
10585
|
+
*/
|
|
10586
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10587
|
+
// The number of total index scans we union together. This is similar to a
|
|
10588
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10589
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10590
|
+
// combined with the values from the query bounds.
|
|
10591
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10592
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10593
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10594
|
+
const indexRanges = [];
|
|
10595
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10596
|
+
const arrayValue = arrayValues
|
|
10597
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10598
|
+
: EMPTY_VALUE;
|
|
10599
|
+
const lowerBound = lowerBounds
|
|
10600
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10601
|
+
: this.generateEmptyBound(indexId);
|
|
10602
|
+
const upperBound = upperBounds
|
|
10603
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10604
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10605
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10606
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10607
|
+
/* inclusive= */ true))));
|
|
10608
|
+
}
|
|
10609
|
+
return indexRanges;
|
|
10610
|
+
}
|
|
10611
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10612
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10613
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10614
|
+
return inclusive ? entry : entry.successor();
|
|
10615
|
+
}
|
|
10616
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10617
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10618
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10619
|
+
return inclusive ? entry.successor() : entry;
|
|
10620
|
+
}
|
|
10621
|
+
/**
|
|
10622
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10623
|
+
* and user.
|
|
10624
|
+
*/
|
|
10625
|
+
generateEmptyBound(indexId) {
|
|
10626
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10627
|
+
}
|
|
10628
|
+
getFieldIndex(transaction, target) {
|
|
10629
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10630
|
+
const collectionGroup = target.collectionGroup != null
|
|
10631
|
+
? target.collectionGroup
|
|
10632
|
+
: target.path.lastSegment();
|
|
10633
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10634
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10635
|
+
// Return the index that matches the most number of segments.
|
|
10636
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10637
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10638
|
+
});
|
|
10639
|
+
}
|
|
10640
|
+
/**
|
|
10641
|
+
* Returns the byte encoded form of the directional values in the field index.
|
|
10642
|
+
* Returns `null` if the document does not have all fields specified in the
|
|
10643
|
+
* index.
|
|
10644
|
+
*/
|
|
10645
|
+
encodeDirectionalElements(fieldIndex, document) {
|
|
10646
|
+
const encoder = new IndexByteEncoder();
|
|
10647
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10648
|
+
const field = document.data.field(segment.fieldPath);
|
|
10649
|
+
if (field == null) {
|
|
10650
|
+
return null;
|
|
10651
|
+
}
|
|
10652
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10653
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10654
|
+
}
|
|
10655
|
+
return encoder.encodedBytes();
|
|
10656
|
+
}
|
|
10657
|
+
/** Encodes a single value to the ascending index format. */
|
|
10658
|
+
encodeSingleElement(value) {
|
|
10659
|
+
const encoder = new IndexByteEncoder();
|
|
10660
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10661
|
+
return encoder.encodedBytes();
|
|
10662
|
+
}
|
|
10663
|
+
/**
|
|
10664
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10665
|
+
* For IN queries, a list of possible values is returned.
|
|
10666
|
+
*/
|
|
10667
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10668
|
+
if (bound === null) {
|
|
10669
|
+
return [];
|
|
10670
|
+
}
|
|
10671
|
+
let encoders = [];
|
|
10672
|
+
encoders.push(new IndexByteEncoder());
|
|
10673
|
+
let boundIdx = 0;
|
|
10674
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10675
|
+
const value = bound[boundIdx++];
|
|
10676
|
+
for (const encoder of encoders) {
|
|
10677
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10678
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10679
|
+
}
|
|
10680
|
+
else {
|
|
10681
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10682
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
10685
|
+
}
|
|
10686
|
+
return this.getEncodedBytes(encoders);
|
|
10687
|
+
}
|
|
10688
|
+
/**
|
|
10689
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10690
|
+
* queries, a list of possible values is returned.
|
|
10691
|
+
*/
|
|
10692
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10693
|
+
if (bound == null) {
|
|
10694
|
+
return null;
|
|
10695
|
+
}
|
|
10696
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10697
|
+
}
|
|
10698
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10699
|
+
getEncodedBytes(encoders) {
|
|
10700
|
+
const result = [];
|
|
10701
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10702
|
+
result[i] = encoders[i].encodedBytes();
|
|
10703
|
+
}
|
|
10704
|
+
return result;
|
|
10705
|
+
}
|
|
10706
|
+
/**
|
|
10707
|
+
* Creates a separate encoder for each element of an array.
|
|
10708
|
+
*
|
|
10709
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10710
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10711
|
+
* list of new encoders is returned.
|
|
10712
|
+
*/
|
|
10713
|
+
expandIndexValues(encoders, segment, value) {
|
|
10714
|
+
const prefixes = [...encoders];
|
|
10715
|
+
const results = [];
|
|
10716
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10717
|
+
for (const prefix of prefixes) {
|
|
10718
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10719
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10720
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10721
|
+
results.push(clonedEncoder);
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
return results;
|
|
10725
|
+
}
|
|
10726
|
+
isInFilter(target, fieldPath) {
|
|
10727
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10728
|
+
f.field.isEqual(fieldPath) &&
|
|
10729
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
10730
|
+
}
|
|
10731
|
+
getFieldIndexes(transaction, collectionGroup) {
|
|
10732
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10733
|
+
const states = indexStateStore(transaction);
|
|
10734
|
+
return (collectionGroup
|
|
10735
|
+
? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10736
|
+
: indexes.loadAll()).next(indexConfigs => {
|
|
10737
|
+
const result = [];
|
|
10738
|
+
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
10739
|
+
return states
|
|
10740
|
+
.get([indexConfig.indexId, this.uid])
|
|
10741
|
+
.next(indexState => {
|
|
10742
|
+
result.push(fromDbIndexConfiguration(indexConfig, indexState));
|
|
10743
|
+
});
|
|
10744
|
+
}).next(() => result);
|
|
10745
|
+
});
|
|
10746
|
+
}
|
|
10747
|
+
getNextCollectionGroupToUpdate(transaction) {
|
|
10748
|
+
return this.getFieldIndexes(transaction).next(indexes => {
|
|
10749
|
+
if (indexes.length === 0) {
|
|
10750
|
+
return null;
|
|
10751
|
+
}
|
|
10752
|
+
indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
|
|
10753
|
+
return indexes[0].collectionGroup;
|
|
10754
|
+
});
|
|
10755
|
+
}
|
|
10756
|
+
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10757
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10758
|
+
const states = indexStateStore(transaction);
|
|
10759
|
+
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
10760
|
+
.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10761
|
+
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
10762
|
+
}
|
|
10763
|
+
updateIndexEntries(transaction, documents) {
|
|
10764
|
+
// Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
|
|
10765
|
+
// it could be used across different IndexedDB transactions. As any cached
|
|
10766
|
+
// data might be invalidated by other multi-tab clients, we can only trust
|
|
10767
|
+
// data within a single IndexedDB transaction. We therefore add a cache
|
|
10768
|
+
// here.
|
|
10769
|
+
const memoizedIndexes = new Map();
|
|
10770
|
+
return PersistencePromise.forEach(documents, (key, doc) => {
|
|
10771
|
+
const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
|
|
10772
|
+
const fieldIndexes = memoizedCollectionIndexes
|
|
10773
|
+
? PersistencePromise.resolve(memoizedCollectionIndexes)
|
|
10774
|
+
: this.getFieldIndexes(transaction, key.collectionGroup);
|
|
10775
|
+
return fieldIndexes.next(fieldIndexes => {
|
|
10776
|
+
memoizedIndexes.set(key.collectionGroup, fieldIndexes);
|
|
10777
|
+
return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
|
|
10778
|
+
return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
|
|
10779
|
+
const newEntries = this.computeIndexEntries(doc, fieldIndex);
|
|
10780
|
+
if (!existingEntries.isEqual(newEntries)) {
|
|
10781
|
+
return this.updateEntries(transaction, doc, existingEntries, newEntries);
|
|
10782
|
+
}
|
|
10783
|
+
return PersistencePromise.resolve();
|
|
10784
|
+
});
|
|
10785
|
+
});
|
|
10786
|
+
});
|
|
10787
|
+
});
|
|
10788
|
+
}
|
|
10789
|
+
addIndexEntry(transaction, document, indexEntry) {
|
|
10790
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10791
|
+
return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
|
|
10792
|
+
}
|
|
10793
|
+
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10794
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10795
|
+
return indexEntries.delete([
|
|
10796
|
+
indexEntry.indexId,
|
|
10797
|
+
this.uid,
|
|
10798
|
+
indexEntry.arrayValue,
|
|
10799
|
+
indexEntry.directionalValue,
|
|
10800
|
+
encodeResourcePath(document.key.path)
|
|
10801
|
+
]);
|
|
10802
|
+
}
|
|
10803
|
+
getExistingIndexEntries(transaction, documentKey, fieldIndex) {
|
|
10804
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10805
|
+
let results = new SortedSet(indexEntryComparator);
|
|
10806
|
+
return indexEntries
|
|
10807
|
+
.iterate({
|
|
10808
|
+
index: DbIndexEntry.documentKeyIndex,
|
|
10809
|
+
range: IDBKeyRange.only([
|
|
10810
|
+
fieldIndex.indexId,
|
|
10811
|
+
this.uid,
|
|
10812
|
+
encodeResourcePath(documentKey.path)
|
|
10813
|
+
])
|
|
10814
|
+
}, (_, entry) => {
|
|
10815
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
|
|
10816
|
+
})
|
|
10817
|
+
.next(() => results);
|
|
10818
|
+
}
|
|
10819
|
+
/** Creates the index entries for the given document. */
|
|
10820
|
+
computeIndexEntries(document, fieldIndex) {
|
|
10821
|
+
let results = new SortedSet(indexEntryComparator);
|
|
10822
|
+
const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
|
|
10823
|
+
if (directionalValue == null) {
|
|
10824
|
+
return results;
|
|
10825
|
+
}
|
|
10826
|
+
const arraySegment = fieldIndexGetArraySegment(fieldIndex);
|
|
10827
|
+
if (arraySegment != null) {
|
|
10828
|
+
const value = document.data.field(arraySegment.fieldPath);
|
|
10829
|
+
if (isArray(value)) {
|
|
10830
|
+
for (const arrayValue of value.arrayValue.values || []) {
|
|
10831
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
|
|
10832
|
+
}
|
|
10833
|
+
}
|
|
10834
|
+
}
|
|
10835
|
+
else {
|
|
10836
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10837
|
+
}
|
|
10838
|
+
return results;
|
|
10839
|
+
}
|
|
10840
|
+
/**
|
|
10841
|
+
* Updates the index entries for the provided document by deleting entries
|
|
10842
|
+
* that are no longer referenced in `newEntries` and adding all newly added
|
|
10843
|
+
* entries.
|
|
10844
|
+
*/
|
|
10845
|
+
updateEntries(transaction, document, existingEntries, newEntries) {
|
|
10846
|
+
logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
|
|
10847
|
+
const promises = [];
|
|
10848
|
+
diffSortedSets(existingEntries, newEntries, indexEntryComparator,
|
|
10849
|
+
/* onAdd= */ entry => {
|
|
10850
|
+
promises.push(this.addIndexEntry(transaction, document, entry));
|
|
10851
|
+
},
|
|
10852
|
+
/* onRemove= */ entry => {
|
|
10853
|
+
promises.push(this.deleteIndexEntry(transaction, document, entry));
|
|
10854
|
+
});
|
|
10855
|
+
return PersistencePromise.waitFor(promises);
|
|
10856
|
+
}
|
|
10857
|
+
getNextSequenceNumber(transaction) {
|
|
10858
|
+
let nextSequenceNumber = 1;
|
|
10859
|
+
const states = indexStateStore(transaction);
|
|
10860
|
+
return states
|
|
10861
|
+
.iterate({
|
|
10862
|
+
index: DbIndexState.sequenceNumberIndex,
|
|
10863
|
+
reverse: true,
|
|
10864
|
+
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10865
|
+
}, (_, state, controller) => {
|
|
10866
|
+
controller.done();
|
|
10867
|
+
nextSequenceNumber = state.sequenceNumber + 1;
|
|
10868
|
+
})
|
|
10869
|
+
.next(() => nextSequenceNumber);
|
|
10870
|
+
}
|
|
10871
|
+
/**
|
|
10872
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10873
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10874
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10875
|
+
*/
|
|
10876
|
+
createRange(lower, upper, notInValues) {
|
|
10877
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10878
|
+
// sorted set of non-overlapping ranges.
|
|
10879
|
+
notInValues = notInValues
|
|
10880
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10881
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10882
|
+
const bounds = [];
|
|
10883
|
+
bounds.push(lower);
|
|
10884
|
+
for (const notInValue of notInValues) {
|
|
10885
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10886
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10887
|
+
if (cmpToLower === 0) {
|
|
10888
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10889
|
+
// to the next value.
|
|
10890
|
+
bounds[0] = lower.successor();
|
|
10891
|
+
}
|
|
10892
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10893
|
+
// `notInValue` is in the middle of the range
|
|
10894
|
+
bounds.push(notInValue);
|
|
10895
|
+
bounds.push(notInValue.successor());
|
|
10896
|
+
}
|
|
10897
|
+
else if (cmpToUpper > 0) {
|
|
10898
|
+
// `notInValue` (and all following values) are out of the range
|
|
10899
|
+
break;
|
|
10900
|
+
}
|
|
10901
|
+
}
|
|
10902
|
+
bounds.push(upper);
|
|
10903
|
+
const ranges = [];
|
|
10904
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10905
|
+
ranges.push(IDBKeyRange.bound([
|
|
10906
|
+
bounds[i].indexId,
|
|
10907
|
+
this.uid,
|
|
10908
|
+
bounds[i].arrayValue,
|
|
10909
|
+
bounds[i].directionalValue,
|
|
10910
|
+
''
|
|
10911
|
+
], [
|
|
10912
|
+
bounds[i + 1].indexId,
|
|
10913
|
+
this.uid,
|
|
10914
|
+
bounds[i + 1].arrayValue,
|
|
10915
|
+
bounds[i + 1].directionalValue,
|
|
10916
|
+
''
|
|
10917
|
+
]));
|
|
10918
|
+
}
|
|
10919
|
+
return ranges;
|
|
10920
|
+
}
|
|
8590
10921
|
}
|
|
8591
10922
|
/**
|
|
8592
10923
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -8594,6 +10925,24 @@ class IndexedDbIndexManager {
|
|
|
8594
10925
|
*/
|
|
8595
10926
|
function collectionParentsStore(txn) {
|
|
8596
10927
|
return getStore(txn, DbCollectionParent.store);
|
|
10928
|
+
}
|
|
10929
|
+
/**
|
|
10930
|
+
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10931
|
+
*/
|
|
10932
|
+
function indexEntriesStore(txn) {
|
|
10933
|
+
return getStore(txn, DbIndexEntry.store);
|
|
10934
|
+
}
|
|
10935
|
+
/**
|
|
10936
|
+
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10937
|
+
*/
|
|
10938
|
+
function indexConfigurationStore(txn) {
|
|
10939
|
+
return getStore(txn, DbIndexConfiguration.store);
|
|
10940
|
+
}
|
|
10941
|
+
/**
|
|
10942
|
+
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10943
|
+
*/
|
|
10944
|
+
function indexStateStore(txn) {
|
|
10945
|
+
return getStore(txn, DbIndexState.store);
|
|
8597
10946
|
}
|
|
8598
10947
|
|
|
8599
10948
|
/**
|
|
@@ -9757,7 +12106,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
9757
12106
|
// Our size accounting requires us to read all documents before
|
|
9758
12107
|
// removing them.
|
|
9759
12108
|
return changeBuffer.getEntry(txn, docKey).next(() => {
|
|
9760
|
-
changeBuffer.removeEntry(docKey);
|
|
12109
|
+
changeBuffer.removeEntry(docKey, SnapshotVersion.min());
|
|
9761
12110
|
return documentTargetStore(txn).delete(sentinelKey$1(docKey));
|
|
9762
12111
|
});
|
|
9763
12112
|
}
|
|
@@ -9779,163 +12128,62 @@ class IndexedDbLruDelegateImpl {
|
|
|
9779
12128
|
}
|
|
9780
12129
|
/**
|
|
9781
12130
|
* Call provided function for each document in the cache that is 'orphaned'. Orphaned
|
|
9782
|
-
* means not a part of any target, so the only entry in the target-document index for
|
|
9783
|
-
* that document will be the sentinel row (targetId 0), which will also have the sequence
|
|
9784
|
-
* number for the last time the document was accessed.
|
|
9785
|
-
*/
|
|
9786
|
-
forEachOrphanedDocument(txn, f) {
|
|
9787
|
-
const store = documentTargetStore(txn);
|
|
9788
|
-
let nextToReport = ListenSequence.INVALID;
|
|
9789
|
-
let nextPath;
|
|
9790
|
-
return store
|
|
9791
|
-
.iterate({
|
|
9792
|
-
index: DbTargetDocument.documentTargetsIndex
|
|
9793
|
-
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
9794
|
-
if (targetId === 0) {
|
|
9795
|
-
// if nextToReport is valid, report it, this is a new key so the
|
|
9796
|
-
// last one must not be a member of any targets.
|
|
9797
|
-
if (nextToReport !== ListenSequence.INVALID) {
|
|
9798
|
-
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9799
|
-
}
|
|
9800
|
-
// set nextToReport to be this sequence number. It's the next one we
|
|
9801
|
-
// might report, if we don't find any targets for this document.
|
|
9802
|
-
// Note that the sequence number must be defined when the targetId
|
|
9803
|
-
// is 0.
|
|
9804
|
-
nextToReport = sequenceNumber;
|
|
9805
|
-
nextPath = path;
|
|
9806
|
-
}
|
|
9807
|
-
else {
|
|
9808
|
-
// set nextToReport to be invalid, we know we don't need to report
|
|
9809
|
-
// this one since we found a target for it.
|
|
9810
|
-
nextToReport = ListenSequence.INVALID;
|
|
9811
|
-
}
|
|
9812
|
-
})
|
|
9813
|
-
.next(() => {
|
|
9814
|
-
// Since we report sequence numbers after getting to the next key, we
|
|
9815
|
-
// need to check if the last key we iterated over was an orphaned
|
|
9816
|
-
// document and report it.
|
|
9817
|
-
if (nextToReport !== ListenSequence.INVALID) {
|
|
9818
|
-
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9819
|
-
}
|
|
9820
|
-
});
|
|
9821
|
-
}
|
|
9822
|
-
getCacheSize(txn) {
|
|
9823
|
-
return this.db.getRemoteDocumentCache().getSize(txn);
|
|
9824
|
-
}
|
|
9825
|
-
}
|
|
9826
|
-
function sentinelKey$1(key) {
|
|
9827
|
-
return [0, encodeResourcePath(key.path)];
|
|
9828
|
-
}
|
|
9829
|
-
/**
|
|
9830
|
-
* @returns A value suitable for writing a sentinel row in the target-document
|
|
9831
|
-
* store.
|
|
9832
|
-
*/
|
|
9833
|
-
function sentinelRow(key, sequenceNumber) {
|
|
9834
|
-
return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
|
|
9835
|
-
}
|
|
9836
|
-
function writeSentinelKey(txn, key) {
|
|
9837
|
-
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
9838
|
-
}
|
|
9839
|
-
|
|
9840
|
-
/**
|
|
9841
|
-
* @license
|
|
9842
|
-
* Copyright 2017 Google LLC
|
|
9843
|
-
*
|
|
9844
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9845
|
-
* you may not use this file except in compliance with the License.
|
|
9846
|
-
* You may obtain a copy of the License at
|
|
9847
|
-
*
|
|
9848
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9849
|
-
*
|
|
9850
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
9851
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9852
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9853
|
-
* See the License for the specific language governing permissions and
|
|
9854
|
-
* limitations under the License.
|
|
9855
|
-
*/
|
|
9856
|
-
/**
|
|
9857
|
-
* A map implementation that uses objects as keys. Objects must have an
|
|
9858
|
-
* associated equals function and must be immutable. Entries in the map are
|
|
9859
|
-
* stored together with the key being produced from the mapKeyFn. This map
|
|
9860
|
-
* automatically handles collisions of keys.
|
|
9861
|
-
*/
|
|
9862
|
-
class ObjectMap {
|
|
9863
|
-
constructor(mapKeyFn, equalsFn) {
|
|
9864
|
-
this.mapKeyFn = mapKeyFn;
|
|
9865
|
-
this.equalsFn = equalsFn;
|
|
9866
|
-
/**
|
|
9867
|
-
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
9868
|
-
* keep a list of entries that we do a linear search through to find an actual
|
|
9869
|
-
* match. Note that collisions should be rare, so we still expect near
|
|
9870
|
-
* constant time lookups in practice.
|
|
9871
|
-
*/
|
|
9872
|
-
this.inner = {};
|
|
9873
|
-
}
|
|
9874
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
9875
|
-
get(key) {
|
|
9876
|
-
const id = this.mapKeyFn(key);
|
|
9877
|
-
const matches = this.inner[id];
|
|
9878
|
-
if (matches === undefined) {
|
|
9879
|
-
return undefined;
|
|
9880
|
-
}
|
|
9881
|
-
for (const [otherKey, value] of matches) {
|
|
9882
|
-
if (this.equalsFn(otherKey, key)) {
|
|
9883
|
-
return value;
|
|
9884
|
-
}
|
|
9885
|
-
}
|
|
9886
|
-
return undefined;
|
|
9887
|
-
}
|
|
9888
|
-
has(key) {
|
|
9889
|
-
return this.get(key) !== undefined;
|
|
9890
|
-
}
|
|
9891
|
-
/** Put this key and value in the map. */
|
|
9892
|
-
set(key, value) {
|
|
9893
|
-
const id = this.mapKeyFn(key);
|
|
9894
|
-
const matches = this.inner[id];
|
|
9895
|
-
if (matches === undefined) {
|
|
9896
|
-
this.inner[id] = [[key, value]];
|
|
9897
|
-
return;
|
|
9898
|
-
}
|
|
9899
|
-
for (let i = 0; i < matches.length; i++) {
|
|
9900
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
9901
|
-
matches[i] = [key, value];
|
|
9902
|
-
return;
|
|
9903
|
-
}
|
|
9904
|
-
}
|
|
9905
|
-
matches.push([key, value]);
|
|
9906
|
-
}
|
|
9907
|
-
/**
|
|
9908
|
-
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
9909
|
-
*/
|
|
9910
|
-
delete(key) {
|
|
9911
|
-
const id = this.mapKeyFn(key);
|
|
9912
|
-
const matches = this.inner[id];
|
|
9913
|
-
if (matches === undefined) {
|
|
9914
|
-
return false;
|
|
9915
|
-
}
|
|
9916
|
-
for (let i = 0; i < matches.length; i++) {
|
|
9917
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
9918
|
-
if (matches.length === 1) {
|
|
9919
|
-
delete this.inner[id];
|
|
9920
|
-
}
|
|
9921
|
-
else {
|
|
9922
|
-
matches.splice(i, 1);
|
|
12131
|
+
* means not a part of any target, so the only entry in the target-document index for
|
|
12132
|
+
* that document will be the sentinel row (targetId 0), which will also have the sequence
|
|
12133
|
+
* number for the last time the document was accessed.
|
|
12134
|
+
*/
|
|
12135
|
+
forEachOrphanedDocument(txn, f) {
|
|
12136
|
+
const store = documentTargetStore(txn);
|
|
12137
|
+
let nextToReport = ListenSequence.INVALID;
|
|
12138
|
+
let nextPath;
|
|
12139
|
+
return store
|
|
12140
|
+
.iterate({
|
|
12141
|
+
index: DbTargetDocument.documentTargetsIndex
|
|
12142
|
+
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
12143
|
+
if (targetId === 0) {
|
|
12144
|
+
// if nextToReport is valid, report it, this is a new key so the
|
|
12145
|
+
// last one must not be a member of any targets.
|
|
12146
|
+
if (nextToReport !== ListenSequence.INVALID) {
|
|
12147
|
+
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9923
12148
|
}
|
|
9924
|
-
|
|
12149
|
+
// set nextToReport to be this sequence number. It's the next one we
|
|
12150
|
+
// might report, if we don't find any targets for this document.
|
|
12151
|
+
// Note that the sequence number must be defined when the targetId
|
|
12152
|
+
// is 0.
|
|
12153
|
+
nextToReport = sequenceNumber;
|
|
12154
|
+
nextPath = path;
|
|
9925
12155
|
}
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
12156
|
+
else {
|
|
12157
|
+
// set nextToReport to be invalid, we know we don't need to report
|
|
12158
|
+
// this one since we found a target for it.
|
|
12159
|
+
nextToReport = ListenSequence.INVALID;
|
|
12160
|
+
}
|
|
12161
|
+
})
|
|
12162
|
+
.next(() => {
|
|
12163
|
+
// Since we report sequence numbers after getting to the next key, we
|
|
12164
|
+
// need to check if the last key we iterated over was an orphaned
|
|
12165
|
+
// document and report it.
|
|
12166
|
+
if (nextToReport !== ListenSequence.INVALID) {
|
|
12167
|
+
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9933
12168
|
}
|
|
9934
12169
|
});
|
|
9935
12170
|
}
|
|
9936
|
-
|
|
9937
|
-
return
|
|
12171
|
+
getCacheSize(txn) {
|
|
12172
|
+
return this.db.getRemoteDocumentCache().getSize(txn);
|
|
9938
12173
|
}
|
|
12174
|
+
}
|
|
12175
|
+
function sentinelKey$1(key) {
|
|
12176
|
+
return [0, encodeResourcePath(key.path)];
|
|
12177
|
+
}
|
|
12178
|
+
/**
|
|
12179
|
+
* @returns A value suitable for writing a sentinel row in the target-document
|
|
12180
|
+
* store.
|
|
12181
|
+
*/
|
|
12182
|
+
function sentinelRow(key, sequenceNumber) {
|
|
12183
|
+
return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
|
|
12184
|
+
}
|
|
12185
|
+
function writeSentinelKey(txn, key) {
|
|
12186
|
+
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
9939
12187
|
}
|
|
9940
12188
|
|
|
9941
12189
|
/**
|
|
@@ -9970,27 +12218,19 @@ class ObjectMap {
|
|
|
9970
12218
|
*/
|
|
9971
12219
|
class RemoteDocumentChangeBuffer {
|
|
9972
12220
|
constructor() {
|
|
9973
|
-
// A mapping of document key to the new cache entry that should be written
|
|
9974
|
-
// existing cache entry should be removed).
|
|
12221
|
+
// A mapping of document key to the new cache entry that should be written.
|
|
9975
12222
|
this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
9976
12223
|
this.changesApplied = false;
|
|
9977
12224
|
}
|
|
9978
|
-
getReadTime(key) {
|
|
9979
|
-
const change = this.changes.get(key);
|
|
9980
|
-
if (change) {
|
|
9981
|
-
return change.readTime;
|
|
9982
|
-
}
|
|
9983
|
-
return SnapshotVersion.min();
|
|
9984
|
-
}
|
|
9985
12225
|
/**
|
|
9986
12226
|
* Buffers a `RemoteDocumentCache.addEntry()` call.
|
|
9987
12227
|
*
|
|
9988
12228
|
* You can only modify documents that have already been retrieved via
|
|
9989
12229
|
* `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
|
|
9990
12230
|
*/
|
|
9991
|
-
addEntry(document
|
|
12231
|
+
addEntry(document) {
|
|
9992
12232
|
this.assertNotApplied();
|
|
9993
|
-
this.changes.set(document.key,
|
|
12233
|
+
this.changes.set(document.key, document);
|
|
9994
12234
|
}
|
|
9995
12235
|
/**
|
|
9996
12236
|
* Buffers a `RemoteDocumentCache.removeEntry()` call.
|
|
@@ -9998,12 +12238,9 @@ class RemoteDocumentChangeBuffer {
|
|
|
9998
12238
|
* You can only remove documents that have already been retrieved via
|
|
9999
12239
|
* `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
|
|
10000
12240
|
*/
|
|
10001
|
-
removeEntry(key, readTime
|
|
12241
|
+
removeEntry(key, readTime) {
|
|
10002
12242
|
this.assertNotApplied();
|
|
10003
|
-
this.changes.set(key,
|
|
10004
|
-
document: MutableDocument.newInvalidDocument(key),
|
|
10005
|
-
readTime
|
|
10006
|
-
});
|
|
12243
|
+
this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
|
|
10007
12244
|
}
|
|
10008
12245
|
/**
|
|
10009
12246
|
* Looks up an entry in the cache. The buffered changes will first be checked,
|
|
@@ -10020,7 +12257,7 @@ class RemoteDocumentChangeBuffer {
|
|
|
10020
12257
|
this.assertNotApplied();
|
|
10021
12258
|
const bufferedEntry = this.changes.get(documentKey);
|
|
10022
12259
|
if (bufferedEntry !== undefined) {
|
|
10023
|
-
return PersistencePromise.resolve(bufferedEntry
|
|
12260
|
+
return PersistencePromise.resolve(bufferedEntry);
|
|
10024
12261
|
}
|
|
10025
12262
|
else {
|
|
10026
12263
|
return this.getFromCache(transaction, documentKey);
|
|
@@ -10074,12 +12311,10 @@ class RemoteDocumentChangeBuffer {
|
|
|
10074
12311
|
* `newIndexedDbRemoteDocumentCache()`.
|
|
10075
12312
|
*/
|
|
10076
12313
|
class IndexedDbRemoteDocumentCacheImpl {
|
|
10077
|
-
|
|
10078
|
-
* @param serializer - The document serializer.
|
|
10079
|
-
* @param indexManager - The query indexes that need to be maintained.
|
|
10080
|
-
*/
|
|
10081
|
-
constructor(serializer, indexManager) {
|
|
12314
|
+
constructor(serializer) {
|
|
10082
12315
|
this.serializer = serializer;
|
|
12316
|
+
}
|
|
12317
|
+
setIndexManager(indexManager) {
|
|
10083
12318
|
this.indexManager = indexManager;
|
|
10084
12319
|
}
|
|
10085
12320
|
/**
|
|
@@ -10201,21 +12436,21 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10201
12436
|
}
|
|
10202
12437
|
});
|
|
10203
12438
|
}
|
|
10204
|
-
|
|
12439
|
+
getAll(transaction, collection, sinceReadTime) {
|
|
10205
12440
|
let results = mutableDocumentMap();
|
|
10206
|
-
const immediateChildrenPathLength =
|
|
12441
|
+
const immediateChildrenPathLength = collection.length + 1;
|
|
10207
12442
|
const iterationOptions = {};
|
|
10208
12443
|
if (sinceReadTime.isEqual(SnapshotVersion.min())) {
|
|
10209
12444
|
// Documents are ordered by key, so we can use a prefix scan to narrow
|
|
10210
12445
|
// down the documents we need to match the query against.
|
|
10211
|
-
const startKey =
|
|
12446
|
+
const startKey = collection.toArray();
|
|
10212
12447
|
iterationOptions.range = IDBKeyRange.lowerBound(startKey);
|
|
10213
12448
|
}
|
|
10214
12449
|
else {
|
|
10215
12450
|
// Execute an index-free query and filter by read time. This is safe
|
|
10216
12451
|
// since all document changes to queries that have a
|
|
10217
12452
|
// lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
|
|
10218
|
-
const collectionKey =
|
|
12453
|
+
const collectionKey = collection.toArray();
|
|
10219
12454
|
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
10220
12455
|
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
10221
12456
|
/* open= */ true);
|
|
@@ -10231,13 +12466,13 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10231
12466
|
if (key.length !== immediateChildrenPathLength) {
|
|
10232
12467
|
return;
|
|
10233
12468
|
}
|
|
10234
|
-
const document =
|
|
10235
|
-
if (
|
|
10236
|
-
control.done();
|
|
10237
|
-
}
|
|
10238
|
-
else if (queryMatches(query, document)) {
|
|
12469
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
|
|
12470
|
+
if (collection.isPrefixOf(document.key.path)) {
|
|
10239
12471
|
results = results.insert(document.key, document);
|
|
10240
12472
|
}
|
|
12473
|
+
else {
|
|
12474
|
+
control.done();
|
|
12475
|
+
}
|
|
10241
12476
|
})
|
|
10242
12477
|
.next(() => results);
|
|
10243
12478
|
}
|
|
@@ -10259,8 +12494,8 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10259
12494
|
return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
|
|
10260
12495
|
}
|
|
10261
12496
|
/**
|
|
10262
|
-
* Decodes `
|
|
10263
|
-
* corresponds to the format used for sentinel deletes).
|
|
12497
|
+
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
12498
|
+
* the document corresponds to the format used for sentinel deletes).
|
|
10264
12499
|
*/
|
|
10265
12500
|
maybeDecodeDocument(documentKey, dbRemoteDoc) {
|
|
10266
12501
|
if (dbRemoteDoc) {
|
|
@@ -10275,14 +12510,9 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10275
12510
|
return MutableDocument.newInvalidDocument(documentKey);
|
|
10276
12511
|
}
|
|
10277
12512
|
}
|
|
10278
|
-
/**
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
* @param serializer - The document serializer.
|
|
10282
|
-
* @param indexManager - The query indexes that need to be maintained.
|
|
10283
|
-
*/
|
|
10284
|
-
function newIndexedDbRemoteDocumentCache(serializer, indexManager) {
|
|
10285
|
-
return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
|
|
12513
|
+
/** Creates a new IndexedDbRemoteDocumentCache. */
|
|
12514
|
+
function newIndexedDbRemoteDocumentCache(serializer) {
|
|
12515
|
+
return new IndexedDbRemoteDocumentCacheImpl(serializer);
|
|
10286
12516
|
}
|
|
10287
12517
|
/**
|
|
10288
12518
|
* Returns the set of documents that have changed since the specified read
|
|
@@ -10354,8 +12584,8 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
10354
12584
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10355
12585
|
this.changes.forEach((key, documentChange) => {
|
|
10356
12586
|
const previousSize = this.documentSizes.get(key);
|
|
10357
|
-
if (documentChange.
|
|
10358
|
-
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange
|
|
12587
|
+
if (documentChange.isValidDocument()) {
|
|
12588
|
+
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
|
|
10359
12589
|
collectionParents = collectionParents.add(key.path.popLast());
|
|
10360
12590
|
const size = dbDocumentSize(doc);
|
|
10361
12591
|
sizeDelta += size - previousSize;
|
|
@@ -10368,7 +12598,7 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
10368
12598
|
// RemoteDocumentCache. This entry is represented by a NoDocument
|
|
10369
12599
|
// with a version of 0 and ignored by `maybeDecodeDocument()` but
|
|
10370
12600
|
// preserved in `getNewDocumentChanges()`.
|
|
10371
|
-
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer,
|
|
12601
|
+
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
|
|
10372
12602
|
promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
|
|
10373
12603
|
}
|
|
10374
12604
|
else {
|
|
@@ -10449,9 +12679,6 @@ class SchemaConverter {
|
|
|
10449
12679
|
* and local feature development.
|
|
10450
12680
|
*/
|
|
10451
12681
|
createOrUpgrade(db, txn, fromVersion, toVersion) {
|
|
10452
|
-
hardAssert(fromVersion < toVersion &&
|
|
10453
|
-
fromVersion >= 0 &&
|
|
10454
|
-
toVersion <= SCHEMA_VERSION);
|
|
10455
12682
|
const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
|
|
10456
12683
|
if (fromVersion < 1 && toVersion >= 1) {
|
|
10457
12684
|
createPrimaryClientStore(db);
|
|
@@ -10518,6 +12745,16 @@ class SchemaConverter {
|
|
|
10518
12745
|
createNamedQueriesStore(db);
|
|
10519
12746
|
});
|
|
10520
12747
|
}
|
|
12748
|
+
if (fromVersion < 12 && toVersion >= 12) {
|
|
12749
|
+
p = p.next(() => {
|
|
12750
|
+
createDocumentOverlayStore(db);
|
|
12751
|
+
});
|
|
12752
|
+
}
|
|
12753
|
+
if (fromVersion < 13 && toVersion >= 13) {
|
|
12754
|
+
p = p.next(() => {
|
|
12755
|
+
createFieldIndex(db);
|
|
12756
|
+
});
|
|
12757
|
+
}
|
|
10521
12758
|
return p;
|
|
10522
12759
|
}
|
|
10523
12760
|
addDocumentGlobal(txn) {
|
|
@@ -10724,6 +12961,28 @@ function createNamedQueriesStore(db) {
|
|
|
10724
12961
|
db.createObjectStore(DbNamedQuery.store, {
|
|
10725
12962
|
keyPath: DbNamedQuery.keyPath
|
|
10726
12963
|
});
|
|
12964
|
+
}
|
|
12965
|
+
function createFieldIndex(db) {
|
|
12966
|
+
const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
|
|
12967
|
+
keyPath: DbIndexConfiguration.keyPath,
|
|
12968
|
+
autoIncrement: true
|
|
12969
|
+
});
|
|
12970
|
+
indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
|
|
12971
|
+
const indexStateStore = db.createObjectStore(DbIndexState.store, {
|
|
12972
|
+
keyPath: DbIndexState.keyPath
|
|
12973
|
+
});
|
|
12974
|
+
indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
|
|
12975
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
|
|
12976
|
+
keyPath: DbIndexEntry.keyPath
|
|
12977
|
+
});
|
|
12978
|
+
indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
|
|
12979
|
+
}
|
|
12980
|
+
function createDocumentOverlayStore(db) {
|
|
12981
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
|
|
12982
|
+
keyPath: DbDocumentOverlay.keyPath
|
|
12983
|
+
});
|
|
12984
|
+
documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
|
|
12985
|
+
documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
|
|
10727
12986
|
}
|
|
10728
12987
|
|
|
10729
12988
|
/**
|
|
@@ -10835,7 +13094,7 @@ class IndexedDbPersistence {
|
|
|
10835
13094
|
* If set to true, forcefully obtains database access. Existing tabs will
|
|
10836
13095
|
* no longer be able to access IndexedDB.
|
|
10837
13096
|
*/
|
|
10838
|
-
forceOwningTab) {
|
|
13097
|
+
forceOwningTab, schemaVersion = SCHEMA_VERSION) {
|
|
10839
13098
|
this.allowTabSynchronization = allowTabSynchronization;
|
|
10840
13099
|
this.persistenceKey = persistenceKey;
|
|
10841
13100
|
this.clientId = clientId;
|
|
@@ -10844,6 +13103,7 @@ class IndexedDbPersistence {
|
|
|
10844
13103
|
this.document = document;
|
|
10845
13104
|
this.sequenceNumberSyncer = sequenceNumberSyncer;
|
|
10846
13105
|
this.forceOwningTab = forceOwningTab;
|
|
13106
|
+
this.schemaVersion = schemaVersion;
|
|
10847
13107
|
this.listenSequence = null;
|
|
10848
13108
|
this._started = false;
|
|
10849
13109
|
this.isPrimary = false;
|
|
@@ -10865,10 +13125,9 @@ class IndexedDbPersistence {
|
|
|
10865
13125
|
this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
|
|
10866
13126
|
this.dbName = persistenceKey + MAIN_DATABASE;
|
|
10867
13127
|
this.serializer = new LocalSerializer(serializer);
|
|
10868
|
-
this.simpleDb = new SimpleDb(this.dbName,
|
|
13128
|
+
this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
|
|
10869
13129
|
this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
|
|
10870
|
-
this.
|
|
10871
|
-
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer, this.indexManager);
|
|
13130
|
+
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
|
|
10872
13131
|
this.bundleCache = new IndexedDbBundleCache();
|
|
10873
13132
|
if (this.window && this.window.localStorage) {
|
|
10874
13133
|
this.webStorage = this.window.localStorage;
|
|
@@ -11204,8 +13463,8 @@ class IndexedDbPersistence {
|
|
|
11204
13463
|
get started() {
|
|
11205
13464
|
return this._started;
|
|
11206
13465
|
}
|
|
11207
|
-
getMutationQueue(user) {
|
|
11208
|
-
return IndexedDbMutationQueue.forUser(user, this.serializer,
|
|
13466
|
+
getMutationQueue(user, indexManager) {
|
|
13467
|
+
return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
|
|
11209
13468
|
}
|
|
11210
13469
|
getTargetCache() {
|
|
11211
13470
|
return this.targetCache;
|
|
@@ -11213,8 +13472,11 @@ class IndexedDbPersistence {
|
|
|
11213
13472
|
getRemoteDocumentCache() {
|
|
11214
13473
|
return this.remoteDocumentCache;
|
|
11215
13474
|
}
|
|
11216
|
-
getIndexManager() {
|
|
11217
|
-
return
|
|
13475
|
+
getIndexManager(user) {
|
|
13476
|
+
return new IndexedDbIndexManager(user);
|
|
13477
|
+
}
|
|
13478
|
+
getDocumentOverlayCache(user) {
|
|
13479
|
+
return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
|
|
11218
13480
|
}
|
|
11219
13481
|
getBundleCache() {
|
|
11220
13482
|
return this.bundleCache;
|
|
@@ -11222,11 +13484,12 @@ class IndexedDbPersistence {
|
|
|
11222
13484
|
runTransaction(action, mode, transactionOperation) {
|
|
11223
13485
|
logDebug(LOG_TAG$d, 'Starting transaction:', action);
|
|
11224
13486
|
const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
|
|
13487
|
+
const objectStores = getObjectStores(this.schemaVersion);
|
|
11225
13488
|
let persistenceTransaction;
|
|
11226
13489
|
// Do all transactions as readwrite against all object stores, since we
|
|
11227
13490
|
// are the only reader/writer.
|
|
11228
13491
|
return this.simpleDb
|
|
11229
|
-
.runTransaction(action, simpleDbMode,
|
|
13492
|
+
.runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
|
|
11230
13493
|
persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
|
|
11231
13494
|
? this.listenSequence.next()
|
|
11232
13495
|
: ListenSequence.INVALID);
|
|
@@ -11598,39 +13861,28 @@ class LocalDocumentsView {
|
|
|
11598
13861
|
getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
|
|
11599
13862
|
// Query the remote documents and overlay mutations.
|
|
11600
13863
|
let results;
|
|
11601
|
-
let mutationBatches;
|
|
11602
13864
|
return this.remoteDocumentCache
|
|
11603
|
-
.
|
|
13865
|
+
.getAll(transaction, query.path, sinceReadTime)
|
|
11604
13866
|
.next(queryResults => {
|
|
11605
13867
|
results = queryResults;
|
|
11606
13868
|
return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
|
|
11607
13869
|
})
|
|
11608
|
-
.next(
|
|
11609
|
-
mutationBatches
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
let document = results.get(key);
|
|
11622
|
-
if (document == null) {
|
|
11623
|
-
// Create invalid document to apply mutations on top of
|
|
11624
|
-
document = MutableDocument.newInvalidDocument(key);
|
|
11625
|
-
results = results.insert(key, document);
|
|
11626
|
-
}
|
|
11627
|
-
mutationApplyToLocalView(mutation, document, batch.localWriteTime);
|
|
11628
|
-
if (!document.isFoundDocument()) {
|
|
11629
|
-
results = results.remove(key);
|
|
11630
|
-
}
|
|
13870
|
+
.next(mutationBatches => {
|
|
13871
|
+
for (const batch of mutationBatches) {
|
|
13872
|
+
for (const mutation of batch.mutations) {
|
|
13873
|
+
const key = mutation.key;
|
|
13874
|
+
let document = results.get(key);
|
|
13875
|
+
if (document == null) {
|
|
13876
|
+
// Create invalid document to apply mutations on top of
|
|
13877
|
+
document = MutableDocument.newInvalidDocument(key);
|
|
13878
|
+
results = results.insert(key, document);
|
|
13879
|
+
}
|
|
13880
|
+
mutationApplyToLocalView(mutation, document, batch.localWriteTime);
|
|
13881
|
+
if (!document.isFoundDocument()) {
|
|
13882
|
+
results = results.remove(key);
|
|
11631
13883
|
}
|
|
11632
13884
|
}
|
|
11633
|
-
}
|
|
13885
|
+
}
|
|
11634
13886
|
})
|
|
11635
13887
|
.next(() => {
|
|
11636
13888
|
// Finally, filter out any documents that don't actually match
|
|
@@ -11643,29 +13895,6 @@ class LocalDocumentsView {
|
|
|
11643
13895
|
return results;
|
|
11644
13896
|
});
|
|
11645
13897
|
}
|
|
11646
|
-
addMissingBaseDocuments(transaction, matchingMutationBatches, existingDocuments) {
|
|
11647
|
-
let missingBaseDocEntriesForPatching = documentKeySet();
|
|
11648
|
-
for (const batch of matchingMutationBatches) {
|
|
11649
|
-
for (const mutation of batch.mutations) {
|
|
11650
|
-
if (mutation instanceof PatchMutation &&
|
|
11651
|
-
existingDocuments.get(mutation.key) === null) {
|
|
11652
|
-
missingBaseDocEntriesForPatching =
|
|
11653
|
-
missingBaseDocEntriesForPatching.add(mutation.key);
|
|
11654
|
-
}
|
|
11655
|
-
}
|
|
11656
|
-
}
|
|
11657
|
-
let mergedDocuments = existingDocuments;
|
|
11658
|
-
return this.remoteDocumentCache
|
|
11659
|
-
.getEntries(transaction, missingBaseDocEntriesForPatching)
|
|
11660
|
-
.next(missingBaseDocs => {
|
|
11661
|
-
missingBaseDocs.forEach((key, doc) => {
|
|
11662
|
-
if (doc.isFoundDocument()) {
|
|
11663
|
-
mergedDocuments = mergedDocuments.insert(key, doc);
|
|
11664
|
-
}
|
|
11665
|
-
});
|
|
11666
|
-
return mergedDocuments;
|
|
11667
|
-
});
|
|
11668
|
-
}
|
|
11669
13898
|
}
|
|
11670
13899
|
|
|
11671
13900
|
/**
|
|
@@ -11724,11 +13953,18 @@ class LocalStoreImpl {
|
|
|
11724
13953
|
* PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11725
13954
|
*/
|
|
11726
13955
|
this.lastDocumentChangeReadTime = SnapshotVersion.min();
|
|
11727
|
-
this.mutationQueue = persistence.getMutationQueue(initialUser);
|
|
11728
13956
|
this.remoteDocuments = persistence.getRemoteDocumentCache();
|
|
11729
13957
|
this.targetCache = persistence.getTargetCache();
|
|
11730
|
-
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.persistence.getIndexManager());
|
|
11731
13958
|
this.bundleCache = persistence.getBundleCache();
|
|
13959
|
+
this.initializeUserComponents(initialUser);
|
|
13960
|
+
}
|
|
13961
|
+
initializeUserComponents(user) {
|
|
13962
|
+
// TODO(indexing): Add spec tests that test these components change after a
|
|
13963
|
+
// user change
|
|
13964
|
+
this.indexManager = this.persistence.getIndexManager(user);
|
|
13965
|
+
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13966
|
+
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13967
|
+
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
11732
13968
|
this.queryEngine.setLocalDocumentsView(this.localDocuments);
|
|
11733
13969
|
}
|
|
11734
13970
|
collectGarbage(garbageCollector) {
|
|
@@ -11750,8 +13986,6 @@ persistence, queryEngine, initialUser, serializer) {
|
|
|
11750
13986
|
// change.
|
|
11751
13987
|
async function localStoreHandleUserChange(localStore, user) {
|
|
11752
13988
|
const localStoreImpl = debugCast(localStore);
|
|
11753
|
-
let newMutationQueue = localStoreImpl.mutationQueue;
|
|
11754
|
-
let newLocalDocuments = localStoreImpl.localDocuments;
|
|
11755
13989
|
const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
|
|
11756
13990
|
// Swap out the mutation queue, grabbing the pending mutation batches
|
|
11757
13991
|
// before and after.
|
|
@@ -11760,11 +13994,8 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11760
13994
|
.getAllMutationBatches(txn)
|
|
11761
13995
|
.next(promisedOldBatches => {
|
|
11762
13996
|
oldBatches = promisedOldBatches;
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
// MutationQueue.
|
|
11766
|
-
newLocalDocuments = new LocalDocumentsView(localStoreImpl.remoteDocuments, newMutationQueue, localStoreImpl.persistence.getIndexManager());
|
|
11767
|
-
return newMutationQueue.getAllMutationBatches(txn);
|
|
13997
|
+
localStoreImpl.initializeUserComponents(user);
|
|
13998
|
+
return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
|
|
11768
13999
|
})
|
|
11769
14000
|
.next(newBatches => {
|
|
11770
14001
|
const removedBatchIds = [];
|
|
@@ -11785,7 +14016,7 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11785
14016
|
}
|
|
11786
14017
|
// Return the set of all (potentially) changed documents and the list
|
|
11787
14018
|
// of mutation batch IDs that were affected by change.
|
|
11788
|
-
return
|
|
14019
|
+
return localStoreImpl.localDocuments
|
|
11789
14020
|
.getDocuments(txn, changedKeys)
|
|
11790
14021
|
.next(affectedDocuments => {
|
|
11791
14022
|
return {
|
|
@@ -11796,9 +14027,6 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11796
14027
|
});
|
|
11797
14028
|
});
|
|
11798
14029
|
});
|
|
11799
|
-
localStoreImpl.mutationQueue = newMutationQueue;
|
|
11800
|
-
localStoreImpl.localDocuments = newLocalDocuments;
|
|
11801
|
-
localStoreImpl.queryEngine.setLocalDocumentsView(localStoreImpl.localDocuments);
|
|
11802
14030
|
return result;
|
|
11803
14031
|
}
|
|
11804
14032
|
/* Accepts locally generated Mutations and commit them to storage. */
|
|
@@ -11955,14 +14183,14 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
|
|
|
11955
14183
|
}
|
|
11956
14184
|
});
|
|
11957
14185
|
let changedDocs = mutableDocumentMap();
|
|
11958
|
-
remoteEvent.documentUpdates.forEach(
|
|
14186
|
+
remoteEvent.documentUpdates.forEach(key => {
|
|
11959
14187
|
if (remoteEvent.resolvedLimboDocuments.has(key)) {
|
|
11960
14188
|
promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
|
|
11961
14189
|
}
|
|
11962
14190
|
});
|
|
11963
14191
|
// Each loop iteration only affects its "own" doc, so it's safe to get all the remote
|
|
11964
14192
|
// documents in advance in a single call.
|
|
11965
|
-
promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates
|
|
14193
|
+
promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
|
|
11966
14194
|
changedDocs = result;
|
|
11967
14195
|
}));
|
|
11968
14196
|
// HACK: The only reason we allow a null snapshot version is so that we
|
|
@@ -12003,17 +14231,13 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
|
|
|
12003
14231
|
* Note: this function will use `documentVersions` if it is defined;
|
|
12004
14232
|
* when it is not defined, resorts to `globalVersion`.
|
|
12005
14233
|
*/
|
|
12006
|
-
function populateDocumentChangeBuffer(txn, documentBuffer, documents
|
|
12007
|
-
// TODO(wuandy): We could add `readTime` to MaybeDocument instead to remove
|
|
12008
|
-
// this parameter.
|
|
12009
|
-
documentVersions) {
|
|
14234
|
+
function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
|
|
12010
14235
|
let updatedKeys = documentKeySet();
|
|
12011
14236
|
documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
|
|
12012
14237
|
return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
|
|
12013
14238
|
let changedDocs = mutableDocumentMap();
|
|
12014
14239
|
documents.forEach((key, doc) => {
|
|
12015
14240
|
const existingDoc = existingDocs.get(key);
|
|
12016
|
-
const docReadTime = (documentVersions === null || documentVersions === void 0 ? void 0 : documentVersions.get(key)) || globalVersion;
|
|
12017
14241
|
// Note: The order of the steps below is important, since we want
|
|
12018
14242
|
// to ensure that rejected limbo resolutions (which fabricate
|
|
12019
14243
|
// NoDocuments with SnapshotVersion.min()) never add documents to
|
|
@@ -12022,14 +14246,14 @@ documentVersions) {
|
|
|
12022
14246
|
// NoDocuments with SnapshotVersion.min() are used in manufactured
|
|
12023
14247
|
// events. We remove these documents from cache since we lost
|
|
12024
14248
|
// access.
|
|
12025
|
-
documentBuffer.removeEntry(key,
|
|
14249
|
+
documentBuffer.removeEntry(key, doc.readTime);
|
|
12026
14250
|
changedDocs = changedDocs.insert(key, doc);
|
|
12027
14251
|
}
|
|
12028
14252
|
else if (!existingDoc.isValidDocument() ||
|
|
12029
14253
|
doc.version.compareTo(existingDoc.version) > 0 ||
|
|
12030
14254
|
(doc.version.compareTo(existingDoc.version) === 0 &&
|
|
12031
14255
|
existingDoc.hasPendingWrites)) {
|
|
12032
|
-
documentBuffer.addEntry(doc
|
|
14256
|
+
documentBuffer.addEntry(doc);
|
|
12033
14257
|
changedDocs = changedDocs.insert(key, doc);
|
|
12034
14258
|
}
|
|
12035
14259
|
else {
|
|
@@ -12283,7 +14507,8 @@ function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentB
|
|
|
12283
14507
|
// We use the commitVersion as the readTime rather than the
|
|
12284
14508
|
// document's updateTime since the updateTime is not advanced
|
|
12285
14509
|
// for updates that do not modify the underlying document.
|
|
12286
|
-
|
|
14510
|
+
doc.setReadTime(batchResult.commitVersion);
|
|
14511
|
+
documentBuffer.addEntry(doc);
|
|
12287
14512
|
}
|
|
12288
14513
|
}
|
|
12289
14514
|
});
|
|
@@ -12384,14 +14609,14 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
|
|
|
12384
14609
|
const localStoreImpl = debugCast(localStore);
|
|
12385
14610
|
let documentKeys = documentKeySet();
|
|
12386
14611
|
let documentMap = mutableDocumentMap();
|
|
12387
|
-
let versionMap = documentVersionMap();
|
|
12388
14612
|
for (const bundleDoc of documents) {
|
|
12389
14613
|
const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
|
|
12390
14614
|
if (bundleDoc.document) {
|
|
12391
14615
|
documentKeys = documentKeys.add(documentKey);
|
|
12392
14616
|
}
|
|
12393
|
-
|
|
12394
|
-
|
|
14617
|
+
const doc = bundleConverter.toMutableDocument(bundleDoc);
|
|
14618
|
+
doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
|
|
14619
|
+
documentMap = documentMap.insert(documentKey, doc);
|
|
12395
14620
|
}
|
|
12396
14621
|
const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
|
|
12397
14622
|
trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
|
|
@@ -12400,7 +14625,7 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
|
|
|
12400
14625
|
// they will not get garbage collected right away.
|
|
12401
14626
|
const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
|
|
12402
14627
|
return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
|
|
12403
|
-
return populateDocumentChangeBuffer(txn, documentBuffer, documentMap
|
|
14628
|
+
return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
|
|
12404
14629
|
.next(changedDocs => {
|
|
12405
14630
|
documentBuffer.apply(txn);
|
|
12406
14631
|
return changedDocs;
|
|
@@ -12514,6 +14739,122 @@ class MemoryBundleCache {
|
|
|
12514
14739
|
}
|
|
12515
14740
|
}
|
|
12516
14741
|
|
|
14742
|
+
/**
|
|
14743
|
+
* @license
|
|
14744
|
+
* Copyright 2022 Google LLC
|
|
14745
|
+
*
|
|
14746
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
14747
|
+
* you may not use this file except in compliance with the License.
|
|
14748
|
+
* You may obtain a copy of the License at
|
|
14749
|
+
*
|
|
14750
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
14751
|
+
*
|
|
14752
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14753
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14754
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14755
|
+
* See the License for the specific language governing permissions and
|
|
14756
|
+
* limitations under the License.
|
|
14757
|
+
*/
|
|
14758
|
+
/**
|
|
14759
|
+
* An in-memory implementation of DocumentOverlayCache.
|
|
14760
|
+
*/
|
|
14761
|
+
class MemoryDocumentOverlayCache {
|
|
14762
|
+
constructor() {
|
|
14763
|
+
// A map sorted by DocumentKey, whose value is a pair of the largest batch id
|
|
14764
|
+
// for the overlay and the overlay itself.
|
|
14765
|
+
this.overlays = new SortedMap(DocumentKey.comparator);
|
|
14766
|
+
this.overlayByBatchId = new Map();
|
|
14767
|
+
}
|
|
14768
|
+
getOverlay(transaction, key) {
|
|
14769
|
+
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14770
|
+
}
|
|
14771
|
+
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14772
|
+
overlays.forEach(mutation => {
|
|
14773
|
+
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14774
|
+
});
|
|
14775
|
+
return PersistencePromise.resolve();
|
|
14776
|
+
}
|
|
14777
|
+
removeOverlaysForBatchId(transaction, documentKeys, batchId) {
|
|
14778
|
+
const keys = this.overlayByBatchId.get(batchId);
|
|
14779
|
+
if (keys !== undefined) {
|
|
14780
|
+
keys.forEach(key => (this.overlays = this.overlays.remove(key)));
|
|
14781
|
+
this.overlayByBatchId.delete(batchId);
|
|
14782
|
+
}
|
|
14783
|
+
return PersistencePromise.resolve();
|
|
14784
|
+
}
|
|
14785
|
+
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14786
|
+
const result = new Map();
|
|
14787
|
+
const immediateChildrenPathLength = collection.length + 1;
|
|
14788
|
+
const prefix = new DocumentKey(collection.child(''));
|
|
14789
|
+
const iter = this.overlays.getIteratorFrom(prefix);
|
|
14790
|
+
while (iter.hasNext()) {
|
|
14791
|
+
const entry = iter.getNext();
|
|
14792
|
+
const overlay = entry.value;
|
|
14793
|
+
const key = overlay.getKey();
|
|
14794
|
+
if (!collection.isPrefixOf(key.path)) {
|
|
14795
|
+
break;
|
|
14796
|
+
}
|
|
14797
|
+
// Documents from sub-collections
|
|
14798
|
+
if (key.path.length !== immediateChildrenPathLength) {
|
|
14799
|
+
continue;
|
|
14800
|
+
}
|
|
14801
|
+
if (overlay.largestBatchId > sinceBatchId) {
|
|
14802
|
+
result.set(overlay.getKey(), overlay);
|
|
14803
|
+
}
|
|
14804
|
+
}
|
|
14805
|
+
return PersistencePromise.resolve(result);
|
|
14806
|
+
}
|
|
14807
|
+
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
14808
|
+
let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
|
|
14809
|
+
const iter = this.overlays.getIterator();
|
|
14810
|
+
while (iter.hasNext()) {
|
|
14811
|
+
const entry = iter.getNext();
|
|
14812
|
+
const overlay = entry.value;
|
|
14813
|
+
const key = overlay.getKey();
|
|
14814
|
+
if (key.getCollectionGroup() !== collectionGroup) {
|
|
14815
|
+
continue;
|
|
14816
|
+
}
|
|
14817
|
+
if (overlay.largestBatchId > sinceBatchId) {
|
|
14818
|
+
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14819
|
+
if (overlaysForBatchId === null) {
|
|
14820
|
+
overlaysForBatchId = new Map();
|
|
14821
|
+
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14822
|
+
}
|
|
14823
|
+
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14824
|
+
}
|
|
14825
|
+
}
|
|
14826
|
+
const result = new Map();
|
|
14827
|
+
const batchIter = batchIdToOverlays.getIterator();
|
|
14828
|
+
while (batchIter.hasNext()) {
|
|
14829
|
+
const entry = batchIter.getNext();
|
|
14830
|
+
const overlays = entry.value;
|
|
14831
|
+
overlays.forEach((overlay, key) => result.set(key, overlay));
|
|
14832
|
+
if (result.size >= count) {
|
|
14833
|
+
break;
|
|
14834
|
+
}
|
|
14835
|
+
}
|
|
14836
|
+
return PersistencePromise.resolve(result);
|
|
14837
|
+
}
|
|
14838
|
+
saveOverlay(transaction, largestBatchId, mutation) {
|
|
14839
|
+
if (mutation === null) {
|
|
14840
|
+
return;
|
|
14841
|
+
}
|
|
14842
|
+
// Remove the association of the overlay to its batch id.
|
|
14843
|
+
const existing = this.overlays.get(mutation.key);
|
|
14844
|
+
if (existing !== null) {
|
|
14845
|
+
this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
|
|
14846
|
+
}
|
|
14847
|
+
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14848
|
+
// Create the association of this overlay to the given largestBatchId.
|
|
14849
|
+
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14850
|
+
if (batch === undefined) {
|
|
14851
|
+
batch = new Set();
|
|
14852
|
+
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14853
|
+
}
|
|
14854
|
+
batch.add(mutation.key);
|
|
14855
|
+
}
|
|
14856
|
+
}
|
|
14857
|
+
|
|
12517
14858
|
/**
|
|
12518
14859
|
* @license
|
|
12519
14860
|
* Copyright 2017 Google LLC
|
|
@@ -12867,29 +15208,30 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12867
15208
|
* expected to just return 0 to avoid unnecessarily doing the work of
|
|
12868
15209
|
* calculating the size.
|
|
12869
15210
|
*/
|
|
12870
|
-
constructor(
|
|
12871
|
-
this.indexManager = indexManager;
|
|
15211
|
+
constructor(sizer) {
|
|
12872
15212
|
this.sizer = sizer;
|
|
12873
15213
|
/** Underlying cache of documents and their read times. */
|
|
12874
15214
|
this.docs = documentEntryMap();
|
|
12875
15215
|
/** Size of all cached documents. */
|
|
12876
15216
|
this.size = 0;
|
|
12877
15217
|
}
|
|
15218
|
+
setIndexManager(indexManager) {
|
|
15219
|
+
this.indexManager = indexManager;
|
|
15220
|
+
}
|
|
12878
15221
|
/**
|
|
12879
15222
|
* Adds the supplied entry to the cache and updates the cache size as appropriate.
|
|
12880
15223
|
*
|
|
12881
15224
|
* All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
|
|
12882
15225
|
* returned by `newChangeBuffer()`.
|
|
12883
15226
|
*/
|
|
12884
|
-
addEntry(transaction, doc
|
|
15227
|
+
addEntry(transaction, doc) {
|
|
12885
15228
|
const key = doc.key;
|
|
12886
15229
|
const entry = this.docs.get(key);
|
|
12887
15230
|
const previousSize = entry ? entry.size : 0;
|
|
12888
15231
|
const currentSize = this.sizer(doc);
|
|
12889
15232
|
this.docs = this.docs.insert(key, {
|
|
12890
15233
|
document: doc.mutableCopy(),
|
|
12891
|
-
size: currentSize
|
|
12892
|
-
readTime
|
|
15234
|
+
size: currentSize
|
|
12893
15235
|
});
|
|
12894
15236
|
this.size += currentSize - previousSize;
|
|
12895
15237
|
return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
|
|
@@ -12923,21 +15265,22 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12923
15265
|
});
|
|
12924
15266
|
return PersistencePromise.resolve(results);
|
|
12925
15267
|
}
|
|
12926
|
-
|
|
15268
|
+
getAll(transaction, collectionPath, sinceReadTime) {
|
|
12927
15269
|
let results = mutableDocumentMap();
|
|
12928
15270
|
// Documents are ordered by key, so we can use a prefix scan to narrow down
|
|
12929
15271
|
// the documents we need to match the query against.
|
|
12930
|
-
const prefix = new DocumentKey(
|
|
15272
|
+
const prefix = new DocumentKey(collectionPath.child(''));
|
|
12931
15273
|
const iterator = this.docs.getIteratorFrom(prefix);
|
|
12932
15274
|
while (iterator.hasNext()) {
|
|
12933
|
-
const { key, value: { document
|
|
12934
|
-
if (!
|
|
15275
|
+
const { key, value: { document } } = iterator.getNext();
|
|
15276
|
+
if (!collectionPath.isPrefixOf(key.path)) {
|
|
12935
15277
|
break;
|
|
12936
15278
|
}
|
|
12937
|
-
if (
|
|
15279
|
+
if (key.path.length > collectionPath.length + 1) {
|
|
15280
|
+
// Exclude entries from subcollections.
|
|
12938
15281
|
continue;
|
|
12939
15282
|
}
|
|
12940
|
-
if (
|
|
15283
|
+
if (document.readTime.compareTo(sinceReadTime) <= 0) {
|
|
12941
15284
|
continue;
|
|
12942
15285
|
}
|
|
12943
15286
|
results = results.insert(document.key, document.mutableCopy());
|
|
@@ -12959,13 +15302,12 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12959
15302
|
/**
|
|
12960
15303
|
* Creates a new memory-only RemoteDocumentCache.
|
|
12961
15304
|
*
|
|
12962
|
-
* @param indexManager - A class that manages collection group indices.
|
|
12963
15305
|
* @param sizer - Used to assess the size of a document. For eager GC, this is
|
|
12964
15306
|
* expected to just return 0 to avoid unnecessarily doing the work of
|
|
12965
15307
|
* calculating the size.
|
|
12966
15308
|
*/
|
|
12967
|
-
function newMemoryRemoteDocumentCache(
|
|
12968
|
-
return new MemoryRemoteDocumentCacheImpl(
|
|
15309
|
+
function newMemoryRemoteDocumentCache(sizer) {
|
|
15310
|
+
return new MemoryRemoteDocumentCacheImpl(sizer);
|
|
12969
15311
|
}
|
|
12970
15312
|
/**
|
|
12971
15313
|
* Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
|
|
@@ -12978,8 +15320,8 @@ class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
12978
15320
|
applyChanges(transaction) {
|
|
12979
15321
|
const promises = [];
|
|
12980
15322
|
this.changes.forEach((key, doc) => {
|
|
12981
|
-
if (doc.
|
|
12982
|
-
promises.push(this.documentCache.addEntry(transaction, doc
|
|
15323
|
+
if (doc.isValidDocument()) {
|
|
15324
|
+
promises.push(this.documentCache.addEntry(transaction, doc));
|
|
12983
15325
|
}
|
|
12984
15326
|
else {
|
|
12985
15327
|
this.documentCache.removeEntry(key);
|
|
@@ -13159,6 +15501,7 @@ class MemoryPersistence {
|
|
|
13159
15501
|
*/
|
|
13160
15502
|
constructor(referenceDelegateFactory, serializer) {
|
|
13161
15503
|
this.mutationQueues = {};
|
|
15504
|
+
this.overlays = {};
|
|
13162
15505
|
this.listenSequence = new ListenSequence(0);
|
|
13163
15506
|
this._started = false;
|
|
13164
15507
|
this._started = true;
|
|
@@ -13166,7 +15509,7 @@ class MemoryPersistence {
|
|
|
13166
15509
|
this.targetCache = new MemoryTargetCache(this);
|
|
13167
15510
|
const sizer = (doc) => this.referenceDelegate.documentSize(doc);
|
|
13168
15511
|
this.indexManager = new MemoryIndexManager();
|
|
13169
|
-
this.remoteDocumentCache = newMemoryRemoteDocumentCache(
|
|
15512
|
+
this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
|
|
13170
15513
|
this.serializer = new LocalSerializer(serializer);
|
|
13171
15514
|
this.bundleCache = new MemoryBundleCache(this.serializer);
|
|
13172
15515
|
}
|
|
@@ -13187,13 +15530,23 @@ class MemoryPersistence {
|
|
|
13187
15530
|
setNetworkEnabled() {
|
|
13188
15531
|
// No op.
|
|
13189
15532
|
}
|
|
13190
|
-
getIndexManager() {
|
|
15533
|
+
getIndexManager(user) {
|
|
15534
|
+
// We do not currently support indices for memory persistence, so we can
|
|
15535
|
+
// return the same shared instance of the memory index manager.
|
|
13191
15536
|
return this.indexManager;
|
|
13192
15537
|
}
|
|
13193
|
-
|
|
15538
|
+
getDocumentOverlayCache(user) {
|
|
15539
|
+
let overlay = this.overlays[user.toKey()];
|
|
15540
|
+
if (!overlay) {
|
|
15541
|
+
overlay = new MemoryDocumentOverlayCache();
|
|
15542
|
+
this.overlays[user.toKey()] = overlay;
|
|
15543
|
+
}
|
|
15544
|
+
return overlay;
|
|
15545
|
+
}
|
|
15546
|
+
getMutationQueue(user, indexManager) {
|
|
13194
15547
|
let queue = this.mutationQueues[user.toKey()];
|
|
13195
15548
|
if (!queue) {
|
|
13196
|
-
queue = new MemoryMutationQueue(
|
|
15549
|
+
queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
|
|
13197
15550
|
this.mutationQueues[user.toKey()] = queue;
|
|
13198
15551
|
}
|
|
13199
15552
|
return queue;
|
|
@@ -13292,7 +15645,7 @@ class MemoryEagerDelegate {
|
|
|
13292
15645
|
const key = DocumentKey.fromPath(path);
|
|
13293
15646
|
return this.isReferenced(txn, key).next(isReferenced => {
|
|
13294
15647
|
if (!isReferenced) {
|
|
13295
|
-
changeBuffer.removeEntry(key);
|
|
15648
|
+
changeBuffer.removeEntry(key, SnapshotVersion.min());
|
|
13296
15649
|
}
|
|
13297
15650
|
});
|
|
13298
15651
|
}).next(() => {
|
|
@@ -14338,7 +16691,7 @@ const LOG_TAG$9 = 'Connection';
|
|
|
14338
16691
|
const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
|
|
14339
16692
|
function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
14340
16693
|
hardAssert(authToken === null || authToken.type === 'OAuth');
|
|
14341
|
-
const metadata = new
|
|
16694
|
+
const metadata = new grpc__namespace.Metadata();
|
|
14342
16695
|
if (authToken) {
|
|
14343
16696
|
authToken.headers.forEach((value, key) => metadata.set(key, value));
|
|
14344
16697
|
}
|
|
@@ -14369,10 +16722,10 @@ class GrpcConnection {
|
|
|
14369
16722
|
ensureActiveStub() {
|
|
14370
16723
|
if (!this.cachedStub) {
|
|
14371
16724
|
logDebug(LOG_TAG$9, 'Creating Firestore stub.');
|
|
14372
|
-
const credentials
|
|
14373
|
-
?
|
|
14374
|
-
:
|
|
14375
|
-
this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials
|
|
16725
|
+
const credentials = this.databaseInfo.ssl
|
|
16726
|
+
? grpc__namespace.credentials.createSsl()
|
|
16727
|
+
: grpc__namespace.credentials.createInsecure();
|
|
16728
|
+
this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
|
|
14376
16729
|
}
|
|
14377
16730
|
return this.cachedStub;
|
|
14378
16731
|
}
|
|
@@ -17072,6 +19425,40 @@ const nested = {
|
|
|
17072
19425
|
id: 2
|
|
17073
19426
|
}
|
|
17074
19427
|
}
|
|
19428
|
+
},
|
|
19429
|
+
methodSignature: {
|
|
19430
|
+
rule: "repeated",
|
|
19431
|
+
type: "string",
|
|
19432
|
+
id: 1051,
|
|
19433
|
+
extend: "google.protobuf.MethodOptions"
|
|
19434
|
+
},
|
|
19435
|
+
defaultHost: {
|
|
19436
|
+
type: "string",
|
|
19437
|
+
id: 1049,
|
|
19438
|
+
extend: "google.protobuf.ServiceOptions"
|
|
19439
|
+
},
|
|
19440
|
+
oauthScopes: {
|
|
19441
|
+
type: "string",
|
|
19442
|
+
id: 1050,
|
|
19443
|
+
extend: "google.protobuf.ServiceOptions"
|
|
19444
|
+
},
|
|
19445
|
+
fieldBehavior: {
|
|
19446
|
+
rule: "repeated",
|
|
19447
|
+
type: "google.api.FieldBehavior",
|
|
19448
|
+
id: 1052,
|
|
19449
|
+
extend: "google.protobuf.FieldOptions"
|
|
19450
|
+
},
|
|
19451
|
+
FieldBehavior: {
|
|
19452
|
+
values: {
|
|
19453
|
+
FIELD_BEHAVIOR_UNSPECIFIED: 0,
|
|
19454
|
+
OPTIONAL: 1,
|
|
19455
|
+
REQUIRED: 2,
|
|
19456
|
+
OUTPUT_ONLY: 3,
|
|
19457
|
+
INPUT_ONLY: 4,
|
|
19458
|
+
IMMUTABLE: 5,
|
|
19459
|
+
UNORDERED_LIST: 6,
|
|
19460
|
+
NON_EMPTY_DEFAULT: 7
|
|
19461
|
+
}
|
|
17075
19462
|
}
|
|
17076
19463
|
}
|
|
17077
19464
|
},
|
|
@@ -17170,8 +19557,8 @@ const protoLoaderOptions = {
|
|
|
17170
19557
|
* @returns The GrpcObject representing our protos.
|
|
17171
19558
|
*/
|
|
17172
19559
|
function loadProtos() {
|
|
17173
|
-
const packageDefinition =
|
|
17174
|
-
return
|
|
19560
|
+
const packageDefinition = protoLoader__namespace.fromJSON(protos$1, protoLoaderOptions);
|
|
19561
|
+
return grpc__namespace.loadPackageDefinition(packageDefinition);
|
|
17175
19562
|
}
|
|
17176
19563
|
|
|
17177
19564
|
/**
|
|
@@ -20122,12 +22509,12 @@ async function syncEngineListen(syncEngine, query) {
|
|
|
20122
22509
|
}
|
|
20123
22510
|
else {
|
|
20124
22511
|
const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
|
|
20125
|
-
const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
|
|
20126
|
-
targetId = targetData.targetId;
|
|
20127
|
-
viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
|
|
20128
22512
|
if (syncEngineImpl.isPrimaryClient) {
|
|
20129
22513
|
remoteStoreListen(syncEngineImpl.remoteStore, targetData);
|
|
20130
22514
|
}
|
|
22515
|
+
const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
|
|
22516
|
+
targetId = targetData.targetId;
|
|
22517
|
+
viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
|
|
20131
22518
|
}
|
|
20132
22519
|
return viewSnapshot;
|
|
20133
22520
|
}
|
|
@@ -20315,6 +22702,9 @@ async function syncEngineRejectListen(syncEngine, targetId, err) {
|
|
|
20315
22702
|
// store to purge a document. However, it would be tricky to keep all of
|
|
20316
22703
|
// the local store's invariants with another method.
|
|
20317
22704
|
let documentUpdates = new SortedMap(DocumentKey.comparator);
|
|
22705
|
+
// TODO(b/217189216): This limbo document should ideally have a read time,
|
|
22706
|
+
// so that it is picked up by any read-time based scans. The backend,
|
|
22707
|
+
// however, does not send a read time for target removals.
|
|
20318
22708
|
documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
|
|
20319
22709
|
const resolvedLimboDocuments = documentKeySet().add(limboKey);
|
|
20320
22710
|
const event = new RemoteEvent(SnapshotVersion.min(),
|
|
@@ -22259,72 +24649,6 @@ function createBundleReader(data, serializer) {
|
|
|
22259
24649
|
return newBundleReader(toByteStreamReader(content), serializer);
|
|
22260
24650
|
}
|
|
22261
24651
|
|
|
22262
|
-
/**
|
|
22263
|
-
* @license
|
|
22264
|
-
* Copyright 2017 Google LLC
|
|
22265
|
-
*
|
|
22266
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
22267
|
-
* you may not use this file except in compliance with the License.
|
|
22268
|
-
* You may obtain a copy of the License at
|
|
22269
|
-
*
|
|
22270
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
22271
|
-
*
|
|
22272
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
22273
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
22274
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22275
|
-
* See the License for the specific language governing permissions and
|
|
22276
|
-
* limitations under the License.
|
|
22277
|
-
*/
|
|
22278
|
-
class DatabaseInfo {
|
|
22279
|
-
/**
|
|
22280
|
-
* Constructs a DatabaseInfo using the provided host, databaseId and
|
|
22281
|
-
* persistenceKey.
|
|
22282
|
-
*
|
|
22283
|
-
* @param databaseId - The database to use.
|
|
22284
|
-
* @param appId - The Firebase App Id.
|
|
22285
|
-
* @param persistenceKey - A unique identifier for this Firestore's local
|
|
22286
|
-
* storage (used in conjunction with the databaseId).
|
|
22287
|
-
* @param host - The Firestore backend host to connect to.
|
|
22288
|
-
* @param ssl - Whether to use SSL when connecting.
|
|
22289
|
-
* @param forceLongPolling - Whether to use the forceLongPolling option
|
|
22290
|
-
* when using WebChannel as the network transport.
|
|
22291
|
-
* @param autoDetectLongPolling - Whether to use the detectBufferingProxy
|
|
22292
|
-
* option when using WebChannel as the network transport.
|
|
22293
|
-
* @param useFetchStreams Whether to use the Fetch API instead of
|
|
22294
|
-
* XMLHTTPRequest
|
|
22295
|
-
*/
|
|
22296
|
-
constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
|
|
22297
|
-
this.databaseId = databaseId;
|
|
22298
|
-
this.appId = appId;
|
|
22299
|
-
this.persistenceKey = persistenceKey;
|
|
22300
|
-
this.host = host;
|
|
22301
|
-
this.ssl = ssl;
|
|
22302
|
-
this.forceLongPolling = forceLongPolling;
|
|
22303
|
-
this.autoDetectLongPolling = autoDetectLongPolling;
|
|
22304
|
-
this.useFetchStreams = useFetchStreams;
|
|
22305
|
-
}
|
|
22306
|
-
}
|
|
22307
|
-
/** The default database name for a project. */
|
|
22308
|
-
const DEFAULT_DATABASE_NAME = '(default)';
|
|
22309
|
-
/**
|
|
22310
|
-
* Represents the database ID a Firestore client is associated with.
|
|
22311
|
-
* @internal
|
|
22312
|
-
*/
|
|
22313
|
-
class DatabaseId {
|
|
22314
|
-
constructor(projectId, database) {
|
|
22315
|
-
this.projectId = projectId;
|
|
22316
|
-
this.database = database ? database : DEFAULT_DATABASE_NAME;
|
|
22317
|
-
}
|
|
22318
|
-
get isDefaultDatabase() {
|
|
22319
|
-
return this.database === DEFAULT_DATABASE_NAME;
|
|
22320
|
-
}
|
|
22321
|
-
isEqual(other) {
|
|
22322
|
-
return (other instanceof DatabaseId &&
|
|
22323
|
-
other.projectId === this.projectId &&
|
|
22324
|
-
other.database === this.database);
|
|
22325
|
-
}
|
|
22326
|
-
}
|
|
22327
|
-
|
|
22328
24652
|
/**
|
|
22329
24653
|
* @license
|
|
22330
24654
|
* Copyright 2020 Google LLC
|
|
@@ -24698,8 +27022,7 @@ class DocumentSnapshot extends DocumentSnapshot$1 {
|
|
|
24698
27022
|
this.metadata = metadata;
|
|
24699
27023
|
}
|
|
24700
27024
|
/**
|
|
24701
|
-
*
|
|
24702
|
-
* exists. True if the document exists.
|
|
27025
|
+
* Returns whether or not the data exists. True if the document exists.
|
|
24703
27026
|
*/
|
|
24704
27027
|
exists() {
|
|
24705
27028
|
return super.exists();
|
|
@@ -25073,51 +27396,53 @@ function limitToLast(limit) {
|
|
|
25073
27396
|
return new QueryLimitConstraint('limitToLast', limit, "L" /* Last */);
|
|
25074
27397
|
}
|
|
25075
27398
|
class QueryStartAtConstraint extends QueryConstraint {
|
|
25076
|
-
constructor(type, _docOrFields,
|
|
27399
|
+
constructor(type, _docOrFields, _inclusive) {
|
|
25077
27400
|
super();
|
|
25078
27401
|
this.type = type;
|
|
25079
27402
|
this._docOrFields = _docOrFields;
|
|
25080
|
-
this.
|
|
27403
|
+
this._inclusive = _inclusive;
|
|
25081
27404
|
}
|
|
25082
27405
|
_apply(query) {
|
|
25083
|
-
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this.
|
|
27406
|
+
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
|
|
25084
27407
|
return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
|
|
25085
27408
|
}
|
|
25086
27409
|
}
|
|
25087
27410
|
function startAt(...docOrFields) {
|
|
25088
|
-
return new QueryStartAtConstraint('startAt', docOrFields,
|
|
27411
|
+
return new QueryStartAtConstraint('startAt', docOrFields,
|
|
27412
|
+
/*inclusive=*/ true);
|
|
25089
27413
|
}
|
|
25090
27414
|
function startAfter(...docOrFields) {
|
|
25091
27415
|
return new QueryStartAtConstraint('startAfter', docOrFields,
|
|
25092
|
-
/*
|
|
27416
|
+
/*inclusive=*/ false);
|
|
25093
27417
|
}
|
|
25094
27418
|
class QueryEndAtConstraint extends QueryConstraint {
|
|
25095
|
-
constructor(type, _docOrFields,
|
|
27419
|
+
constructor(type, _docOrFields, _inclusive) {
|
|
25096
27420
|
super();
|
|
25097
27421
|
this.type = type;
|
|
25098
27422
|
this._docOrFields = _docOrFields;
|
|
25099
|
-
this.
|
|
27423
|
+
this._inclusive = _inclusive;
|
|
25100
27424
|
}
|
|
25101
27425
|
_apply(query) {
|
|
25102
|
-
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this.
|
|
27426
|
+
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
|
|
25103
27427
|
return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
|
|
25104
27428
|
}
|
|
25105
27429
|
}
|
|
25106
27430
|
function endBefore(...docOrFields) {
|
|
25107
|
-
return new QueryEndAtConstraint('endBefore', docOrFields,
|
|
27431
|
+
return new QueryEndAtConstraint('endBefore', docOrFields,
|
|
27432
|
+
/*inclusive=*/ false);
|
|
25108
27433
|
}
|
|
25109
27434
|
function endAt(...docOrFields) {
|
|
25110
|
-
return new QueryEndAtConstraint('endAt', docOrFields, /*
|
|
27435
|
+
return new QueryEndAtConstraint('endAt', docOrFields, /*inclusive=*/ true);
|
|
25111
27436
|
}
|
|
25112
27437
|
/** Helper function to create a bound from a document or fields */
|
|
25113
|
-
function newQueryBoundFromDocOrFields(query, methodName, docOrFields,
|
|
27438
|
+
function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
|
|
25114
27439
|
docOrFields[0] = util$1.getModularInstance(docOrFields[0]);
|
|
25115
27440
|
if (docOrFields[0] instanceof DocumentSnapshot$1) {
|
|
25116
|
-
return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document,
|
|
27441
|
+
return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
|
|
25117
27442
|
}
|
|
25118
27443
|
else {
|
|
25119
27444
|
const reader = newUserDataReader(query.firestore);
|
|
25120
|
-
return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields,
|
|
27445
|
+
return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
|
|
25121
27446
|
}
|
|
25122
27447
|
}
|
|
25123
27448
|
function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
|
|
@@ -25175,7 +27500,7 @@ function newQueryOrderBy(query, fieldPath, direction) {
|
|
|
25175
27500
|
* of the query or if any of the fields in the order by are an uncommitted
|
|
25176
27501
|
* server timestamp.
|
|
25177
27502
|
*/
|
|
25178
|
-
function newQueryBoundFromDocument(query, databaseId, methodName, doc,
|
|
27503
|
+
function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
|
|
25179
27504
|
if (!doc) {
|
|
25180
27505
|
throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
|
|
25181
27506
|
`${methodName}().`);
|
|
@@ -25212,12 +27537,12 @@ function newQueryBoundFromDocument(query, databaseId, methodName, doc, before) {
|
|
|
25212
27537
|
}
|
|
25213
27538
|
}
|
|
25214
27539
|
}
|
|
25215
|
-
return new Bound(components,
|
|
27540
|
+
return new Bound(components, inclusive);
|
|
25216
27541
|
}
|
|
25217
27542
|
/**
|
|
25218
27543
|
* Converts a list of field values to a `Bound` for the given query.
|
|
25219
27544
|
*/
|
|
25220
|
-
function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values,
|
|
27545
|
+
function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
|
|
25221
27546
|
// Use explicit order by's because it has to match the query the user made
|
|
25222
27547
|
const orderBy = query.explicitOrderBy;
|
|
25223
27548
|
if (values.length > orderBy.length) {
|
|
@@ -25254,7 +27579,7 @@ function newQueryBoundFromFields(query, databaseId, dataReader, methodName, valu
|
|
|
25254
27579
|
components.push(wrapped);
|
|
25255
27580
|
}
|
|
25256
27581
|
}
|
|
25257
|
-
return new Bound(components,
|
|
27582
|
+
return new Bound(components, inclusive);
|
|
25258
27583
|
}
|
|
25259
27584
|
/**
|
|
25260
27585
|
* Parses the given `documentIdValue` into a `ReferenceValue`, throwing
|
|
@@ -26238,17 +28563,6 @@ function writeBatch(firestore) {
|
|
|
26238
28563
|
ensureFirestoreConfigured(firestore);
|
|
26239
28564
|
return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
|
|
26240
28565
|
}
|
|
26241
|
-
/** An index component consisting of field path and index type. */
|
|
26242
|
-
class Segment {
|
|
26243
|
-
constructor(
|
|
26244
|
-
/** The field path of the component. */
|
|
26245
|
-
fieldPath,
|
|
26246
|
-
/** The fields sorting order. */
|
|
26247
|
-
kind) {
|
|
26248
|
-
this.fieldPath = fieldPath;
|
|
26249
|
-
this.kind = kind;
|
|
26250
|
-
}
|
|
26251
|
-
}
|
|
26252
28566
|
|
|
26253
28567
|
/**
|
|
26254
28568
|
* @license
|
|
@@ -26272,27 +28586,29 @@ function setIndexConfiguration(firestore, jsonOrConfiguration) {
|
|
|
26272
28586
|
const indexConfiguration = typeof jsonOrConfiguration === 'string'
|
|
26273
28587
|
? tryParseJson(jsonOrConfiguration)
|
|
26274
28588
|
: jsonOrConfiguration;
|
|
28589
|
+
const parsedIndexes = [];
|
|
26275
28590
|
// PORTING NOTE: We don't return an error if the user has not enabled
|
|
26276
28591
|
// persistence since `enableIndexeddbPersistence()` can fail on the Web.
|
|
26277
28592
|
if (Array.isArray(indexConfiguration.indexes)) {
|
|
26278
28593
|
for (const index of indexConfiguration.indexes) {
|
|
26279
|
-
tryGetString(index, 'collectionGroup');
|
|
28594
|
+
const collectionGroup = tryGetString(index, 'collectionGroup');
|
|
26280
28595
|
const segments = [];
|
|
26281
28596
|
if (Array.isArray(index.fields)) {
|
|
26282
28597
|
for (const field of index.fields) {
|
|
26283
28598
|
const fieldPathString = tryGetString(field, 'fieldPath');
|
|
26284
28599
|
const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
|
|
26285
28600
|
if (field.arrayConfig === 'CONTAINS') {
|
|
26286
|
-
segments.push(new
|
|
28601
|
+
segments.push(new IndexSegment(fieldPath, 2 /* CONTAINS */));
|
|
26287
28602
|
}
|
|
26288
28603
|
else if (field.order === 'ASCENDING') {
|
|
26289
|
-
segments.push(new
|
|
28604
|
+
segments.push(new IndexSegment(fieldPath, 0 /* ASCENDING */));
|
|
26290
28605
|
}
|
|
26291
28606
|
else if (field.order === 'DESCENDING') {
|
|
26292
|
-
segments.push(new
|
|
28607
|
+
segments.push(new IndexSegment(fieldPath, 1 /* DESCENDING */));
|
|
26293
28608
|
}
|
|
26294
28609
|
}
|
|
26295
28610
|
}
|
|
28611
|
+
parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
|
|
26296
28612
|
}
|
|
26297
28613
|
}
|
|
26298
28614
|
// TODO(indexing): Configure indexes
|