@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.mjs
CHANGED
|
@@ -4,11 +4,11 @@ import { Logger, LogLevel } from '@firebase/logger';
|
|
|
4
4
|
import { inspect, TextEncoder, TextDecoder } from 'util';
|
|
5
5
|
import { FirebaseError, createMockUserToken, getModularInstance, deepEqual, getUA, isIndexedDBAvailable, isSafari } from '@firebase/util';
|
|
6
6
|
import { randomBytes as randomBytes$1 } from 'crypto';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import * as grpc from '@grpc/grpc-js';
|
|
8
|
+
import * as protoLoader from '@grpc/proto-loader';
|
|
9
9
|
|
|
10
10
|
const name = "@firebase/firestore";
|
|
11
|
-
const version$1 = "3.4.
|
|
11
|
+
const version$1 = "3.4.5-canary.8ac8fb099";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
61
61
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
62
62
|
User.MOCK_USER = new User('mock-user');
|
|
63
63
|
|
|
64
|
-
const version = "9.6.
|
|
64
|
+
const version = "9.6.7-canary.8ac8fb099";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @license
|
|
@@ -666,13 +666,19 @@ class FirebaseAppCheckTokenProvider {
|
|
|
666
666
|
this.appCheckProvider = appCheckProvider;
|
|
667
667
|
this.forceRefresh = false;
|
|
668
668
|
this.appCheck = null;
|
|
669
|
+
this.latestAppCheckToken = null;
|
|
669
670
|
}
|
|
670
671
|
start(asyncQueue, changeListener) {
|
|
671
672
|
const onTokenChanged = tokenResult => {
|
|
672
673
|
if (tokenResult.error != null) {
|
|
673
674
|
logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
|
|
674
675
|
}
|
|
675
|
-
|
|
676
|
+
const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
|
|
677
|
+
this.latestAppCheckToken = tokenResult.token;
|
|
678
|
+
logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
|
|
679
|
+
return tokenUpdated
|
|
680
|
+
? changeListener(tokenResult.token)
|
|
681
|
+
: Promise.resolve();
|
|
676
682
|
};
|
|
677
683
|
this.tokenListener = (tokenResult) => {
|
|
678
684
|
asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
|
|
@@ -707,6 +713,7 @@ class FirebaseAppCheckTokenProvider {
|
|
|
707
713
|
return this.appCheck.getToken(forceRefresh).then(tokenResult => {
|
|
708
714
|
if (tokenResult) {
|
|
709
715
|
hardAssert(typeof tokenResult.token === 'string');
|
|
716
|
+
this.latestAppCheckToken = tokenResult.token;
|
|
710
717
|
return new AppCheckToken(tokenResult.token);
|
|
711
718
|
}
|
|
712
719
|
else {
|
|
@@ -1192,23 +1199,6 @@ function decodeResourcePath(path) {
|
|
|
1192
1199
|
}
|
|
1193
1200
|
return new ResourcePath(segments);
|
|
1194
1201
|
}
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* @license
|
|
1198
|
-
* Copyright 2017 Google LLC
|
|
1199
|
-
*
|
|
1200
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1201
|
-
* you may not use this file except in compliance with the License.
|
|
1202
|
-
* You may obtain a copy of the License at
|
|
1203
|
-
*
|
|
1204
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1205
|
-
*
|
|
1206
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1207
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1208
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1209
|
-
* See the License for the specific language governing permissions and
|
|
1210
|
-
* limitations under the License.
|
|
1211
|
-
*/
|
|
1212
1202
|
/**
|
|
1213
1203
|
* Schema Version for the Web client:
|
|
1214
1204
|
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
@@ -1227,8 +1217,10 @@ function decodeResourcePath(path) {
|
|
|
1227
1217
|
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
1228
1218
|
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
1229
1219
|
* 11. Add bundles and named_queries for bundle support.
|
|
1220
|
+
* 12. Add document overlays.
|
|
1221
|
+
* 13. Add indexing support.
|
|
1230
1222
|
*/
|
|
1231
|
-
const SCHEMA_VERSION =
|
|
1223
|
+
const SCHEMA_VERSION = 12;
|
|
1232
1224
|
/**
|
|
1233
1225
|
* Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
|
|
1234
1226
|
*/
|
|
@@ -1753,9 +1745,7 @@ class DbClientMetadata {
|
|
|
1753
1745
|
DbClientMetadata.store = 'clientMetadata';
|
|
1754
1746
|
/** Keys are automatically assigned via the clientId properties. */
|
|
1755
1747
|
DbClientMetadata.keyPath = 'clientId';
|
|
1756
|
-
/**
|
|
1757
|
-
* A object representing a bundle loaded by the SDK.
|
|
1758
|
-
*/
|
|
1748
|
+
/** An object representing a bundle loaded by the SDK. */
|
|
1759
1749
|
class DbBundle {
|
|
1760
1750
|
constructor(
|
|
1761
1751
|
/** The ID of the loaded bundle. */
|
|
@@ -1772,9 +1762,7 @@ class DbBundle {
|
|
|
1772
1762
|
/** Name of the IndexedDb object store. */
|
|
1773
1763
|
DbBundle.store = 'bundles';
|
|
1774
1764
|
DbBundle.keyPath = 'bundleId';
|
|
1775
|
-
/**
|
|
1776
|
-
* A object representing a named query loaded by the SDK via a bundle.
|
|
1777
|
-
*/
|
|
1765
|
+
/** An object representing a named query loaded by the SDK via a bundle. */
|
|
1778
1766
|
class DbNamedQuery {
|
|
1779
1767
|
constructor(
|
|
1780
1768
|
/** The name of the query. */
|
|
@@ -1791,6 +1779,158 @@ class DbNamedQuery {
|
|
|
1791
1779
|
/** Name of the IndexedDb object store. */
|
|
1792
1780
|
DbNamedQuery.store = 'namedQueries';
|
|
1793
1781
|
DbNamedQuery.keyPath = 'name';
|
|
1782
|
+
/** An object representing the global configuration for a field index. */
|
|
1783
|
+
class DbIndexConfiguration {
|
|
1784
|
+
constructor(
|
|
1785
|
+
/**
|
|
1786
|
+
* The index id for this entry. Undefined for indexes that are not yet
|
|
1787
|
+
* persisted.
|
|
1788
|
+
*/
|
|
1789
|
+
indexId,
|
|
1790
|
+
/** The collection group this index belongs to. */
|
|
1791
|
+
collectionGroup,
|
|
1792
|
+
/** The fields to index for this index. */
|
|
1793
|
+
fields) {
|
|
1794
|
+
this.indexId = indexId;
|
|
1795
|
+
this.collectionGroup = collectionGroup;
|
|
1796
|
+
this.fields = fields;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
/** Name of the IndexedDb object store. */
|
|
1800
|
+
DbIndexConfiguration.store = 'indexConfiguration';
|
|
1801
|
+
DbIndexConfiguration.keyPath = 'indexId';
|
|
1802
|
+
/**
|
|
1803
|
+
* An index that provides access to the index configurations by collection
|
|
1804
|
+
* group.
|
|
1805
|
+
*
|
|
1806
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1807
|
+
* not possible here as the Web client supports concurrent access to
|
|
1808
|
+
* persistence via multi-tab.
|
|
1809
|
+
*/
|
|
1810
|
+
DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
|
|
1811
|
+
DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
|
|
1812
|
+
/**
|
|
1813
|
+
* An object describing how up-to-date the index backfill is for each user and
|
|
1814
|
+
* index.
|
|
1815
|
+
*/
|
|
1816
|
+
class DbIndexState {
|
|
1817
|
+
constructor(
|
|
1818
|
+
/** The index id for this entry. */
|
|
1819
|
+
indexId,
|
|
1820
|
+
/** The user id for this entry. */
|
|
1821
|
+
uid,
|
|
1822
|
+
/**
|
|
1823
|
+
* A number that indicates when the index was last updated (relative to
|
|
1824
|
+
* other indexes).
|
|
1825
|
+
*/
|
|
1826
|
+
sequenceNumber,
|
|
1827
|
+
/**
|
|
1828
|
+
* The latest read time that has been indexed by Firestore for this field
|
|
1829
|
+
* index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
|
|
1830
|
+
*/
|
|
1831
|
+
readTime,
|
|
1832
|
+
/**
|
|
1833
|
+
* The last document that has been indexed for this field index. Empty if
|
|
1834
|
+
* no documents have been indexed.
|
|
1835
|
+
*/
|
|
1836
|
+
documentKey,
|
|
1837
|
+
/**
|
|
1838
|
+
* The largest mutation batch id that has been processed for this index. -1
|
|
1839
|
+
* if no mutations have been indexed.
|
|
1840
|
+
*/
|
|
1841
|
+
largestBatchId) {
|
|
1842
|
+
this.indexId = indexId;
|
|
1843
|
+
this.uid = uid;
|
|
1844
|
+
this.sequenceNumber = sequenceNumber;
|
|
1845
|
+
this.readTime = readTime;
|
|
1846
|
+
this.documentKey = documentKey;
|
|
1847
|
+
this.largestBatchId = largestBatchId;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
/** Name of the IndexedDb object store. */
|
|
1851
|
+
DbIndexState.store = 'indexState';
|
|
1852
|
+
DbIndexState.keyPath = ['indexId', 'uid'];
|
|
1853
|
+
/**
|
|
1854
|
+
* An index that provides access to documents in a collection sorted by last
|
|
1855
|
+
* update time. Used by the backfiller.
|
|
1856
|
+
*
|
|
1857
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1858
|
+
* not possible here as the Web client supports concurrent access to
|
|
1859
|
+
* persistence via multi-tab.
|
|
1860
|
+
*/
|
|
1861
|
+
DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
|
|
1862
|
+
DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1863
|
+
/** An object that stores the encoded entries for all documents and fields. */
|
|
1864
|
+
class DbIndexEntry {
|
|
1865
|
+
constructor(
|
|
1866
|
+
/** The index id for this entry. */
|
|
1867
|
+
indexId,
|
|
1868
|
+
/** The user id for this entry. */
|
|
1869
|
+
uid,
|
|
1870
|
+
/** The encoded array index value for this entry. */
|
|
1871
|
+
arrayValue,
|
|
1872
|
+
/** The encoded directional value for equality and inequality filters. */
|
|
1873
|
+
directionalValue,
|
|
1874
|
+
/** The document key this entry points to. */
|
|
1875
|
+
documentKey) {
|
|
1876
|
+
this.indexId = indexId;
|
|
1877
|
+
this.uid = uid;
|
|
1878
|
+
this.arrayValue = arrayValue;
|
|
1879
|
+
this.directionalValue = directionalValue;
|
|
1880
|
+
this.documentKey = documentKey;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
/** Name of the IndexedDb object store. */
|
|
1884
|
+
DbIndexEntry.store = 'indexEntries';
|
|
1885
|
+
DbIndexEntry.keyPath = [
|
|
1886
|
+
'indexId',
|
|
1887
|
+
'uid',
|
|
1888
|
+
'arrayValue',
|
|
1889
|
+
'directionalValue',
|
|
1890
|
+
'documentKey'
|
|
1891
|
+
];
|
|
1892
|
+
DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
|
|
1893
|
+
DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
|
|
1894
|
+
/**
|
|
1895
|
+
* An object representing a document overlay.
|
|
1896
|
+
*/
|
|
1897
|
+
class DbDocumentOverlay {
|
|
1898
|
+
constructor(
|
|
1899
|
+
/** The user ID to whom this overlay belongs. */
|
|
1900
|
+
userId,
|
|
1901
|
+
/** The path to the collection that contains the document. */
|
|
1902
|
+
collectionPath,
|
|
1903
|
+
/** The ID (key) of the document within the collection. */
|
|
1904
|
+
documentId,
|
|
1905
|
+
/** The collection group to which the document belongs. */
|
|
1906
|
+
collectionGroup,
|
|
1907
|
+
/** The largest batch ID that's been applied for this overlay. */
|
|
1908
|
+
largestBatchId,
|
|
1909
|
+
/** The overlay mutation. */
|
|
1910
|
+
overlayMutation) {
|
|
1911
|
+
this.userId = userId;
|
|
1912
|
+
this.collectionPath = collectionPath;
|
|
1913
|
+
this.documentId = documentId;
|
|
1914
|
+
this.collectionGroup = collectionGroup;
|
|
1915
|
+
this.largestBatchId = largestBatchId;
|
|
1916
|
+
this.overlayMutation = overlayMutation;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
/** Name of the IndexedDb object store. */
|
|
1920
|
+
DbDocumentOverlay.store = 'documentOverlays';
|
|
1921
|
+
DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
|
|
1922
|
+
DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1923
|
+
DbDocumentOverlay.collectionPathOverlayIndexPath = [
|
|
1924
|
+
'userId',
|
|
1925
|
+
'collectionPath',
|
|
1926
|
+
'largestBatchId'
|
|
1927
|
+
];
|
|
1928
|
+
DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1929
|
+
DbDocumentOverlay.collectionGroupOverlayIndexPath = [
|
|
1930
|
+
'userId',
|
|
1931
|
+
'collectionGroup',
|
|
1932
|
+
'largestBatchId'
|
|
1933
|
+
];
|
|
1794
1934
|
// Visible for testing
|
|
1795
1935
|
const V1_STORES = [
|
|
1796
1936
|
DbMutationQueue.store,
|
|
@@ -1805,7 +1945,6 @@ const V1_STORES = [
|
|
|
1805
1945
|
// V2 is no longer usable (see comment at top of file)
|
|
1806
1946
|
// Visible for testing
|
|
1807
1947
|
const V3_STORES = V1_STORES;
|
|
1808
|
-
// Visible for testing
|
|
1809
1948
|
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
1810
1949
|
const V4_STORES = [...V3_STORES, DbClientMetadata.store];
|
|
1811
1950
|
// V5 does not change the set of stores.
|
|
@@ -1815,12 +1954,28 @@ const V8_STORES = [...V6_STORES, DbCollectionParent.store];
|
|
|
1815
1954
|
// V9 does not change the set of stores.
|
|
1816
1955
|
// V10 does not change the set of stores.
|
|
1817
1956
|
const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1957
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
|
|
1958
|
+
const V13_STORES = [
|
|
1959
|
+
...V12_STORES,
|
|
1960
|
+
DbIndexConfiguration.store,
|
|
1961
|
+
DbIndexState.store,
|
|
1962
|
+
DbIndexEntry.store
|
|
1963
|
+
];
|
|
1964
|
+
/** Returns the object stores for the provided schema. */
|
|
1965
|
+
function getObjectStores(schemaVersion) {
|
|
1966
|
+
if (schemaVersion === 13) {
|
|
1967
|
+
return V13_STORES;
|
|
1968
|
+
}
|
|
1969
|
+
else if (schemaVersion === 12) {
|
|
1970
|
+
return V12_STORES;
|
|
1971
|
+
}
|
|
1972
|
+
else if (schemaVersion === 11) {
|
|
1973
|
+
return V11_STORES;
|
|
1974
|
+
}
|
|
1975
|
+
else {
|
|
1976
|
+
fail();
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1824
1979
|
|
|
1825
1980
|
/**
|
|
1826
1981
|
* @license
|
|
@@ -2061,7 +2216,7 @@ class PersistencePromise {
|
|
|
2061
2216
|
*/
|
|
2062
2217
|
// References to `window` are guarded by SimpleDb.isAvailable()
|
|
2063
2218
|
/* eslint-disable no-restricted-globals */
|
|
2064
|
-
const LOG_TAG$
|
|
2219
|
+
const LOG_TAG$h = 'SimpleDb';
|
|
2065
2220
|
/**
|
|
2066
2221
|
* The maximum number of retry attempts for an IndexedDb transaction that fails
|
|
2067
2222
|
* with a DOMException.
|
|
@@ -2112,11 +2267,20 @@ class SimpleDbTransaction {
|
|
|
2112
2267
|
this.completionDeferred.reject(error);
|
|
2113
2268
|
}
|
|
2114
2269
|
if (!this.aborted) {
|
|
2115
|
-
logDebug(LOG_TAG$
|
|
2270
|
+
logDebug(LOG_TAG$h, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
|
|
2116
2271
|
this.aborted = true;
|
|
2117
2272
|
this.transaction.abort();
|
|
2118
2273
|
}
|
|
2119
2274
|
}
|
|
2275
|
+
maybeCommit() {
|
|
2276
|
+
// If the browser supports V3 IndexedDB, we invoke commit() explicitly to
|
|
2277
|
+
// speed up index DB processing if the event loop remains blocks.
|
|
2278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2279
|
+
const maybeV3IndexedDb = this.transaction;
|
|
2280
|
+
if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
|
|
2281
|
+
maybeV3IndexedDb.commit();
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2120
2284
|
/**
|
|
2121
2285
|
* Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
|
|
2122
2286
|
* operations performed on the SimpleDbStore happen within the context of this
|
|
@@ -2166,7 +2330,7 @@ class SimpleDb {
|
|
|
2166
2330
|
}
|
|
2167
2331
|
/** Deletes the specified database. */
|
|
2168
2332
|
static delete(name) {
|
|
2169
|
-
logDebug(LOG_TAG$
|
|
2333
|
+
logDebug(LOG_TAG$h, 'Removing database:', name);
|
|
2170
2334
|
return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
|
|
2171
2335
|
}
|
|
2172
2336
|
/** Returns true if IndexedDB is available in the current environment. */
|
|
@@ -2245,7 +2409,7 @@ class SimpleDb {
|
|
|
2245
2409
|
*/
|
|
2246
2410
|
async ensureDb(action) {
|
|
2247
2411
|
if (!this.db) {
|
|
2248
|
-
logDebug(LOG_TAG$
|
|
2412
|
+
logDebug(LOG_TAG$h, 'Opening database:', this.name);
|
|
2249
2413
|
this.db = await new Promise((resolve, reject) => {
|
|
2250
2414
|
// TODO(mikelehen): Investigate browser compatibility.
|
|
2251
2415
|
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
|
|
@@ -2281,12 +2445,12 @@ class SimpleDb {
|
|
|
2281
2445
|
}
|
|
2282
2446
|
};
|
|
2283
2447
|
request.onupgradeneeded = (event) => {
|
|
2284
|
-
logDebug(LOG_TAG$
|
|
2448
|
+
logDebug(LOG_TAG$h, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
|
|
2285
2449
|
const db = event.target.result;
|
|
2286
2450
|
this.schemaConverter
|
|
2287
2451
|
.createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
|
|
2288
2452
|
.next(() => {
|
|
2289
|
-
logDebug(LOG_TAG$
|
|
2453
|
+
logDebug(LOG_TAG$h, 'Database upgrade to version ' + this.version + ' complete');
|
|
2290
2454
|
});
|
|
2291
2455
|
};
|
|
2292
2456
|
});
|
|
@@ -2313,6 +2477,10 @@ class SimpleDb {
|
|
|
2313
2477
|
this.db = await this.ensureDb(action);
|
|
2314
2478
|
const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
|
|
2315
2479
|
const transactionFnResult = transactionFn(transaction)
|
|
2480
|
+
.next(result => {
|
|
2481
|
+
transaction.maybeCommit();
|
|
2482
|
+
return result;
|
|
2483
|
+
})
|
|
2316
2484
|
.catch(error => {
|
|
2317
2485
|
// Abort the transaction if there was an error.
|
|
2318
2486
|
transaction.abort(error);
|
|
@@ -2340,7 +2508,7 @@ class SimpleDb {
|
|
|
2340
2508
|
// exception is wrapped in a generic error by our async/await handling.
|
|
2341
2509
|
const retryable = error.name !== 'FirebaseError' &&
|
|
2342
2510
|
attemptNumber < TRANSACTION_RETRY_COUNT;
|
|
2343
|
-
logDebug(LOG_TAG$
|
|
2511
|
+
logDebug(LOG_TAG$h, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
|
|
2344
2512
|
this.close();
|
|
2345
2513
|
if (!retryable) {
|
|
2346
2514
|
return Promise.reject(error);
|
|
@@ -2427,11 +2595,11 @@ class SimpleDbStore {
|
|
|
2427
2595
|
put(keyOrValue, value) {
|
|
2428
2596
|
let request;
|
|
2429
2597
|
if (value !== undefined) {
|
|
2430
|
-
logDebug(LOG_TAG$
|
|
2598
|
+
logDebug(LOG_TAG$h, 'PUT', this.store.name, keyOrValue, value);
|
|
2431
2599
|
request = this.store.put(value, keyOrValue);
|
|
2432
2600
|
}
|
|
2433
2601
|
else {
|
|
2434
|
-
logDebug(LOG_TAG$
|
|
2602
|
+
logDebug(LOG_TAG$h, 'PUT', this.store.name, '<auto-key>', keyOrValue);
|
|
2435
2603
|
request = this.store.put(keyOrValue);
|
|
2436
2604
|
}
|
|
2437
2605
|
return wrapRequest(request);
|
|
@@ -2444,7 +2612,7 @@ class SimpleDbStore {
|
|
|
2444
2612
|
* @returns The key of the value to add.
|
|
2445
2613
|
*/
|
|
2446
2614
|
add(value) {
|
|
2447
|
-
logDebug(LOG_TAG$
|
|
2615
|
+
logDebug(LOG_TAG$h, 'ADD', this.store.name, value, value);
|
|
2448
2616
|
const request = this.store.add(value);
|
|
2449
2617
|
return wrapRequest(request);
|
|
2450
2618
|
}
|
|
@@ -2464,12 +2632,12 @@ class SimpleDbStore {
|
|
|
2464
2632
|
if (result === undefined) {
|
|
2465
2633
|
result = null;
|
|
2466
2634
|
}
|
|
2467
|
-
logDebug(LOG_TAG$
|
|
2635
|
+
logDebug(LOG_TAG$h, 'GET', this.store.name, key, result);
|
|
2468
2636
|
return result;
|
|
2469
2637
|
});
|
|
2470
2638
|
}
|
|
2471
2639
|
delete(key) {
|
|
2472
|
-
logDebug(LOG_TAG$
|
|
2640
|
+
logDebug(LOG_TAG$h, 'DELETE', this.store.name, key);
|
|
2473
2641
|
const request = this.store.delete(key);
|
|
2474
2642
|
return wrapRequest(request);
|
|
2475
2643
|
}
|
|
@@ -2480,21 +2648,52 @@ class SimpleDbStore {
|
|
|
2480
2648
|
* Returns the number of rows in the store.
|
|
2481
2649
|
*/
|
|
2482
2650
|
count() {
|
|
2483
|
-
logDebug(LOG_TAG$
|
|
2651
|
+
logDebug(LOG_TAG$h, 'COUNT', this.store.name);
|
|
2484
2652
|
const request = this.store.count();
|
|
2485
2653
|
return wrapRequest(request);
|
|
2486
2654
|
}
|
|
2487
2655
|
loadAll(indexOrRange, range) {
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
return
|
|
2656
|
+
const iterateOptions = this.options(indexOrRange, range);
|
|
2657
|
+
// Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
|
|
2658
|
+
// 20% faster. Unfortunately, getAll() does not support custom indices.
|
|
2659
|
+
if (!iterateOptions.index && typeof this.store.getAll === 'function') {
|
|
2660
|
+
const request = this.store.getAll(iterateOptions.range);
|
|
2661
|
+
return new PersistencePromise((resolve, reject) => {
|
|
2662
|
+
request.onerror = (event) => {
|
|
2663
|
+
reject(event.target.error);
|
|
2664
|
+
};
|
|
2665
|
+
request.onsuccess = (event) => {
|
|
2666
|
+
resolve(event.target.result);
|
|
2667
|
+
};
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
else {
|
|
2671
|
+
const cursor = this.cursor(iterateOptions);
|
|
2672
|
+
const results = [];
|
|
2673
|
+
return this.iterateCursor(cursor, (key, value) => {
|
|
2674
|
+
results.push(value);
|
|
2675
|
+
}).next(() => {
|
|
2676
|
+
return results;
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Loads the first `count` elements from the provided index range. Loads all
|
|
2682
|
+
* elements if no limit is provided.
|
|
2683
|
+
*/
|
|
2684
|
+
loadFirst(range, count) {
|
|
2685
|
+
const request = this.store.getAll(range, count === null ? undefined : count);
|
|
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
|
+
};
|
|
2494
2693
|
});
|
|
2495
2694
|
}
|
|
2496
2695
|
deleteAll(indexOrRange, range) {
|
|
2497
|
-
logDebug(LOG_TAG$
|
|
2696
|
+
logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
|
|
2498
2697
|
const options = this.options(indexOrRange, range);
|
|
2499
2698
|
options.keysOnly = false;
|
|
2500
2699
|
const cursor = this.cursor(options);
|
|
@@ -3103,17 +3302,15 @@ class FieldMask {
|
|
|
3103
3302
|
* See the License for the specific language governing permissions and
|
|
3104
3303
|
* limitations under the License.
|
|
3105
3304
|
*/
|
|
3305
|
+
/** Converts a Base64 encoded string to a binary string. */
|
|
3106
3306
|
function decodeBase64(encoded) {
|
|
3107
|
-
//
|
|
3108
|
-
//
|
|
3109
|
-
|
|
3110
|
-
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
|
|
3111
|
-
}
|
|
3112
|
-
return new Buffer(encoded, 'base64').toString('binary');
|
|
3307
|
+
// Note: We used to validate the base64 string here via a regular expression.
|
|
3308
|
+
// This was removed to improve the performance of indexing.
|
|
3309
|
+
return Buffer.from(encoded, 'base64').toString('binary');
|
|
3113
3310
|
}
|
|
3114
3311
|
/** Converts a binary string to a Base64 encoded string. */
|
|
3115
3312
|
function encodeBase64(raw) {
|
|
3116
|
-
return
|
|
3313
|
+
return Buffer.from(raw, 'binary').toString('base64');
|
|
3117
3314
|
}
|
|
3118
3315
|
/** True if and only if the Base64 conversion functions are available. */
|
|
3119
3316
|
function isBase64Available() {
|
|
@@ -3154,6 +3351,8 @@ class ByteString {
|
|
|
3154
3351
|
return new ByteString(binaryString);
|
|
3155
3352
|
}
|
|
3156
3353
|
static fromUint8Array(array) {
|
|
3354
|
+
// TODO(indexing); Remove the copy of the byte string here as this method
|
|
3355
|
+
// is frequently called during indexing.
|
|
3157
3356
|
const binaryString = binaryStringFromUint8Array(array);
|
|
3158
3357
|
return new ByteString(binaryString);
|
|
3159
3358
|
}
|
|
@@ -3374,6 +3573,75 @@ function getLocalWriteTime(value) {
|
|
|
3374
3573
|
return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
|
|
3375
3574
|
}
|
|
3376
3575
|
|
|
3576
|
+
/**
|
|
3577
|
+
* @license
|
|
3578
|
+
* Copyright 2017 Google LLC
|
|
3579
|
+
*
|
|
3580
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3581
|
+
* you may not use this file except in compliance with the License.
|
|
3582
|
+
* You may obtain a copy of the License at
|
|
3583
|
+
*
|
|
3584
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
3585
|
+
*
|
|
3586
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
3587
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
3588
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3589
|
+
* See the License for the specific language governing permissions and
|
|
3590
|
+
* limitations under the License.
|
|
3591
|
+
*/
|
|
3592
|
+
class DatabaseInfo {
|
|
3593
|
+
/**
|
|
3594
|
+
* Constructs a DatabaseInfo using the provided host, databaseId and
|
|
3595
|
+
* persistenceKey.
|
|
3596
|
+
*
|
|
3597
|
+
* @param databaseId - The database to use.
|
|
3598
|
+
* @param appId - The Firebase App Id.
|
|
3599
|
+
* @param persistenceKey - A unique identifier for this Firestore's local
|
|
3600
|
+
* storage (used in conjunction with the databaseId).
|
|
3601
|
+
* @param host - The Firestore backend host to connect to.
|
|
3602
|
+
* @param ssl - Whether to use SSL when connecting.
|
|
3603
|
+
* @param forceLongPolling - Whether to use the forceLongPolling option
|
|
3604
|
+
* when using WebChannel as the network transport.
|
|
3605
|
+
* @param autoDetectLongPolling - Whether to use the detectBufferingProxy
|
|
3606
|
+
* option when using WebChannel as the network transport.
|
|
3607
|
+
* @param useFetchStreams Whether to use the Fetch API instead of
|
|
3608
|
+
* XMLHTTPRequest
|
|
3609
|
+
*/
|
|
3610
|
+
constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
|
|
3611
|
+
this.databaseId = databaseId;
|
|
3612
|
+
this.appId = appId;
|
|
3613
|
+
this.persistenceKey = persistenceKey;
|
|
3614
|
+
this.host = host;
|
|
3615
|
+
this.ssl = ssl;
|
|
3616
|
+
this.forceLongPolling = forceLongPolling;
|
|
3617
|
+
this.autoDetectLongPolling = autoDetectLongPolling;
|
|
3618
|
+
this.useFetchStreams = useFetchStreams;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
/** The default database name for a project. */
|
|
3622
|
+
const DEFAULT_DATABASE_NAME = '(default)';
|
|
3623
|
+
/**
|
|
3624
|
+
* Represents the database ID a Firestore client is associated with.
|
|
3625
|
+
* @internal
|
|
3626
|
+
*/
|
|
3627
|
+
class DatabaseId {
|
|
3628
|
+
constructor(projectId, database) {
|
|
3629
|
+
this.projectId = projectId;
|
|
3630
|
+
this.database = database ? database : DEFAULT_DATABASE_NAME;
|
|
3631
|
+
}
|
|
3632
|
+
static empty() {
|
|
3633
|
+
return new DatabaseId('', '');
|
|
3634
|
+
}
|
|
3635
|
+
get isDefaultDatabase() {
|
|
3636
|
+
return this.database === DEFAULT_DATABASE_NAME;
|
|
3637
|
+
}
|
|
3638
|
+
isEqual(other) {
|
|
3639
|
+
return (other instanceof DatabaseId &&
|
|
3640
|
+
other.projectId === this.projectId &&
|
|
3641
|
+
other.database === this.database);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3377
3645
|
/**
|
|
3378
3646
|
* @license
|
|
3379
3647
|
* Copyright 2017 Google LLC
|
|
@@ -3445,11 +3713,25 @@ class DocumentKey {
|
|
|
3445
3713
|
static fromName(name) {
|
|
3446
3714
|
return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
|
|
3447
3715
|
}
|
|
3716
|
+
static empty() {
|
|
3717
|
+
return new DocumentKey(ResourcePath.emptyPath());
|
|
3718
|
+
}
|
|
3719
|
+
get collectionGroup() {
|
|
3720
|
+
return this.path.popLast().lastSegment();
|
|
3721
|
+
}
|
|
3448
3722
|
/** Returns true if the document is in the specified collectionId. */
|
|
3449
3723
|
hasCollectionId(collectionId) {
|
|
3450
3724
|
return (this.path.length >= 2 &&
|
|
3451
3725
|
this.path.get(this.path.length - 2) === collectionId);
|
|
3452
3726
|
}
|
|
3727
|
+
/** Returns the collection group (i.e. the name of the parent collection) for this key. */
|
|
3728
|
+
getCollectionGroup() {
|
|
3729
|
+
return this.path.get(this.path.length - 2);
|
|
3730
|
+
}
|
|
3731
|
+
/** Returns the fully qualified path to the parent collection. */
|
|
3732
|
+
getCollectionPath() {
|
|
3733
|
+
return this.path.popLast();
|
|
3734
|
+
}
|
|
3453
3735
|
isEqual(other) {
|
|
3454
3736
|
return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
|
|
3455
3737
|
}
|
|
@@ -3489,6 +3771,17 @@ class DocumentKey {
|
|
|
3489
3771
|
* See the License for the specific language governing permissions and
|
|
3490
3772
|
* limitations under the License.
|
|
3491
3773
|
*/
|
|
3774
|
+
const MAX_VALUE_TYPE = '__max__';
|
|
3775
|
+
const MAX_VALUE = {
|
|
3776
|
+
mapValue: {
|
|
3777
|
+
fields: {
|
|
3778
|
+
'__type__': { stringValue: MAX_VALUE_TYPE }
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
const MIN_VALUE = {
|
|
3783
|
+
nullValue: 'NULL_VALUE'
|
|
3784
|
+
};
|
|
3492
3785
|
/** Extracts the backend's type order for the provided value. */
|
|
3493
3786
|
function typeOrder(value) {
|
|
3494
3787
|
if ('nullValue' in value) {
|
|
@@ -3522,6 +3815,9 @@ function typeOrder(value) {
|
|
|
3522
3815
|
if (isServerTimestamp(value)) {
|
|
3523
3816
|
return 4 /* ServerTimestampValue */;
|
|
3524
3817
|
}
|
|
3818
|
+
else if (isMaxValue(value)) {
|
|
3819
|
+
return 9 /* ArrayValue */;
|
|
3820
|
+
}
|
|
3525
3821
|
return 10 /* ObjectValue */;
|
|
3526
3822
|
}
|
|
3527
3823
|
else {
|
|
@@ -3561,6 +3857,8 @@ function valueEquals(left, right) {
|
|
|
3561
3857
|
return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
|
|
3562
3858
|
case 10 /* ObjectValue */:
|
|
3563
3859
|
return objectEquals(left, right);
|
|
3860
|
+
case 9007199254740991 /* MaxValue */:
|
|
3861
|
+
return true;
|
|
3564
3862
|
default:
|
|
3565
3863
|
return fail();
|
|
3566
3864
|
}
|
|
@@ -3633,6 +3931,7 @@ function valueCompare(left, right) {
|
|
|
3633
3931
|
}
|
|
3634
3932
|
switch (leftType) {
|
|
3635
3933
|
case 0 /* NullValue */:
|
|
3934
|
+
case 9007199254740991 /* MaxValue */:
|
|
3636
3935
|
return 0;
|
|
3637
3936
|
case 1 /* BooleanValue */:
|
|
3638
3937
|
return primitiveComparator(left.booleanValue, right.booleanValue);
|
|
@@ -3898,6 +4197,105 @@ function deepClone(source) {
|
|
|
3898
4197
|
else {
|
|
3899
4198
|
return Object.assign({}, source);
|
|
3900
4199
|
}
|
|
4200
|
+
}
|
|
4201
|
+
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
|
|
4202
|
+
function isMaxValue(value) {
|
|
4203
|
+
return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
|
|
4204
|
+
MAX_VALUE_TYPE);
|
|
4205
|
+
}
|
|
4206
|
+
/** Returns the lowest value for the given value type (inclusive). */
|
|
4207
|
+
function valuesGetLowerBound(value) {
|
|
4208
|
+
if ('nullValue' in value) {
|
|
4209
|
+
return MIN_VALUE;
|
|
4210
|
+
}
|
|
4211
|
+
else if ('booleanValue' in value) {
|
|
4212
|
+
return { booleanValue: false };
|
|
4213
|
+
}
|
|
4214
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
4215
|
+
return { doubleValue: NaN };
|
|
4216
|
+
}
|
|
4217
|
+
else if ('timestampValue' in value) {
|
|
4218
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
4219
|
+
}
|
|
4220
|
+
else if ('stringValue' in value) {
|
|
4221
|
+
return { stringValue: '' };
|
|
4222
|
+
}
|
|
4223
|
+
else if ('bytesValue' in value) {
|
|
4224
|
+
return { bytesValue: '' };
|
|
4225
|
+
}
|
|
4226
|
+
else if ('referenceValue' in value) {
|
|
4227
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
4228
|
+
}
|
|
4229
|
+
else if ('geoPointValue' in value) {
|
|
4230
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
4231
|
+
}
|
|
4232
|
+
else if ('arrayValue' in value) {
|
|
4233
|
+
return { arrayValue: {} };
|
|
4234
|
+
}
|
|
4235
|
+
else if ('mapValue' in value) {
|
|
4236
|
+
return { mapValue: {} };
|
|
4237
|
+
}
|
|
4238
|
+
else {
|
|
4239
|
+
return fail();
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
/** Returns the largest value for the given value type (exclusive). */
|
|
4243
|
+
function valuesGetUpperBound(value) {
|
|
4244
|
+
if ('nullValue' in value) {
|
|
4245
|
+
return { booleanValue: false };
|
|
4246
|
+
}
|
|
4247
|
+
else if ('booleanValue' in value) {
|
|
4248
|
+
return { doubleValue: NaN };
|
|
4249
|
+
}
|
|
4250
|
+
else if ('integerValue' in value || 'doubleValue' in value) {
|
|
4251
|
+
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
|
|
4252
|
+
}
|
|
4253
|
+
else if ('timestampValue' in value) {
|
|
4254
|
+
return { stringValue: '' };
|
|
4255
|
+
}
|
|
4256
|
+
else if ('stringValue' in value) {
|
|
4257
|
+
return { bytesValue: '' };
|
|
4258
|
+
}
|
|
4259
|
+
else if ('bytesValue' in value) {
|
|
4260
|
+
return refValue(DatabaseId.empty(), DocumentKey.empty());
|
|
4261
|
+
}
|
|
4262
|
+
else if ('referenceValue' in value) {
|
|
4263
|
+
return { geoPointValue: { latitude: -90, longitude: -180 } };
|
|
4264
|
+
}
|
|
4265
|
+
else if ('geoPointValue' in value) {
|
|
4266
|
+
return { arrayValue: {} };
|
|
4267
|
+
}
|
|
4268
|
+
else if ('arrayValue' in value) {
|
|
4269
|
+
return { mapValue: {} };
|
|
4270
|
+
}
|
|
4271
|
+
else if ('mapValue' in value) {
|
|
4272
|
+
return MAX_VALUE;
|
|
4273
|
+
}
|
|
4274
|
+
else {
|
|
4275
|
+
return fail();
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
function valuesMax(left, right) {
|
|
4279
|
+
if (left === undefined) {
|
|
4280
|
+
return right;
|
|
4281
|
+
}
|
|
4282
|
+
else if (right === undefined) {
|
|
4283
|
+
return left;
|
|
4284
|
+
}
|
|
4285
|
+
else {
|
|
4286
|
+
return valueCompare(left, right) > 0 ? left : right;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
function valuesMin(left, right) {
|
|
4290
|
+
if (left === undefined) {
|
|
4291
|
+
return right;
|
|
4292
|
+
}
|
|
4293
|
+
else if (right === undefined) {
|
|
4294
|
+
return left;
|
|
4295
|
+
}
|
|
4296
|
+
else {
|
|
4297
|
+
return valueCompare(left, right) < 0 ? left : right;
|
|
4298
|
+
}
|
|
3901
4299
|
}
|
|
3902
4300
|
|
|
3903
4301
|
/**
|
|
@@ -4093,10 +4491,11 @@ function extractFieldMask(value) {
|
|
|
4093
4491
|
* from all views.
|
|
4094
4492
|
*/
|
|
4095
4493
|
class MutableDocument {
|
|
4096
|
-
constructor(key, documentType, version, data, documentState) {
|
|
4494
|
+
constructor(key, documentType, version, readTime, data, documentState) {
|
|
4097
4495
|
this.key = key;
|
|
4098
4496
|
this.documentType = documentType;
|
|
4099
4497
|
this.version = version;
|
|
4498
|
+
this.readTime = readTime;
|
|
4100
4499
|
this.data = data;
|
|
4101
4500
|
this.documentState = documentState;
|
|
4102
4501
|
}
|
|
@@ -4105,18 +4504,18 @@ class MutableDocument {
|
|
|
4105
4504
|
* base document for mutations.
|
|
4106
4505
|
*/
|
|
4107
4506
|
static newInvalidDocument(documentKey) {
|
|
4108
|
-
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4507
|
+
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4109
4508
|
}
|
|
4110
4509
|
/**
|
|
4111
4510
|
* Creates a new document that is known to exist with the given data at the
|
|
4112
4511
|
* given version.
|
|
4113
4512
|
*/
|
|
4114
4513
|
static newFoundDocument(documentKey, version, value) {
|
|
4115
|
-
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, value, 0 /* SYNCED */);
|
|
4514
|
+
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
|
|
4116
4515
|
}
|
|
4117
4516
|
/** Creates a new document that is known to not exist at the given version. */
|
|
4118
4517
|
static newNoDocument(documentKey, version) {
|
|
4119
|
-
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, ObjectValue.empty(), 0 /* SYNCED */);
|
|
4518
|
+
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4120
4519
|
}
|
|
4121
4520
|
/**
|
|
4122
4521
|
* Creates a new document that is known to exist at the given version but
|
|
@@ -4124,7 +4523,7 @@ class MutableDocument {
|
|
|
4124
4523
|
* base document).
|
|
4125
4524
|
*/
|
|
4126
4525
|
static newUnknownDocument(documentKey, version) {
|
|
4127
|
-
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
4526
|
+
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
4128
4527
|
}
|
|
4129
4528
|
/**
|
|
4130
4529
|
* Changes the document type to indicate that it exists and that its version
|
|
@@ -4168,6 +4567,10 @@ class MutableDocument {
|
|
|
4168
4567
|
this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4169
4568
|
return this;
|
|
4170
4569
|
}
|
|
4570
|
+
setReadTime(readTime) {
|
|
4571
|
+
this.readTime = readTime;
|
|
4572
|
+
return this;
|
|
4573
|
+
}
|
|
4171
4574
|
get hasLocalMutations() {
|
|
4172
4575
|
return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4173
4576
|
}
|
|
@@ -4198,7 +4601,7 @@ class MutableDocument {
|
|
|
4198
4601
|
this.data.isEqual(other.data));
|
|
4199
4602
|
}
|
|
4200
4603
|
mutableCopy() {
|
|
4201
|
-
return new MutableDocument(this.key, this.documentType, this.version, this.data.clone(), this.documentState);
|
|
4604
|
+
return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
|
|
4202
4605
|
}
|
|
4203
4606
|
toString() {
|
|
4204
4607
|
return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
|
|
@@ -4223,7 +4626,7 @@ function compareDocumentsByField(field, d1, d2) {
|
|
|
4223
4626
|
|
|
4224
4627
|
/**
|
|
4225
4628
|
* @license
|
|
4226
|
-
* Copyright
|
|
4629
|
+
* Copyright 2021 Google LLC
|
|
4227
4630
|
*
|
|
4228
4631
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4229
4632
|
* you may not use this file except in compliance with the License.
|
|
@@ -4237,9 +4640,140 @@ function compareDocumentsByField(field, d1, d2) {
|
|
|
4237
4640
|
* See the License for the specific language governing permissions and
|
|
4238
4641
|
* limitations under the License.
|
|
4239
4642
|
*/
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4643
|
+
/**
|
|
4644
|
+
* The initial mutation batch id for each index. Gets updated during index
|
|
4645
|
+
* backfill.
|
|
4646
|
+
*/
|
|
4647
|
+
const INITIAL_LARGEST_BATCH_ID = -1;
|
|
4648
|
+
/**
|
|
4649
|
+
* The initial sequence number for each index. Gets updated during index
|
|
4650
|
+
* backfill.
|
|
4651
|
+
*/
|
|
4652
|
+
const INITIAL_SEQUENCE_NUMBER = 0;
|
|
4653
|
+
/**
|
|
4654
|
+
* An index definition for field indexes in Firestore.
|
|
4655
|
+
*
|
|
4656
|
+
* Every index is associated with a collection. The definition contains a list
|
|
4657
|
+
* of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
|
|
4658
|
+
* `CONTAINS` for ArrayContains/ArrayContainsAny queries).
|
|
4659
|
+
*
|
|
4660
|
+
* Unlike the backend, the SDK does not differentiate between collection or
|
|
4661
|
+
* collection group-scoped indices. Every index can be used for both single
|
|
4662
|
+
* collection and collection group queries.
|
|
4663
|
+
*/
|
|
4664
|
+
class FieldIndex {
|
|
4665
|
+
constructor(
|
|
4666
|
+
/**
|
|
4667
|
+
* The index ID. Returns -1 if the index ID is not available (e.g. the index
|
|
4668
|
+
* has not yet been persisted).
|
|
4669
|
+
*/
|
|
4670
|
+
indexId,
|
|
4671
|
+
/** The collection ID this index applies to. */
|
|
4672
|
+
collectionGroup,
|
|
4673
|
+
/** The field segments for this index. */
|
|
4674
|
+
fields,
|
|
4675
|
+
/** Shows how up-to-date the index is for the current user. */
|
|
4676
|
+
indexState) {
|
|
4677
|
+
this.indexId = indexId;
|
|
4678
|
+
this.collectionGroup = collectionGroup;
|
|
4679
|
+
this.fields = fields;
|
|
4680
|
+
this.indexState = indexState;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
/** An ID for an index that has not yet been added to persistence. */
|
|
4684
|
+
FieldIndex.UNKNOWN_ID = -1;
|
|
4685
|
+
/** Returns the ArrayContains/ArrayContainsAny segment for this index. */
|
|
4686
|
+
function fieldIndexGetArraySegment(fieldIndex) {
|
|
4687
|
+
return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
|
|
4688
|
+
}
|
|
4689
|
+
/** Returns all directional (ascending/descending) segments for this index. */
|
|
4690
|
+
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4691
|
+
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4692
|
+
}
|
|
4693
|
+
/** Returns a debug representation of the field index */
|
|
4694
|
+
function fieldIndexToString(fieldIndex) {
|
|
4695
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4696
|
+
}
|
|
4697
|
+
/** An index component consisting of field path and index type. */
|
|
4698
|
+
class IndexSegment {
|
|
4699
|
+
constructor(
|
|
4700
|
+
/** The field path of the component. */
|
|
4701
|
+
fieldPath,
|
|
4702
|
+
/** The fields sorting order. */
|
|
4703
|
+
kind) {
|
|
4704
|
+
this.fieldPath = fieldPath;
|
|
4705
|
+
this.kind = kind;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
/**
|
|
4709
|
+
* Stores the "high water mark" that indicates how updated the Index is for the
|
|
4710
|
+
* current user.
|
|
4711
|
+
*/
|
|
4712
|
+
class IndexState {
|
|
4713
|
+
constructor(
|
|
4714
|
+
/**
|
|
4715
|
+
* Indicates when the index was last updated (relative to other indexes).
|
|
4716
|
+
*/
|
|
4717
|
+
sequenceNumber,
|
|
4718
|
+
/** The the latest indexed read time, document and batch id. */
|
|
4719
|
+
offset) {
|
|
4720
|
+
this.sequenceNumber = sequenceNumber;
|
|
4721
|
+
this.offset = offset;
|
|
4722
|
+
}
|
|
4723
|
+
/** The state of an index that has not yet been backfilled. */
|
|
4724
|
+
static empty() {
|
|
4725
|
+
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Stores the latest read time, document and batch ID that were processed for an
|
|
4730
|
+
* index.
|
|
4731
|
+
*/
|
|
4732
|
+
class IndexOffset {
|
|
4733
|
+
constructor(
|
|
4734
|
+
/**
|
|
4735
|
+
* The latest read time version that has been indexed by Firestore for this
|
|
4736
|
+
* field index.
|
|
4737
|
+
*/
|
|
4738
|
+
readTime,
|
|
4739
|
+
/**
|
|
4740
|
+
* The key of the last document that was indexed for this query. Use
|
|
4741
|
+
* `DocumentKey.empty()` if no document has been indexed.
|
|
4742
|
+
*/
|
|
4743
|
+
documentKey,
|
|
4744
|
+
/*
|
|
4745
|
+
* The largest mutation batch id that's been processed by Firestore.
|
|
4746
|
+
*/
|
|
4747
|
+
largestBatchId) {
|
|
4748
|
+
this.readTime = readTime;
|
|
4749
|
+
this.documentKey = documentKey;
|
|
4750
|
+
this.largestBatchId = largestBatchId;
|
|
4751
|
+
}
|
|
4752
|
+
/** The state of an index that has not yet been backfilled. */
|
|
4753
|
+
static min() {
|
|
4754
|
+
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
/**
|
|
4759
|
+
* @license
|
|
4760
|
+
* Copyright 2019 Google LLC
|
|
4761
|
+
*
|
|
4762
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4763
|
+
* you may not use this file except in compliance with the License.
|
|
4764
|
+
* You may obtain a copy of the License at
|
|
4765
|
+
*
|
|
4766
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
4767
|
+
*
|
|
4768
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
4769
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
4770
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4771
|
+
* See the License for the specific language governing permissions and
|
|
4772
|
+
* limitations under the License.
|
|
4773
|
+
*/
|
|
4774
|
+
// Visible for testing
|
|
4775
|
+
class TargetImpl {
|
|
4776
|
+
constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
4243
4777
|
this.path = path;
|
|
4244
4778
|
this.collectionGroup = collectionGroup;
|
|
4245
4779
|
this.orderBy = orderBy;
|
|
@@ -4264,27 +4798,29 @@ function newTarget(path, collectionGroup = null, orderBy = [], filters = [], lim
|
|
|
4264
4798
|
function canonifyTarget(target) {
|
|
4265
4799
|
const targetImpl = debugCast(target);
|
|
4266
4800
|
if (targetImpl.memoizedCanonicalId === null) {
|
|
4267
|
-
let
|
|
4801
|
+
let str = targetImpl.path.canonicalString();
|
|
4268
4802
|
if (targetImpl.collectionGroup !== null) {
|
|
4269
|
-
|
|
4803
|
+
str += '|cg:' + targetImpl.collectionGroup;
|
|
4270
4804
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4805
|
+
str += '|f:';
|
|
4806
|
+
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4807
|
+
str += '|ob:';
|
|
4808
|
+
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4275
4809
|
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4276
|
-
|
|
4277
|
-
|
|
4810
|
+
str += '|l:';
|
|
4811
|
+
str += targetImpl.limit;
|
|
4278
4812
|
}
|
|
4279
4813
|
if (targetImpl.startAt) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4814
|
+
str += '|lb:';
|
|
4815
|
+
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4816
|
+
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4282
4817
|
}
|
|
4283
4818
|
if (targetImpl.endAt) {
|
|
4284
|
-
|
|
4285
|
-
|
|
4819
|
+
str += '|ub:';
|
|
4820
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4821
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4286
4822
|
}
|
|
4287
|
-
targetImpl.memoizedCanonicalId =
|
|
4823
|
+
targetImpl.memoizedCanonicalId = str;
|
|
4288
4824
|
}
|
|
4289
4825
|
return targetImpl.memoizedCanonicalId;
|
|
4290
4826
|
}
|
|
@@ -4307,10 +4843,14 @@ function stringifyTarget(target) {
|
|
|
4307
4843
|
.join(', ')}]`;
|
|
4308
4844
|
}
|
|
4309
4845
|
if (target.startAt) {
|
|
4310
|
-
str += ', startAt: '
|
|
4846
|
+
str += ', startAt: ';
|
|
4847
|
+
str += target.startAt.inclusive ? 'b:' : 'a:';
|
|
4848
|
+
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4311
4849
|
}
|
|
4312
4850
|
if (target.endAt) {
|
|
4313
|
-
str += ', endAt: '
|
|
4851
|
+
str += ', endAt: ';
|
|
4852
|
+
str += target.endAt.inclusive ? 'a:' : 'b:';
|
|
4853
|
+
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4314
4854
|
}
|
|
4315
4855
|
return `Target(${str})`;
|
|
4316
4856
|
}
|
|
@@ -4345,11 +4885,196 @@ function targetEquals(left, right) {
|
|
|
4345
4885
|
}
|
|
4346
4886
|
return boundEquals(left.endAt, right.endAt);
|
|
4347
4887
|
}
|
|
4348
|
-
function
|
|
4888
|
+
function targetIsDocumentTarget(target) {
|
|
4349
4889
|
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4350
4890
|
target.collectionGroup === null &&
|
|
4351
4891
|
target.filters.length === 0);
|
|
4352
4892
|
}
|
|
4893
|
+
/** Returns the field filters that target the given field path. */
|
|
4894
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
4895
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4896
|
+
}
|
|
4897
|
+
/**
|
|
4898
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4899
|
+
* filters. Returns `null` if there are no such filters.
|
|
4900
|
+
*/
|
|
4901
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
4902
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4903
|
+
if (segment === undefined) {
|
|
4904
|
+
return null;
|
|
4905
|
+
}
|
|
4906
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4907
|
+
switch (fieldFilter.op) {
|
|
4908
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4909
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
4910
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4911
|
+
return [fieldFilter.value];
|
|
4912
|
+
// Remaining filters are not array filters.
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
4919
|
+
* `null` if there are no such filters.
|
|
4920
|
+
*/
|
|
4921
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
4922
|
+
const values = new Map();
|
|
4923
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4924
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4925
|
+
switch (fieldFilter.op) {
|
|
4926
|
+
case "==" /* EQUAL */:
|
|
4927
|
+
case "in" /* IN */:
|
|
4928
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
4929
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4930
|
+
// `value != 'ab'`).
|
|
4931
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4932
|
+
break;
|
|
4933
|
+
case "not-in" /* NOT_IN */:
|
|
4934
|
+
case "!=" /* NOT_EQUAL */:
|
|
4935
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4936
|
+
// segments and hence we can return early here.
|
|
4937
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4938
|
+
return Array.from(values.values());
|
|
4939
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
return null;
|
|
4944
|
+
}
|
|
4945
|
+
/**
|
|
4946
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
4947
|
+
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
|
|
4948
|
+
* exists.
|
|
4949
|
+
*/
|
|
4950
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
4951
|
+
const values = [];
|
|
4952
|
+
let inclusive = true;
|
|
4953
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4954
|
+
// startAt.
|
|
4955
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4956
|
+
let segmentValue = undefined;
|
|
4957
|
+
let segmentInclusive = true;
|
|
4958
|
+
// Process all filters to find a value for the current field segment
|
|
4959
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4960
|
+
let filterValue = undefined;
|
|
4961
|
+
let filterInclusive = true;
|
|
4962
|
+
switch (fieldFilter.op) {
|
|
4963
|
+
case "<" /* LESS_THAN */:
|
|
4964
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4965
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4966
|
+
break;
|
|
4967
|
+
case "==" /* EQUAL */:
|
|
4968
|
+
case "in" /* IN */:
|
|
4969
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4970
|
+
filterValue = fieldFilter.value;
|
|
4971
|
+
break;
|
|
4972
|
+
case ">" /* GREATER_THAN */:
|
|
4973
|
+
filterValue = fieldFilter.value;
|
|
4974
|
+
filterInclusive = false;
|
|
4975
|
+
break;
|
|
4976
|
+
case "!=" /* NOT_EQUAL */:
|
|
4977
|
+
case "not-in" /* NOT_IN */:
|
|
4978
|
+
filterValue = MIN_VALUE;
|
|
4979
|
+
break;
|
|
4980
|
+
// Remaining filters cannot be used as lower bounds.
|
|
4981
|
+
}
|
|
4982
|
+
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4983
|
+
segmentValue = filterValue;
|
|
4984
|
+
segmentInclusive = filterInclusive;
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
// If there is a startAt bound, compare the values against the existing
|
|
4988
|
+
// boundary to see if we can narrow the scope.
|
|
4989
|
+
if (target.startAt !== null) {
|
|
4990
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4991
|
+
const orderBy = target.orderBy[i];
|
|
4992
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4993
|
+
const cursorValue = target.startAt.position[i];
|
|
4994
|
+
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4995
|
+
segmentValue = cursorValue;
|
|
4996
|
+
segmentInclusive = target.startAt.inclusive;
|
|
4997
|
+
}
|
|
4998
|
+
break;
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
if (segmentValue === undefined) {
|
|
5003
|
+
// No lower bound exists
|
|
5004
|
+
return null;
|
|
5005
|
+
}
|
|
5006
|
+
values.push(segmentValue);
|
|
5007
|
+
inclusive && (inclusive = segmentInclusive);
|
|
5008
|
+
}
|
|
5009
|
+
return new Bound(values, inclusive);
|
|
5010
|
+
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
5013
|
+
* when scanning the index defined by `fieldIndex`. Returns `null` if no
|
|
5014
|
+
* upper bound exists.
|
|
5015
|
+
*/
|
|
5016
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
5017
|
+
const values = [];
|
|
5018
|
+
let inclusive = true;
|
|
5019
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
5020
|
+
// endAt.
|
|
5021
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5022
|
+
let segmentValue = undefined;
|
|
5023
|
+
let segmentInclusive = true;
|
|
5024
|
+
// Process all filters to find a value for the current field segment
|
|
5025
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5026
|
+
let filterValue = undefined;
|
|
5027
|
+
let filterInclusive = true;
|
|
5028
|
+
switch (fieldFilter.op) {
|
|
5029
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5030
|
+
case ">" /* GREATER_THAN */:
|
|
5031
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
5032
|
+
filterInclusive = false;
|
|
5033
|
+
break;
|
|
5034
|
+
case "==" /* EQUAL */:
|
|
5035
|
+
case "in" /* IN */:
|
|
5036
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5037
|
+
filterValue = fieldFilter.value;
|
|
5038
|
+
break;
|
|
5039
|
+
case "<" /* LESS_THAN */:
|
|
5040
|
+
filterValue = fieldFilter.value;
|
|
5041
|
+
filterInclusive = false;
|
|
5042
|
+
break;
|
|
5043
|
+
case "!=" /* NOT_EQUAL */:
|
|
5044
|
+
case "not-in" /* NOT_IN */:
|
|
5045
|
+
filterValue = MAX_VALUE;
|
|
5046
|
+
break;
|
|
5047
|
+
// Remaining filters cannot be used as upper bounds.
|
|
5048
|
+
}
|
|
5049
|
+
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
5050
|
+
segmentValue = filterValue;
|
|
5051
|
+
segmentInclusive = filterInclusive;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
// If there is a endAt bound, compare the values against the existing
|
|
5055
|
+
// boundary to see if we can narrow the scope.
|
|
5056
|
+
if (target.endAt !== null) {
|
|
5057
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5058
|
+
const orderBy = target.orderBy[i];
|
|
5059
|
+
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
5060
|
+
const cursorValue = target.endAt.position[i];
|
|
5061
|
+
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
5062
|
+
segmentValue = cursorValue;
|
|
5063
|
+
segmentInclusive = target.endAt.inclusive;
|
|
5064
|
+
}
|
|
5065
|
+
break;
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
if (segmentValue === undefined) {
|
|
5070
|
+
// No upper bound exists
|
|
5071
|
+
return null;
|
|
5072
|
+
}
|
|
5073
|
+
values.push(segmentValue);
|
|
5074
|
+
inclusive && (inclusive = segmentInclusive);
|
|
5075
|
+
}
|
|
5076
|
+
return new Bound(values, inclusive);
|
|
5077
|
+
}
|
|
4353
5078
|
class Filter {
|
|
4354
5079
|
}
|
|
4355
5080
|
class FieldFilter extends Filter {
|
|
@@ -4548,17 +5273,11 @@ class ArrayContainsAnyFilter extends FieldFilter {
|
|
|
4548
5273
|
* just after the provided values.
|
|
4549
5274
|
*/
|
|
4550
5275
|
class Bound {
|
|
4551
|
-
constructor(position,
|
|
5276
|
+
constructor(position, inclusive) {
|
|
4552
5277
|
this.position = position;
|
|
4553
|
-
this.
|
|
5278
|
+
this.inclusive = inclusive;
|
|
4554
5279
|
}
|
|
4555
5280
|
}
|
|
4556
|
-
function canonifyBound(bound) {
|
|
4557
|
-
// TODO(b/29183165): Make this collision robust.
|
|
4558
|
-
return `${bound.before ? 'b' : 'a'}:${bound.position
|
|
4559
|
-
.map(p => canonicalId(p))
|
|
4560
|
-
.join(',')}`;
|
|
4561
|
-
}
|
|
4562
5281
|
/**
|
|
4563
5282
|
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
4564
5283
|
*/
|
|
@@ -4578,11 +5297,7 @@ function stringifyOrderBy(orderBy) {
|
|
|
4578
5297
|
function orderByEquals(left, right) {
|
|
4579
5298
|
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4580
5299
|
}
|
|
4581
|
-
|
|
4582
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4583
|
-
* order.
|
|
4584
|
-
*/
|
|
4585
|
-
function sortsBeforeDocument(bound, orderBy, doc) {
|
|
5300
|
+
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4586
5301
|
let comparison = 0;
|
|
4587
5302
|
for (let i = 0; i < bound.position.length; i++) {
|
|
4588
5303
|
const orderByComponent = orderBy[i];
|
|
@@ -4601,7 +5316,23 @@ function sortsBeforeDocument(bound, orderBy, doc) {
|
|
|
4601
5316
|
break;
|
|
4602
5317
|
}
|
|
4603
5318
|
}
|
|
4604
|
-
return
|
|
5319
|
+
return comparison;
|
|
5320
|
+
}
|
|
5321
|
+
/**
|
|
5322
|
+
* Returns true if a document sorts after a bound using the provided sort
|
|
5323
|
+
* order.
|
|
5324
|
+
*/
|
|
5325
|
+
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
5326
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5327
|
+
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
5328
|
+
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Returns true if a document sorts before a bound using the provided sort
|
|
5331
|
+
* order.
|
|
5332
|
+
*/
|
|
5333
|
+
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
5334
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5335
|
+
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4605
5336
|
}
|
|
4606
5337
|
function boundEquals(left, right) {
|
|
4607
5338
|
if (left === null) {
|
|
@@ -4610,7 +5341,7 @@ function boundEquals(left, right) {
|
|
|
4610
5341
|
else if (right === null) {
|
|
4611
5342
|
return false;
|
|
4612
5343
|
}
|
|
4613
|
-
if (left.
|
|
5344
|
+
if (left.inclusive !== right.inclusive ||
|
|
4614
5345
|
left.position.length !== right.position.length) {
|
|
4615
5346
|
return false;
|
|
4616
5347
|
}
|
|
@@ -4814,10 +5545,10 @@ function queryToTarget(query) {
|
|
|
4814
5545
|
}
|
|
4815
5546
|
// We need to swap the cursors to match the now-flipped query ordering.
|
|
4816
5547
|
const startAt = queryImpl.endAt
|
|
4817
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.
|
|
5548
|
+
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
4818
5549
|
: null;
|
|
4819
5550
|
const endAt = queryImpl.startAt
|
|
4820
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.
|
|
5551
|
+
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
4821
5552
|
: null;
|
|
4822
5553
|
// Now return as a LimitType.First query.
|
|
4823
5554
|
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
@@ -4905,11 +5636,11 @@ function queryMatchesFilters(query, doc) {
|
|
|
4905
5636
|
/** Makes sure a document is within the bounds, if provided. */
|
|
4906
5637
|
function queryMatchesBounds(query, doc) {
|
|
4907
5638
|
if (query.startAt &&
|
|
4908
|
-
!
|
|
5639
|
+
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
4909
5640
|
return false;
|
|
4910
5641
|
}
|
|
4911
5642
|
if (query.endAt &&
|
|
4912
|
-
|
|
5643
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
4913
5644
|
return false;
|
|
4914
5645
|
}
|
|
4915
5646
|
return true;
|
|
@@ -5087,7 +5818,7 @@ class SortedMapIterator {
|
|
|
5087
5818
|
while (!node.isEmpty()) {
|
|
5088
5819
|
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5089
5820
|
// flip the comparison if we're going in reverse
|
|
5090
|
-
if (isReverse) {
|
|
5821
|
+
if (startKey && isReverse) {
|
|
5091
5822
|
cmp *= -1;
|
|
5092
5823
|
}
|
|
5093
5824
|
if (cmp < 0) {
|
|
@@ -5564,6 +6295,72 @@ class SortedSetIterator {
|
|
|
5564
6295
|
hasNext() {
|
|
5565
6296
|
return this.iter.hasNext();
|
|
5566
6297
|
}
|
|
6298
|
+
}
|
|
6299
|
+
/**
|
|
6300
|
+
* Compares two sorted sets for equality using their natural ordering. The
|
|
6301
|
+
* method computes the intersection and invokes `onAdd` for every element that
|
|
6302
|
+
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
6303
|
+
* `before` but missing from `after`.
|
|
6304
|
+
*
|
|
6305
|
+
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
6306
|
+
* n), where n is the size of the two lists.
|
|
6307
|
+
*
|
|
6308
|
+
* @param before - The elements that exist in the original set.
|
|
6309
|
+
* @param after - The elements to diff against the original set.
|
|
6310
|
+
* @param comparator - The comparator for the elements in before and after.
|
|
6311
|
+
* @param onAdd - A function to invoke for every element that is part of `
|
|
6312
|
+
* after` but not `before`.
|
|
6313
|
+
* @param onRemove - A function to invoke for every element that is part of
|
|
6314
|
+
* `before` but not `after`.
|
|
6315
|
+
*/
|
|
6316
|
+
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
6317
|
+
const beforeIt = before.getIterator();
|
|
6318
|
+
const afterIt = after.getIterator();
|
|
6319
|
+
let beforeValue = advanceIterator(beforeIt);
|
|
6320
|
+
let afterValue = advanceIterator(afterIt);
|
|
6321
|
+
// Walk through the two sets at the same time, using the ordering defined by
|
|
6322
|
+
// `comparator`.
|
|
6323
|
+
while (beforeValue || afterValue) {
|
|
6324
|
+
let added = false;
|
|
6325
|
+
let removed = false;
|
|
6326
|
+
if (beforeValue && afterValue) {
|
|
6327
|
+
const cmp = comparator(beforeValue, afterValue);
|
|
6328
|
+
if (cmp < 0) {
|
|
6329
|
+
// The element was removed if the next element in our ordered
|
|
6330
|
+
// walkthrough is only in `before`.
|
|
6331
|
+
removed = true;
|
|
6332
|
+
}
|
|
6333
|
+
else if (cmp > 0) {
|
|
6334
|
+
// The element was added if the next element in our ordered walkthrough
|
|
6335
|
+
// is only in `after`.
|
|
6336
|
+
added = true;
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
else if (beforeValue != null) {
|
|
6340
|
+
removed = true;
|
|
6341
|
+
}
|
|
6342
|
+
else {
|
|
6343
|
+
added = true;
|
|
6344
|
+
}
|
|
6345
|
+
if (added) {
|
|
6346
|
+
onAdd(afterValue);
|
|
6347
|
+
afterValue = advanceIterator(afterIt);
|
|
6348
|
+
}
|
|
6349
|
+
else if (removed) {
|
|
6350
|
+
onRemove(beforeValue);
|
|
6351
|
+
beforeValue = advanceIterator(beforeIt);
|
|
6352
|
+
}
|
|
6353
|
+
else {
|
|
6354
|
+
beforeValue = advanceIterator(beforeIt);
|
|
6355
|
+
afterValue = advanceIterator(afterIt);
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
/**
|
|
6360
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
6361
|
+
*/
|
|
6362
|
+
function advanceIterator(it) {
|
|
6363
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5567
6364
|
}
|
|
5568
6365
|
|
|
5569
6366
|
/**
|
|
@@ -6402,6 +7199,47 @@ class MutationBatchResult {
|
|
|
6402
7199
|
}
|
|
6403
7200
|
}
|
|
6404
7201
|
|
|
7202
|
+
/**
|
|
7203
|
+
* @license
|
|
7204
|
+
* Copyright 2022 Google LLC
|
|
7205
|
+
*
|
|
7206
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7207
|
+
* you may not use this file except in compliance with the License.
|
|
7208
|
+
* You may obtain a copy of the License at
|
|
7209
|
+
*
|
|
7210
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
7211
|
+
*
|
|
7212
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7213
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
7214
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
7215
|
+
* See the License for the specific language governing permissions and
|
|
7216
|
+
* limitations under the License.
|
|
7217
|
+
*/
|
|
7218
|
+
/**
|
|
7219
|
+
* Representation of an overlay computed by Firestore.
|
|
7220
|
+
*
|
|
7221
|
+
* Holds information about a mutation and the largest batch id in Firestore when
|
|
7222
|
+
* the mutation was created.
|
|
7223
|
+
*/
|
|
7224
|
+
class Overlay {
|
|
7225
|
+
constructor(largestBatchId, mutation) {
|
|
7226
|
+
this.largestBatchId = largestBatchId;
|
|
7227
|
+
this.mutation = mutation;
|
|
7228
|
+
}
|
|
7229
|
+
getKey() {
|
|
7230
|
+
return this.mutation.key;
|
|
7231
|
+
}
|
|
7232
|
+
isEqual(other) {
|
|
7233
|
+
return other !== null && this.mutation === other.mutation;
|
|
7234
|
+
}
|
|
7235
|
+
toString() {
|
|
7236
|
+
return `Overlay{
|
|
7237
|
+
largestBatchId: ${this.largestBatchId},
|
|
7238
|
+
mutation: ${this.mutation.toString()}
|
|
7239
|
+
}`;
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
|
|
6405
7243
|
/**
|
|
6406
7244
|
* @license
|
|
6407
7245
|
* Copyright 2017 Google LLC
|
|
@@ -6874,7 +7712,7 @@ class TargetState {
|
|
|
6874
7712
|
this._current = true;
|
|
6875
7713
|
}
|
|
6876
7714
|
}
|
|
6877
|
-
const LOG_TAG$
|
|
7715
|
+
const LOG_TAG$g = 'WatchChangeAggregator';
|
|
6878
7716
|
/**
|
|
6879
7717
|
* A helper class to accumulate watch changes into a RemoteEvent.
|
|
6880
7718
|
*/
|
|
@@ -6990,7 +7828,7 @@ class WatchChangeAggregator {
|
|
|
6990
7828
|
const targetData = this.targetDataForActiveTarget(targetId);
|
|
6991
7829
|
if (targetData) {
|
|
6992
7830
|
const target = targetData.target;
|
|
6993
|
-
if (
|
|
7831
|
+
if (targetIsDocumentTarget(target)) {
|
|
6994
7832
|
if (expectedCount === 0) {
|
|
6995
7833
|
// The existence filter told us the document does not exist. We deduce
|
|
6996
7834
|
// that this document does not exist and apply a deleted document to
|
|
@@ -7025,7 +7863,7 @@ class WatchChangeAggregator {
|
|
|
7025
7863
|
this.targetStates.forEach((targetState, targetId) => {
|
|
7026
7864
|
const targetData = this.targetDataForActiveTarget(targetId);
|
|
7027
7865
|
if (targetData) {
|
|
7028
|
-
if (targetState.current &&
|
|
7866
|
+
if (targetState.current && targetIsDocumentTarget(targetData.target)) {
|
|
7029
7867
|
// Document queries for document that don't exist can produce an empty
|
|
7030
7868
|
// result set. To update our local cache, we synthesize a document
|
|
7031
7869
|
// delete if we have not previously received the document. This
|
|
@@ -7068,6 +7906,7 @@ class WatchChangeAggregator {
|
|
|
7068
7906
|
resolvedLimboDocuments = resolvedLimboDocuments.add(key);
|
|
7069
7907
|
}
|
|
7070
7908
|
});
|
|
7909
|
+
this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
|
|
7071
7910
|
const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
|
|
7072
7911
|
this.pendingDocumentUpdates = mutableDocumentMap();
|
|
7073
7912
|
this.pendingDocumentTargetMapping = documentTargetMap();
|
|
@@ -7168,7 +8007,7 @@ class WatchChangeAggregator {
|
|
|
7168
8007
|
isActiveTarget(targetId) {
|
|
7169
8008
|
const targetActive = this.targetDataForActiveTarget(targetId) !== null;
|
|
7170
8009
|
if (!targetActive) {
|
|
7171
|
-
logDebug(LOG_TAG$
|
|
8010
|
+
logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
|
|
7172
8011
|
}
|
|
7173
8012
|
return targetActive;
|
|
7174
8013
|
}
|
|
@@ -7787,10 +8626,10 @@ function toQueryTarget(serializer, target) {
|
|
|
7787
8626
|
result.structuredQuery.limit = limit;
|
|
7788
8627
|
}
|
|
7789
8628
|
if (target.startAt) {
|
|
7790
|
-
result.structuredQuery.startAt =
|
|
8629
|
+
result.structuredQuery.startAt = toStartAtCursor(target.startAt);
|
|
7791
8630
|
}
|
|
7792
8631
|
if (target.endAt) {
|
|
7793
|
-
result.structuredQuery.endAt =
|
|
8632
|
+
result.structuredQuery.endAt = toEndAtCursor(target.endAt);
|
|
7794
8633
|
}
|
|
7795
8634
|
return result;
|
|
7796
8635
|
}
|
|
@@ -7823,11 +8662,11 @@ function convertQueryTargetToQuery(target) {
|
|
|
7823
8662
|
}
|
|
7824
8663
|
let startAt = null;
|
|
7825
8664
|
if (query.startAt) {
|
|
7826
|
-
startAt =
|
|
8665
|
+
startAt = fromStartAtCursor(query.startAt);
|
|
7827
8666
|
}
|
|
7828
8667
|
let endAt = null;
|
|
7829
8668
|
if (query.endAt) {
|
|
7830
|
-
endAt =
|
|
8669
|
+
endAt = fromEndAtCursor(query.endAt);
|
|
7831
8670
|
}
|
|
7832
8671
|
return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* First */, startAt, endAt);
|
|
7833
8672
|
}
|
|
@@ -7860,7 +8699,7 @@ function toLabel(serializer, purpose) {
|
|
|
7860
8699
|
function toTarget(serializer, targetData) {
|
|
7861
8700
|
let result;
|
|
7862
8701
|
const target = targetData.target;
|
|
7863
|
-
if (
|
|
8702
|
+
if (targetIsDocumentTarget(target)) {
|
|
7864
8703
|
result = { documents: toDocumentsTarget(serializer, target) };
|
|
7865
8704
|
}
|
|
7866
8705
|
else {
|
|
@@ -7918,16 +8757,27 @@ function toOrder(orderBys) {
|
|
|
7918
8757
|
function fromOrder(orderBys) {
|
|
7919
8758
|
return orderBys.map(order => fromPropertyOrder(order));
|
|
7920
8759
|
}
|
|
7921
|
-
function
|
|
8760
|
+
function toStartAtCursor(cursor) {
|
|
8761
|
+
return {
|
|
8762
|
+
before: cursor.inclusive,
|
|
8763
|
+
values: cursor.position
|
|
8764
|
+
};
|
|
8765
|
+
}
|
|
8766
|
+
function toEndAtCursor(cursor) {
|
|
7922
8767
|
return {
|
|
7923
|
-
before: cursor.
|
|
8768
|
+
before: !cursor.inclusive,
|
|
7924
8769
|
values: cursor.position
|
|
7925
8770
|
};
|
|
7926
8771
|
}
|
|
7927
|
-
function
|
|
7928
|
-
const
|
|
8772
|
+
function fromStartAtCursor(cursor) {
|
|
8773
|
+
const inclusive = !!cursor.before;
|
|
8774
|
+
const position = cursor.values || [];
|
|
8775
|
+
return new Bound(position, inclusive);
|
|
8776
|
+
}
|
|
8777
|
+
function fromEndAtCursor(cursor) {
|
|
8778
|
+
const inclusive = !cursor.before;
|
|
7929
8779
|
const position = cursor.values || [];
|
|
7930
|
-
return new Bound(position,
|
|
8780
|
+
return new Bound(position, inclusive);
|
|
7931
8781
|
}
|
|
7932
8782
|
// visible for testing
|
|
7933
8783
|
function toDirection(dir) {
|
|
@@ -8188,29 +9038,34 @@ class LocalSerializer {
|
|
|
8188
9038
|
}
|
|
8189
9039
|
/** Decodes a remote document from storage locally to a Document. */
|
|
8190
9040
|
function fromDbRemoteDocument(localSerializer, remoteDoc) {
|
|
9041
|
+
let doc;
|
|
8191
9042
|
if (remoteDoc.document) {
|
|
8192
|
-
|
|
9043
|
+
doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
|
|
8193
9044
|
}
|
|
8194
9045
|
else if (remoteDoc.noDocument) {
|
|
8195
9046
|
const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
|
|
8196
9047
|
const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
9048
|
+
doc = MutableDocument.newNoDocument(key, version);
|
|
9049
|
+
if (remoteDoc.hasCommittedMutations) {
|
|
9050
|
+
doc.setHasCommittedMutations();
|
|
9051
|
+
}
|
|
8201
9052
|
}
|
|
8202
9053
|
else if (remoteDoc.unknownDocument) {
|
|
8203
9054
|
const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
|
|
8204
9055
|
const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
|
|
8205
|
-
|
|
9056
|
+
doc = MutableDocument.newUnknownDocument(key, version);
|
|
8206
9057
|
}
|
|
8207
9058
|
else {
|
|
8208
9059
|
return fail();
|
|
8209
9060
|
}
|
|
9061
|
+
if (remoteDoc.readTime) {
|
|
9062
|
+
doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
|
|
9063
|
+
}
|
|
9064
|
+
return doc;
|
|
8210
9065
|
}
|
|
8211
9066
|
/** Encodes a document for storage locally. */
|
|
8212
|
-
function toDbRemoteDocument(localSerializer, document
|
|
8213
|
-
const dbReadTime = toDbTimestampKey(readTime);
|
|
9067
|
+
function toDbRemoteDocument(localSerializer, document) {
|
|
9068
|
+
const dbReadTime = toDbTimestampKey(document.readTime);
|
|
8214
9069
|
const parentPath = document.key.path.popLast().toArray();
|
|
8215
9070
|
if (document.isFoundDocument()) {
|
|
8216
9071
|
const doc = toDocument(localSerializer.remoteSerializer, document);
|
|
@@ -8221,10 +9076,10 @@ function toDbRemoteDocument(localSerializer, document, readTime) {
|
|
|
8221
9076
|
}
|
|
8222
9077
|
else if (document.isNoDocument()) {
|
|
8223
9078
|
const path = document.key.path.toArray();
|
|
8224
|
-
const
|
|
9079
|
+
const version = toDbTimestamp(document.version);
|
|
8225
9080
|
const hasCommittedMutations = document.hasCommittedMutations;
|
|
8226
9081
|
return new DbRemoteDocument(
|
|
8227
|
-
/* unknownDocument= */ null, new DbNoDocument(path,
|
|
9082
|
+
/* unknownDocument= */ null, new DbNoDocument(path, version),
|
|
8228
9083
|
/* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
|
|
8229
9084
|
}
|
|
8230
9085
|
else if (document.isUnknownDocument()) {
|
|
@@ -8305,7 +9160,7 @@ function toDbTarget(localSerializer, targetData) {
|
|
|
8305
9160
|
const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
|
|
8306
9161
|
const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
|
|
8307
9162
|
let queryProto;
|
|
8308
|
-
if (
|
|
9163
|
+
if (targetIsDocumentTarget(targetData.target)) {
|
|
8309
9164
|
queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
|
|
8310
9165
|
}
|
|
8311
9166
|
else {
|
|
@@ -8386,6 +9241,37 @@ function fromBundleMetadata(metadata) {
|
|
|
8386
9241
|
version: metadata.version,
|
|
8387
9242
|
createTime: fromVersion(metadata.createTime)
|
|
8388
9243
|
};
|
|
9244
|
+
}
|
|
9245
|
+
/** Encodes a DbDocumentOverlay object to an Overlay model object. */
|
|
9246
|
+
function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
|
|
9247
|
+
return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
|
|
9248
|
+
}
|
|
9249
|
+
/** Decodes an Overlay model object into a DbDocumentOverlay object. */
|
|
9250
|
+
function toDbDocumentOverlay(localSerializer, userId, overlay) {
|
|
9251
|
+
const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
|
|
9252
|
+
return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
|
|
9253
|
+
}
|
|
9254
|
+
/**
|
|
9255
|
+
* Returns the DbDocumentOverlayKey corresponding to the given user and
|
|
9256
|
+
* document key.
|
|
9257
|
+
*/
|
|
9258
|
+
function toDbDocumentOverlayKey(userId, docKey) {
|
|
9259
|
+
const docId = docKey.path.lastSegment();
|
|
9260
|
+
const collectionPath = encodeResourcePath(docKey.path.popLast());
|
|
9261
|
+
return [userId, collectionPath, docId];
|
|
9262
|
+
}
|
|
9263
|
+
function toDbIndexConfiguration(index) {
|
|
9264
|
+
return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
|
|
9265
|
+
}
|
|
9266
|
+
function fromDbIndexConfiguration(index, state) {
|
|
9267
|
+
const decodedState = state
|
|
9268
|
+
? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
|
|
9269
|
+
: IndexState.empty();
|
|
9270
|
+
const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
|
|
9271
|
+
return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
|
|
9272
|
+
}
|
|
9273
|
+
function toDbIndexState(indexId, user, sequenceNumber, offset) {
|
|
9274
|
+
return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
|
|
8389
9275
|
}
|
|
8390
9276
|
|
|
8391
9277
|
/**
|
|
@@ -8447,7 +9333,7 @@ function namedQueriesStore(txn) {
|
|
|
8447
9333
|
|
|
8448
9334
|
/**
|
|
8449
9335
|
* @license
|
|
8450
|
-
* Copyright
|
|
9336
|
+
* Copyright 2022 Google LLC
|
|
8451
9337
|
*
|
|
8452
9338
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8453
9339
|
* you may not use this file except in compliance with the License.
|
|
@@ -8462,53 +9348,1057 @@ function namedQueriesStore(txn) {
|
|
|
8462
9348
|
* limitations under the License.
|
|
8463
9349
|
*/
|
|
8464
9350
|
/**
|
|
8465
|
-
*
|
|
9351
|
+
* Implementation of DocumentOverlayCache using IndexedDb.
|
|
8466
9352
|
*/
|
|
8467
|
-
class
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
9353
|
+
class IndexedDbDocumentOverlayCache {
|
|
9354
|
+
/**
|
|
9355
|
+
* @param serializer - The document serializer.
|
|
9356
|
+
* @param userId - The userId for which we are accessing overlays.
|
|
9357
|
+
*/
|
|
9358
|
+
constructor(serializer, userId) {
|
|
9359
|
+
this.serializer = serializer;
|
|
9360
|
+
this.userId = userId;
|
|
8474
9361
|
}
|
|
8475
|
-
|
|
8476
|
-
|
|
9362
|
+
static forUser(serializer, user) {
|
|
9363
|
+
const userId = user.uid || '';
|
|
9364
|
+
return new IndexedDbDocumentOverlayCache(serializer, userId);
|
|
8477
9365
|
}
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
9366
|
+
getOverlay(transaction, key) {
|
|
9367
|
+
return documentOverlayStore(transaction)
|
|
9368
|
+
.get(toDbDocumentOverlayKey(this.userId, key))
|
|
9369
|
+
.next(dbOverlay => {
|
|
9370
|
+
if (dbOverlay) {
|
|
9371
|
+
return fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9372
|
+
}
|
|
9373
|
+
return null;
|
|
9374
|
+
});
|
|
8487
9375
|
}
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
this.index[collectionId] = existingParents.add(parentPath);
|
|
8496
|
-
return added;
|
|
9376
|
+
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9377
|
+
const promises = [];
|
|
9378
|
+
overlays.forEach(mutation => {
|
|
9379
|
+
const overlay = new Overlay(largestBatchId, mutation);
|
|
9380
|
+
promises.push(this.saveOverlay(transaction, overlay));
|
|
9381
|
+
});
|
|
9382
|
+
return PersistencePromise.waitFor(promises);
|
|
8497
9383
|
}
|
|
8498
|
-
|
|
8499
|
-
const
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
9384
|
+
removeOverlaysForBatchId(transaction, documentKeys, batchId) {
|
|
9385
|
+
const collectionPaths = new Set();
|
|
9386
|
+
// Get the set of unique collection paths.
|
|
9387
|
+
documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
|
|
9388
|
+
const promises = [];
|
|
9389
|
+
collectionPaths.forEach(collectionPath => {
|
|
9390
|
+
const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
|
|
9391
|
+
/*lowerOpen=*/ false,
|
|
9392
|
+
/*upperOpen=*/ true);
|
|
9393
|
+
promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
|
|
9394
|
+
});
|
|
9395
|
+
return PersistencePromise.waitFor(promises);
|
|
8503
9396
|
}
|
|
8504
|
-
|
|
8505
|
-
const
|
|
8506
|
-
|
|
8507
|
-
|
|
9397
|
+
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
9398
|
+
const result = new Map();
|
|
9399
|
+
const collectionPath = encodeResourcePath(collection);
|
|
9400
|
+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9401
|
+
// is not inclusive.
|
|
9402
|
+
const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
|
|
9403
|
+
/*lowerOpen=*/ true);
|
|
9404
|
+
return documentOverlayStore(transaction)
|
|
9405
|
+
.loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
|
|
9406
|
+
.next(dbOverlays => {
|
|
9407
|
+
for (const dbOverlay of dbOverlays) {
|
|
9408
|
+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9409
|
+
result.set(overlay.getKey(), overlay);
|
|
9410
|
+
}
|
|
9411
|
+
return result;
|
|
9412
|
+
});
|
|
8508
9413
|
}
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
9414
|
+
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
9415
|
+
const result = new Map();
|
|
9416
|
+
let currentBatchId = undefined;
|
|
9417
|
+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
|
|
9418
|
+
// is not inclusive.
|
|
9419
|
+
const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
|
|
9420
|
+
/*lowerOpen=*/ true);
|
|
9421
|
+
return documentOverlayStore(transaction)
|
|
9422
|
+
.iterate({
|
|
9423
|
+
index: DbDocumentOverlay.collectionGroupOverlayIndex,
|
|
9424
|
+
range
|
|
9425
|
+
}, (_, dbOverlay, control) => {
|
|
9426
|
+
// We do not want to return partial batch overlays, even if the size
|
|
9427
|
+
// of the result set exceeds the given `count` argument. Therefore, we
|
|
9428
|
+
// continue to aggregate results even after the result size exceeds
|
|
9429
|
+
// `count` if there are more overlays from the `currentBatchId`.
|
|
9430
|
+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
|
|
9431
|
+
if (result.size < count ||
|
|
9432
|
+
overlay.largestBatchId === currentBatchId) {
|
|
9433
|
+
result.set(overlay.getKey(), overlay);
|
|
9434
|
+
currentBatchId = overlay.largestBatchId;
|
|
9435
|
+
}
|
|
9436
|
+
else {
|
|
9437
|
+
control.done();
|
|
9438
|
+
}
|
|
9439
|
+
})
|
|
9440
|
+
.next(() => result);
|
|
9441
|
+
}
|
|
9442
|
+
saveOverlay(transaction, overlay) {
|
|
9443
|
+
return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
/**
|
|
9447
|
+
* Helper to get a typed SimpleDbStore for the document overlay object store.
|
|
9448
|
+
*/
|
|
9449
|
+
function documentOverlayStore(txn) {
|
|
9450
|
+
return getStore(txn, DbDocumentOverlay.store);
|
|
9451
|
+
}
|
|
9452
|
+
|
|
9453
|
+
/**
|
|
9454
|
+
* @license
|
|
9455
|
+
* Copyright 2021 Google LLC
|
|
9456
|
+
*
|
|
9457
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9458
|
+
* you may not use this file except in compliance with the License.
|
|
9459
|
+
* You may obtain a copy of the License at
|
|
9460
|
+
*
|
|
9461
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9462
|
+
*
|
|
9463
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9464
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9465
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9466
|
+
* See the License for the specific language governing permissions and
|
|
9467
|
+
* limitations under the License.
|
|
9468
|
+
*/
|
|
9469
|
+
// Note: This code is copied from the backend. Code that is not used by
|
|
9470
|
+
// Firestore was removed.
|
|
9471
|
+
const INDEX_TYPE_NULL = 5;
|
|
9472
|
+
const INDEX_TYPE_BOOLEAN = 10;
|
|
9473
|
+
const INDEX_TYPE_NAN = 13;
|
|
9474
|
+
const INDEX_TYPE_NUMBER = 15;
|
|
9475
|
+
const INDEX_TYPE_TIMESTAMP = 20;
|
|
9476
|
+
const INDEX_TYPE_STRING = 25;
|
|
9477
|
+
const INDEX_TYPE_BLOB = 30;
|
|
9478
|
+
const INDEX_TYPE_REFERENCE = 37;
|
|
9479
|
+
const INDEX_TYPE_GEOPOINT = 45;
|
|
9480
|
+
const INDEX_TYPE_ARRAY = 50;
|
|
9481
|
+
const INDEX_TYPE_MAP = 55;
|
|
9482
|
+
const INDEX_TYPE_REFERENCE_SEGMENT = 60;
|
|
9483
|
+
// A terminator that indicates that a truncatable value was not truncated.
|
|
9484
|
+
// This must be smaller than all other type labels.
|
|
9485
|
+
const NOT_TRUNCATED = 2;
|
|
9486
|
+
/** Firestore index value writer. */
|
|
9487
|
+
class FirestoreIndexValueWriter {
|
|
9488
|
+
constructor() { }
|
|
9489
|
+
// The write methods below short-circuit writing terminators for values
|
|
9490
|
+
// containing a (terminating) truncated value.
|
|
9491
|
+
//
|
|
9492
|
+
// As an example, consider the resulting encoding for:
|
|
9493
|
+
//
|
|
9494
|
+
// ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
|
|
9495
|
+
// ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
|
|
9496
|
+
// ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
|
|
9497
|
+
/** Writes an index value. */
|
|
9498
|
+
writeIndexValue(value, encoder) {
|
|
9499
|
+
this.writeIndexValueAux(value, encoder);
|
|
9500
|
+
// Write separator to split index values
|
|
9501
|
+
// (see go/firestore-storage-format#encodings).
|
|
9502
|
+
encoder.writeInfinity();
|
|
9503
|
+
}
|
|
9504
|
+
writeIndexValueAux(indexValue, encoder) {
|
|
9505
|
+
if ('nullValue' in indexValue) {
|
|
9506
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
|
|
9507
|
+
}
|
|
9508
|
+
else if ('booleanValue' in indexValue) {
|
|
9509
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
|
|
9510
|
+
encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
|
|
9511
|
+
}
|
|
9512
|
+
else if ('integerValue' in indexValue) {
|
|
9513
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
|
|
9514
|
+
encoder.writeNumber(normalizeNumber(indexValue.integerValue));
|
|
9515
|
+
}
|
|
9516
|
+
else if ('doubleValue' in indexValue) {
|
|
9517
|
+
const n = normalizeNumber(indexValue.doubleValue);
|
|
9518
|
+
if (isNaN(n)) {
|
|
9519
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
|
|
9520
|
+
}
|
|
9521
|
+
else {
|
|
9522
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
|
|
9523
|
+
if (isNegativeZero(n)) {
|
|
9524
|
+
// -0.0, 0 and 0.0 are all considered the same
|
|
9525
|
+
encoder.writeNumber(0.0);
|
|
9526
|
+
}
|
|
9527
|
+
else {
|
|
9528
|
+
encoder.writeNumber(n);
|
|
9529
|
+
}
|
|
9530
|
+
}
|
|
9531
|
+
}
|
|
9532
|
+
else if ('timestampValue' in indexValue) {
|
|
9533
|
+
const timestamp = indexValue.timestampValue;
|
|
9534
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
|
|
9535
|
+
if (typeof timestamp === 'string') {
|
|
9536
|
+
encoder.writeString(timestamp);
|
|
9537
|
+
}
|
|
9538
|
+
else {
|
|
9539
|
+
encoder.writeString(`${timestamp.seconds || ''}`);
|
|
9540
|
+
encoder.writeNumber(timestamp.nanos || 0);
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
else if ('stringValue' in indexValue) {
|
|
9544
|
+
this.writeIndexString(indexValue.stringValue, encoder);
|
|
9545
|
+
this.writeTruncationMarker(encoder);
|
|
9546
|
+
}
|
|
9547
|
+
else if ('bytesValue' in indexValue) {
|
|
9548
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
|
|
9549
|
+
encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
|
|
9550
|
+
this.writeTruncationMarker(encoder);
|
|
9551
|
+
}
|
|
9552
|
+
else if ('referenceValue' in indexValue) {
|
|
9553
|
+
this.writeIndexEntityRef(indexValue.referenceValue, encoder);
|
|
9554
|
+
}
|
|
9555
|
+
else if ('geoPointValue' in indexValue) {
|
|
9556
|
+
const geoPoint = indexValue.geoPointValue;
|
|
9557
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
|
|
9558
|
+
encoder.writeNumber(geoPoint.latitude || 0);
|
|
9559
|
+
encoder.writeNumber(geoPoint.longitude || 0);
|
|
9560
|
+
}
|
|
9561
|
+
else if ('mapValue' in indexValue) {
|
|
9562
|
+
if (isMaxValue(indexValue)) {
|
|
9563
|
+
this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
|
|
9564
|
+
}
|
|
9565
|
+
else {
|
|
9566
|
+
this.writeIndexMap(indexValue.mapValue, encoder);
|
|
9567
|
+
this.writeTruncationMarker(encoder);
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
else if ('arrayValue' in indexValue) {
|
|
9571
|
+
this.writeIndexArray(indexValue.arrayValue, encoder);
|
|
9572
|
+
this.writeTruncationMarker(encoder);
|
|
9573
|
+
}
|
|
9574
|
+
else {
|
|
9575
|
+
fail();
|
|
9576
|
+
}
|
|
9577
|
+
}
|
|
9578
|
+
writeIndexString(stringIndexValue, encoder) {
|
|
9579
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
|
|
9580
|
+
this.writeUnlabeledIndexString(stringIndexValue, encoder);
|
|
9581
|
+
}
|
|
9582
|
+
writeUnlabeledIndexString(stringIndexValue, encoder) {
|
|
9583
|
+
encoder.writeString(stringIndexValue);
|
|
9584
|
+
}
|
|
9585
|
+
writeIndexMap(mapIndexValue, encoder) {
|
|
9586
|
+
const map = mapIndexValue.fields || {};
|
|
9587
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
|
|
9588
|
+
for (const key of Object.keys(map)) {
|
|
9589
|
+
this.writeIndexString(key, encoder);
|
|
9590
|
+
this.writeIndexValueAux(map[key], encoder);
|
|
9591
|
+
}
|
|
9592
|
+
}
|
|
9593
|
+
writeIndexArray(arrayIndexValue, encoder) {
|
|
9594
|
+
const values = arrayIndexValue.values || [];
|
|
9595
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
|
|
9596
|
+
for (const element of values) {
|
|
9597
|
+
this.writeIndexValueAux(element, encoder);
|
|
9598
|
+
}
|
|
9599
|
+
}
|
|
9600
|
+
writeIndexEntityRef(referenceValue, encoder) {
|
|
9601
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
|
|
9602
|
+
const path = DocumentKey.fromName(referenceValue).path;
|
|
9603
|
+
path.forEach(segment => {
|
|
9604
|
+
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
|
|
9605
|
+
this.writeUnlabeledIndexString(segment, encoder);
|
|
9606
|
+
});
|
|
9607
|
+
}
|
|
9608
|
+
writeValueTypeLabel(encoder, typeOrder) {
|
|
9609
|
+
encoder.writeNumber(typeOrder);
|
|
9610
|
+
}
|
|
9611
|
+
writeTruncationMarker(encoder) {
|
|
9612
|
+
// While the SDK does not implement truncation, the truncation marker is
|
|
9613
|
+
// used to terminate all variable length values (which are strings, bytes,
|
|
9614
|
+
// references, arrays and maps).
|
|
9615
|
+
encoder.writeNumber(NOT_TRUNCATED);
|
|
9616
|
+
}
|
|
9617
|
+
}
|
|
9618
|
+
FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
|
|
9619
|
+
|
|
9620
|
+
/**
|
|
9621
|
+
* @license
|
|
9622
|
+
* Copyright 2021 Google LLC
|
|
9623
|
+
*
|
|
9624
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9625
|
+
* you may not use this file except in compliance with the License.
|
|
9626
|
+
* You may obtain a copy of the License at
|
|
9627
|
+
*
|
|
9628
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9629
|
+
*
|
|
9630
|
+
* Unless required by applicable law | agreed to in writing, software
|
|
9631
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9632
|
+
* WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
|
|
9633
|
+
* See the License for the specific language governing permissions and
|
|
9634
|
+
* limitations under the License.
|
|
9635
|
+
*/
|
|
9636
|
+
/** These constants are taken from the backend. */
|
|
9637
|
+
const MIN_SURROGATE = '\uD800';
|
|
9638
|
+
const MAX_SURROGATE = '\uDBFF';
|
|
9639
|
+
const ESCAPE1 = 0x00;
|
|
9640
|
+
const NULL_BYTE = 0xff; // Combined with ESCAPE1
|
|
9641
|
+
const SEPARATOR = 0x01; // Combined with ESCAPE1
|
|
9642
|
+
const ESCAPE2 = 0xff;
|
|
9643
|
+
const INFINITY = 0xff; // Combined with ESCAPE2
|
|
9644
|
+
const FF_BYTE = 0x00; // Combined with ESCAPE2
|
|
9645
|
+
const LONG_SIZE = 64;
|
|
9646
|
+
const BYTE_SIZE = 8;
|
|
9647
|
+
/**
|
|
9648
|
+
* The default size of the buffer. This is arbitrary, but likely larger than
|
|
9649
|
+
* most index values so that less copies of the underlying buffer will be made.
|
|
9650
|
+
* For large values, a single copy will made to double the buffer length.
|
|
9651
|
+
*/
|
|
9652
|
+
const DEFAULT_BUFFER_SIZE = 1024;
|
|
9653
|
+
/** Converts a JavaScript number to a byte array (using big endian encoding). */
|
|
9654
|
+
function doubleToLongBits(value) {
|
|
9655
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
9656
|
+
dv.setFloat64(0, value, /* littleEndian= */ false);
|
|
9657
|
+
return new Uint8Array(dv.buffer);
|
|
9658
|
+
}
|
|
9659
|
+
/**
|
|
9660
|
+
* Counts the number of zeros in a byte.
|
|
9661
|
+
*
|
|
9662
|
+
* Visible for testing.
|
|
9663
|
+
*/
|
|
9664
|
+
function numberOfLeadingZerosInByte(x) {
|
|
9665
|
+
if (x === 0) {
|
|
9666
|
+
return 8;
|
|
9667
|
+
}
|
|
9668
|
+
let zeros = 0;
|
|
9669
|
+
if (x >> 4 === 0) {
|
|
9670
|
+
// Test if the first four bits are zero.
|
|
9671
|
+
zeros += 4;
|
|
9672
|
+
x = x << 4;
|
|
9673
|
+
}
|
|
9674
|
+
if (x >> 6 === 0) {
|
|
9675
|
+
// Test if the first two (or next two) bits are zero.
|
|
9676
|
+
zeros += 2;
|
|
9677
|
+
x = x << 2;
|
|
9678
|
+
}
|
|
9679
|
+
if (x >> 7 === 0) {
|
|
9680
|
+
// Test if the remaining bit is zero.
|
|
9681
|
+
zeros += 1;
|
|
9682
|
+
}
|
|
9683
|
+
return zeros;
|
|
9684
|
+
}
|
|
9685
|
+
/** Counts the number of leading zeros in the given byte array. */
|
|
9686
|
+
function numberOfLeadingZeros(bytes) {
|
|
9687
|
+
let leadingZeros = 0;
|
|
9688
|
+
for (let i = 0; i < 8; ++i) {
|
|
9689
|
+
const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
|
|
9690
|
+
leadingZeros += zeros;
|
|
9691
|
+
if (zeros !== 8) {
|
|
9692
|
+
break;
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
return leadingZeros;
|
|
9696
|
+
}
|
|
9697
|
+
/**
|
|
9698
|
+
* Returns the number of bytes required to store "value". Leading zero bytes
|
|
9699
|
+
* are skipped.
|
|
9700
|
+
*/
|
|
9701
|
+
function unsignedNumLength(value) {
|
|
9702
|
+
// This is just the number of bytes for the unsigned representation of the number.
|
|
9703
|
+
const numBits = LONG_SIZE - numberOfLeadingZeros(value);
|
|
9704
|
+
return Math.ceil(numBits / BYTE_SIZE);
|
|
9705
|
+
}
|
|
9706
|
+
/**
|
|
9707
|
+
* OrderedCodeWriter is a minimal-allocation implementation of the writing
|
|
9708
|
+
* behavior defined by the backend.
|
|
9709
|
+
*
|
|
9710
|
+
* The code is ported from its Java counterpart.
|
|
9711
|
+
*/
|
|
9712
|
+
class OrderedCodeWriter {
|
|
9713
|
+
constructor() {
|
|
9714
|
+
this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
|
|
9715
|
+
this.position = 0;
|
|
9716
|
+
}
|
|
9717
|
+
writeBytesAscending(value) {
|
|
9718
|
+
const it = value[Symbol.iterator]();
|
|
9719
|
+
let byte = it.next();
|
|
9720
|
+
while (!byte.done) {
|
|
9721
|
+
this.writeByteAscending(byte.value);
|
|
9722
|
+
byte = it.next();
|
|
9723
|
+
}
|
|
9724
|
+
this.writeSeparatorAscending();
|
|
9725
|
+
}
|
|
9726
|
+
writeBytesDescending(value) {
|
|
9727
|
+
const it = value[Symbol.iterator]();
|
|
9728
|
+
let byte = it.next();
|
|
9729
|
+
while (!byte.done) {
|
|
9730
|
+
this.writeByteDescending(byte.value);
|
|
9731
|
+
byte = it.next();
|
|
9732
|
+
}
|
|
9733
|
+
this.writeSeparatorDescending();
|
|
9734
|
+
}
|
|
9735
|
+
/** Writes utf8 bytes into this byte sequence, ascending. */
|
|
9736
|
+
writeUtf8Ascending(sequence) {
|
|
9737
|
+
for (const c of sequence) {
|
|
9738
|
+
const charCode = c.charCodeAt(0);
|
|
9739
|
+
if (charCode < 0x80) {
|
|
9740
|
+
this.writeByteAscending(charCode);
|
|
9741
|
+
}
|
|
9742
|
+
else if (charCode < 0x800) {
|
|
9743
|
+
this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
|
|
9744
|
+
this.writeByteAscending(0x80 | (0x3f & charCode));
|
|
9745
|
+
}
|
|
9746
|
+
else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
|
|
9747
|
+
this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
|
|
9748
|
+
this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
|
|
9749
|
+
this.writeByteAscending(0x80 | (0x3f & charCode));
|
|
9750
|
+
}
|
|
9751
|
+
else {
|
|
9752
|
+
const codePoint = c.codePointAt(0);
|
|
9753
|
+
this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
|
|
9754
|
+
this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
|
|
9755
|
+
this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
|
|
9756
|
+
this.writeByteAscending(0x80 | (0x3f & codePoint));
|
|
9757
|
+
}
|
|
9758
|
+
}
|
|
9759
|
+
this.writeSeparatorAscending();
|
|
9760
|
+
}
|
|
9761
|
+
/** Writes utf8 bytes into this byte sequence, descending */
|
|
9762
|
+
writeUtf8Descending(sequence) {
|
|
9763
|
+
for (const c of sequence) {
|
|
9764
|
+
const charCode = c.charCodeAt(0);
|
|
9765
|
+
if (charCode < 0x80) {
|
|
9766
|
+
this.writeByteDescending(charCode);
|
|
9767
|
+
}
|
|
9768
|
+
else if (charCode < 0x800) {
|
|
9769
|
+
this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
|
|
9770
|
+
this.writeByteDescending(0x80 | (0x3f & charCode));
|
|
9771
|
+
}
|
|
9772
|
+
else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
|
|
9773
|
+
this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
|
|
9774
|
+
this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
|
|
9775
|
+
this.writeByteDescending(0x80 | (0x3f & charCode));
|
|
9776
|
+
}
|
|
9777
|
+
else {
|
|
9778
|
+
const codePoint = c.codePointAt(0);
|
|
9779
|
+
this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
|
|
9780
|
+
this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
|
|
9781
|
+
this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
|
|
9782
|
+
this.writeByteDescending(0x80 | (0x3f & codePoint));
|
|
9783
|
+
}
|
|
9784
|
+
}
|
|
9785
|
+
this.writeSeparatorDescending();
|
|
9786
|
+
}
|
|
9787
|
+
writeNumberAscending(val) {
|
|
9788
|
+
// Values are encoded with a single byte length prefix, followed by the
|
|
9789
|
+
// actual value in big-endian format with leading 0 bytes dropped.
|
|
9790
|
+
const value = this.toOrderedBits(val);
|
|
9791
|
+
const len = unsignedNumLength(value);
|
|
9792
|
+
this.ensureAvailable(1 + len);
|
|
9793
|
+
this.buffer[this.position++] = len & 0xff; // Write the length
|
|
9794
|
+
for (let i = value.length - len; i < value.length; ++i) {
|
|
9795
|
+
this.buffer[this.position++] = value[i] & 0xff;
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
writeNumberDescending(val) {
|
|
9799
|
+
// Values are encoded with a single byte length prefix, followed by the
|
|
9800
|
+
// inverted value in big-endian format with leading 0 bytes dropped.
|
|
9801
|
+
const value = this.toOrderedBits(val);
|
|
9802
|
+
const len = unsignedNumLength(value);
|
|
9803
|
+
this.ensureAvailable(1 + len);
|
|
9804
|
+
this.buffer[this.position++] = ~(len & 0xff); // Write the length
|
|
9805
|
+
for (let i = value.length - len; i < value.length; ++i) {
|
|
9806
|
+
this.buffer[this.position++] = ~(value[i] & 0xff);
|
|
9807
|
+
}
|
|
9808
|
+
}
|
|
9809
|
+
/**
|
|
9810
|
+
* Writes the "infinity" byte sequence that sorts after all other byte
|
|
9811
|
+
* sequences written in ascending order.
|
|
9812
|
+
*/
|
|
9813
|
+
writeInfinityAscending() {
|
|
9814
|
+
this.writeEscapedByteAscending(ESCAPE2);
|
|
9815
|
+
this.writeEscapedByteAscending(INFINITY);
|
|
9816
|
+
}
|
|
9817
|
+
/**
|
|
9818
|
+
* Writes the "infinity" byte sequence that sorts before all other byte
|
|
9819
|
+
* sequences written in descending order.
|
|
9820
|
+
*/
|
|
9821
|
+
writeInfinityDescending() {
|
|
9822
|
+
this.writeEscapedByteDescending(ESCAPE2);
|
|
9823
|
+
this.writeEscapedByteDescending(INFINITY);
|
|
9824
|
+
}
|
|
9825
|
+
/**
|
|
9826
|
+
* Resets the buffer such that it is the same as when it was newly
|
|
9827
|
+
* constructed.
|
|
9828
|
+
*/
|
|
9829
|
+
reset() {
|
|
9830
|
+
this.position = 0;
|
|
9831
|
+
}
|
|
9832
|
+
seed(encodedBytes) {
|
|
9833
|
+
this.ensureAvailable(encodedBytes.length);
|
|
9834
|
+
this.buffer.set(encodedBytes, this.position);
|
|
9835
|
+
this.position += encodedBytes.length;
|
|
9836
|
+
}
|
|
9837
|
+
/** Makes a copy of the encoded bytes in this buffer. */
|
|
9838
|
+
encodedBytes() {
|
|
9839
|
+
return this.buffer.slice(0, this.position);
|
|
9840
|
+
}
|
|
9841
|
+
/**
|
|
9842
|
+
* Encodes `val` into an encoding so that the order matches the IEEE 754
|
|
9843
|
+
* floating-point comparison results with the following exceptions:
|
|
9844
|
+
* -0.0 < 0.0
|
|
9845
|
+
* all non-NaN < NaN
|
|
9846
|
+
* NaN = NaN
|
|
9847
|
+
*/
|
|
9848
|
+
toOrderedBits(val) {
|
|
9849
|
+
const value = doubleToLongBits(val);
|
|
9850
|
+
// Check if the first bit is set. We use a bit mask since value[0] is
|
|
9851
|
+
// encoded as a number from 0 to 255.
|
|
9852
|
+
const isNegative = (value[0] & 0x80) !== 0;
|
|
9853
|
+
// Revert the two complement to get natural ordering
|
|
9854
|
+
value[0] ^= isNegative ? 0xff : 0x80;
|
|
9855
|
+
for (let i = 1; i < value.length; ++i) {
|
|
9856
|
+
value[i] ^= isNegative ? 0xff : 0x00;
|
|
9857
|
+
}
|
|
9858
|
+
return value;
|
|
9859
|
+
}
|
|
9860
|
+
/** Writes a single byte ascending to the buffer. */
|
|
9861
|
+
writeByteAscending(b) {
|
|
9862
|
+
const masked = b & 0xff;
|
|
9863
|
+
if (masked === ESCAPE1) {
|
|
9864
|
+
this.writeEscapedByteAscending(ESCAPE1);
|
|
9865
|
+
this.writeEscapedByteAscending(NULL_BYTE);
|
|
9866
|
+
}
|
|
9867
|
+
else if (masked === ESCAPE2) {
|
|
9868
|
+
this.writeEscapedByteAscending(ESCAPE2);
|
|
9869
|
+
this.writeEscapedByteAscending(FF_BYTE);
|
|
9870
|
+
}
|
|
9871
|
+
else {
|
|
9872
|
+
this.writeEscapedByteAscending(masked);
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
/** Writes a single byte descending to the buffer. */
|
|
9876
|
+
writeByteDescending(b) {
|
|
9877
|
+
const masked = b & 0xff;
|
|
9878
|
+
if (masked === ESCAPE1) {
|
|
9879
|
+
this.writeEscapedByteDescending(ESCAPE1);
|
|
9880
|
+
this.writeEscapedByteDescending(NULL_BYTE);
|
|
9881
|
+
}
|
|
9882
|
+
else if (masked === ESCAPE2) {
|
|
9883
|
+
this.writeEscapedByteDescending(ESCAPE2);
|
|
9884
|
+
this.writeEscapedByteDescending(FF_BYTE);
|
|
9885
|
+
}
|
|
9886
|
+
else {
|
|
9887
|
+
this.writeEscapedByteDescending(b);
|
|
9888
|
+
}
|
|
9889
|
+
}
|
|
9890
|
+
writeSeparatorAscending() {
|
|
9891
|
+
this.writeEscapedByteAscending(ESCAPE1);
|
|
9892
|
+
this.writeEscapedByteAscending(SEPARATOR);
|
|
9893
|
+
}
|
|
9894
|
+
writeSeparatorDescending() {
|
|
9895
|
+
this.writeEscapedByteDescending(ESCAPE1);
|
|
9896
|
+
this.writeEscapedByteDescending(SEPARATOR);
|
|
9897
|
+
}
|
|
9898
|
+
writeEscapedByteAscending(b) {
|
|
9899
|
+
this.ensureAvailable(1);
|
|
9900
|
+
this.buffer[this.position++] = b;
|
|
9901
|
+
}
|
|
9902
|
+
writeEscapedByteDescending(b) {
|
|
9903
|
+
this.ensureAvailable(1);
|
|
9904
|
+
this.buffer[this.position++] = ~b;
|
|
9905
|
+
}
|
|
9906
|
+
ensureAvailable(bytes) {
|
|
9907
|
+
const minCapacity = bytes + this.position;
|
|
9908
|
+
if (minCapacity <= this.buffer.length) {
|
|
9909
|
+
return;
|
|
9910
|
+
}
|
|
9911
|
+
// Try doubling.
|
|
9912
|
+
let newLength = this.buffer.length * 2;
|
|
9913
|
+
// Still not big enough? Just allocate the right size.
|
|
9914
|
+
if (newLength < minCapacity) {
|
|
9915
|
+
newLength = minCapacity;
|
|
9916
|
+
}
|
|
9917
|
+
// Create the new buffer.
|
|
9918
|
+
const newBuffer = new Uint8Array(newLength);
|
|
9919
|
+
newBuffer.set(this.buffer); // copy old data
|
|
9920
|
+
this.buffer = newBuffer;
|
|
9921
|
+
}
|
|
9922
|
+
}
|
|
9923
|
+
|
|
9924
|
+
class AscendingIndexByteEncoder {
|
|
9925
|
+
constructor(orderedCode) {
|
|
9926
|
+
this.orderedCode = orderedCode;
|
|
9927
|
+
}
|
|
9928
|
+
writeBytes(value) {
|
|
9929
|
+
this.orderedCode.writeBytesAscending(value);
|
|
9930
|
+
}
|
|
9931
|
+
writeString(value) {
|
|
9932
|
+
this.orderedCode.writeUtf8Ascending(value);
|
|
9933
|
+
}
|
|
9934
|
+
writeNumber(value) {
|
|
9935
|
+
this.orderedCode.writeNumberAscending(value);
|
|
9936
|
+
}
|
|
9937
|
+
writeInfinity() {
|
|
9938
|
+
this.orderedCode.writeInfinityAscending();
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9941
|
+
class DescendingIndexByteEncoder {
|
|
9942
|
+
constructor(orderedCode) {
|
|
9943
|
+
this.orderedCode = orderedCode;
|
|
9944
|
+
}
|
|
9945
|
+
writeBytes(value) {
|
|
9946
|
+
this.orderedCode.writeBytesDescending(value);
|
|
9947
|
+
}
|
|
9948
|
+
writeString(value) {
|
|
9949
|
+
this.orderedCode.writeUtf8Descending(value);
|
|
9950
|
+
}
|
|
9951
|
+
writeNumber(value) {
|
|
9952
|
+
this.orderedCode.writeNumberDescending(value);
|
|
9953
|
+
}
|
|
9954
|
+
writeInfinity() {
|
|
9955
|
+
this.orderedCode.writeInfinityDescending();
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
9958
|
+
/**
|
|
9959
|
+
* Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
|
|
9960
|
+
* actual encoding.
|
|
9961
|
+
*/
|
|
9962
|
+
class IndexByteEncoder {
|
|
9963
|
+
constructor() {
|
|
9964
|
+
this.orderedCode = new OrderedCodeWriter();
|
|
9965
|
+
this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
|
|
9966
|
+
this.descending = new DescendingIndexByteEncoder(this.orderedCode);
|
|
9967
|
+
}
|
|
9968
|
+
seed(encodedBytes) {
|
|
9969
|
+
this.orderedCode.seed(encodedBytes);
|
|
9970
|
+
}
|
|
9971
|
+
forKind(kind) {
|
|
9972
|
+
return kind === 0 /* ASCENDING */ ? this.ascending : this.descending;
|
|
9973
|
+
}
|
|
9974
|
+
encodedBytes() {
|
|
9975
|
+
return this.orderedCode.encodedBytes();
|
|
9976
|
+
}
|
|
9977
|
+
reset() {
|
|
9978
|
+
this.orderedCode.reset();
|
|
9979
|
+
}
|
|
9980
|
+
}
|
|
9981
|
+
|
|
9982
|
+
/**
|
|
9983
|
+
* @license
|
|
9984
|
+
* Copyright 2022 Google LLC
|
|
9985
|
+
*
|
|
9986
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9987
|
+
* you may not use this file except in compliance with the License.
|
|
9988
|
+
* You may obtain a copy of the License at
|
|
9989
|
+
*
|
|
9990
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9991
|
+
*
|
|
9992
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
9993
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9994
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9995
|
+
* See the License for the specific language governing permissions and
|
|
9996
|
+
* limitations under the License.
|
|
9997
|
+
*/
|
|
9998
|
+
/** Represents an index entry saved by the SDK in persisted storage. */
|
|
9999
|
+
class IndexEntry {
|
|
10000
|
+
constructor(indexId, documentKey, arrayValue, directionalValue) {
|
|
10001
|
+
this.indexId = indexId;
|
|
10002
|
+
this.documentKey = documentKey;
|
|
10003
|
+
this.arrayValue = arrayValue;
|
|
10004
|
+
this.directionalValue = directionalValue;
|
|
10005
|
+
}
|
|
10006
|
+
/**
|
|
10007
|
+
* Returns an IndexEntry entry that sorts immediately after the current
|
|
10008
|
+
* directional value.
|
|
10009
|
+
*/
|
|
10010
|
+
successor() {
|
|
10011
|
+
const currentLength = this.directionalValue.length;
|
|
10012
|
+
const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
|
|
10013
|
+
? currentLength + 1
|
|
10014
|
+
: currentLength;
|
|
10015
|
+
const successor = new Uint8Array(newLength);
|
|
10016
|
+
successor.set(this.directionalValue, 0);
|
|
10017
|
+
if (newLength !== currentLength) {
|
|
10018
|
+
successor.set([0], this.directionalValue.length);
|
|
10019
|
+
}
|
|
10020
|
+
else {
|
|
10021
|
+
++successor[successor.length - 1];
|
|
10022
|
+
}
|
|
10023
|
+
return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
|
|
10024
|
+
}
|
|
10025
|
+
}
|
|
10026
|
+
function indexEntryComparator(left, right) {
|
|
10027
|
+
let cmp = left.indexId - right.indexId;
|
|
10028
|
+
if (cmp !== 0) {
|
|
10029
|
+
return cmp;
|
|
10030
|
+
}
|
|
10031
|
+
cmp = compareByteArrays(left.arrayValue, right.arrayValue);
|
|
10032
|
+
if (cmp !== 0) {
|
|
10033
|
+
return cmp;
|
|
10034
|
+
}
|
|
10035
|
+
cmp = compareByteArrays(left.directionalValue, right.directionalValue);
|
|
10036
|
+
if (cmp !== 0) {
|
|
10037
|
+
return cmp;
|
|
10038
|
+
}
|
|
10039
|
+
return DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
10040
|
+
}
|
|
10041
|
+
function compareByteArrays(left, right) {
|
|
10042
|
+
for (let i = 0; i < left.length && i < right.length; ++i) {
|
|
10043
|
+
const compare = left[i] - right[i];
|
|
10044
|
+
if (compare !== 0) {
|
|
10045
|
+
return compare;
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
return left.length - right.length;
|
|
10049
|
+
}
|
|
10050
|
+
|
|
10051
|
+
/**
|
|
10052
|
+
* @license
|
|
10053
|
+
* Copyright 2022 Google LLC
|
|
10054
|
+
*
|
|
10055
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10056
|
+
* you may not use this file except in compliance with the License.
|
|
10057
|
+
* You may obtain a copy of the License at
|
|
10058
|
+
*
|
|
10059
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10060
|
+
*
|
|
10061
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10062
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10063
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10064
|
+
* See the License for the specific language governing permissions and
|
|
10065
|
+
* limitations under the License.
|
|
10066
|
+
*/
|
|
10067
|
+
/**
|
|
10068
|
+
* A light query planner for Firestore.
|
|
10069
|
+
*
|
|
10070
|
+
* This class matches a `FieldIndex` against a Firestore Query `Target`. It
|
|
10071
|
+
* determines whether a given index can be used to serve the specified target.
|
|
10072
|
+
*
|
|
10073
|
+
* The following table showcases some possible index configurations:
|
|
10074
|
+
*
|
|
10075
|
+
* Query | Index
|
|
10076
|
+
* -----------------------------------------------------------------------------
|
|
10077
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
|
|
10078
|
+
* where('a', '==', 'a').where('b', '==', 'b') | a ASC
|
|
10079
|
+
* where('a', '==', 'a').where('b', '==', 'b') | b DESC
|
|
10080
|
+
* where('a', '>=', 'a').orderBy('a') | a ASC
|
|
10081
|
+
* where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
|
|
10082
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
|
|
10083
|
+
* where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
|
|
10084
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
|
|
10085
|
+
* where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
|
|
10086
|
+
*/
|
|
10087
|
+
class TargetIndexMatcher {
|
|
10088
|
+
constructor(target) {
|
|
10089
|
+
this.collectionId =
|
|
10090
|
+
target.collectionGroup != null
|
|
10091
|
+
? target.collectionGroup
|
|
10092
|
+
: target.path.lastSegment();
|
|
10093
|
+
this.orderBys = target.orderBy;
|
|
10094
|
+
this.equalityFilters = [];
|
|
10095
|
+
for (const filter of target.filters) {
|
|
10096
|
+
const fieldFilter = filter;
|
|
10097
|
+
if (fieldFilter.isInequality()) {
|
|
10098
|
+
this.inequalityFilter = fieldFilter;
|
|
10099
|
+
}
|
|
10100
|
+
else {
|
|
10101
|
+
this.equalityFilters.push(fieldFilter);
|
|
10102
|
+
}
|
|
10103
|
+
}
|
|
10104
|
+
}
|
|
10105
|
+
/**
|
|
10106
|
+
* Returns whether the index can be used to serve the TargetIndexMatcher's
|
|
10107
|
+
* target.
|
|
10108
|
+
*
|
|
10109
|
+
* An index is considered capable of serving the target when:
|
|
10110
|
+
* - The target uses all index segments for its filters and orderBy clauses.
|
|
10111
|
+
* The target can have additional filter and orderBy clauses, but not
|
|
10112
|
+
* fewer.
|
|
10113
|
+
* - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
|
|
10114
|
+
* have a corresponding `CONTAINS` segment.
|
|
10115
|
+
* - All directional index segments can be mapped to the target as a series of
|
|
10116
|
+
* equality filters, a single inequality filter and a series of orderBy
|
|
10117
|
+
* clauses.
|
|
10118
|
+
* - The segments that represent the equality filters may appear out of order.
|
|
10119
|
+
* - The optional segment for the inequality filter must appear after all
|
|
10120
|
+
* equality segments.
|
|
10121
|
+
* - The segments that represent that orderBy clause of the target must appear
|
|
10122
|
+
* in order after all equality and inequality segments. Single orderBy
|
|
10123
|
+
* clauses cannot be skipped, but a continuous orderBy suffix may be
|
|
10124
|
+
* omitted.
|
|
10125
|
+
*/
|
|
10126
|
+
servedByIndex(index) {
|
|
10127
|
+
// If there is an array element, find a matching filter.
|
|
10128
|
+
const arraySegment = fieldIndexGetArraySegment(index);
|
|
10129
|
+
if (arraySegment !== undefined &&
|
|
10130
|
+
!this.hasMatchingEqualityFilter(arraySegment)) {
|
|
10131
|
+
return false;
|
|
10132
|
+
}
|
|
10133
|
+
const segments = fieldIndexGetDirectionalSegments(index);
|
|
10134
|
+
let segmentIndex = 0;
|
|
10135
|
+
let orderBysIndex = 0;
|
|
10136
|
+
// Process all equalities first. Equalities can appear out of order.
|
|
10137
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
10138
|
+
// We attempt to greedily match all segments to equality filters. If a
|
|
10139
|
+
// filter matches an index segment, we can mark the segment as used.
|
|
10140
|
+
// Since it is not possible to use the same field path in both an equality
|
|
10141
|
+
// and inequality/oderBy clause, we do not have to consider the possibility
|
|
10142
|
+
// that a matching equality segment should instead be used to map to an
|
|
10143
|
+
// inequality filter or orderBy clause.
|
|
10144
|
+
if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
|
|
10145
|
+
// If we cannot find a matching filter, we need to verify whether the
|
|
10146
|
+
// remaining segments map to the target's inequality and its orderBy
|
|
10147
|
+
// clauses.
|
|
10148
|
+
break;
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
// If we already have processed all segments, all segments are used to serve
|
|
10152
|
+
// the equality filters and we do not need to map any segments to the
|
|
10153
|
+
// target's inequality and orderBy clauses.
|
|
10154
|
+
if (segmentIndex === segments.length) {
|
|
10155
|
+
return true;
|
|
10156
|
+
}
|
|
10157
|
+
// If there is an inequality filter, the next segment must match both the
|
|
10158
|
+
// filter and the first orderBy clause.
|
|
10159
|
+
if (this.inequalityFilter !== undefined) {
|
|
10160
|
+
const segment = segments[segmentIndex];
|
|
10161
|
+
if (!this.matchesFilter(this.inequalityFilter, segment) ||
|
|
10162
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
10163
|
+
return false;
|
|
10164
|
+
}
|
|
10165
|
+
++segmentIndex;
|
|
10166
|
+
}
|
|
10167
|
+
// All remaining segments need to represent the prefix of the target's
|
|
10168
|
+
// orderBy.
|
|
10169
|
+
for (; segmentIndex < segments.length; ++segmentIndex) {
|
|
10170
|
+
const segment = segments[segmentIndex];
|
|
10171
|
+
if (orderBysIndex >= this.orderBys.length ||
|
|
10172
|
+
!this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
|
|
10173
|
+
return false;
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
return true;
|
|
10177
|
+
}
|
|
10178
|
+
hasMatchingEqualityFilter(segment) {
|
|
10179
|
+
for (const filter of this.equalityFilters) {
|
|
10180
|
+
if (this.matchesFilter(filter, segment)) {
|
|
10181
|
+
return true;
|
|
10182
|
+
}
|
|
10183
|
+
}
|
|
10184
|
+
return false;
|
|
10185
|
+
}
|
|
10186
|
+
matchesFilter(filter, segment) {
|
|
10187
|
+
if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
|
|
10188
|
+
return false;
|
|
10189
|
+
}
|
|
10190
|
+
const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
10191
|
+
filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
|
|
10192
|
+
return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
|
|
10193
|
+
}
|
|
10194
|
+
matchesOrderBy(orderBy, segment) {
|
|
10195
|
+
if (!orderBy.field.isEqual(segment.fieldPath)) {
|
|
10196
|
+
return false;
|
|
10197
|
+
}
|
|
10198
|
+
return ((segment.kind === 0 /* ASCENDING */ &&
|
|
10199
|
+
orderBy.dir === "asc" /* ASCENDING */) ||
|
|
10200
|
+
(segment.kind === 1 /* DESCENDING */ &&
|
|
10201
|
+
orderBy.dir === "desc" /* DESCENDING */));
|
|
10202
|
+
}
|
|
10203
|
+
}
|
|
10204
|
+
|
|
10205
|
+
/**
|
|
10206
|
+
* @license
|
|
10207
|
+
* Copyright 2017 Google LLC
|
|
10208
|
+
*
|
|
10209
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10210
|
+
* you may not use this file except in compliance with the License.
|
|
10211
|
+
* You may obtain a copy of the License at
|
|
10212
|
+
*
|
|
10213
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10214
|
+
*
|
|
10215
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10216
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10217
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10218
|
+
* See the License for the specific language governing permissions and
|
|
10219
|
+
* limitations under the License.
|
|
10220
|
+
*/
|
|
10221
|
+
/**
|
|
10222
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
10223
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
10224
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
10225
|
+
* automatically handles collisions of keys.
|
|
10226
|
+
*/
|
|
10227
|
+
class ObjectMap {
|
|
10228
|
+
constructor(mapKeyFn, equalsFn) {
|
|
10229
|
+
this.mapKeyFn = mapKeyFn;
|
|
10230
|
+
this.equalsFn = equalsFn;
|
|
10231
|
+
/**
|
|
10232
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
10233
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
10234
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
10235
|
+
* constant time lookups in practice.
|
|
10236
|
+
*/
|
|
10237
|
+
this.inner = {};
|
|
10238
|
+
}
|
|
10239
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
10240
|
+
get(key) {
|
|
10241
|
+
const id = this.mapKeyFn(key);
|
|
10242
|
+
const matches = this.inner[id];
|
|
10243
|
+
if (matches === undefined) {
|
|
10244
|
+
return undefined;
|
|
10245
|
+
}
|
|
10246
|
+
for (const [otherKey, value] of matches) {
|
|
10247
|
+
if (this.equalsFn(otherKey, key)) {
|
|
10248
|
+
return value;
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
return undefined;
|
|
10252
|
+
}
|
|
10253
|
+
has(key) {
|
|
10254
|
+
return this.get(key) !== undefined;
|
|
10255
|
+
}
|
|
10256
|
+
/** Put this key and value in the map. */
|
|
10257
|
+
set(key, value) {
|
|
10258
|
+
const id = this.mapKeyFn(key);
|
|
10259
|
+
const matches = this.inner[id];
|
|
10260
|
+
if (matches === undefined) {
|
|
10261
|
+
this.inner[id] = [[key, value]];
|
|
10262
|
+
return;
|
|
10263
|
+
}
|
|
10264
|
+
for (let i = 0; i < matches.length; i++) {
|
|
10265
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
10266
|
+
matches[i] = [key, value];
|
|
10267
|
+
return;
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
matches.push([key, value]);
|
|
10271
|
+
}
|
|
10272
|
+
/**
|
|
10273
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
10274
|
+
*/
|
|
10275
|
+
delete(key) {
|
|
10276
|
+
const id = this.mapKeyFn(key);
|
|
10277
|
+
const matches = this.inner[id];
|
|
10278
|
+
if (matches === undefined) {
|
|
10279
|
+
return false;
|
|
10280
|
+
}
|
|
10281
|
+
for (let i = 0; i < matches.length; i++) {
|
|
10282
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
10283
|
+
if (matches.length === 1) {
|
|
10284
|
+
delete this.inner[id];
|
|
10285
|
+
}
|
|
10286
|
+
else {
|
|
10287
|
+
matches.splice(i, 1);
|
|
10288
|
+
}
|
|
10289
|
+
return true;
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
return false;
|
|
10293
|
+
}
|
|
10294
|
+
forEach(fn) {
|
|
10295
|
+
forEach(this.inner, (_, entries) => {
|
|
10296
|
+
for (const [k, v] of entries) {
|
|
10297
|
+
fn(k, v);
|
|
10298
|
+
}
|
|
10299
|
+
});
|
|
10300
|
+
}
|
|
10301
|
+
isEmpty() {
|
|
10302
|
+
return isEmpty(this.inner);
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
|
|
10306
|
+
/**
|
|
10307
|
+
* @license
|
|
10308
|
+
* Copyright 2019 Google LLC
|
|
10309
|
+
*
|
|
10310
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10311
|
+
* you may not use this file except in compliance with the License.
|
|
10312
|
+
* You may obtain a copy of the License at
|
|
10313
|
+
*
|
|
10314
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10315
|
+
*
|
|
10316
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10317
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
10318
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10319
|
+
* See the License for the specific language governing permissions and
|
|
10320
|
+
* limitations under the License.
|
|
10321
|
+
*/
|
|
10322
|
+
/**
|
|
10323
|
+
* An in-memory implementation of IndexManager.
|
|
10324
|
+
*/
|
|
10325
|
+
class MemoryIndexManager {
|
|
10326
|
+
constructor() {
|
|
10327
|
+
this.collectionParentIndex = new MemoryCollectionParentIndex();
|
|
10328
|
+
}
|
|
10329
|
+
addToCollectionParentIndex(transaction, collectionPath) {
|
|
10330
|
+
this.collectionParentIndex.add(collectionPath);
|
|
10331
|
+
return PersistencePromise.resolve();
|
|
10332
|
+
}
|
|
10333
|
+
getCollectionParents(transaction, collectionId) {
|
|
10334
|
+
return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
|
|
10335
|
+
}
|
|
10336
|
+
addFieldIndex(transaction, index) {
|
|
10337
|
+
// Field indices are not supported with memory persistence.
|
|
10338
|
+
return PersistencePromise.resolve();
|
|
10339
|
+
}
|
|
10340
|
+
deleteFieldIndex(transaction, index) {
|
|
10341
|
+
// Field indices are not supported with memory persistence.
|
|
10342
|
+
return PersistencePromise.resolve();
|
|
10343
|
+
}
|
|
10344
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10345
|
+
// Field indices are not supported with memory persistence.
|
|
10346
|
+
return PersistencePromise.resolve(null);
|
|
10347
|
+
}
|
|
10348
|
+
getFieldIndex(transaction, target) {
|
|
10349
|
+
// Field indices are not supported with memory persistence.
|
|
10350
|
+
return PersistencePromise.resolve(null);
|
|
10351
|
+
}
|
|
10352
|
+
getFieldIndexes(transaction, collectionGroup) {
|
|
10353
|
+
// Field indices are not supported with memory persistence.
|
|
10354
|
+
return PersistencePromise.resolve([]);
|
|
10355
|
+
}
|
|
10356
|
+
getNextCollectionGroupToUpdate(transaction) {
|
|
10357
|
+
// Field indices are not supported with memory persistence.
|
|
10358
|
+
return PersistencePromise.resolve(null);
|
|
10359
|
+
}
|
|
10360
|
+
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10361
|
+
// Field indices are not supported with memory persistence.
|
|
10362
|
+
return PersistencePromise.resolve();
|
|
10363
|
+
}
|
|
10364
|
+
updateIndexEntries(transaction, documents) {
|
|
10365
|
+
// Field indices are not supported with memory persistence.
|
|
10366
|
+
return PersistencePromise.resolve();
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
/**
|
|
10370
|
+
* Internal implementation of the collection-parent index exposed by MemoryIndexManager.
|
|
10371
|
+
* Also used for in-memory caching by IndexedDbIndexManager and initial index population
|
|
10372
|
+
* in indexeddb_schema.ts
|
|
10373
|
+
*/
|
|
10374
|
+
class MemoryCollectionParentIndex {
|
|
10375
|
+
constructor() {
|
|
10376
|
+
this.index = {};
|
|
10377
|
+
}
|
|
10378
|
+
// Returns false if the entry already existed.
|
|
10379
|
+
add(collectionPath) {
|
|
10380
|
+
const collectionId = collectionPath.lastSegment();
|
|
10381
|
+
const parentPath = collectionPath.popLast();
|
|
10382
|
+
const existingParents = this.index[collectionId] ||
|
|
10383
|
+
new SortedSet(ResourcePath.comparator);
|
|
10384
|
+
const added = !existingParents.has(parentPath);
|
|
10385
|
+
this.index[collectionId] = existingParents.add(parentPath);
|
|
10386
|
+
return added;
|
|
10387
|
+
}
|
|
10388
|
+
has(collectionPath) {
|
|
10389
|
+
const collectionId = collectionPath.lastSegment();
|
|
10390
|
+
const parentPath = collectionPath.popLast();
|
|
10391
|
+
const existingParents = this.index[collectionId];
|
|
10392
|
+
return existingParents && existingParents.has(parentPath);
|
|
10393
|
+
}
|
|
10394
|
+
getEntries(collectionId) {
|
|
10395
|
+
const parentPaths = this.index[collectionId] ||
|
|
10396
|
+
new SortedSet(ResourcePath.comparator);
|
|
10397
|
+
return parentPaths.toArray();
|
|
10398
|
+
}
|
|
10399
|
+
}
|
|
10400
|
+
|
|
10401
|
+
/**
|
|
8512
10402
|
* @license
|
|
8513
10403
|
* Copyright 2019 Google LLC
|
|
8514
10404
|
*
|
|
@@ -8524,19 +10414,31 @@ class MemoryCollectionParentIndex {
|
|
|
8524
10414
|
* See the License for the specific language governing permissions and
|
|
8525
10415
|
* limitations under the License.
|
|
8526
10416
|
*/
|
|
10417
|
+
const LOG_TAG$f = 'IndexedDbIndexManager';
|
|
10418
|
+
const EMPTY_VALUE = new Uint8Array(0);
|
|
8527
10419
|
/**
|
|
8528
10420
|
* A persisted implementation of IndexManager.
|
|
10421
|
+
*
|
|
10422
|
+
* PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
|
|
10423
|
+
* data as it supports multi-tab access.
|
|
8529
10424
|
*/
|
|
8530
10425
|
class IndexedDbIndexManager {
|
|
8531
|
-
constructor() {
|
|
10426
|
+
constructor(user) {
|
|
10427
|
+
this.user = user;
|
|
8532
10428
|
/**
|
|
8533
10429
|
* An in-memory copy of the index entries we've already written since the SDK
|
|
8534
10430
|
* launched. Used to avoid re-writing the same entry repeatedly.
|
|
8535
10431
|
*
|
|
8536
|
-
* This is *NOT* a complete cache of what's in persistence and so can never be
|
|
8537
|
-
* satisfy reads.
|
|
10432
|
+
* This is *NOT* a complete cache of what's in persistence and so can never be
|
|
10433
|
+
* used to satisfy reads.
|
|
8538
10434
|
*/
|
|
8539
10435
|
this.collectionParentsCache = new MemoryCollectionParentIndex();
|
|
10436
|
+
/**
|
|
10437
|
+
* Maps from a target to its equivalent list of sub-targets. Each sub-target
|
|
10438
|
+
* contains only one term from the target's disjunctive normal form (DNF).
|
|
10439
|
+
*/
|
|
10440
|
+
this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
|
|
10441
|
+
this.uid = user.uid || '';
|
|
8540
10442
|
}
|
|
8541
10443
|
/**
|
|
8542
10444
|
* Adds a new entry to the collection parent index.
|
|
@@ -8583,6 +10485,414 @@ class IndexedDbIndexManager {
|
|
|
8583
10485
|
return parentPaths;
|
|
8584
10486
|
});
|
|
8585
10487
|
}
|
|
10488
|
+
addFieldIndex(transaction, index) {
|
|
10489
|
+
// TODO(indexing): Verify that the auto-incrementing index ID works in
|
|
10490
|
+
// Safari & Firefox.
|
|
10491
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10492
|
+
const dbIndex = toDbIndexConfiguration(index);
|
|
10493
|
+
delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
|
|
10494
|
+
return indexes.add(dbIndex).next();
|
|
10495
|
+
}
|
|
10496
|
+
deleteFieldIndex(transaction, index) {
|
|
10497
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10498
|
+
const states = indexStateStore(transaction);
|
|
10499
|
+
const entries = indexEntriesStore(transaction);
|
|
10500
|
+
return indexes
|
|
10501
|
+
.delete(index.indexId)
|
|
10502
|
+
.next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
|
|
10503
|
+
/*lowerOpen=*/ false,
|
|
10504
|
+
/*upperOpen=*/ true)))
|
|
10505
|
+
.next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
|
|
10506
|
+
/*lowerOpen=*/ false,
|
|
10507
|
+
/*upperOpen=*/ true)));
|
|
10508
|
+
}
|
|
10509
|
+
getDocumentsMatchingTarget(transaction, target) {
|
|
10510
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10511
|
+
let canServeTarget = true;
|
|
10512
|
+
const indexes = new Map();
|
|
10513
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
|
|
10514
|
+
return this.getFieldIndex(transaction, subTarget).next(index => {
|
|
10515
|
+
canServeTarget && (canServeTarget = !!index);
|
|
10516
|
+
indexes.set(subTarget, index);
|
|
10517
|
+
});
|
|
10518
|
+
}).next(() => {
|
|
10519
|
+
if (!canServeTarget) {
|
|
10520
|
+
return PersistencePromise.resolve(null);
|
|
10521
|
+
}
|
|
10522
|
+
else {
|
|
10523
|
+
let result = documentKeySet();
|
|
10524
|
+
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10525
|
+
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10526
|
+
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
10527
|
+
const notInValues = targetGetNotInValues(subTarget, index);
|
|
10528
|
+
const lowerBound = targetGetLowerBound(subTarget, index);
|
|
10529
|
+
const upperBound = targetGetUpperBound(subTarget, index);
|
|
10530
|
+
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10531
|
+
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10532
|
+
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10533
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
|
|
10534
|
+
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10535
|
+
return indexEntries
|
|
10536
|
+
.loadFirst(indexRange, target.limit)
|
|
10537
|
+
.next(entries => {
|
|
10538
|
+
entries.forEach(entry => {
|
|
10539
|
+
result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
|
|
10540
|
+
});
|
|
10541
|
+
});
|
|
10542
|
+
});
|
|
10543
|
+
}).next(() => result);
|
|
10544
|
+
}
|
|
10545
|
+
});
|
|
10546
|
+
}
|
|
10547
|
+
getSubTargets(target) {
|
|
10548
|
+
let subTargets = this.targetToDnfSubTargets.get(target);
|
|
10549
|
+
if (subTargets) {
|
|
10550
|
+
return subTargets;
|
|
10551
|
+
}
|
|
10552
|
+
// TODO(orquery): Implement DNF transform
|
|
10553
|
+
subTargets = [target];
|
|
10554
|
+
this.targetToDnfSubTargets.set(target, subTargets);
|
|
10555
|
+
return subTargets;
|
|
10556
|
+
}
|
|
10557
|
+
/**
|
|
10558
|
+
* Constructs a key range query on `DbIndexEntry.store` that unions all
|
|
10559
|
+
* bounds.
|
|
10560
|
+
*/
|
|
10561
|
+
generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
|
|
10562
|
+
// The number of total index scans we union together. This is similar to a
|
|
10563
|
+
// distributed normal form, but adapted for array values. We create a single
|
|
10564
|
+
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10565
|
+
// combined with the values from the query bounds.
|
|
10566
|
+
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10567
|
+
Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
|
|
10568
|
+
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10569
|
+
const indexRanges = [];
|
|
10570
|
+
for (let i = 0; i < totalScans; ++i) {
|
|
10571
|
+
const arrayValue = arrayValues
|
|
10572
|
+
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10573
|
+
: EMPTY_VALUE;
|
|
10574
|
+
const lowerBound = lowerBounds
|
|
10575
|
+
? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
|
|
10576
|
+
: this.generateEmptyBound(indexId);
|
|
10577
|
+
const upperBound = upperBounds
|
|
10578
|
+
? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
|
|
10579
|
+
: this.generateEmptyBound(indexId + 1);
|
|
10580
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10581
|
+
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10582
|
+
/* inclusive= */ true))));
|
|
10583
|
+
}
|
|
10584
|
+
return indexRanges;
|
|
10585
|
+
}
|
|
10586
|
+
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
|
|
10587
|
+
generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10588
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10589
|
+
return inclusive ? entry : entry.successor();
|
|
10590
|
+
}
|
|
10591
|
+
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
|
|
10592
|
+
generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
|
|
10593
|
+
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10594
|
+
return inclusive ? entry.successor() : entry;
|
|
10595
|
+
}
|
|
10596
|
+
/**
|
|
10597
|
+
* Generates an empty bound that scopes the index scan to the current index
|
|
10598
|
+
* and user.
|
|
10599
|
+
*/
|
|
10600
|
+
generateEmptyBound(indexId) {
|
|
10601
|
+
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10602
|
+
}
|
|
10603
|
+
getFieldIndex(transaction, target) {
|
|
10604
|
+
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10605
|
+
const collectionGroup = target.collectionGroup != null
|
|
10606
|
+
? target.collectionGroup
|
|
10607
|
+
: target.path.lastSegment();
|
|
10608
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10609
|
+
const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
|
|
10610
|
+
// Return the index that matches the most number of segments.
|
|
10611
|
+
matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
|
|
10612
|
+
return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
|
|
10613
|
+
});
|
|
10614
|
+
}
|
|
10615
|
+
/**
|
|
10616
|
+
* Returns the byte encoded form of the directional values in the field index.
|
|
10617
|
+
* Returns `null` if the document does not have all fields specified in the
|
|
10618
|
+
* index.
|
|
10619
|
+
*/
|
|
10620
|
+
encodeDirectionalElements(fieldIndex, document) {
|
|
10621
|
+
const encoder = new IndexByteEncoder();
|
|
10622
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10623
|
+
const field = document.data.field(segment.fieldPath);
|
|
10624
|
+
if (field == null) {
|
|
10625
|
+
return null;
|
|
10626
|
+
}
|
|
10627
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10628
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
|
|
10629
|
+
}
|
|
10630
|
+
return encoder.encodedBytes();
|
|
10631
|
+
}
|
|
10632
|
+
/** Encodes a single value to the ascending index format. */
|
|
10633
|
+
encodeSingleElement(value) {
|
|
10634
|
+
const encoder = new IndexByteEncoder();
|
|
10635
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10636
|
+
return encoder.encodedBytes();
|
|
10637
|
+
}
|
|
10638
|
+
/**
|
|
10639
|
+
* Encodes the given field values according to the specification in `target`.
|
|
10640
|
+
* For IN queries, a list of possible values is returned.
|
|
10641
|
+
*/
|
|
10642
|
+
encodeValues(fieldIndex, target, bound) {
|
|
10643
|
+
if (bound === null) {
|
|
10644
|
+
return [];
|
|
10645
|
+
}
|
|
10646
|
+
let encoders = [];
|
|
10647
|
+
encoders.push(new IndexByteEncoder());
|
|
10648
|
+
let boundIdx = 0;
|
|
10649
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
10650
|
+
const value = bound[boundIdx++];
|
|
10651
|
+
for (const encoder of encoders) {
|
|
10652
|
+
if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
|
|
10653
|
+
encoders = this.expandIndexValues(encoders, segment, value);
|
|
10654
|
+
}
|
|
10655
|
+
else {
|
|
10656
|
+
const directionalEncoder = encoder.forKind(segment.kind);
|
|
10657
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
}
|
|
10661
|
+
return this.getEncodedBytes(encoders);
|
|
10662
|
+
}
|
|
10663
|
+
/**
|
|
10664
|
+
* Encodes the given bounds according to the specification in `target`. For IN
|
|
10665
|
+
* queries, a list of possible values is returned.
|
|
10666
|
+
*/
|
|
10667
|
+
encodeBound(fieldIndex, target, bound) {
|
|
10668
|
+
if (bound == null) {
|
|
10669
|
+
return null;
|
|
10670
|
+
}
|
|
10671
|
+
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10672
|
+
}
|
|
10673
|
+
/** Returns the byte representation for the provided encoders. */
|
|
10674
|
+
getEncodedBytes(encoders) {
|
|
10675
|
+
const result = [];
|
|
10676
|
+
for (let i = 0; i < encoders.length; ++i) {
|
|
10677
|
+
result[i] = encoders[i].encodedBytes();
|
|
10678
|
+
}
|
|
10679
|
+
return result;
|
|
10680
|
+
}
|
|
10681
|
+
/**
|
|
10682
|
+
* Creates a separate encoder for each element of an array.
|
|
10683
|
+
*
|
|
10684
|
+
* The method appends each value to all existing encoders (e.g. filter("a",
|
|
10685
|
+
* "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
|
|
10686
|
+
* list of new encoders is returned.
|
|
10687
|
+
*/
|
|
10688
|
+
expandIndexValues(encoders, segment, value) {
|
|
10689
|
+
const prefixes = [...encoders];
|
|
10690
|
+
const results = [];
|
|
10691
|
+
for (const arrayElement of value.arrayValue.values || []) {
|
|
10692
|
+
for (const prefix of prefixes) {
|
|
10693
|
+
const clonedEncoder = new IndexByteEncoder();
|
|
10694
|
+
clonedEncoder.seed(prefix.encodedBytes());
|
|
10695
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
|
|
10696
|
+
results.push(clonedEncoder);
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
return results;
|
|
10700
|
+
}
|
|
10701
|
+
isInFilter(target, fieldPath) {
|
|
10702
|
+
return !!target.filters.find(f => f instanceof FieldFilter &&
|
|
10703
|
+
f.field.isEqual(fieldPath) &&
|
|
10704
|
+
(f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
|
|
10705
|
+
}
|
|
10706
|
+
getFieldIndexes(transaction, collectionGroup) {
|
|
10707
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10708
|
+
const states = indexStateStore(transaction);
|
|
10709
|
+
return (collectionGroup
|
|
10710
|
+
? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10711
|
+
: indexes.loadAll()).next(indexConfigs => {
|
|
10712
|
+
const result = [];
|
|
10713
|
+
return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
|
|
10714
|
+
return states
|
|
10715
|
+
.get([indexConfig.indexId, this.uid])
|
|
10716
|
+
.next(indexState => {
|
|
10717
|
+
result.push(fromDbIndexConfiguration(indexConfig, indexState));
|
|
10718
|
+
});
|
|
10719
|
+
}).next(() => result);
|
|
10720
|
+
});
|
|
10721
|
+
}
|
|
10722
|
+
getNextCollectionGroupToUpdate(transaction) {
|
|
10723
|
+
return this.getFieldIndexes(transaction).next(indexes => {
|
|
10724
|
+
if (indexes.length === 0) {
|
|
10725
|
+
return null;
|
|
10726
|
+
}
|
|
10727
|
+
indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
|
|
10728
|
+
return indexes[0].collectionGroup;
|
|
10729
|
+
});
|
|
10730
|
+
}
|
|
10731
|
+
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10732
|
+
const indexes = indexConfigurationStore(transaction);
|
|
10733
|
+
const states = indexStateStore(transaction);
|
|
10734
|
+
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
|
|
10735
|
+
.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
|
|
10736
|
+
.next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
|
|
10737
|
+
}
|
|
10738
|
+
updateIndexEntries(transaction, documents) {
|
|
10739
|
+
// Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
|
|
10740
|
+
// it could be used across different IndexedDB transactions. As any cached
|
|
10741
|
+
// data might be invalidated by other multi-tab clients, we can only trust
|
|
10742
|
+
// data within a single IndexedDB transaction. We therefore add a cache
|
|
10743
|
+
// here.
|
|
10744
|
+
const memoizedIndexes = new Map();
|
|
10745
|
+
return PersistencePromise.forEach(documents, (key, doc) => {
|
|
10746
|
+
const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
|
|
10747
|
+
const fieldIndexes = memoizedCollectionIndexes
|
|
10748
|
+
? PersistencePromise.resolve(memoizedCollectionIndexes)
|
|
10749
|
+
: this.getFieldIndexes(transaction, key.collectionGroup);
|
|
10750
|
+
return fieldIndexes.next(fieldIndexes => {
|
|
10751
|
+
memoizedIndexes.set(key.collectionGroup, fieldIndexes);
|
|
10752
|
+
return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
|
|
10753
|
+
return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
|
|
10754
|
+
const newEntries = this.computeIndexEntries(doc, fieldIndex);
|
|
10755
|
+
if (!existingEntries.isEqual(newEntries)) {
|
|
10756
|
+
return this.updateEntries(transaction, doc, existingEntries, newEntries);
|
|
10757
|
+
}
|
|
10758
|
+
return PersistencePromise.resolve();
|
|
10759
|
+
});
|
|
10760
|
+
});
|
|
10761
|
+
});
|
|
10762
|
+
});
|
|
10763
|
+
}
|
|
10764
|
+
addIndexEntry(transaction, document, indexEntry) {
|
|
10765
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10766
|
+
return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
|
|
10767
|
+
}
|
|
10768
|
+
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10769
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10770
|
+
return indexEntries.delete([
|
|
10771
|
+
indexEntry.indexId,
|
|
10772
|
+
this.uid,
|
|
10773
|
+
indexEntry.arrayValue,
|
|
10774
|
+
indexEntry.directionalValue,
|
|
10775
|
+
encodeResourcePath(document.key.path)
|
|
10776
|
+
]);
|
|
10777
|
+
}
|
|
10778
|
+
getExistingIndexEntries(transaction, documentKey, fieldIndex) {
|
|
10779
|
+
const indexEntries = indexEntriesStore(transaction);
|
|
10780
|
+
let results = new SortedSet(indexEntryComparator);
|
|
10781
|
+
return indexEntries
|
|
10782
|
+
.iterate({
|
|
10783
|
+
index: DbIndexEntry.documentKeyIndex,
|
|
10784
|
+
range: IDBKeyRange.only([
|
|
10785
|
+
fieldIndex.indexId,
|
|
10786
|
+
this.uid,
|
|
10787
|
+
encodeResourcePath(documentKey.path)
|
|
10788
|
+
])
|
|
10789
|
+
}, (_, entry) => {
|
|
10790
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
|
|
10791
|
+
})
|
|
10792
|
+
.next(() => results);
|
|
10793
|
+
}
|
|
10794
|
+
/** Creates the index entries for the given document. */
|
|
10795
|
+
computeIndexEntries(document, fieldIndex) {
|
|
10796
|
+
let results = new SortedSet(indexEntryComparator);
|
|
10797
|
+
const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
|
|
10798
|
+
if (directionalValue == null) {
|
|
10799
|
+
return results;
|
|
10800
|
+
}
|
|
10801
|
+
const arraySegment = fieldIndexGetArraySegment(fieldIndex);
|
|
10802
|
+
if (arraySegment != null) {
|
|
10803
|
+
const value = document.data.field(arraySegment.fieldPath);
|
|
10804
|
+
if (isArray(value)) {
|
|
10805
|
+
for (const arrayValue of value.arrayValue.values || []) {
|
|
10806
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
}
|
|
10810
|
+
else {
|
|
10811
|
+
results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
|
|
10812
|
+
}
|
|
10813
|
+
return results;
|
|
10814
|
+
}
|
|
10815
|
+
/**
|
|
10816
|
+
* Updates the index entries for the provided document by deleting entries
|
|
10817
|
+
* that are no longer referenced in `newEntries` and adding all newly added
|
|
10818
|
+
* entries.
|
|
10819
|
+
*/
|
|
10820
|
+
updateEntries(transaction, document, existingEntries, newEntries) {
|
|
10821
|
+
logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
|
|
10822
|
+
const promises = [];
|
|
10823
|
+
diffSortedSets(existingEntries, newEntries, indexEntryComparator,
|
|
10824
|
+
/* onAdd= */ entry => {
|
|
10825
|
+
promises.push(this.addIndexEntry(transaction, document, entry));
|
|
10826
|
+
},
|
|
10827
|
+
/* onRemove= */ entry => {
|
|
10828
|
+
promises.push(this.deleteIndexEntry(transaction, document, entry));
|
|
10829
|
+
});
|
|
10830
|
+
return PersistencePromise.waitFor(promises);
|
|
10831
|
+
}
|
|
10832
|
+
getNextSequenceNumber(transaction) {
|
|
10833
|
+
let nextSequenceNumber = 1;
|
|
10834
|
+
const states = indexStateStore(transaction);
|
|
10835
|
+
return states
|
|
10836
|
+
.iterate({
|
|
10837
|
+
index: DbIndexState.sequenceNumberIndex,
|
|
10838
|
+
reverse: true,
|
|
10839
|
+
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
|
|
10840
|
+
}, (_, state, controller) => {
|
|
10841
|
+
controller.done();
|
|
10842
|
+
nextSequenceNumber = state.sequenceNumber + 1;
|
|
10843
|
+
})
|
|
10844
|
+
.next(() => nextSequenceNumber);
|
|
10845
|
+
}
|
|
10846
|
+
/**
|
|
10847
|
+
* Returns a new set of IDB ranges that splits the existing range and excludes
|
|
10848
|
+
* any values that match the `notInValue` from these ranges. As an example,
|
|
10849
|
+
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10850
|
+
*/
|
|
10851
|
+
createRange(lower, upper, notInValues) {
|
|
10852
|
+
// The notIb values need to be sorted and unique so that we can return a
|
|
10853
|
+
// sorted set of non-overlapping ranges.
|
|
10854
|
+
notInValues = notInValues
|
|
10855
|
+
.sort((l, r) => indexEntryComparator(l, r))
|
|
10856
|
+
.filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
|
|
10857
|
+
const bounds = [];
|
|
10858
|
+
bounds.push(lower);
|
|
10859
|
+
for (const notInValue of notInValues) {
|
|
10860
|
+
const cmpToLower = indexEntryComparator(notInValue, lower);
|
|
10861
|
+
const cmpToUpper = indexEntryComparator(notInValue, upper);
|
|
10862
|
+
if (cmpToLower === 0) {
|
|
10863
|
+
// `notInValue` is the lower bound. We therefore need to raise the bound
|
|
10864
|
+
// to the next value.
|
|
10865
|
+
bounds[0] = lower.successor();
|
|
10866
|
+
}
|
|
10867
|
+
else if (cmpToLower > 0 && cmpToUpper < 0) {
|
|
10868
|
+
// `notInValue` is in the middle of the range
|
|
10869
|
+
bounds.push(notInValue);
|
|
10870
|
+
bounds.push(notInValue.successor());
|
|
10871
|
+
}
|
|
10872
|
+
else if (cmpToUpper > 0) {
|
|
10873
|
+
// `notInValue` (and all following values) are out of the range
|
|
10874
|
+
break;
|
|
10875
|
+
}
|
|
10876
|
+
}
|
|
10877
|
+
bounds.push(upper);
|
|
10878
|
+
const ranges = [];
|
|
10879
|
+
for (let i = 0; i < bounds.length; i += 2) {
|
|
10880
|
+
ranges.push(IDBKeyRange.bound([
|
|
10881
|
+
bounds[i].indexId,
|
|
10882
|
+
this.uid,
|
|
10883
|
+
bounds[i].arrayValue,
|
|
10884
|
+
bounds[i].directionalValue,
|
|
10885
|
+
''
|
|
10886
|
+
], [
|
|
10887
|
+
bounds[i + 1].indexId,
|
|
10888
|
+
this.uid,
|
|
10889
|
+
bounds[i + 1].arrayValue,
|
|
10890
|
+
bounds[i + 1].directionalValue,
|
|
10891
|
+
''
|
|
10892
|
+
]));
|
|
10893
|
+
}
|
|
10894
|
+
return ranges;
|
|
10895
|
+
}
|
|
8586
10896
|
}
|
|
8587
10897
|
/**
|
|
8588
10898
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -8590,6 +10900,24 @@ class IndexedDbIndexManager {
|
|
|
8590
10900
|
*/
|
|
8591
10901
|
function collectionParentsStore(txn) {
|
|
8592
10902
|
return getStore(txn, DbCollectionParent.store);
|
|
10903
|
+
}
|
|
10904
|
+
/**
|
|
10905
|
+
* Helper to get a typed SimpleDbStore for the index entry object store.
|
|
10906
|
+
*/
|
|
10907
|
+
function indexEntriesStore(txn) {
|
|
10908
|
+
return getStore(txn, DbIndexEntry.store);
|
|
10909
|
+
}
|
|
10910
|
+
/**
|
|
10911
|
+
* Helper to get a typed SimpleDbStore for the index configuration object store.
|
|
10912
|
+
*/
|
|
10913
|
+
function indexConfigurationStore(txn) {
|
|
10914
|
+
return getStore(txn, DbIndexConfiguration.store);
|
|
10915
|
+
}
|
|
10916
|
+
/**
|
|
10917
|
+
* Helper to get a typed SimpleDbStore for the index state object store.
|
|
10918
|
+
*/
|
|
10919
|
+
function indexStateStore(txn) {
|
|
10920
|
+
return getStore(txn, DbIndexState.store);
|
|
8593
10921
|
}
|
|
8594
10922
|
|
|
8595
10923
|
/**
|
|
@@ -9753,7 +12081,7 @@ class IndexedDbLruDelegateImpl {
|
|
|
9753
12081
|
// Our size accounting requires us to read all documents before
|
|
9754
12082
|
// removing them.
|
|
9755
12083
|
return changeBuffer.getEntry(txn, docKey).next(() => {
|
|
9756
|
-
changeBuffer.removeEntry(docKey);
|
|
12084
|
+
changeBuffer.removeEntry(docKey, SnapshotVersion.min());
|
|
9757
12085
|
return documentTargetStore(txn).delete(sentinelKey$1(docKey));
|
|
9758
12086
|
});
|
|
9759
12087
|
}
|
|
@@ -9775,163 +12103,62 @@ class IndexedDbLruDelegateImpl {
|
|
|
9775
12103
|
}
|
|
9776
12104
|
/**
|
|
9777
12105
|
* Call provided function for each document in the cache that is 'orphaned'. Orphaned
|
|
9778
|
-
* means not a part of any target, so the only entry in the target-document index for
|
|
9779
|
-
* that document will be the sentinel row (targetId 0), which will also have the sequence
|
|
9780
|
-
* number for the last time the document was accessed.
|
|
9781
|
-
*/
|
|
9782
|
-
forEachOrphanedDocument(txn, f) {
|
|
9783
|
-
const store = documentTargetStore(txn);
|
|
9784
|
-
let nextToReport = ListenSequence.INVALID;
|
|
9785
|
-
let nextPath;
|
|
9786
|
-
return store
|
|
9787
|
-
.iterate({
|
|
9788
|
-
index: DbTargetDocument.documentTargetsIndex
|
|
9789
|
-
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
9790
|
-
if (targetId === 0) {
|
|
9791
|
-
// if nextToReport is valid, report it, this is a new key so the
|
|
9792
|
-
// last one must not be a member of any targets.
|
|
9793
|
-
if (nextToReport !== ListenSequence.INVALID) {
|
|
9794
|
-
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9795
|
-
}
|
|
9796
|
-
// set nextToReport to be this sequence number. It's the next one we
|
|
9797
|
-
// might report, if we don't find any targets for this document.
|
|
9798
|
-
// Note that the sequence number must be defined when the targetId
|
|
9799
|
-
// is 0.
|
|
9800
|
-
nextToReport = sequenceNumber;
|
|
9801
|
-
nextPath = path;
|
|
9802
|
-
}
|
|
9803
|
-
else {
|
|
9804
|
-
// set nextToReport to be invalid, we know we don't need to report
|
|
9805
|
-
// this one since we found a target for it.
|
|
9806
|
-
nextToReport = ListenSequence.INVALID;
|
|
9807
|
-
}
|
|
9808
|
-
})
|
|
9809
|
-
.next(() => {
|
|
9810
|
-
// Since we report sequence numbers after getting to the next key, we
|
|
9811
|
-
// need to check if the last key we iterated over was an orphaned
|
|
9812
|
-
// document and report it.
|
|
9813
|
-
if (nextToReport !== ListenSequence.INVALID) {
|
|
9814
|
-
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9815
|
-
}
|
|
9816
|
-
});
|
|
9817
|
-
}
|
|
9818
|
-
getCacheSize(txn) {
|
|
9819
|
-
return this.db.getRemoteDocumentCache().getSize(txn);
|
|
9820
|
-
}
|
|
9821
|
-
}
|
|
9822
|
-
function sentinelKey$1(key) {
|
|
9823
|
-
return [0, encodeResourcePath(key.path)];
|
|
9824
|
-
}
|
|
9825
|
-
/**
|
|
9826
|
-
* @returns A value suitable for writing a sentinel row in the target-document
|
|
9827
|
-
* store.
|
|
9828
|
-
*/
|
|
9829
|
-
function sentinelRow(key, sequenceNumber) {
|
|
9830
|
-
return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
|
|
9831
|
-
}
|
|
9832
|
-
function writeSentinelKey(txn, key) {
|
|
9833
|
-
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
9834
|
-
}
|
|
9835
|
-
|
|
9836
|
-
/**
|
|
9837
|
-
* @license
|
|
9838
|
-
* Copyright 2017 Google LLC
|
|
9839
|
-
*
|
|
9840
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9841
|
-
* you may not use this file except in compliance with the License.
|
|
9842
|
-
* You may obtain a copy of the License at
|
|
9843
|
-
*
|
|
9844
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9845
|
-
*
|
|
9846
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
9847
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
9848
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9849
|
-
* See the License for the specific language governing permissions and
|
|
9850
|
-
* limitations under the License.
|
|
9851
|
-
*/
|
|
9852
|
-
/**
|
|
9853
|
-
* A map implementation that uses objects as keys. Objects must have an
|
|
9854
|
-
* associated equals function and must be immutable. Entries in the map are
|
|
9855
|
-
* stored together with the key being produced from the mapKeyFn. This map
|
|
9856
|
-
* automatically handles collisions of keys.
|
|
9857
|
-
*/
|
|
9858
|
-
class ObjectMap {
|
|
9859
|
-
constructor(mapKeyFn, equalsFn) {
|
|
9860
|
-
this.mapKeyFn = mapKeyFn;
|
|
9861
|
-
this.equalsFn = equalsFn;
|
|
9862
|
-
/**
|
|
9863
|
-
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
9864
|
-
* keep a list of entries that we do a linear search through to find an actual
|
|
9865
|
-
* match. Note that collisions should be rare, so we still expect near
|
|
9866
|
-
* constant time lookups in practice.
|
|
9867
|
-
*/
|
|
9868
|
-
this.inner = {};
|
|
9869
|
-
}
|
|
9870
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
9871
|
-
get(key) {
|
|
9872
|
-
const id = this.mapKeyFn(key);
|
|
9873
|
-
const matches = this.inner[id];
|
|
9874
|
-
if (matches === undefined) {
|
|
9875
|
-
return undefined;
|
|
9876
|
-
}
|
|
9877
|
-
for (const [otherKey, value] of matches) {
|
|
9878
|
-
if (this.equalsFn(otherKey, key)) {
|
|
9879
|
-
return value;
|
|
9880
|
-
}
|
|
9881
|
-
}
|
|
9882
|
-
return undefined;
|
|
9883
|
-
}
|
|
9884
|
-
has(key) {
|
|
9885
|
-
return this.get(key) !== undefined;
|
|
9886
|
-
}
|
|
9887
|
-
/** Put this key and value in the map. */
|
|
9888
|
-
set(key, value) {
|
|
9889
|
-
const id = this.mapKeyFn(key);
|
|
9890
|
-
const matches = this.inner[id];
|
|
9891
|
-
if (matches === undefined) {
|
|
9892
|
-
this.inner[id] = [[key, value]];
|
|
9893
|
-
return;
|
|
9894
|
-
}
|
|
9895
|
-
for (let i = 0; i < matches.length; i++) {
|
|
9896
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
9897
|
-
matches[i] = [key, value];
|
|
9898
|
-
return;
|
|
9899
|
-
}
|
|
9900
|
-
}
|
|
9901
|
-
matches.push([key, value]);
|
|
9902
|
-
}
|
|
9903
|
-
/**
|
|
9904
|
-
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
9905
|
-
*/
|
|
9906
|
-
delete(key) {
|
|
9907
|
-
const id = this.mapKeyFn(key);
|
|
9908
|
-
const matches = this.inner[id];
|
|
9909
|
-
if (matches === undefined) {
|
|
9910
|
-
return false;
|
|
9911
|
-
}
|
|
9912
|
-
for (let i = 0; i < matches.length; i++) {
|
|
9913
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
9914
|
-
if (matches.length === 1) {
|
|
9915
|
-
delete this.inner[id];
|
|
9916
|
-
}
|
|
9917
|
-
else {
|
|
9918
|
-
matches.splice(i, 1);
|
|
12106
|
+
* means not a part of any target, so the only entry in the target-document index for
|
|
12107
|
+
* that document will be the sentinel row (targetId 0), which will also have the sequence
|
|
12108
|
+
* number for the last time the document was accessed.
|
|
12109
|
+
*/
|
|
12110
|
+
forEachOrphanedDocument(txn, f) {
|
|
12111
|
+
const store = documentTargetStore(txn);
|
|
12112
|
+
let nextToReport = ListenSequence.INVALID;
|
|
12113
|
+
let nextPath;
|
|
12114
|
+
return store
|
|
12115
|
+
.iterate({
|
|
12116
|
+
index: DbTargetDocument.documentTargetsIndex
|
|
12117
|
+
}, ([targetId, docKey], { path, sequenceNumber }) => {
|
|
12118
|
+
if (targetId === 0) {
|
|
12119
|
+
// if nextToReport is valid, report it, this is a new key so the
|
|
12120
|
+
// last one must not be a member of any targets.
|
|
12121
|
+
if (nextToReport !== ListenSequence.INVALID) {
|
|
12122
|
+
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9919
12123
|
}
|
|
9920
|
-
|
|
12124
|
+
// set nextToReport to be this sequence number. It's the next one we
|
|
12125
|
+
// might report, if we don't find any targets for this document.
|
|
12126
|
+
// Note that the sequence number must be defined when the targetId
|
|
12127
|
+
// is 0.
|
|
12128
|
+
nextToReport = sequenceNumber;
|
|
12129
|
+
nextPath = path;
|
|
9921
12130
|
}
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
12131
|
+
else {
|
|
12132
|
+
// set nextToReport to be invalid, we know we don't need to report
|
|
12133
|
+
// this one since we found a target for it.
|
|
12134
|
+
nextToReport = ListenSequence.INVALID;
|
|
12135
|
+
}
|
|
12136
|
+
})
|
|
12137
|
+
.next(() => {
|
|
12138
|
+
// Since we report sequence numbers after getting to the next key, we
|
|
12139
|
+
// need to check if the last key we iterated over was an orphaned
|
|
12140
|
+
// document and report it.
|
|
12141
|
+
if (nextToReport !== ListenSequence.INVALID) {
|
|
12142
|
+
f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
|
|
9929
12143
|
}
|
|
9930
12144
|
});
|
|
9931
12145
|
}
|
|
9932
|
-
|
|
9933
|
-
return
|
|
12146
|
+
getCacheSize(txn) {
|
|
12147
|
+
return this.db.getRemoteDocumentCache().getSize(txn);
|
|
9934
12148
|
}
|
|
12149
|
+
}
|
|
12150
|
+
function sentinelKey$1(key) {
|
|
12151
|
+
return [0, encodeResourcePath(key.path)];
|
|
12152
|
+
}
|
|
12153
|
+
/**
|
|
12154
|
+
* @returns A value suitable for writing a sentinel row in the target-document
|
|
12155
|
+
* store.
|
|
12156
|
+
*/
|
|
12157
|
+
function sentinelRow(key, sequenceNumber) {
|
|
12158
|
+
return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
|
|
12159
|
+
}
|
|
12160
|
+
function writeSentinelKey(txn, key) {
|
|
12161
|
+
return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
|
|
9935
12162
|
}
|
|
9936
12163
|
|
|
9937
12164
|
/**
|
|
@@ -9966,27 +12193,19 @@ class ObjectMap {
|
|
|
9966
12193
|
*/
|
|
9967
12194
|
class RemoteDocumentChangeBuffer {
|
|
9968
12195
|
constructor() {
|
|
9969
|
-
// A mapping of document key to the new cache entry that should be written
|
|
9970
|
-
// existing cache entry should be removed).
|
|
12196
|
+
// A mapping of document key to the new cache entry that should be written.
|
|
9971
12197
|
this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
9972
12198
|
this.changesApplied = false;
|
|
9973
12199
|
}
|
|
9974
|
-
getReadTime(key) {
|
|
9975
|
-
const change = this.changes.get(key);
|
|
9976
|
-
if (change) {
|
|
9977
|
-
return change.readTime;
|
|
9978
|
-
}
|
|
9979
|
-
return SnapshotVersion.min();
|
|
9980
|
-
}
|
|
9981
12200
|
/**
|
|
9982
12201
|
* Buffers a `RemoteDocumentCache.addEntry()` call.
|
|
9983
12202
|
*
|
|
9984
12203
|
* You can only modify documents that have already been retrieved via
|
|
9985
12204
|
* `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
|
|
9986
12205
|
*/
|
|
9987
|
-
addEntry(document
|
|
12206
|
+
addEntry(document) {
|
|
9988
12207
|
this.assertNotApplied();
|
|
9989
|
-
this.changes.set(document.key,
|
|
12208
|
+
this.changes.set(document.key, document);
|
|
9990
12209
|
}
|
|
9991
12210
|
/**
|
|
9992
12211
|
* Buffers a `RemoteDocumentCache.removeEntry()` call.
|
|
@@ -9994,12 +12213,9 @@ class RemoteDocumentChangeBuffer {
|
|
|
9994
12213
|
* You can only remove documents that have already been retrieved via
|
|
9995
12214
|
* `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
|
|
9996
12215
|
*/
|
|
9997
|
-
removeEntry(key, readTime
|
|
12216
|
+
removeEntry(key, readTime) {
|
|
9998
12217
|
this.assertNotApplied();
|
|
9999
|
-
this.changes.set(key,
|
|
10000
|
-
document: MutableDocument.newInvalidDocument(key),
|
|
10001
|
-
readTime
|
|
10002
|
-
});
|
|
12218
|
+
this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
|
|
10003
12219
|
}
|
|
10004
12220
|
/**
|
|
10005
12221
|
* Looks up an entry in the cache. The buffered changes will first be checked,
|
|
@@ -10016,7 +12232,7 @@ class RemoteDocumentChangeBuffer {
|
|
|
10016
12232
|
this.assertNotApplied();
|
|
10017
12233
|
const bufferedEntry = this.changes.get(documentKey);
|
|
10018
12234
|
if (bufferedEntry !== undefined) {
|
|
10019
|
-
return PersistencePromise.resolve(bufferedEntry
|
|
12235
|
+
return PersistencePromise.resolve(bufferedEntry);
|
|
10020
12236
|
}
|
|
10021
12237
|
else {
|
|
10022
12238
|
return this.getFromCache(transaction, documentKey);
|
|
@@ -10070,12 +12286,10 @@ class RemoteDocumentChangeBuffer {
|
|
|
10070
12286
|
* `newIndexedDbRemoteDocumentCache()`.
|
|
10071
12287
|
*/
|
|
10072
12288
|
class IndexedDbRemoteDocumentCacheImpl {
|
|
10073
|
-
|
|
10074
|
-
* @param serializer - The document serializer.
|
|
10075
|
-
* @param indexManager - The query indexes that need to be maintained.
|
|
10076
|
-
*/
|
|
10077
|
-
constructor(serializer, indexManager) {
|
|
12289
|
+
constructor(serializer) {
|
|
10078
12290
|
this.serializer = serializer;
|
|
12291
|
+
}
|
|
12292
|
+
setIndexManager(indexManager) {
|
|
10079
12293
|
this.indexManager = indexManager;
|
|
10080
12294
|
}
|
|
10081
12295
|
/**
|
|
@@ -10197,21 +12411,21 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10197
12411
|
}
|
|
10198
12412
|
});
|
|
10199
12413
|
}
|
|
10200
|
-
|
|
12414
|
+
getAll(transaction, collection, sinceReadTime) {
|
|
10201
12415
|
let results = mutableDocumentMap();
|
|
10202
|
-
const immediateChildrenPathLength =
|
|
12416
|
+
const immediateChildrenPathLength = collection.length + 1;
|
|
10203
12417
|
const iterationOptions = {};
|
|
10204
12418
|
if (sinceReadTime.isEqual(SnapshotVersion.min())) {
|
|
10205
12419
|
// Documents are ordered by key, so we can use a prefix scan to narrow
|
|
10206
12420
|
// down the documents we need to match the query against.
|
|
10207
|
-
const startKey =
|
|
12421
|
+
const startKey = collection.toArray();
|
|
10208
12422
|
iterationOptions.range = IDBKeyRange.lowerBound(startKey);
|
|
10209
12423
|
}
|
|
10210
12424
|
else {
|
|
10211
12425
|
// Execute an index-free query and filter by read time. This is safe
|
|
10212
12426
|
// since all document changes to queries that have a
|
|
10213
12427
|
// lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
|
|
10214
|
-
const collectionKey =
|
|
12428
|
+
const collectionKey = collection.toArray();
|
|
10215
12429
|
const readTimeKey = toDbTimestampKey(sinceReadTime);
|
|
10216
12430
|
iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
|
|
10217
12431
|
/* open= */ true);
|
|
@@ -10227,13 +12441,13 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10227
12441
|
if (key.length !== immediateChildrenPathLength) {
|
|
10228
12442
|
return;
|
|
10229
12443
|
}
|
|
10230
|
-
const document =
|
|
10231
|
-
if (
|
|
10232
|
-
control.done();
|
|
10233
|
-
}
|
|
10234
|
-
else if (queryMatches(query, document)) {
|
|
12444
|
+
const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
|
|
12445
|
+
if (collection.isPrefixOf(document.key.path)) {
|
|
10235
12446
|
results = results.insert(document.key, document);
|
|
10236
12447
|
}
|
|
12448
|
+
else {
|
|
12449
|
+
control.done();
|
|
12450
|
+
}
|
|
10237
12451
|
})
|
|
10238
12452
|
.next(() => results);
|
|
10239
12453
|
}
|
|
@@ -10255,8 +12469,8 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10255
12469
|
return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
|
|
10256
12470
|
}
|
|
10257
12471
|
/**
|
|
10258
|
-
* Decodes `
|
|
10259
|
-
* corresponds to the format used for sentinel deletes).
|
|
12472
|
+
* Decodes `dbRemoteDoc` and returns the document (or an invalid document if
|
|
12473
|
+
* the document corresponds to the format used for sentinel deletes).
|
|
10260
12474
|
*/
|
|
10261
12475
|
maybeDecodeDocument(documentKey, dbRemoteDoc) {
|
|
10262
12476
|
if (dbRemoteDoc) {
|
|
@@ -10271,14 +12485,9 @@ class IndexedDbRemoteDocumentCacheImpl {
|
|
|
10271
12485
|
return MutableDocument.newInvalidDocument(documentKey);
|
|
10272
12486
|
}
|
|
10273
12487
|
}
|
|
10274
|
-
/**
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
* @param serializer - The document serializer.
|
|
10278
|
-
* @param indexManager - The query indexes that need to be maintained.
|
|
10279
|
-
*/
|
|
10280
|
-
function newIndexedDbRemoteDocumentCache(serializer, indexManager) {
|
|
10281
|
-
return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
|
|
12488
|
+
/** Creates a new IndexedDbRemoteDocumentCache. */
|
|
12489
|
+
function newIndexedDbRemoteDocumentCache(serializer) {
|
|
12490
|
+
return new IndexedDbRemoteDocumentCacheImpl(serializer);
|
|
10282
12491
|
}
|
|
10283
12492
|
/**
|
|
10284
12493
|
* Returns the set of documents that have changed since the specified read
|
|
@@ -10350,8 +12559,8 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
10350
12559
|
let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
|
|
10351
12560
|
this.changes.forEach((key, documentChange) => {
|
|
10352
12561
|
const previousSize = this.documentSizes.get(key);
|
|
10353
|
-
if (documentChange.
|
|
10354
|
-
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange
|
|
12562
|
+
if (documentChange.isValidDocument()) {
|
|
12563
|
+
const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
|
|
10355
12564
|
collectionParents = collectionParents.add(key.path.popLast());
|
|
10356
12565
|
const size = dbDocumentSize(doc);
|
|
10357
12566
|
sizeDelta += size - previousSize;
|
|
@@ -10364,7 +12573,7 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
10364
12573
|
// RemoteDocumentCache. This entry is represented by a NoDocument
|
|
10365
12574
|
// with a version of 0 and ignored by `maybeDecodeDocument()` but
|
|
10366
12575
|
// preserved in `getNewDocumentChanges()`.
|
|
10367
|
-
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer,
|
|
12576
|
+
const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
|
|
10368
12577
|
promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
|
|
10369
12578
|
}
|
|
10370
12579
|
else {
|
|
@@ -10445,9 +12654,6 @@ class SchemaConverter {
|
|
|
10445
12654
|
* and local feature development.
|
|
10446
12655
|
*/
|
|
10447
12656
|
createOrUpgrade(db, txn, fromVersion, toVersion) {
|
|
10448
|
-
hardAssert(fromVersion < toVersion &&
|
|
10449
|
-
fromVersion >= 0 &&
|
|
10450
|
-
toVersion <= SCHEMA_VERSION);
|
|
10451
12657
|
const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
|
|
10452
12658
|
if (fromVersion < 1 && toVersion >= 1) {
|
|
10453
12659
|
createPrimaryClientStore(db);
|
|
@@ -10514,6 +12720,16 @@ class SchemaConverter {
|
|
|
10514
12720
|
createNamedQueriesStore(db);
|
|
10515
12721
|
});
|
|
10516
12722
|
}
|
|
12723
|
+
if (fromVersion < 12 && toVersion >= 12) {
|
|
12724
|
+
p = p.next(() => {
|
|
12725
|
+
createDocumentOverlayStore(db);
|
|
12726
|
+
});
|
|
12727
|
+
}
|
|
12728
|
+
if (fromVersion < 13 && toVersion >= 13) {
|
|
12729
|
+
p = p.next(() => {
|
|
12730
|
+
createFieldIndex(db);
|
|
12731
|
+
});
|
|
12732
|
+
}
|
|
10517
12733
|
return p;
|
|
10518
12734
|
}
|
|
10519
12735
|
addDocumentGlobal(txn) {
|
|
@@ -10720,6 +12936,28 @@ function createNamedQueriesStore(db) {
|
|
|
10720
12936
|
db.createObjectStore(DbNamedQuery.store, {
|
|
10721
12937
|
keyPath: DbNamedQuery.keyPath
|
|
10722
12938
|
});
|
|
12939
|
+
}
|
|
12940
|
+
function createFieldIndex(db) {
|
|
12941
|
+
const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
|
|
12942
|
+
keyPath: DbIndexConfiguration.keyPath,
|
|
12943
|
+
autoIncrement: true
|
|
12944
|
+
});
|
|
12945
|
+
indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
|
|
12946
|
+
const indexStateStore = db.createObjectStore(DbIndexState.store, {
|
|
12947
|
+
keyPath: DbIndexState.keyPath
|
|
12948
|
+
});
|
|
12949
|
+
indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
|
|
12950
|
+
const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
|
|
12951
|
+
keyPath: DbIndexEntry.keyPath
|
|
12952
|
+
});
|
|
12953
|
+
indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
|
|
12954
|
+
}
|
|
12955
|
+
function createDocumentOverlayStore(db) {
|
|
12956
|
+
const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
|
|
12957
|
+
keyPath: DbDocumentOverlay.keyPath
|
|
12958
|
+
});
|
|
12959
|
+
documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
|
|
12960
|
+
documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
|
|
10723
12961
|
}
|
|
10724
12962
|
|
|
10725
12963
|
/**
|
|
@@ -10831,7 +13069,7 @@ class IndexedDbPersistence {
|
|
|
10831
13069
|
* If set to true, forcefully obtains database access. Existing tabs will
|
|
10832
13070
|
* no longer be able to access IndexedDB.
|
|
10833
13071
|
*/
|
|
10834
|
-
forceOwningTab) {
|
|
13072
|
+
forceOwningTab, schemaVersion = SCHEMA_VERSION) {
|
|
10835
13073
|
this.allowTabSynchronization = allowTabSynchronization;
|
|
10836
13074
|
this.persistenceKey = persistenceKey;
|
|
10837
13075
|
this.clientId = clientId;
|
|
@@ -10840,6 +13078,7 @@ class IndexedDbPersistence {
|
|
|
10840
13078
|
this.document = document;
|
|
10841
13079
|
this.sequenceNumberSyncer = sequenceNumberSyncer;
|
|
10842
13080
|
this.forceOwningTab = forceOwningTab;
|
|
13081
|
+
this.schemaVersion = schemaVersion;
|
|
10843
13082
|
this.listenSequence = null;
|
|
10844
13083
|
this._started = false;
|
|
10845
13084
|
this.isPrimary = false;
|
|
@@ -10861,10 +13100,9 @@ class IndexedDbPersistence {
|
|
|
10861
13100
|
this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
|
|
10862
13101
|
this.dbName = persistenceKey + MAIN_DATABASE;
|
|
10863
13102
|
this.serializer = new LocalSerializer(serializer);
|
|
10864
|
-
this.simpleDb = new SimpleDb(this.dbName,
|
|
13103
|
+
this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
|
|
10865
13104
|
this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
|
|
10866
|
-
this.
|
|
10867
|
-
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer, this.indexManager);
|
|
13105
|
+
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
|
|
10868
13106
|
this.bundleCache = new IndexedDbBundleCache();
|
|
10869
13107
|
if (this.window && this.window.localStorage) {
|
|
10870
13108
|
this.webStorage = this.window.localStorage;
|
|
@@ -11200,8 +13438,8 @@ class IndexedDbPersistence {
|
|
|
11200
13438
|
get started() {
|
|
11201
13439
|
return this._started;
|
|
11202
13440
|
}
|
|
11203
|
-
getMutationQueue(user) {
|
|
11204
|
-
return IndexedDbMutationQueue.forUser(user, this.serializer,
|
|
13441
|
+
getMutationQueue(user, indexManager) {
|
|
13442
|
+
return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
|
|
11205
13443
|
}
|
|
11206
13444
|
getTargetCache() {
|
|
11207
13445
|
return this.targetCache;
|
|
@@ -11209,8 +13447,11 @@ class IndexedDbPersistence {
|
|
|
11209
13447
|
getRemoteDocumentCache() {
|
|
11210
13448
|
return this.remoteDocumentCache;
|
|
11211
13449
|
}
|
|
11212
|
-
getIndexManager() {
|
|
11213
|
-
return
|
|
13450
|
+
getIndexManager(user) {
|
|
13451
|
+
return new IndexedDbIndexManager(user);
|
|
13452
|
+
}
|
|
13453
|
+
getDocumentOverlayCache(user) {
|
|
13454
|
+
return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
|
|
11214
13455
|
}
|
|
11215
13456
|
getBundleCache() {
|
|
11216
13457
|
return this.bundleCache;
|
|
@@ -11218,11 +13459,12 @@ class IndexedDbPersistence {
|
|
|
11218
13459
|
runTransaction(action, mode, transactionOperation) {
|
|
11219
13460
|
logDebug(LOG_TAG$d, 'Starting transaction:', action);
|
|
11220
13461
|
const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
|
|
13462
|
+
const objectStores = getObjectStores(this.schemaVersion);
|
|
11221
13463
|
let persistenceTransaction;
|
|
11222
13464
|
// Do all transactions as readwrite against all object stores, since we
|
|
11223
13465
|
// are the only reader/writer.
|
|
11224
13466
|
return this.simpleDb
|
|
11225
|
-
.runTransaction(action, simpleDbMode,
|
|
13467
|
+
.runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
|
|
11226
13468
|
persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
|
|
11227
13469
|
? this.listenSequence.next()
|
|
11228
13470
|
: ListenSequence.INVALID);
|
|
@@ -11594,39 +13836,28 @@ class LocalDocumentsView {
|
|
|
11594
13836
|
getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
|
|
11595
13837
|
// Query the remote documents and overlay mutations.
|
|
11596
13838
|
let results;
|
|
11597
|
-
let mutationBatches;
|
|
11598
13839
|
return this.remoteDocumentCache
|
|
11599
|
-
.
|
|
13840
|
+
.getAll(transaction, query.path, sinceReadTime)
|
|
11600
13841
|
.next(queryResults => {
|
|
11601
13842
|
results = queryResults;
|
|
11602
13843
|
return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
|
|
11603
13844
|
})
|
|
11604
|
-
.next(
|
|
11605
|
-
mutationBatches
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
let document = results.get(key);
|
|
11618
|
-
if (document == null) {
|
|
11619
|
-
// Create invalid document to apply mutations on top of
|
|
11620
|
-
document = MutableDocument.newInvalidDocument(key);
|
|
11621
|
-
results = results.insert(key, document);
|
|
11622
|
-
}
|
|
11623
|
-
mutationApplyToLocalView(mutation, document, batch.localWriteTime);
|
|
11624
|
-
if (!document.isFoundDocument()) {
|
|
11625
|
-
results = results.remove(key);
|
|
11626
|
-
}
|
|
13845
|
+
.next(mutationBatches => {
|
|
13846
|
+
for (const batch of mutationBatches) {
|
|
13847
|
+
for (const mutation of batch.mutations) {
|
|
13848
|
+
const key = mutation.key;
|
|
13849
|
+
let document = results.get(key);
|
|
13850
|
+
if (document == null) {
|
|
13851
|
+
// Create invalid document to apply mutations on top of
|
|
13852
|
+
document = MutableDocument.newInvalidDocument(key);
|
|
13853
|
+
results = results.insert(key, document);
|
|
13854
|
+
}
|
|
13855
|
+
mutationApplyToLocalView(mutation, document, batch.localWriteTime);
|
|
13856
|
+
if (!document.isFoundDocument()) {
|
|
13857
|
+
results = results.remove(key);
|
|
11627
13858
|
}
|
|
11628
13859
|
}
|
|
11629
|
-
}
|
|
13860
|
+
}
|
|
11630
13861
|
})
|
|
11631
13862
|
.next(() => {
|
|
11632
13863
|
// Finally, filter out any documents that don't actually match
|
|
@@ -11639,29 +13870,6 @@ class LocalDocumentsView {
|
|
|
11639
13870
|
return results;
|
|
11640
13871
|
});
|
|
11641
13872
|
}
|
|
11642
|
-
addMissingBaseDocuments(transaction, matchingMutationBatches, existingDocuments) {
|
|
11643
|
-
let missingBaseDocEntriesForPatching = documentKeySet();
|
|
11644
|
-
for (const batch of matchingMutationBatches) {
|
|
11645
|
-
for (const mutation of batch.mutations) {
|
|
11646
|
-
if (mutation instanceof PatchMutation &&
|
|
11647
|
-
existingDocuments.get(mutation.key) === null) {
|
|
11648
|
-
missingBaseDocEntriesForPatching =
|
|
11649
|
-
missingBaseDocEntriesForPatching.add(mutation.key);
|
|
11650
|
-
}
|
|
11651
|
-
}
|
|
11652
|
-
}
|
|
11653
|
-
let mergedDocuments = existingDocuments;
|
|
11654
|
-
return this.remoteDocumentCache
|
|
11655
|
-
.getEntries(transaction, missingBaseDocEntriesForPatching)
|
|
11656
|
-
.next(missingBaseDocs => {
|
|
11657
|
-
missingBaseDocs.forEach((key, doc) => {
|
|
11658
|
-
if (doc.isFoundDocument()) {
|
|
11659
|
-
mergedDocuments = mergedDocuments.insert(key, doc);
|
|
11660
|
-
}
|
|
11661
|
-
});
|
|
11662
|
-
return mergedDocuments;
|
|
11663
|
-
});
|
|
11664
|
-
}
|
|
11665
13873
|
}
|
|
11666
13874
|
|
|
11667
13875
|
/**
|
|
@@ -11720,11 +13928,18 @@ class LocalStoreImpl {
|
|
|
11720
13928
|
* PORTING NOTE: This is only used for multi-tab synchronization.
|
|
11721
13929
|
*/
|
|
11722
13930
|
this.lastDocumentChangeReadTime = SnapshotVersion.min();
|
|
11723
|
-
this.mutationQueue = persistence.getMutationQueue(initialUser);
|
|
11724
13931
|
this.remoteDocuments = persistence.getRemoteDocumentCache();
|
|
11725
13932
|
this.targetCache = persistence.getTargetCache();
|
|
11726
|
-
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.persistence.getIndexManager());
|
|
11727
13933
|
this.bundleCache = persistence.getBundleCache();
|
|
13934
|
+
this.initializeUserComponents(initialUser);
|
|
13935
|
+
}
|
|
13936
|
+
initializeUserComponents(user) {
|
|
13937
|
+
// TODO(indexing): Add spec tests that test these components change after a
|
|
13938
|
+
// user change
|
|
13939
|
+
this.indexManager = this.persistence.getIndexManager(user);
|
|
13940
|
+
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13941
|
+
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13942
|
+
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
11728
13943
|
this.queryEngine.setLocalDocumentsView(this.localDocuments);
|
|
11729
13944
|
}
|
|
11730
13945
|
collectGarbage(garbageCollector) {
|
|
@@ -11746,8 +13961,6 @@ persistence, queryEngine, initialUser, serializer) {
|
|
|
11746
13961
|
// change.
|
|
11747
13962
|
async function localStoreHandleUserChange(localStore, user) {
|
|
11748
13963
|
const localStoreImpl = debugCast(localStore);
|
|
11749
|
-
let newMutationQueue = localStoreImpl.mutationQueue;
|
|
11750
|
-
let newLocalDocuments = localStoreImpl.localDocuments;
|
|
11751
13964
|
const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
|
|
11752
13965
|
// Swap out the mutation queue, grabbing the pending mutation batches
|
|
11753
13966
|
// before and after.
|
|
@@ -11756,11 +13969,8 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11756
13969
|
.getAllMutationBatches(txn)
|
|
11757
13970
|
.next(promisedOldBatches => {
|
|
11758
13971
|
oldBatches = promisedOldBatches;
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
// MutationQueue.
|
|
11762
|
-
newLocalDocuments = new LocalDocumentsView(localStoreImpl.remoteDocuments, newMutationQueue, localStoreImpl.persistence.getIndexManager());
|
|
11763
|
-
return newMutationQueue.getAllMutationBatches(txn);
|
|
13972
|
+
localStoreImpl.initializeUserComponents(user);
|
|
13973
|
+
return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
|
|
11764
13974
|
})
|
|
11765
13975
|
.next(newBatches => {
|
|
11766
13976
|
const removedBatchIds = [];
|
|
@@ -11781,7 +13991,7 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11781
13991
|
}
|
|
11782
13992
|
// Return the set of all (potentially) changed documents and the list
|
|
11783
13993
|
// of mutation batch IDs that were affected by change.
|
|
11784
|
-
return
|
|
13994
|
+
return localStoreImpl.localDocuments
|
|
11785
13995
|
.getDocuments(txn, changedKeys)
|
|
11786
13996
|
.next(affectedDocuments => {
|
|
11787
13997
|
return {
|
|
@@ -11792,9 +14002,6 @@ async function localStoreHandleUserChange(localStore, user) {
|
|
|
11792
14002
|
});
|
|
11793
14003
|
});
|
|
11794
14004
|
});
|
|
11795
|
-
localStoreImpl.mutationQueue = newMutationQueue;
|
|
11796
|
-
localStoreImpl.localDocuments = newLocalDocuments;
|
|
11797
|
-
localStoreImpl.queryEngine.setLocalDocumentsView(localStoreImpl.localDocuments);
|
|
11798
14005
|
return result;
|
|
11799
14006
|
}
|
|
11800
14007
|
/* Accepts locally generated Mutations and commit them to storage. */
|
|
@@ -11951,14 +14158,14 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
|
|
|
11951
14158
|
}
|
|
11952
14159
|
});
|
|
11953
14160
|
let changedDocs = mutableDocumentMap();
|
|
11954
|
-
remoteEvent.documentUpdates.forEach(
|
|
14161
|
+
remoteEvent.documentUpdates.forEach(key => {
|
|
11955
14162
|
if (remoteEvent.resolvedLimboDocuments.has(key)) {
|
|
11956
14163
|
promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
|
|
11957
14164
|
}
|
|
11958
14165
|
});
|
|
11959
14166
|
// Each loop iteration only affects its "own" doc, so it's safe to get all the remote
|
|
11960
14167
|
// documents in advance in a single call.
|
|
11961
|
-
promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates
|
|
14168
|
+
promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
|
|
11962
14169
|
changedDocs = result;
|
|
11963
14170
|
}));
|
|
11964
14171
|
// HACK: The only reason we allow a null snapshot version is so that we
|
|
@@ -11999,17 +14206,13 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
|
|
|
11999
14206
|
* Note: this function will use `documentVersions` if it is defined;
|
|
12000
14207
|
* when it is not defined, resorts to `globalVersion`.
|
|
12001
14208
|
*/
|
|
12002
|
-
function populateDocumentChangeBuffer(txn, documentBuffer, documents
|
|
12003
|
-
// TODO(wuandy): We could add `readTime` to MaybeDocument instead to remove
|
|
12004
|
-
// this parameter.
|
|
12005
|
-
documentVersions) {
|
|
14209
|
+
function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
|
|
12006
14210
|
let updatedKeys = documentKeySet();
|
|
12007
14211
|
documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
|
|
12008
14212
|
return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
|
|
12009
14213
|
let changedDocs = mutableDocumentMap();
|
|
12010
14214
|
documents.forEach((key, doc) => {
|
|
12011
14215
|
const existingDoc = existingDocs.get(key);
|
|
12012
|
-
const docReadTime = (documentVersions === null || documentVersions === void 0 ? void 0 : documentVersions.get(key)) || globalVersion;
|
|
12013
14216
|
// Note: The order of the steps below is important, since we want
|
|
12014
14217
|
// to ensure that rejected limbo resolutions (which fabricate
|
|
12015
14218
|
// NoDocuments with SnapshotVersion.min()) never add documents to
|
|
@@ -12018,14 +14221,14 @@ documentVersions) {
|
|
|
12018
14221
|
// NoDocuments with SnapshotVersion.min() are used in manufactured
|
|
12019
14222
|
// events. We remove these documents from cache since we lost
|
|
12020
14223
|
// access.
|
|
12021
|
-
documentBuffer.removeEntry(key,
|
|
14224
|
+
documentBuffer.removeEntry(key, doc.readTime);
|
|
12022
14225
|
changedDocs = changedDocs.insert(key, doc);
|
|
12023
14226
|
}
|
|
12024
14227
|
else if (!existingDoc.isValidDocument() ||
|
|
12025
14228
|
doc.version.compareTo(existingDoc.version) > 0 ||
|
|
12026
14229
|
(doc.version.compareTo(existingDoc.version) === 0 &&
|
|
12027
14230
|
existingDoc.hasPendingWrites)) {
|
|
12028
|
-
documentBuffer.addEntry(doc
|
|
14231
|
+
documentBuffer.addEntry(doc);
|
|
12029
14232
|
changedDocs = changedDocs.insert(key, doc);
|
|
12030
14233
|
}
|
|
12031
14234
|
else {
|
|
@@ -12279,7 +14482,8 @@ function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentB
|
|
|
12279
14482
|
// We use the commitVersion as the readTime rather than the
|
|
12280
14483
|
// document's updateTime since the updateTime is not advanced
|
|
12281
14484
|
// for updates that do not modify the underlying document.
|
|
12282
|
-
|
|
14485
|
+
doc.setReadTime(batchResult.commitVersion);
|
|
14486
|
+
documentBuffer.addEntry(doc);
|
|
12283
14487
|
}
|
|
12284
14488
|
}
|
|
12285
14489
|
});
|
|
@@ -12380,14 +14584,14 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
|
|
|
12380
14584
|
const localStoreImpl = debugCast(localStore);
|
|
12381
14585
|
let documentKeys = documentKeySet();
|
|
12382
14586
|
let documentMap = mutableDocumentMap();
|
|
12383
|
-
let versionMap = documentVersionMap();
|
|
12384
14587
|
for (const bundleDoc of documents) {
|
|
12385
14588
|
const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
|
|
12386
14589
|
if (bundleDoc.document) {
|
|
12387
14590
|
documentKeys = documentKeys.add(documentKey);
|
|
12388
14591
|
}
|
|
12389
|
-
|
|
12390
|
-
|
|
14592
|
+
const doc = bundleConverter.toMutableDocument(bundleDoc);
|
|
14593
|
+
doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
|
|
14594
|
+
documentMap = documentMap.insert(documentKey, doc);
|
|
12391
14595
|
}
|
|
12392
14596
|
const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
|
|
12393
14597
|
trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
|
|
@@ -12396,7 +14600,7 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
|
|
|
12396
14600
|
// they will not get garbage collected right away.
|
|
12397
14601
|
const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
|
|
12398
14602
|
return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
|
|
12399
|
-
return populateDocumentChangeBuffer(txn, documentBuffer, documentMap
|
|
14603
|
+
return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
|
|
12400
14604
|
.next(changedDocs => {
|
|
12401
14605
|
documentBuffer.apply(txn);
|
|
12402
14606
|
return changedDocs;
|
|
@@ -12510,6 +14714,122 @@ class MemoryBundleCache {
|
|
|
12510
14714
|
}
|
|
12511
14715
|
}
|
|
12512
14716
|
|
|
14717
|
+
/**
|
|
14718
|
+
* @license
|
|
14719
|
+
* Copyright 2022 Google LLC
|
|
14720
|
+
*
|
|
14721
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
14722
|
+
* you may not use this file except in compliance with the License.
|
|
14723
|
+
* You may obtain a copy of the License at
|
|
14724
|
+
*
|
|
14725
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
14726
|
+
*
|
|
14727
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14728
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14729
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14730
|
+
* See the License for the specific language governing permissions and
|
|
14731
|
+
* limitations under the License.
|
|
14732
|
+
*/
|
|
14733
|
+
/**
|
|
14734
|
+
* An in-memory implementation of DocumentOverlayCache.
|
|
14735
|
+
*/
|
|
14736
|
+
class MemoryDocumentOverlayCache {
|
|
14737
|
+
constructor() {
|
|
14738
|
+
// A map sorted by DocumentKey, whose value is a pair of the largest batch id
|
|
14739
|
+
// for the overlay and the overlay itself.
|
|
14740
|
+
this.overlays = new SortedMap(DocumentKey.comparator);
|
|
14741
|
+
this.overlayByBatchId = new Map();
|
|
14742
|
+
}
|
|
14743
|
+
getOverlay(transaction, key) {
|
|
14744
|
+
return PersistencePromise.resolve(this.overlays.get(key));
|
|
14745
|
+
}
|
|
14746
|
+
saveOverlays(transaction, largestBatchId, overlays) {
|
|
14747
|
+
overlays.forEach(mutation => {
|
|
14748
|
+
this.saveOverlay(transaction, largestBatchId, mutation);
|
|
14749
|
+
});
|
|
14750
|
+
return PersistencePromise.resolve();
|
|
14751
|
+
}
|
|
14752
|
+
removeOverlaysForBatchId(transaction, documentKeys, batchId) {
|
|
14753
|
+
const keys = this.overlayByBatchId.get(batchId);
|
|
14754
|
+
if (keys !== undefined) {
|
|
14755
|
+
keys.forEach(key => (this.overlays = this.overlays.remove(key)));
|
|
14756
|
+
this.overlayByBatchId.delete(batchId);
|
|
14757
|
+
}
|
|
14758
|
+
return PersistencePromise.resolve();
|
|
14759
|
+
}
|
|
14760
|
+
getOverlaysForCollection(transaction, collection, sinceBatchId) {
|
|
14761
|
+
const result = new Map();
|
|
14762
|
+
const immediateChildrenPathLength = collection.length + 1;
|
|
14763
|
+
const prefix = new DocumentKey(collection.child(''));
|
|
14764
|
+
const iter = this.overlays.getIteratorFrom(prefix);
|
|
14765
|
+
while (iter.hasNext()) {
|
|
14766
|
+
const entry = iter.getNext();
|
|
14767
|
+
const overlay = entry.value;
|
|
14768
|
+
const key = overlay.getKey();
|
|
14769
|
+
if (!collection.isPrefixOf(key.path)) {
|
|
14770
|
+
break;
|
|
14771
|
+
}
|
|
14772
|
+
// Documents from sub-collections
|
|
14773
|
+
if (key.path.length !== immediateChildrenPathLength) {
|
|
14774
|
+
continue;
|
|
14775
|
+
}
|
|
14776
|
+
if (overlay.largestBatchId > sinceBatchId) {
|
|
14777
|
+
result.set(overlay.getKey(), overlay);
|
|
14778
|
+
}
|
|
14779
|
+
}
|
|
14780
|
+
return PersistencePromise.resolve(result);
|
|
14781
|
+
}
|
|
14782
|
+
getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
|
|
14783
|
+
let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
|
|
14784
|
+
const iter = this.overlays.getIterator();
|
|
14785
|
+
while (iter.hasNext()) {
|
|
14786
|
+
const entry = iter.getNext();
|
|
14787
|
+
const overlay = entry.value;
|
|
14788
|
+
const key = overlay.getKey();
|
|
14789
|
+
if (key.getCollectionGroup() !== collectionGroup) {
|
|
14790
|
+
continue;
|
|
14791
|
+
}
|
|
14792
|
+
if (overlay.largestBatchId > sinceBatchId) {
|
|
14793
|
+
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
|
|
14794
|
+
if (overlaysForBatchId === null) {
|
|
14795
|
+
overlaysForBatchId = new Map();
|
|
14796
|
+
batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
|
|
14797
|
+
}
|
|
14798
|
+
overlaysForBatchId.set(overlay.getKey(), overlay);
|
|
14799
|
+
}
|
|
14800
|
+
}
|
|
14801
|
+
const result = new Map();
|
|
14802
|
+
const batchIter = batchIdToOverlays.getIterator();
|
|
14803
|
+
while (batchIter.hasNext()) {
|
|
14804
|
+
const entry = batchIter.getNext();
|
|
14805
|
+
const overlays = entry.value;
|
|
14806
|
+
overlays.forEach((overlay, key) => result.set(key, overlay));
|
|
14807
|
+
if (result.size >= count) {
|
|
14808
|
+
break;
|
|
14809
|
+
}
|
|
14810
|
+
}
|
|
14811
|
+
return PersistencePromise.resolve(result);
|
|
14812
|
+
}
|
|
14813
|
+
saveOverlay(transaction, largestBatchId, mutation) {
|
|
14814
|
+
if (mutation === null) {
|
|
14815
|
+
return;
|
|
14816
|
+
}
|
|
14817
|
+
// Remove the association of the overlay to its batch id.
|
|
14818
|
+
const existing = this.overlays.get(mutation.key);
|
|
14819
|
+
if (existing !== null) {
|
|
14820
|
+
this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
|
|
14821
|
+
}
|
|
14822
|
+
this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
|
|
14823
|
+
// Create the association of this overlay to the given largestBatchId.
|
|
14824
|
+
let batch = this.overlayByBatchId.get(largestBatchId);
|
|
14825
|
+
if (batch === undefined) {
|
|
14826
|
+
batch = new Set();
|
|
14827
|
+
this.overlayByBatchId.set(largestBatchId, batch);
|
|
14828
|
+
}
|
|
14829
|
+
batch.add(mutation.key);
|
|
14830
|
+
}
|
|
14831
|
+
}
|
|
14832
|
+
|
|
12513
14833
|
/**
|
|
12514
14834
|
* @license
|
|
12515
14835
|
* Copyright 2017 Google LLC
|
|
@@ -12863,29 +15183,30 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12863
15183
|
* expected to just return 0 to avoid unnecessarily doing the work of
|
|
12864
15184
|
* calculating the size.
|
|
12865
15185
|
*/
|
|
12866
|
-
constructor(
|
|
12867
|
-
this.indexManager = indexManager;
|
|
15186
|
+
constructor(sizer) {
|
|
12868
15187
|
this.sizer = sizer;
|
|
12869
15188
|
/** Underlying cache of documents and their read times. */
|
|
12870
15189
|
this.docs = documentEntryMap();
|
|
12871
15190
|
/** Size of all cached documents. */
|
|
12872
15191
|
this.size = 0;
|
|
12873
15192
|
}
|
|
15193
|
+
setIndexManager(indexManager) {
|
|
15194
|
+
this.indexManager = indexManager;
|
|
15195
|
+
}
|
|
12874
15196
|
/**
|
|
12875
15197
|
* Adds the supplied entry to the cache and updates the cache size as appropriate.
|
|
12876
15198
|
*
|
|
12877
15199
|
* All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
|
|
12878
15200
|
* returned by `newChangeBuffer()`.
|
|
12879
15201
|
*/
|
|
12880
|
-
addEntry(transaction, doc
|
|
15202
|
+
addEntry(transaction, doc) {
|
|
12881
15203
|
const key = doc.key;
|
|
12882
15204
|
const entry = this.docs.get(key);
|
|
12883
15205
|
const previousSize = entry ? entry.size : 0;
|
|
12884
15206
|
const currentSize = this.sizer(doc);
|
|
12885
15207
|
this.docs = this.docs.insert(key, {
|
|
12886
15208
|
document: doc.mutableCopy(),
|
|
12887
|
-
size: currentSize
|
|
12888
|
-
readTime
|
|
15209
|
+
size: currentSize
|
|
12889
15210
|
});
|
|
12890
15211
|
this.size += currentSize - previousSize;
|
|
12891
15212
|
return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
|
|
@@ -12919,21 +15240,22 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12919
15240
|
});
|
|
12920
15241
|
return PersistencePromise.resolve(results);
|
|
12921
15242
|
}
|
|
12922
|
-
|
|
15243
|
+
getAll(transaction, collectionPath, sinceReadTime) {
|
|
12923
15244
|
let results = mutableDocumentMap();
|
|
12924
15245
|
// Documents are ordered by key, so we can use a prefix scan to narrow down
|
|
12925
15246
|
// the documents we need to match the query against.
|
|
12926
|
-
const prefix = new DocumentKey(
|
|
15247
|
+
const prefix = new DocumentKey(collectionPath.child(''));
|
|
12927
15248
|
const iterator = this.docs.getIteratorFrom(prefix);
|
|
12928
15249
|
while (iterator.hasNext()) {
|
|
12929
|
-
const { key, value: { document
|
|
12930
|
-
if (!
|
|
15250
|
+
const { key, value: { document } } = iterator.getNext();
|
|
15251
|
+
if (!collectionPath.isPrefixOf(key.path)) {
|
|
12931
15252
|
break;
|
|
12932
15253
|
}
|
|
12933
|
-
if (
|
|
15254
|
+
if (key.path.length > collectionPath.length + 1) {
|
|
15255
|
+
// Exclude entries from subcollections.
|
|
12934
15256
|
continue;
|
|
12935
15257
|
}
|
|
12936
|
-
if (
|
|
15258
|
+
if (document.readTime.compareTo(sinceReadTime) <= 0) {
|
|
12937
15259
|
continue;
|
|
12938
15260
|
}
|
|
12939
15261
|
results = results.insert(document.key, document.mutableCopy());
|
|
@@ -12955,13 +15277,12 @@ class MemoryRemoteDocumentCacheImpl {
|
|
|
12955
15277
|
/**
|
|
12956
15278
|
* Creates a new memory-only RemoteDocumentCache.
|
|
12957
15279
|
*
|
|
12958
|
-
* @param indexManager - A class that manages collection group indices.
|
|
12959
15280
|
* @param sizer - Used to assess the size of a document. For eager GC, this is
|
|
12960
15281
|
* expected to just return 0 to avoid unnecessarily doing the work of
|
|
12961
15282
|
* calculating the size.
|
|
12962
15283
|
*/
|
|
12963
|
-
function newMemoryRemoteDocumentCache(
|
|
12964
|
-
return new MemoryRemoteDocumentCacheImpl(
|
|
15284
|
+
function newMemoryRemoteDocumentCache(sizer) {
|
|
15285
|
+
return new MemoryRemoteDocumentCacheImpl(sizer);
|
|
12965
15286
|
}
|
|
12966
15287
|
/**
|
|
12967
15288
|
* Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
|
|
@@ -12974,8 +15295,8 @@ class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
|
|
|
12974
15295
|
applyChanges(transaction) {
|
|
12975
15296
|
const promises = [];
|
|
12976
15297
|
this.changes.forEach((key, doc) => {
|
|
12977
|
-
if (doc.
|
|
12978
|
-
promises.push(this.documentCache.addEntry(transaction, doc
|
|
15298
|
+
if (doc.isValidDocument()) {
|
|
15299
|
+
promises.push(this.documentCache.addEntry(transaction, doc));
|
|
12979
15300
|
}
|
|
12980
15301
|
else {
|
|
12981
15302
|
this.documentCache.removeEntry(key);
|
|
@@ -13155,6 +15476,7 @@ class MemoryPersistence {
|
|
|
13155
15476
|
*/
|
|
13156
15477
|
constructor(referenceDelegateFactory, serializer) {
|
|
13157
15478
|
this.mutationQueues = {};
|
|
15479
|
+
this.overlays = {};
|
|
13158
15480
|
this.listenSequence = new ListenSequence(0);
|
|
13159
15481
|
this._started = false;
|
|
13160
15482
|
this._started = true;
|
|
@@ -13162,7 +15484,7 @@ class MemoryPersistence {
|
|
|
13162
15484
|
this.targetCache = new MemoryTargetCache(this);
|
|
13163
15485
|
const sizer = (doc) => this.referenceDelegate.documentSize(doc);
|
|
13164
15486
|
this.indexManager = new MemoryIndexManager();
|
|
13165
|
-
this.remoteDocumentCache = newMemoryRemoteDocumentCache(
|
|
15487
|
+
this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
|
|
13166
15488
|
this.serializer = new LocalSerializer(serializer);
|
|
13167
15489
|
this.bundleCache = new MemoryBundleCache(this.serializer);
|
|
13168
15490
|
}
|
|
@@ -13183,13 +15505,23 @@ class MemoryPersistence {
|
|
|
13183
15505
|
setNetworkEnabled() {
|
|
13184
15506
|
// No op.
|
|
13185
15507
|
}
|
|
13186
|
-
getIndexManager() {
|
|
15508
|
+
getIndexManager(user) {
|
|
15509
|
+
// We do not currently support indices for memory persistence, so we can
|
|
15510
|
+
// return the same shared instance of the memory index manager.
|
|
13187
15511
|
return this.indexManager;
|
|
13188
15512
|
}
|
|
13189
|
-
|
|
15513
|
+
getDocumentOverlayCache(user) {
|
|
15514
|
+
let overlay = this.overlays[user.toKey()];
|
|
15515
|
+
if (!overlay) {
|
|
15516
|
+
overlay = new MemoryDocumentOverlayCache();
|
|
15517
|
+
this.overlays[user.toKey()] = overlay;
|
|
15518
|
+
}
|
|
15519
|
+
return overlay;
|
|
15520
|
+
}
|
|
15521
|
+
getMutationQueue(user, indexManager) {
|
|
13190
15522
|
let queue = this.mutationQueues[user.toKey()];
|
|
13191
15523
|
if (!queue) {
|
|
13192
|
-
queue = new MemoryMutationQueue(
|
|
15524
|
+
queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
|
|
13193
15525
|
this.mutationQueues[user.toKey()] = queue;
|
|
13194
15526
|
}
|
|
13195
15527
|
return queue;
|
|
@@ -13288,7 +15620,7 @@ class MemoryEagerDelegate {
|
|
|
13288
15620
|
const key = DocumentKey.fromPath(path);
|
|
13289
15621
|
return this.isReferenced(txn, key).next(isReferenced => {
|
|
13290
15622
|
if (!isReferenced) {
|
|
13291
|
-
changeBuffer.removeEntry(key);
|
|
15623
|
+
changeBuffer.removeEntry(key, SnapshotVersion.min());
|
|
13292
15624
|
}
|
|
13293
15625
|
});
|
|
13294
15626
|
}).next(() => {
|
|
@@ -14334,7 +16666,7 @@ const LOG_TAG$9 = 'Connection';
|
|
|
14334
16666
|
const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
|
|
14335
16667
|
function createMetadata(databasePath, authToken, appCheckToken, appId) {
|
|
14336
16668
|
hardAssert(authToken === null || authToken.type === 'OAuth');
|
|
14337
|
-
const metadata = new Metadata();
|
|
16669
|
+
const metadata = new grpc.Metadata();
|
|
14338
16670
|
if (authToken) {
|
|
14339
16671
|
authToken.headers.forEach((value, key) => metadata.set(key, value));
|
|
14340
16672
|
}
|
|
@@ -14365,10 +16697,10 @@ class GrpcConnection {
|
|
|
14365
16697
|
ensureActiveStub() {
|
|
14366
16698
|
if (!this.cachedStub) {
|
|
14367
16699
|
logDebug(LOG_TAG$9, 'Creating Firestore stub.');
|
|
14368
|
-
const credentials
|
|
14369
|
-
? credentials.createSsl()
|
|
14370
|
-
: credentials.createInsecure();
|
|
14371
|
-
this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials
|
|
16700
|
+
const credentials = this.databaseInfo.ssl
|
|
16701
|
+
? grpc.credentials.createSsl()
|
|
16702
|
+
: grpc.credentials.createInsecure();
|
|
16703
|
+
this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
|
|
14372
16704
|
}
|
|
14373
16705
|
return this.cachedStub;
|
|
14374
16706
|
}
|
|
@@ -17068,6 +19400,40 @@ const nested = {
|
|
|
17068
19400
|
id: 2
|
|
17069
19401
|
}
|
|
17070
19402
|
}
|
|
19403
|
+
},
|
|
19404
|
+
methodSignature: {
|
|
19405
|
+
rule: "repeated",
|
|
19406
|
+
type: "string",
|
|
19407
|
+
id: 1051,
|
|
19408
|
+
extend: "google.protobuf.MethodOptions"
|
|
19409
|
+
},
|
|
19410
|
+
defaultHost: {
|
|
19411
|
+
type: "string",
|
|
19412
|
+
id: 1049,
|
|
19413
|
+
extend: "google.protobuf.ServiceOptions"
|
|
19414
|
+
},
|
|
19415
|
+
oauthScopes: {
|
|
19416
|
+
type: "string",
|
|
19417
|
+
id: 1050,
|
|
19418
|
+
extend: "google.protobuf.ServiceOptions"
|
|
19419
|
+
},
|
|
19420
|
+
fieldBehavior: {
|
|
19421
|
+
rule: "repeated",
|
|
19422
|
+
type: "google.api.FieldBehavior",
|
|
19423
|
+
id: 1052,
|
|
19424
|
+
extend: "google.protobuf.FieldOptions"
|
|
19425
|
+
},
|
|
19426
|
+
FieldBehavior: {
|
|
19427
|
+
values: {
|
|
19428
|
+
FIELD_BEHAVIOR_UNSPECIFIED: 0,
|
|
19429
|
+
OPTIONAL: 1,
|
|
19430
|
+
REQUIRED: 2,
|
|
19431
|
+
OUTPUT_ONLY: 3,
|
|
19432
|
+
INPUT_ONLY: 4,
|
|
19433
|
+
IMMUTABLE: 5,
|
|
19434
|
+
UNORDERED_LIST: 6,
|
|
19435
|
+
NON_EMPTY_DEFAULT: 7
|
|
19436
|
+
}
|
|
17071
19437
|
}
|
|
17072
19438
|
}
|
|
17073
19439
|
},
|
|
@@ -17166,8 +19532,8 @@ const protoLoaderOptions = {
|
|
|
17166
19532
|
* @returns The GrpcObject representing our protos.
|
|
17167
19533
|
*/
|
|
17168
19534
|
function loadProtos() {
|
|
17169
|
-
const packageDefinition = fromJSON(protos$1, protoLoaderOptions);
|
|
17170
|
-
return loadPackageDefinition(packageDefinition);
|
|
19535
|
+
const packageDefinition = protoLoader.fromJSON(protos$1, protoLoaderOptions);
|
|
19536
|
+
return grpc.loadPackageDefinition(packageDefinition);
|
|
17171
19537
|
}
|
|
17172
19538
|
|
|
17173
19539
|
/**
|
|
@@ -20118,12 +22484,12 @@ async function syncEngineListen(syncEngine, query) {
|
|
|
20118
22484
|
}
|
|
20119
22485
|
else {
|
|
20120
22486
|
const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
|
|
20121
|
-
const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
|
|
20122
|
-
targetId = targetData.targetId;
|
|
20123
|
-
viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
|
|
20124
22487
|
if (syncEngineImpl.isPrimaryClient) {
|
|
20125
22488
|
remoteStoreListen(syncEngineImpl.remoteStore, targetData);
|
|
20126
22489
|
}
|
|
22490
|
+
const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
|
|
22491
|
+
targetId = targetData.targetId;
|
|
22492
|
+
viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
|
|
20127
22493
|
}
|
|
20128
22494
|
return viewSnapshot;
|
|
20129
22495
|
}
|
|
@@ -20311,6 +22677,9 @@ async function syncEngineRejectListen(syncEngine, targetId, err) {
|
|
|
20311
22677
|
// store to purge a document. However, it would be tricky to keep all of
|
|
20312
22678
|
// the local store's invariants with another method.
|
|
20313
22679
|
let documentUpdates = new SortedMap(DocumentKey.comparator);
|
|
22680
|
+
// TODO(b/217189216): This limbo document should ideally have a read time,
|
|
22681
|
+
// so that it is picked up by any read-time based scans. The backend,
|
|
22682
|
+
// however, does not send a read time for target removals.
|
|
20314
22683
|
documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
|
|
20315
22684
|
const resolvedLimboDocuments = documentKeySet().add(limboKey);
|
|
20316
22685
|
const event = new RemoteEvent(SnapshotVersion.min(),
|
|
@@ -22255,72 +24624,6 @@ function createBundleReader(data, serializer) {
|
|
|
22255
24624
|
return newBundleReader(toByteStreamReader(content), serializer);
|
|
22256
24625
|
}
|
|
22257
24626
|
|
|
22258
|
-
/**
|
|
22259
|
-
* @license
|
|
22260
|
-
* Copyright 2017 Google LLC
|
|
22261
|
-
*
|
|
22262
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
22263
|
-
* you may not use this file except in compliance with the License.
|
|
22264
|
-
* You may obtain a copy of the License at
|
|
22265
|
-
*
|
|
22266
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
22267
|
-
*
|
|
22268
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
22269
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
22270
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22271
|
-
* See the License for the specific language governing permissions and
|
|
22272
|
-
* limitations under the License.
|
|
22273
|
-
*/
|
|
22274
|
-
class DatabaseInfo {
|
|
22275
|
-
/**
|
|
22276
|
-
* Constructs a DatabaseInfo using the provided host, databaseId and
|
|
22277
|
-
* persistenceKey.
|
|
22278
|
-
*
|
|
22279
|
-
* @param databaseId - The database to use.
|
|
22280
|
-
* @param appId - The Firebase App Id.
|
|
22281
|
-
* @param persistenceKey - A unique identifier for this Firestore's local
|
|
22282
|
-
* storage (used in conjunction with the databaseId).
|
|
22283
|
-
* @param host - The Firestore backend host to connect to.
|
|
22284
|
-
* @param ssl - Whether to use SSL when connecting.
|
|
22285
|
-
* @param forceLongPolling - Whether to use the forceLongPolling option
|
|
22286
|
-
* when using WebChannel as the network transport.
|
|
22287
|
-
* @param autoDetectLongPolling - Whether to use the detectBufferingProxy
|
|
22288
|
-
* option when using WebChannel as the network transport.
|
|
22289
|
-
* @param useFetchStreams Whether to use the Fetch API instead of
|
|
22290
|
-
* XMLHTTPRequest
|
|
22291
|
-
*/
|
|
22292
|
-
constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
|
|
22293
|
-
this.databaseId = databaseId;
|
|
22294
|
-
this.appId = appId;
|
|
22295
|
-
this.persistenceKey = persistenceKey;
|
|
22296
|
-
this.host = host;
|
|
22297
|
-
this.ssl = ssl;
|
|
22298
|
-
this.forceLongPolling = forceLongPolling;
|
|
22299
|
-
this.autoDetectLongPolling = autoDetectLongPolling;
|
|
22300
|
-
this.useFetchStreams = useFetchStreams;
|
|
22301
|
-
}
|
|
22302
|
-
}
|
|
22303
|
-
/** The default database name for a project. */
|
|
22304
|
-
const DEFAULT_DATABASE_NAME = '(default)';
|
|
22305
|
-
/**
|
|
22306
|
-
* Represents the database ID a Firestore client is associated with.
|
|
22307
|
-
* @internal
|
|
22308
|
-
*/
|
|
22309
|
-
class DatabaseId {
|
|
22310
|
-
constructor(projectId, database) {
|
|
22311
|
-
this.projectId = projectId;
|
|
22312
|
-
this.database = database ? database : DEFAULT_DATABASE_NAME;
|
|
22313
|
-
}
|
|
22314
|
-
get isDefaultDatabase() {
|
|
22315
|
-
return this.database === DEFAULT_DATABASE_NAME;
|
|
22316
|
-
}
|
|
22317
|
-
isEqual(other) {
|
|
22318
|
-
return (other instanceof DatabaseId &&
|
|
22319
|
-
other.projectId === this.projectId &&
|
|
22320
|
-
other.database === this.database);
|
|
22321
|
-
}
|
|
22322
|
-
}
|
|
22323
|
-
|
|
22324
24627
|
/**
|
|
22325
24628
|
* @license
|
|
22326
24629
|
* Copyright 2020 Google LLC
|
|
@@ -24694,8 +26997,7 @@ class DocumentSnapshot extends DocumentSnapshot$1 {
|
|
|
24694
26997
|
this.metadata = metadata;
|
|
24695
26998
|
}
|
|
24696
26999
|
/**
|
|
24697
|
-
*
|
|
24698
|
-
* exists. True if the document exists.
|
|
27000
|
+
* Returns whether or not the data exists. True if the document exists.
|
|
24699
27001
|
*/
|
|
24700
27002
|
exists() {
|
|
24701
27003
|
return super.exists();
|
|
@@ -25069,51 +27371,53 @@ function limitToLast(limit) {
|
|
|
25069
27371
|
return new QueryLimitConstraint('limitToLast', limit, "L" /* Last */);
|
|
25070
27372
|
}
|
|
25071
27373
|
class QueryStartAtConstraint extends QueryConstraint {
|
|
25072
|
-
constructor(type, _docOrFields,
|
|
27374
|
+
constructor(type, _docOrFields, _inclusive) {
|
|
25073
27375
|
super();
|
|
25074
27376
|
this.type = type;
|
|
25075
27377
|
this._docOrFields = _docOrFields;
|
|
25076
|
-
this.
|
|
27378
|
+
this._inclusive = _inclusive;
|
|
25077
27379
|
}
|
|
25078
27380
|
_apply(query) {
|
|
25079
|
-
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this.
|
|
27381
|
+
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
|
|
25080
27382
|
return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
|
|
25081
27383
|
}
|
|
25082
27384
|
}
|
|
25083
27385
|
function startAt(...docOrFields) {
|
|
25084
|
-
return new QueryStartAtConstraint('startAt', docOrFields,
|
|
27386
|
+
return new QueryStartAtConstraint('startAt', docOrFields,
|
|
27387
|
+
/*inclusive=*/ true);
|
|
25085
27388
|
}
|
|
25086
27389
|
function startAfter(...docOrFields) {
|
|
25087
27390
|
return new QueryStartAtConstraint('startAfter', docOrFields,
|
|
25088
|
-
/*
|
|
27391
|
+
/*inclusive=*/ false);
|
|
25089
27392
|
}
|
|
25090
27393
|
class QueryEndAtConstraint extends QueryConstraint {
|
|
25091
|
-
constructor(type, _docOrFields,
|
|
27394
|
+
constructor(type, _docOrFields, _inclusive) {
|
|
25092
27395
|
super();
|
|
25093
27396
|
this.type = type;
|
|
25094
27397
|
this._docOrFields = _docOrFields;
|
|
25095
|
-
this.
|
|
27398
|
+
this._inclusive = _inclusive;
|
|
25096
27399
|
}
|
|
25097
27400
|
_apply(query) {
|
|
25098
|
-
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this.
|
|
27401
|
+
const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
|
|
25099
27402
|
return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
|
|
25100
27403
|
}
|
|
25101
27404
|
}
|
|
25102
27405
|
function endBefore(...docOrFields) {
|
|
25103
|
-
return new QueryEndAtConstraint('endBefore', docOrFields,
|
|
27406
|
+
return new QueryEndAtConstraint('endBefore', docOrFields,
|
|
27407
|
+
/*inclusive=*/ false);
|
|
25104
27408
|
}
|
|
25105
27409
|
function endAt(...docOrFields) {
|
|
25106
|
-
return new QueryEndAtConstraint('endAt', docOrFields, /*
|
|
27410
|
+
return new QueryEndAtConstraint('endAt', docOrFields, /*inclusive=*/ true);
|
|
25107
27411
|
}
|
|
25108
27412
|
/** Helper function to create a bound from a document or fields */
|
|
25109
|
-
function newQueryBoundFromDocOrFields(query, methodName, docOrFields,
|
|
27413
|
+
function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
|
|
25110
27414
|
docOrFields[0] = getModularInstance(docOrFields[0]);
|
|
25111
27415
|
if (docOrFields[0] instanceof DocumentSnapshot$1) {
|
|
25112
|
-
return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document,
|
|
27416
|
+
return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
|
|
25113
27417
|
}
|
|
25114
27418
|
else {
|
|
25115
27419
|
const reader = newUserDataReader(query.firestore);
|
|
25116
|
-
return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields,
|
|
27420
|
+
return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
|
|
25117
27421
|
}
|
|
25118
27422
|
}
|
|
25119
27423
|
function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
|
|
@@ -25171,7 +27475,7 @@ function newQueryOrderBy(query, fieldPath, direction) {
|
|
|
25171
27475
|
* of the query or if any of the fields in the order by are an uncommitted
|
|
25172
27476
|
* server timestamp.
|
|
25173
27477
|
*/
|
|
25174
|
-
function newQueryBoundFromDocument(query, databaseId, methodName, doc,
|
|
27478
|
+
function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
|
|
25175
27479
|
if (!doc) {
|
|
25176
27480
|
throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
|
|
25177
27481
|
`${methodName}().`);
|
|
@@ -25208,12 +27512,12 @@ function newQueryBoundFromDocument(query, databaseId, methodName, doc, before) {
|
|
|
25208
27512
|
}
|
|
25209
27513
|
}
|
|
25210
27514
|
}
|
|
25211
|
-
return new Bound(components,
|
|
27515
|
+
return new Bound(components, inclusive);
|
|
25212
27516
|
}
|
|
25213
27517
|
/**
|
|
25214
27518
|
* Converts a list of field values to a `Bound` for the given query.
|
|
25215
27519
|
*/
|
|
25216
|
-
function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values,
|
|
27520
|
+
function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
|
|
25217
27521
|
// Use explicit order by's because it has to match the query the user made
|
|
25218
27522
|
const orderBy = query.explicitOrderBy;
|
|
25219
27523
|
if (values.length > orderBy.length) {
|
|
@@ -25250,7 +27554,7 @@ function newQueryBoundFromFields(query, databaseId, dataReader, methodName, valu
|
|
|
25250
27554
|
components.push(wrapped);
|
|
25251
27555
|
}
|
|
25252
27556
|
}
|
|
25253
|
-
return new Bound(components,
|
|
27557
|
+
return new Bound(components, inclusive);
|
|
25254
27558
|
}
|
|
25255
27559
|
/**
|
|
25256
27560
|
* Parses the given `documentIdValue` into a `ReferenceValue`, throwing
|
|
@@ -26234,17 +28538,6 @@ function writeBatch(firestore) {
|
|
|
26234
28538
|
ensureFirestoreConfigured(firestore);
|
|
26235
28539
|
return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
|
|
26236
28540
|
}
|
|
26237
|
-
/** An index component consisting of field path and index type. */
|
|
26238
|
-
class Segment {
|
|
26239
|
-
constructor(
|
|
26240
|
-
/** The field path of the component. */
|
|
26241
|
-
fieldPath,
|
|
26242
|
-
/** The fields sorting order. */
|
|
26243
|
-
kind) {
|
|
26244
|
-
this.fieldPath = fieldPath;
|
|
26245
|
-
this.kind = kind;
|
|
26246
|
-
}
|
|
26247
|
-
}
|
|
26248
28541
|
|
|
26249
28542
|
/**
|
|
26250
28543
|
* @license
|
|
@@ -26268,27 +28561,29 @@ function setIndexConfiguration(firestore, jsonOrConfiguration) {
|
|
|
26268
28561
|
const indexConfiguration = typeof jsonOrConfiguration === 'string'
|
|
26269
28562
|
? tryParseJson(jsonOrConfiguration)
|
|
26270
28563
|
: jsonOrConfiguration;
|
|
28564
|
+
const parsedIndexes = [];
|
|
26271
28565
|
// PORTING NOTE: We don't return an error if the user has not enabled
|
|
26272
28566
|
// persistence since `enableIndexeddbPersistence()` can fail on the Web.
|
|
26273
28567
|
if (Array.isArray(indexConfiguration.indexes)) {
|
|
26274
28568
|
for (const index of indexConfiguration.indexes) {
|
|
26275
|
-
tryGetString(index, 'collectionGroup');
|
|
28569
|
+
const collectionGroup = tryGetString(index, 'collectionGroup');
|
|
26276
28570
|
const segments = [];
|
|
26277
28571
|
if (Array.isArray(index.fields)) {
|
|
26278
28572
|
for (const field of index.fields) {
|
|
26279
28573
|
const fieldPathString = tryGetString(field, 'fieldPath');
|
|
26280
28574
|
const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
|
|
26281
28575
|
if (field.arrayConfig === 'CONTAINS') {
|
|
26282
|
-
segments.push(new
|
|
28576
|
+
segments.push(new IndexSegment(fieldPath, 2 /* CONTAINS */));
|
|
26283
28577
|
}
|
|
26284
28578
|
else if (field.order === 'ASCENDING') {
|
|
26285
|
-
segments.push(new
|
|
28579
|
+
segments.push(new IndexSegment(fieldPath, 0 /* ASCENDING */));
|
|
26286
28580
|
}
|
|
26287
28581
|
else if (field.order === 'DESCENDING') {
|
|
26288
|
-
segments.push(new
|
|
28582
|
+
segments.push(new IndexSegment(fieldPath, 1 /* DESCENDING */));
|
|
26289
28583
|
}
|
|
26290
28584
|
}
|
|
26291
28585
|
}
|
|
28586
|
+
parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
|
|
26292
28587
|
}
|
|
26293
28588
|
}
|
|
26294
28589
|
// TODO(indexing): Configure indexes
|