@firebase/firestore 3.4.7 → 3.4.8-canary.19e31a231
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 +10 -0
- package/dist/firestore/src/core/query.d.ts +2 -4
- package/dist/firestore/src/core/target.d.ts +6 -4
- package/dist/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/firestore/src/model/collections.d.ts +1 -1
- package/dist/firestore/src/model/field_index.d.ts +6 -0
- package/dist/firestore/src/model/values.d.ts +14 -2
- package/dist/firestore/src/util/misc.d.ts +3 -0
- package/dist/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/index.esm2017.js +4649 -4498
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4941 -4800
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1764 -1577
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1764 -1577
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4495 -4344
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +26 -4
- package/dist/lite/firestore/src/core/query.d.ts +2 -4
- package/dist/lite/firestore/src/core/target.d.ts +6 -4
- package/dist/lite/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/lite/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/lite/firestore/src/model/collections.d.ts +1 -1
- package/dist/lite/firestore/src/model/field_index.d.ts +6 -0
- package/dist/lite/firestore/src/model/values.d.ts +14 -2
- package/dist/lite/firestore/src/util/misc.d.ts +3 -0
- package/dist/lite/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/lite/index.browser.esm2017.js +123 -117
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +203 -197
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +24 -10
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +24 -10
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +1155 -1149
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/packages/firestore/src/core/query.d.ts +2 -4
- package/dist/lite/packages/firestore/src/core/target.d.ts +6 -4
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/lite/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/packages/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/lite/packages/firestore/src/model/collections.d.ts +1 -1
- package/dist/lite/packages/firestore/src/model/field_index.d.ts +6 -0
- package/dist/lite/packages/firestore/src/model/values.d.ts +14 -2
- package/dist/lite/packages/firestore/src/util/misc.d.ts +3 -0
- package/dist/lite/packages/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/packages/firestore/dist/index.esm2017.d.ts +171 -171
- package/dist/packages/firestore/src/core/query.d.ts +2 -4
- package/dist/packages/firestore/src/core/target.d.ts +6 -4
- package/dist/packages/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/packages/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/packages/firestore/src/model/collections.d.ts +1 -1
- package/dist/packages/firestore/src/model/field_index.d.ts +6 -0
- package/dist/packages/firestore/src/model/values.d.ts +14 -2
- package/dist/packages/firestore/src/util/misc.d.ts +3 -0
- package/dist/packages/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/private.d.ts +26 -4
- package/package.json +9 -9
package/dist/index.node.cjs.js
CHANGED
|
@@ -33,7 +33,7 @@ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
|
|
|
33
33
|
var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
|
|
34
34
|
|
|
35
35
|
const name = "@firebase/firestore";
|
|
36
|
-
const version$1 = "3.4.
|
|
36
|
+
const version$1 = "3.4.8-canary.19e31a231";
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* @license
|
|
@@ -86,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
86
86
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
87
87
|
User.MOCK_USER = new User('mock-user');
|
|
88
88
|
|
|
89
|
-
const version = "9.6.
|
|
89
|
+
const version = "9.6.11-canary.19e31a231";
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @license
|
|
@@ -1418,13 +1418,14 @@ const DbIndexEntryKeyPath = [
|
|
|
1418
1418
|
'uid',
|
|
1419
1419
|
'arrayValue',
|
|
1420
1420
|
'directionalValue',
|
|
1421
|
+
'orderedDocumentKey',
|
|
1421
1422
|
'documentKey'
|
|
1422
1423
|
];
|
|
1423
1424
|
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1424
1425
|
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1425
1426
|
'indexId',
|
|
1426
1427
|
'uid',
|
|
1427
|
-
'
|
|
1428
|
+
'orderedDocumentKey'
|
|
1428
1429
|
];
|
|
1429
1430
|
/** Name of the IndexedDb object store. */
|
|
1430
1431
|
const DbDocumentOverlayStore = 'documentOverlays';
|
|
@@ -2310,9 +2311,7 @@ class SimpleDbStore {
|
|
|
2310
2311
|
cursor.continue(controller.skipToKey);
|
|
2311
2312
|
}
|
|
2312
2313
|
};
|
|
2313
|
-
}).next(() =>
|
|
2314
|
-
return PersistencePromise.waitFor(results);
|
|
2315
|
-
});
|
|
2314
|
+
}).next(() => PersistencePromise.waitFor(results));
|
|
2316
2315
|
}
|
|
2317
2316
|
options(indexOrRange, range) {
|
|
2318
2317
|
let indexName = undefined;
|
|
@@ -3347,7 +3346,7 @@ function typeOrder(value) {
|
|
|
3347
3346
|
return 4 /* ServerTimestampValue */;
|
|
3348
3347
|
}
|
|
3349
3348
|
else if (isMaxValue(value)) {
|
|
3350
|
-
return
|
|
3349
|
+
return 9007199254740991 /* MaxValue */;
|
|
3351
3350
|
}
|
|
3352
3351
|
return 10 /* ObjectValue */;
|
|
3353
3352
|
}
|
|
@@ -3559,6 +3558,15 @@ function compareArrays(left, right) {
|
|
|
3559
3558
|
return primitiveComparator(leftArray.length, rightArray.length);
|
|
3560
3559
|
}
|
|
3561
3560
|
function compareMaps(left, right) {
|
|
3561
|
+
if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
|
|
3562
|
+
return 0;
|
|
3563
|
+
}
|
|
3564
|
+
else if (left === MAX_VALUE.mapValue) {
|
|
3565
|
+
return 1;
|
|
3566
|
+
}
|
|
3567
|
+
else if (right === MAX_VALUE.mapValue) {
|
|
3568
|
+
return -1;
|
|
3569
|
+
}
|
|
3562
3570
|
const leftMap = left.fields || {};
|
|
3563
3571
|
const leftKeys = Object.keys(leftMap);
|
|
3564
3572
|
const rightMap = right.fields || {};
|
|
@@ -3806,27 +3814,31 @@ function valuesGetUpperBound(value) {
|
|
|
3806
3814
|
return fail();
|
|
3807
3815
|
}
|
|
3808
3816
|
}
|
|
3809
|
-
function
|
|
3810
|
-
|
|
3811
|
-
|
|
3817
|
+
function lowerBoundCompare(left, right) {
|
|
3818
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3819
|
+
if (cmp !== 0) {
|
|
3820
|
+
return cmp;
|
|
3812
3821
|
}
|
|
3813
|
-
|
|
3814
|
-
return
|
|
3822
|
+
if (left.inclusive && !right.inclusive) {
|
|
3823
|
+
return -1;
|
|
3815
3824
|
}
|
|
3816
|
-
else {
|
|
3817
|
-
return
|
|
3825
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3826
|
+
return 1;
|
|
3818
3827
|
}
|
|
3828
|
+
return 0;
|
|
3819
3829
|
}
|
|
3820
|
-
function
|
|
3821
|
-
|
|
3822
|
-
|
|
3830
|
+
function upperBoundCompare(left, right) {
|
|
3831
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3832
|
+
if (cmp !== 0) {
|
|
3833
|
+
return cmp;
|
|
3823
3834
|
}
|
|
3824
|
-
|
|
3825
|
-
return
|
|
3835
|
+
if (left.inclusive && !right.inclusive) {
|
|
3836
|
+
return 1;
|
|
3826
3837
|
}
|
|
3827
|
-
else {
|
|
3828
|
-
return
|
|
3838
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3839
|
+
return -1;
|
|
3829
3840
|
}
|
|
3841
|
+
return 0;
|
|
3830
3842
|
}
|
|
3831
3843
|
|
|
3832
3844
|
/**
|
|
@@ -4221,6 +4233,17 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4221
4233
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4222
4234
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4223
4235
|
}
|
|
4236
|
+
/**
|
|
4237
|
+
* Returns the order of the document key component for the given index.
|
|
4238
|
+
*
|
|
4239
|
+
* PORTING NOTE: This is only used in the Web IndexedDb implementation.
|
|
4240
|
+
*/
|
|
4241
|
+
function fieldIndexGetKeyOrder(fieldIndex) {
|
|
4242
|
+
const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
|
|
4243
|
+
return directionalSegments.length === 0
|
|
4244
|
+
? 0 /* ASCENDING */
|
|
4245
|
+
: directionalSegments[directionalSegments.length - 1].kind;
|
|
4246
|
+
}
|
|
4224
4247
|
/** Returns a debug representation of the field index */
|
|
4225
4248
|
function fieldIndexToString(fieldIndex) {
|
|
4226
4249
|
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
@@ -4325,7 +4348,7 @@ function indexOffsetComparator(left, right) {
|
|
|
4325
4348
|
|
|
4326
4349
|
/**
|
|
4327
4350
|
* @license
|
|
4328
|
-
* Copyright
|
|
4351
|
+
* Copyright 2017 Google LLC
|
|
4329
4352
|
*
|
|
4330
4353
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4331
4354
|
* you may not use this file except in compliance with the License.
|
|
@@ -4339,589 +4362,445 @@ function indexOffsetComparator(left, right) {
|
|
|
4339
4362
|
* See the License for the specific language governing permissions and
|
|
4340
4363
|
* limitations under the License.
|
|
4341
4364
|
*/
|
|
4342
|
-
//
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
this.
|
|
4347
|
-
this.
|
|
4348
|
-
this.filters = filters;
|
|
4349
|
-
this.limit = limit;
|
|
4350
|
-
this.startAt = startAt;
|
|
4351
|
-
this.endAt = endAt;
|
|
4352
|
-
this.memoizedCanonicalId = null;
|
|
4365
|
+
// An immutable sorted map implementation, based on a Left-leaning Red-Black
|
|
4366
|
+
// tree.
|
|
4367
|
+
class SortedMap {
|
|
4368
|
+
constructor(comparator, root) {
|
|
4369
|
+
this.comparator = comparator;
|
|
4370
|
+
this.root = root ? root : LLRBNode.EMPTY;
|
|
4353
4371
|
}
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
4360
|
-
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
4361
|
-
* property.
|
|
4362
|
-
*/
|
|
4363
|
-
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
4364
|
-
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
4365
|
-
}
|
|
4366
|
-
function canonifyTarget(target) {
|
|
4367
|
-
const targetImpl = debugCast(target);
|
|
4368
|
-
if (targetImpl.memoizedCanonicalId === null) {
|
|
4369
|
-
let str = targetImpl.path.canonicalString();
|
|
4370
|
-
if (targetImpl.collectionGroup !== null) {
|
|
4371
|
-
str += '|cg:' + targetImpl.collectionGroup;
|
|
4372
|
-
}
|
|
4373
|
-
str += '|f:';
|
|
4374
|
-
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4375
|
-
str += '|ob:';
|
|
4376
|
-
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4377
|
-
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4378
|
-
str += '|l:';
|
|
4379
|
-
str += targetImpl.limit;
|
|
4380
|
-
}
|
|
4381
|
-
if (targetImpl.startAt) {
|
|
4382
|
-
str += '|lb:';
|
|
4383
|
-
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4384
|
-
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4385
|
-
}
|
|
4386
|
-
if (targetImpl.endAt) {
|
|
4387
|
-
str += '|ub:';
|
|
4388
|
-
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4389
|
-
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4390
|
-
}
|
|
4391
|
-
targetImpl.memoizedCanonicalId = str;
|
|
4372
|
+
// Returns a copy of the map, with the specified key/value added or replaced.
|
|
4373
|
+
insert(key, value) {
|
|
4374
|
+
return new SortedMap(this.comparator, this.root
|
|
4375
|
+
.insert(key, value, this.comparator)
|
|
4376
|
+
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
4392
4377
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
str += ' collectionGroup=' + target.collectionGroup;
|
|
4378
|
+
// Returns a copy of the map, with the specified key removed.
|
|
4379
|
+
remove(key) {
|
|
4380
|
+
return new SortedMap(this.comparator, this.root
|
|
4381
|
+
.remove(key, this.comparator)
|
|
4382
|
+
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
4399
4383
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4384
|
+
// Returns the value of the node with the given key, or null.
|
|
4385
|
+
get(key) {
|
|
4386
|
+
let node = this.root;
|
|
4387
|
+
while (!node.isEmpty()) {
|
|
4388
|
+
const cmp = this.comparator(key, node.key);
|
|
4389
|
+
if (cmp === 0) {
|
|
4390
|
+
return node.value;
|
|
4391
|
+
}
|
|
4392
|
+
else if (cmp < 0) {
|
|
4393
|
+
node = node.left;
|
|
4394
|
+
}
|
|
4395
|
+
else if (cmp > 0) {
|
|
4396
|
+
node = node.right;
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
return null;
|
|
4404
4400
|
}
|
|
4405
|
-
if
|
|
4406
|
-
|
|
4401
|
+
// Returns the index of the element in this sorted map, or -1 if it doesn't
|
|
4402
|
+
// exist.
|
|
4403
|
+
indexOf(key) {
|
|
4404
|
+
// Number of nodes that were pruned when descending right
|
|
4405
|
+
let prunedNodes = 0;
|
|
4406
|
+
let node = this.root;
|
|
4407
|
+
while (!node.isEmpty()) {
|
|
4408
|
+
const cmp = this.comparator(key, node.key);
|
|
4409
|
+
if (cmp === 0) {
|
|
4410
|
+
return prunedNodes + node.left.size;
|
|
4411
|
+
}
|
|
4412
|
+
else if (cmp < 0) {
|
|
4413
|
+
node = node.left;
|
|
4414
|
+
}
|
|
4415
|
+
else {
|
|
4416
|
+
// Count all nodes left of the node plus the node itself
|
|
4417
|
+
prunedNodes += node.left.size + 1;
|
|
4418
|
+
node = node.right;
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
// Node not found
|
|
4422
|
+
return -1;
|
|
4407
4423
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
.map(o => stringifyOrderBy(o))
|
|
4411
|
-
.join(', ')}]`;
|
|
4424
|
+
isEmpty() {
|
|
4425
|
+
return this.root.isEmpty();
|
|
4412
4426
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4427
|
+
// Returns the total number of nodes in the map.
|
|
4428
|
+
get size() {
|
|
4429
|
+
return this.root.size;
|
|
4417
4430
|
}
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4431
|
+
// Returns the minimum key in the map.
|
|
4432
|
+
minKey() {
|
|
4433
|
+
return this.root.minKey();
|
|
4422
4434
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
if (left.limit !== right.limit) {
|
|
4427
|
-
return false;
|
|
4435
|
+
// Returns the maximum key in the map.
|
|
4436
|
+
maxKey() {
|
|
4437
|
+
return this.root.maxKey();
|
|
4428
4438
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4439
|
+
// Traverses the map in key order and calls the specified action function
|
|
4440
|
+
// for each key/value pair. If action returns true, traversal is aborted.
|
|
4441
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4442
|
+
// value returned by action.
|
|
4443
|
+
inorderTraversal(action) {
|
|
4444
|
+
return this.root.inorderTraversal(action);
|
|
4431
4445
|
}
|
|
4432
|
-
|
|
4433
|
-
|
|
4446
|
+
forEach(fn) {
|
|
4447
|
+
this.inorderTraversal((k, v) => {
|
|
4448
|
+
fn(k, v);
|
|
4434
4449
|
return false;
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4437
|
-
if (left.filters.length !== right.filters.length) {
|
|
4438
|
-
return false;
|
|
4450
|
+
});
|
|
4439
4451
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4452
|
+
toString() {
|
|
4453
|
+
const descriptions = [];
|
|
4454
|
+
this.inorderTraversal((k, v) => {
|
|
4455
|
+
descriptions.push(`${k}:${v}`);
|
|
4442
4456
|
return false;
|
|
4443
|
-
}
|
|
4457
|
+
});
|
|
4458
|
+
return `{${descriptions.join(', ')}}`;
|
|
4444
4459
|
}
|
|
4445
|
-
|
|
4446
|
-
|
|
4460
|
+
// Traverses the map in reverse key order and calls the specified action
|
|
4461
|
+
// function for each key/value pair. If action returns true, traversal is
|
|
4462
|
+
// aborted.
|
|
4463
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4464
|
+
// value returned by action.
|
|
4465
|
+
reverseTraversal(action) {
|
|
4466
|
+
return this.root.reverseTraversal(action);
|
|
4447
4467
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4468
|
+
// Returns an iterator over the SortedMap.
|
|
4469
|
+
getIterator() {
|
|
4470
|
+
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
4450
4471
|
}
|
|
4451
|
-
|
|
4452
|
-
return false;
|
|
4472
|
+
getIteratorFrom(key) {
|
|
4473
|
+
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
4453
4474
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
function targetIsDocumentTarget(target) {
|
|
4457
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4458
|
-
target.collectionGroup === null &&
|
|
4459
|
-
target.filters.length === 0);
|
|
4460
|
-
}
|
|
4461
|
-
/** Returns the field filters that target the given field path. */
|
|
4462
|
-
function targetGetFieldFiltersForPath(target, path) {
|
|
4463
|
-
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4464
|
-
}
|
|
4465
|
-
/**
|
|
4466
|
-
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4467
|
-
* filters. Returns `null` if there are no such filters.
|
|
4468
|
-
*/
|
|
4469
|
-
function targetGetArrayValues(target, fieldIndex) {
|
|
4470
|
-
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4471
|
-
if (segment === undefined) {
|
|
4472
|
-
return null;
|
|
4475
|
+
getReverseIterator() {
|
|
4476
|
+
return new SortedMapIterator(this.root, null, this.comparator, true);
|
|
4473
4477
|
}
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4477
|
-
return fieldFilter.value.arrayValue.values || [];
|
|
4478
|
-
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4479
|
-
return [fieldFilter.value];
|
|
4480
|
-
// Remaining filters are not array filters.
|
|
4481
|
-
}
|
|
4478
|
+
getReverseIteratorFrom(key) {
|
|
4479
|
+
return new SortedMapIterator(this.root, key, this.comparator, true);
|
|
4482
4480
|
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
case "in" /* IN */:
|
|
4496
|
-
// Encode equality prefix, which is encoded in the index value before
|
|
4497
|
-
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4498
|
-
// `value != 'ab'`).
|
|
4499
|
-
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4500
|
-
break;
|
|
4501
|
-
case "not-in" /* NOT_IN */:
|
|
4502
|
-
case "!=" /* NOT_EQUAL */:
|
|
4503
|
-
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4504
|
-
// segments and hence we can return early here.
|
|
4505
|
-
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4506
|
-
return Array.from(values.values());
|
|
4507
|
-
// Remaining filters cannot be used as notIn bounds.
|
|
4481
|
+
} // end SortedMap
|
|
4482
|
+
// An iterator over an LLRBNode.
|
|
4483
|
+
class SortedMapIterator {
|
|
4484
|
+
constructor(node, startKey, comparator, isReverse) {
|
|
4485
|
+
this.isReverse = isReverse;
|
|
4486
|
+
this.nodeStack = [];
|
|
4487
|
+
let cmp = 1;
|
|
4488
|
+
while (!node.isEmpty()) {
|
|
4489
|
+
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
4490
|
+
// flip the comparison if we're going in reverse
|
|
4491
|
+
if (startKey && isReverse) {
|
|
4492
|
+
cmp *= -1;
|
|
4508
4493
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4534
|
-
break;
|
|
4535
|
-
case "==" /* EQUAL */:
|
|
4536
|
-
case "in" /* IN */:
|
|
4537
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4538
|
-
filterValue = fieldFilter.value;
|
|
4539
|
-
break;
|
|
4540
|
-
case ">" /* GREATER_THAN */:
|
|
4541
|
-
filterValue = fieldFilter.value;
|
|
4542
|
-
filterInclusive = false;
|
|
4543
|
-
break;
|
|
4544
|
-
case "!=" /* NOT_EQUAL */:
|
|
4545
|
-
case "not-in" /* NOT_IN */:
|
|
4546
|
-
filterValue = MIN_VALUE;
|
|
4547
|
-
break;
|
|
4548
|
-
// Remaining filters cannot be used as lower bounds.
|
|
4549
|
-
}
|
|
4550
|
-
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4551
|
-
segmentValue = filterValue;
|
|
4552
|
-
segmentInclusive = filterInclusive;
|
|
4553
|
-
}
|
|
4554
|
-
}
|
|
4555
|
-
// If there is a startAt bound, compare the values against the existing
|
|
4556
|
-
// boundary to see if we can narrow the scope.
|
|
4557
|
-
if (target.startAt !== null) {
|
|
4558
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4559
|
-
const orderBy = target.orderBy[i];
|
|
4560
|
-
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4561
|
-
const cursorValue = target.startAt.position[i];
|
|
4562
|
-
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4563
|
-
segmentValue = cursorValue;
|
|
4564
|
-
segmentInclusive = target.startAt.inclusive;
|
|
4565
|
-
}
|
|
4566
|
-
break;
|
|
4494
|
+
if (cmp < 0) {
|
|
4495
|
+
// This node is less than our start key. ignore it
|
|
4496
|
+
if (this.isReverse) {
|
|
4497
|
+
node = node.left;
|
|
4498
|
+
}
|
|
4499
|
+
else {
|
|
4500
|
+
node = node.right;
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
else if (cmp === 0) {
|
|
4504
|
+
// This node is exactly equal to our start key. Push it on the stack,
|
|
4505
|
+
// but stop iterating;
|
|
4506
|
+
this.nodeStack.push(node);
|
|
4507
|
+
break;
|
|
4508
|
+
}
|
|
4509
|
+
else {
|
|
4510
|
+
// This node is greater than our start key, add it to the stack and move
|
|
4511
|
+
// to the next one
|
|
4512
|
+
this.nodeStack.push(node);
|
|
4513
|
+
if (this.isReverse) {
|
|
4514
|
+
node = node.right;
|
|
4515
|
+
}
|
|
4516
|
+
else {
|
|
4517
|
+
node = node.left;
|
|
4567
4518
|
}
|
|
4568
4519
|
}
|
|
4569
4520
|
}
|
|
4570
|
-
if (segmentValue === undefined) {
|
|
4571
|
-
// No lower bound exists
|
|
4572
|
-
return null;
|
|
4573
|
-
}
|
|
4574
|
-
values.push(segmentValue);
|
|
4575
|
-
inclusive && (inclusive = segmentInclusive);
|
|
4576
4521
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
const values = [];
|
|
4586
|
-
let inclusive = true;
|
|
4587
|
-
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4588
|
-
// endAt.
|
|
4589
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4590
|
-
let segmentValue = undefined;
|
|
4591
|
-
let segmentInclusive = true;
|
|
4592
|
-
// Process all filters to find a value for the current field segment
|
|
4593
|
-
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4594
|
-
let filterValue = undefined;
|
|
4595
|
-
let filterInclusive = true;
|
|
4596
|
-
switch (fieldFilter.op) {
|
|
4597
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4598
|
-
case ">" /* GREATER_THAN */:
|
|
4599
|
-
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4600
|
-
filterInclusive = false;
|
|
4601
|
-
break;
|
|
4602
|
-
case "==" /* EQUAL */:
|
|
4603
|
-
case "in" /* IN */:
|
|
4604
|
-
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4605
|
-
filterValue = fieldFilter.value;
|
|
4606
|
-
break;
|
|
4607
|
-
case "<" /* LESS_THAN */:
|
|
4608
|
-
filterValue = fieldFilter.value;
|
|
4609
|
-
filterInclusive = false;
|
|
4610
|
-
break;
|
|
4611
|
-
case "!=" /* NOT_EQUAL */:
|
|
4612
|
-
case "not-in" /* NOT_IN */:
|
|
4613
|
-
filterValue = MAX_VALUE;
|
|
4614
|
-
break;
|
|
4615
|
-
// Remaining filters cannot be used as upper bounds.
|
|
4616
|
-
}
|
|
4617
|
-
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4618
|
-
segmentValue = filterValue;
|
|
4619
|
-
segmentInclusive = filterInclusive;
|
|
4620
|
-
}
|
|
4621
|
-
}
|
|
4622
|
-
// If there is a endAt bound, compare the values against the existing
|
|
4623
|
-
// boundary to see if we can narrow the scope.
|
|
4624
|
-
if (target.endAt !== null) {
|
|
4625
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4626
|
-
const orderBy = target.orderBy[i];
|
|
4627
|
-
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4628
|
-
const cursorValue = target.endAt.position[i];
|
|
4629
|
-
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4630
|
-
segmentValue = cursorValue;
|
|
4631
|
-
segmentInclusive = target.endAt.inclusive;
|
|
4632
|
-
}
|
|
4633
|
-
break;
|
|
4634
|
-
}
|
|
4522
|
+
getNext() {
|
|
4523
|
+
let node = this.nodeStack.pop();
|
|
4524
|
+
const result = { key: node.key, value: node.value };
|
|
4525
|
+
if (this.isReverse) {
|
|
4526
|
+
node = node.left;
|
|
4527
|
+
while (!node.isEmpty()) {
|
|
4528
|
+
this.nodeStack.push(node);
|
|
4529
|
+
node = node.right;
|
|
4635
4530
|
}
|
|
4636
4531
|
}
|
|
4637
|
-
|
|
4638
|
-
|
|
4532
|
+
else {
|
|
4533
|
+
node = node.right;
|
|
4534
|
+
while (!node.isEmpty()) {
|
|
4535
|
+
this.nodeStack.push(node);
|
|
4536
|
+
node = node.left;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
return result;
|
|
4540
|
+
}
|
|
4541
|
+
hasNext() {
|
|
4542
|
+
return this.nodeStack.length > 0;
|
|
4543
|
+
}
|
|
4544
|
+
peek() {
|
|
4545
|
+
if (this.nodeStack.length === 0) {
|
|
4639
4546
|
return null;
|
|
4640
4547
|
}
|
|
4641
|
-
|
|
4642
|
-
|
|
4548
|
+
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
4549
|
+
return { key: node.key, value: node.value };
|
|
4643
4550
|
}
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
class
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
constructor(field, op, value) {
|
|
4650
|
-
super();
|
|
4651
|
-
this.field = field;
|
|
4652
|
-
this.op = op;
|
|
4551
|
+
} // end SortedMapIterator
|
|
4552
|
+
// Represents a node in a Left-leaning Red-Black tree.
|
|
4553
|
+
class LLRBNode {
|
|
4554
|
+
constructor(key, value, color, left, right) {
|
|
4555
|
+
this.key = key;
|
|
4653
4556
|
this.value = value;
|
|
4557
|
+
this.color = color != null ? color : LLRBNode.RED;
|
|
4558
|
+
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
4559
|
+
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
4560
|
+
this.size = this.left.size + 1 + this.right.size;
|
|
4654
4561
|
}
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4562
|
+
// Returns a copy of the current node, optionally replacing pieces of it.
|
|
4563
|
+
copy(key, value, color, left, right) {
|
|
4564
|
+
return new LLRBNode(key != null ? key : this.key, value != null ? value : this.value, color != null ? color : this.color, left != null ? left : this.left, right != null ? right : this.right);
|
|
4565
|
+
}
|
|
4566
|
+
isEmpty() {
|
|
4567
|
+
return false;
|
|
4568
|
+
}
|
|
4569
|
+
// Traverses the tree in key order and calls the specified action function
|
|
4570
|
+
// for each node. If action returns true, traversal is aborted.
|
|
4571
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4572
|
+
// value returned by action.
|
|
4573
|
+
inorderTraversal(action) {
|
|
4574
|
+
return (this.left.inorderTraversal(action) ||
|
|
4575
|
+
action(this.key, this.value) ||
|
|
4576
|
+
this.right.inorderTraversal(action));
|
|
4577
|
+
}
|
|
4578
|
+
// Traverses the tree in reverse key order and calls the specified action
|
|
4579
|
+
// function for each node. If action returns true, traversal is aborted.
|
|
4580
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4581
|
+
// value returned by action.
|
|
4582
|
+
reverseTraversal(action) {
|
|
4583
|
+
return (this.right.reverseTraversal(action) ||
|
|
4584
|
+
action(this.key, this.value) ||
|
|
4585
|
+
this.left.reverseTraversal(action));
|
|
4586
|
+
}
|
|
4587
|
+
// Returns the minimum node in the tree.
|
|
4588
|
+
min() {
|
|
4589
|
+
if (this.left.isEmpty()) {
|
|
4590
|
+
return this;
|
|
4666
4591
|
}
|
|
4667
|
-
else
|
|
4668
|
-
return
|
|
4592
|
+
else {
|
|
4593
|
+
return this.left.min();
|
|
4669
4594
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4595
|
+
}
|
|
4596
|
+
// Returns the maximum key in the tree.
|
|
4597
|
+
minKey() {
|
|
4598
|
+
return this.min().key;
|
|
4599
|
+
}
|
|
4600
|
+
// Returns the maximum key in the tree.
|
|
4601
|
+
maxKey() {
|
|
4602
|
+
if (this.right.isEmpty()) {
|
|
4603
|
+
return this.key;
|
|
4672
4604
|
}
|
|
4673
|
-
else
|
|
4674
|
-
return
|
|
4605
|
+
else {
|
|
4606
|
+
return this.right.maxKey();
|
|
4675
4607
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4608
|
+
}
|
|
4609
|
+
// Returns new tree, with the key/value added.
|
|
4610
|
+
insert(key, value, comparator) {
|
|
4611
|
+
let n = this;
|
|
4612
|
+
const cmp = comparator(key, n.key);
|
|
4613
|
+
if (cmp < 0) {
|
|
4614
|
+
n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
|
|
4615
|
+
}
|
|
4616
|
+
else if (cmp === 0) {
|
|
4617
|
+
n = n.copy(null, value, null, null, null);
|
|
4678
4618
|
}
|
|
4679
4619
|
else {
|
|
4680
|
-
|
|
4620
|
+
n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
|
|
4681
4621
|
}
|
|
4622
|
+
return n.fixUp();
|
|
4682
4623
|
}
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
: new KeyFieldNotInFilter(field, value);
|
|
4687
|
-
}
|
|
4688
|
-
matches(doc) {
|
|
4689
|
-
const other = doc.data.field(this.field);
|
|
4690
|
-
// Types do not have to match in NOT_EQUAL filters.
|
|
4691
|
-
if (this.op === "!=" /* NOT_EQUAL */) {
|
|
4692
|
-
return (other !== null &&
|
|
4693
|
-
this.matchesComparison(valueCompare(other, this.value)));
|
|
4624
|
+
removeMin() {
|
|
4625
|
+
if (this.left.isEmpty()) {
|
|
4626
|
+
return LLRBNode.EMPTY;
|
|
4694
4627
|
}
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4628
|
+
let n = this;
|
|
4629
|
+
if (!n.left.isRed() && !n.left.left.isRed()) {
|
|
4630
|
+
n = n.moveRedLeft();
|
|
4631
|
+
}
|
|
4632
|
+
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
4633
|
+
return n.fixUp();
|
|
4699
4634
|
}
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4635
|
+
// Returns new tree, with the specified item removed.
|
|
4636
|
+
remove(key, comparator) {
|
|
4637
|
+
let smallest;
|
|
4638
|
+
let n = this;
|
|
4639
|
+
if (comparator(key, n.key) < 0) {
|
|
4640
|
+
if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {
|
|
4641
|
+
n = n.moveRedLeft();
|
|
4642
|
+
}
|
|
4643
|
+
n = n.copy(null, null, null, n.left.remove(key, comparator), null);
|
|
4644
|
+
}
|
|
4645
|
+
else {
|
|
4646
|
+
if (n.left.isRed()) {
|
|
4647
|
+
n = n.rotateRight();
|
|
4648
|
+
}
|
|
4649
|
+
if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {
|
|
4650
|
+
n = n.moveRedRight();
|
|
4651
|
+
}
|
|
4652
|
+
if (comparator(key, n.key) === 0) {
|
|
4653
|
+
if (n.right.isEmpty()) {
|
|
4654
|
+
return LLRBNode.EMPTY;
|
|
4655
|
+
}
|
|
4656
|
+
else {
|
|
4657
|
+
smallest = n.right.min();
|
|
4658
|
+
n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
n = n.copy(null, null, null, null, n.right.remove(key, comparator));
|
|
4716
4662
|
}
|
|
4663
|
+
return n.fixUp();
|
|
4717
4664
|
}
|
|
4718
|
-
|
|
4719
|
-
return
|
|
4720
|
-
"<" /* LESS_THAN */,
|
|
4721
|
-
"<=" /* LESS_THAN_OR_EQUAL */,
|
|
4722
|
-
">" /* GREATER_THAN */,
|
|
4723
|
-
">=" /* GREATER_THAN_OR_EQUAL */,
|
|
4724
|
-
"!=" /* NOT_EQUAL */,
|
|
4725
|
-
"not-in" /* NOT_IN */
|
|
4726
|
-
].indexOf(this.op) >= 0);
|
|
4665
|
+
isRed() {
|
|
4666
|
+
return this.color;
|
|
4727
4667
|
}
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
}
|
|
4742
|
-
/** Returns a debug description for `filter`. */
|
|
4743
|
-
function stringifyFilter(filter) {
|
|
4744
|
-
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
4745
|
-
}
|
|
4746
|
-
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
4747
|
-
class KeyFieldFilter extends FieldFilter {
|
|
4748
|
-
constructor(field, op, value) {
|
|
4749
|
-
super(field, op, value);
|
|
4750
|
-
this.key = DocumentKey.fromName(value.referenceValue);
|
|
4668
|
+
// Returns new tree after performing any needed rotations.
|
|
4669
|
+
fixUp() {
|
|
4670
|
+
let n = this;
|
|
4671
|
+
if (n.right.isRed() && !n.left.isRed()) {
|
|
4672
|
+
n = n.rotateLeft();
|
|
4673
|
+
}
|
|
4674
|
+
if (n.left.isRed() && n.left.left.isRed()) {
|
|
4675
|
+
n = n.rotateRight();
|
|
4676
|
+
}
|
|
4677
|
+
if (n.left.isRed() && n.right.isRed()) {
|
|
4678
|
+
n = n.colorFlip();
|
|
4679
|
+
}
|
|
4680
|
+
return n;
|
|
4751
4681
|
}
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4682
|
+
moveRedLeft() {
|
|
4683
|
+
let n = this.colorFlip();
|
|
4684
|
+
if (n.right.left.isRed()) {
|
|
4685
|
+
n = n.copy(null, null, null, null, n.right.rotateRight());
|
|
4686
|
+
n = n.rotateLeft();
|
|
4687
|
+
n = n.colorFlip();
|
|
4688
|
+
}
|
|
4689
|
+
return n;
|
|
4755
4690
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4691
|
+
moveRedRight() {
|
|
4692
|
+
let n = this.colorFlip();
|
|
4693
|
+
if (n.left.left.isRed()) {
|
|
4694
|
+
n = n.rotateRight();
|
|
4695
|
+
n = n.colorFlip();
|
|
4696
|
+
}
|
|
4697
|
+
return n;
|
|
4762
4698
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4699
|
+
rotateLeft() {
|
|
4700
|
+
const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
|
|
4701
|
+
return this.right.copy(null, null, this.color, nl, null);
|
|
4765
4702
|
}
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
constructor(field, value) {
|
|
4770
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4771
|
-
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
4703
|
+
rotateRight() {
|
|
4704
|
+
const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
|
|
4705
|
+
return this.left.copy(null, null, this.color, null, nr);
|
|
4772
4706
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4707
|
+
colorFlip() {
|
|
4708
|
+
const left = this.left.copy(null, null, !this.left.color, null, null);
|
|
4709
|
+
const right = this.right.copy(null, null, !this.right.color, null, null);
|
|
4710
|
+
return this.copy(null, null, !this.color, left, right);
|
|
4775
4711
|
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
constructor(field, value) {
|
|
4786
|
-
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
4712
|
+
// For testing.
|
|
4713
|
+
checkMaxDepth() {
|
|
4714
|
+
const blackDepth = this.check();
|
|
4715
|
+
if (Math.pow(2.0, blackDepth) <= this.size + 1) {
|
|
4716
|
+
return true;
|
|
4717
|
+
}
|
|
4718
|
+
else {
|
|
4719
|
+
return false;
|
|
4720
|
+
}
|
|
4787
4721
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4722
|
+
// In a balanced RB tree, the black-depth (number of black nodes) from root to
|
|
4723
|
+
// leaves is equal on both sides. This function verifies that or asserts.
|
|
4724
|
+
check() {
|
|
4725
|
+
if (this.isRed() && this.left.isRed()) {
|
|
4726
|
+
throw fail();
|
|
4727
|
+
}
|
|
4728
|
+
if (this.right.isRed()) {
|
|
4729
|
+
throw fail();
|
|
4730
|
+
}
|
|
4731
|
+
const blackDepth = this.left.check();
|
|
4732
|
+
if (blackDepth !== this.right.check()) {
|
|
4733
|
+
throw fail();
|
|
4734
|
+
}
|
|
4735
|
+
else {
|
|
4736
|
+
return blackDepth + (this.isRed() ? 0 : 1);
|
|
4737
|
+
}
|
|
4791
4738
|
}
|
|
4792
|
-
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4739
|
+
} // end LLRBNode
|
|
4740
|
+
// Empty node is shared between all LLRB trees.
|
|
4741
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4742
|
+
LLRBNode.EMPTY = null;
|
|
4743
|
+
LLRBNode.RED = true;
|
|
4744
|
+
LLRBNode.BLACK = false;
|
|
4745
|
+
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
4746
|
+
class LLRBEmptyNode {
|
|
4747
|
+
constructor() {
|
|
4748
|
+
this.size = 0;
|
|
4797
4749
|
}
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
4750
|
+
get key() {
|
|
4751
|
+
throw fail();
|
|
4801
4752
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
class NotInFilter extends FieldFilter {
|
|
4805
|
-
constructor(field, value) {
|
|
4806
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4753
|
+
get value() {
|
|
4754
|
+
throw fail();
|
|
4807
4755
|
}
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
return false;
|
|
4811
|
-
}
|
|
4812
|
-
const other = doc.data.field(this.field);
|
|
4813
|
-
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
4756
|
+
get color() {
|
|
4757
|
+
throw fail();
|
|
4814
4758
|
}
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
class ArrayContainsAnyFilter extends FieldFilter {
|
|
4818
|
-
constructor(field, value) {
|
|
4819
|
-
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
4759
|
+
get left() {
|
|
4760
|
+
throw fail();
|
|
4820
4761
|
}
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
if (!isArray(other) || !other.arrayValue.values) {
|
|
4824
|
-
return false;
|
|
4825
|
-
}
|
|
4826
|
-
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
4762
|
+
get right() {
|
|
4763
|
+
throw fail();
|
|
4827
4764
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
*
|
|
4832
|
-
* The bound is specified with the given components representing a position and
|
|
4833
|
-
* whether it's just before or just after the position (relative to whatever the
|
|
4834
|
-
* query order is).
|
|
4835
|
-
*
|
|
4836
|
-
* The position represents a logical index position for a query. It's a prefix
|
|
4837
|
-
* of values for the (potentially implicit) order by clauses of a query.
|
|
4838
|
-
*
|
|
4839
|
-
* Bound provides a function to determine whether a document comes before or
|
|
4840
|
-
* after a bound. This is influenced by whether the position is just before or
|
|
4841
|
-
* just after the provided values.
|
|
4842
|
-
*/
|
|
4843
|
-
class Bound {
|
|
4844
|
-
constructor(position, inclusive) {
|
|
4845
|
-
this.position = position;
|
|
4846
|
-
this.inclusive = inclusive;
|
|
4765
|
+
// Returns a copy of the current node.
|
|
4766
|
+
copy(key, value, color, left, right) {
|
|
4767
|
+
return this;
|
|
4847
4768
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
*/
|
|
4852
|
-
class OrderBy {
|
|
4853
|
-
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
4854
|
-
this.field = field;
|
|
4855
|
-
this.dir = dir;
|
|
4769
|
+
// Returns a copy of the tree, with the specified key/value added.
|
|
4770
|
+
insert(key, value, comparator) {
|
|
4771
|
+
return new LLRBNode(key, value);
|
|
4856
4772
|
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
return orderBy.field.canonicalString() + orderBy.dir;
|
|
4861
|
-
}
|
|
4862
|
-
function stringifyOrderBy(orderBy) {
|
|
4863
|
-
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
4864
|
-
}
|
|
4865
|
-
function orderByEquals(left, right) {
|
|
4866
|
-
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4867
|
-
}
|
|
4868
|
-
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4869
|
-
let comparison = 0;
|
|
4870
|
-
for (let i = 0; i < bound.position.length; i++) {
|
|
4871
|
-
const orderByComponent = orderBy[i];
|
|
4872
|
-
const component = bound.position[i];
|
|
4873
|
-
if (orderByComponent.field.isKeyField()) {
|
|
4874
|
-
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
4875
|
-
}
|
|
4876
|
-
else {
|
|
4877
|
-
const docValue = doc.data.field(orderByComponent.field);
|
|
4878
|
-
comparison = valueCompare(component, docValue);
|
|
4879
|
-
}
|
|
4880
|
-
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
4881
|
-
comparison = comparison * -1;
|
|
4882
|
-
}
|
|
4883
|
-
if (comparison !== 0) {
|
|
4884
|
-
break;
|
|
4885
|
-
}
|
|
4773
|
+
// Returns a copy of the tree, with the specified key removed.
|
|
4774
|
+
remove(key, comparator) {
|
|
4775
|
+
return this;
|
|
4886
4776
|
}
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
/**
|
|
4890
|
-
* Returns true if a document sorts after a bound using the provided sort
|
|
4891
|
-
* order.
|
|
4892
|
-
*/
|
|
4893
|
-
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
4894
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4895
|
-
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
4896
|
-
}
|
|
4897
|
-
/**
|
|
4898
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4899
|
-
* order.
|
|
4900
|
-
*/
|
|
4901
|
-
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
4902
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4903
|
-
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4904
|
-
}
|
|
4905
|
-
function boundEquals(left, right) {
|
|
4906
|
-
if (left === null) {
|
|
4907
|
-
return right === null;
|
|
4777
|
+
isEmpty() {
|
|
4778
|
+
return true;
|
|
4908
4779
|
}
|
|
4909
|
-
|
|
4780
|
+
inorderTraversal(action) {
|
|
4910
4781
|
return false;
|
|
4911
4782
|
}
|
|
4912
|
-
|
|
4913
|
-
left.position.length !== right.position.length) {
|
|
4783
|
+
reverseTraversal(action) {
|
|
4914
4784
|
return false;
|
|
4915
4785
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
const rightPosition = right.position[i];
|
|
4919
|
-
if (!valueEquals(leftPosition, rightPosition)) {
|
|
4920
|
-
return false;
|
|
4921
|
-
}
|
|
4786
|
+
minKey() {
|
|
4787
|
+
return null;
|
|
4922
4788
|
}
|
|
4923
|
-
|
|
4924
|
-
|
|
4789
|
+
maxKey() {
|
|
4790
|
+
return null;
|
|
4791
|
+
}
|
|
4792
|
+
isRed() {
|
|
4793
|
+
return false;
|
|
4794
|
+
}
|
|
4795
|
+
// For testing.
|
|
4796
|
+
checkMaxDepth() {
|
|
4797
|
+
return true;
|
|
4798
|
+
}
|
|
4799
|
+
check() {
|
|
4800
|
+
return 0;
|
|
4801
|
+
}
|
|
4802
|
+
} // end LLRBEmptyNode
|
|
4803
|
+
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
4925
4804
|
|
|
4926
4805
|
/**
|
|
4927
4806
|
* @license
|
|
@@ -4940,325 +4819,223 @@ function boundEquals(left, right) {
|
|
|
4940
4819
|
* limitations under the License.
|
|
4941
4820
|
*/
|
|
4942
4821
|
/**
|
|
4943
|
-
*
|
|
4944
|
-
*
|
|
4945
|
-
* query the RemoteStore results.
|
|
4822
|
+
* SortedSet is an immutable (copy-on-write) collection that holds elements
|
|
4823
|
+
* in order specified by the provided comparator.
|
|
4946
4824
|
*
|
|
4947
|
-
*
|
|
4825
|
+
* NOTE: if provided comparator returns 0 for two elements, we consider them to
|
|
4826
|
+
* be equal!
|
|
4948
4827
|
*/
|
|
4949
|
-
class
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
*/
|
|
4954
|
-
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
4955
|
-
this.path = path;
|
|
4956
|
-
this.collectionGroup = collectionGroup;
|
|
4957
|
-
this.explicitOrderBy = explicitOrderBy;
|
|
4958
|
-
this.filters = filters;
|
|
4959
|
-
this.limit = limit;
|
|
4960
|
-
this.limitType = limitType;
|
|
4961
|
-
this.startAt = startAt;
|
|
4962
|
-
this.endAt = endAt;
|
|
4963
|
-
this.memoizedOrderBy = null;
|
|
4964
|
-
// The corresponding `Target` of this `Query` instance.
|
|
4965
|
-
this.memoizedTarget = null;
|
|
4966
|
-
if (this.startAt) ;
|
|
4967
|
-
if (this.endAt) ;
|
|
4828
|
+
class SortedSet {
|
|
4829
|
+
constructor(comparator) {
|
|
4830
|
+
this.comparator = comparator;
|
|
4831
|
+
this.data = new SortedMap(this.comparator);
|
|
4968
4832
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
4972
|
-
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
4973
|
-
}
|
|
4974
|
-
/** Creates a new Query for a query that matches all documents at `path` */
|
|
4975
|
-
function newQueryForPath(path) {
|
|
4976
|
-
return new QueryImpl(path);
|
|
4977
|
-
}
|
|
4978
|
-
/**
|
|
4979
|
-
* Helper to convert a collection group query into a collection query at a
|
|
4980
|
-
* specific path. This is used when executing collection group queries, since
|
|
4981
|
-
* we have to split the query into a set of collection queries at multiple
|
|
4982
|
-
* paths.
|
|
4983
|
-
*/
|
|
4984
|
-
function asCollectionQueryAtPath(query, path) {
|
|
4985
|
-
return new QueryImpl(path,
|
|
4986
|
-
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
4987
|
-
}
|
|
4988
|
-
/**
|
|
4989
|
-
* Returns true if this query does not specify any query constraints that
|
|
4990
|
-
* could remove results.
|
|
4991
|
-
*/
|
|
4992
|
-
function matchesAllDocuments(query) {
|
|
4993
|
-
return (query.filters.length === 0 &&
|
|
4994
|
-
query.limit === null &&
|
|
4995
|
-
query.startAt == null &&
|
|
4996
|
-
query.endAt == null &&
|
|
4997
|
-
(query.explicitOrderBy.length === 0 ||
|
|
4998
|
-
(query.explicitOrderBy.length === 1 &&
|
|
4999
|
-
query.explicitOrderBy[0].field.isKeyField())));
|
|
5000
|
-
}
|
|
5001
|
-
function hasLimitToFirst(query) {
|
|
5002
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "F" /* First */;
|
|
5003
|
-
}
|
|
5004
|
-
function hasLimitToLast(query) {
|
|
5005
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "L" /* Last */;
|
|
5006
|
-
}
|
|
5007
|
-
function getFirstOrderByField(query) {
|
|
5008
|
-
return query.explicitOrderBy.length > 0
|
|
5009
|
-
? query.explicitOrderBy[0].field
|
|
5010
|
-
: null;
|
|
5011
|
-
}
|
|
5012
|
-
function getInequalityFilterField(query) {
|
|
5013
|
-
for (const filter of query.filters) {
|
|
5014
|
-
if (filter.isInequality()) {
|
|
5015
|
-
return filter.field;
|
|
5016
|
-
}
|
|
4833
|
+
has(elem) {
|
|
4834
|
+
return this.data.get(elem) !== null;
|
|
5017
4835
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
/**
|
|
5021
|
-
* Checks if any of the provided Operators are included in the query and
|
|
5022
|
-
* returns the first one that is, or null if none are.
|
|
5023
|
-
*/
|
|
5024
|
-
function findFilterOperator(query, operators) {
|
|
5025
|
-
for (const filter of query.filters) {
|
|
5026
|
-
if (operators.indexOf(filter.op) >= 0) {
|
|
5027
|
-
return filter.op;
|
|
5028
|
-
}
|
|
4836
|
+
first() {
|
|
4837
|
+
return this.data.minKey();
|
|
5029
4838
|
}
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
}
|
|
5039
|
-
/**
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
return query.collectionGroup !== null;
|
|
5054
|
-
}
|
|
5055
|
-
/**
|
|
5056
|
-
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5057
|
-
* which can be different from the order by constraints the user provided (e.g.
|
|
5058
|
-
* the SDK and backend always orders by `__name__`).
|
|
5059
|
-
*/
|
|
5060
|
-
function queryOrderBy(query) {
|
|
5061
|
-
const queryImpl = debugCast(query);
|
|
5062
|
-
if (queryImpl.memoizedOrderBy === null) {
|
|
5063
|
-
queryImpl.memoizedOrderBy = [];
|
|
5064
|
-
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5065
|
-
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5066
|
-
if (inequalityField !== null && firstOrderByField === null) {
|
|
5067
|
-
// In order to implicitly add key ordering, we must also add the
|
|
5068
|
-
// inequality filter field for it to be a valid query.
|
|
5069
|
-
// Note that the default inequality field and key ordering is ascending.
|
|
5070
|
-
if (!inequalityField.isKeyField()) {
|
|
5071
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
4839
|
+
last() {
|
|
4840
|
+
return this.data.maxKey();
|
|
4841
|
+
}
|
|
4842
|
+
get size() {
|
|
4843
|
+
return this.data.size;
|
|
4844
|
+
}
|
|
4845
|
+
indexOf(elem) {
|
|
4846
|
+
return this.data.indexOf(elem);
|
|
4847
|
+
}
|
|
4848
|
+
/** Iterates elements in order defined by "comparator" */
|
|
4849
|
+
forEach(cb) {
|
|
4850
|
+
this.data.inorderTraversal((k, v) => {
|
|
4851
|
+
cb(k);
|
|
4852
|
+
return false;
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
4856
|
+
forEachInRange(range, cb) {
|
|
4857
|
+
const iter = this.data.getIteratorFrom(range[0]);
|
|
4858
|
+
while (iter.hasNext()) {
|
|
4859
|
+
const elem = iter.getNext();
|
|
4860
|
+
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
4861
|
+
return;
|
|
5072
4862
|
}
|
|
5073
|
-
|
|
4863
|
+
cb(elem.key);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
/**
|
|
4867
|
+
* Iterates over `elem`s such that: start <= elem until false is returned.
|
|
4868
|
+
*/
|
|
4869
|
+
forEachWhile(cb, start) {
|
|
4870
|
+
let iter;
|
|
4871
|
+
if (start !== undefined) {
|
|
4872
|
+
iter = this.data.getIteratorFrom(start);
|
|
5074
4873
|
}
|
|
5075
4874
|
else {
|
|
5076
|
-
|
|
5077
|
-
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5078
|
-
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5079
|
-
if (orderBy.field.isKeyField()) {
|
|
5080
|
-
foundKeyOrdering = true;
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
if (!foundKeyOrdering) {
|
|
5084
|
-
// The order of the implicit key ordering always matches the last
|
|
5085
|
-
// explicit order by
|
|
5086
|
-
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5087
|
-
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5088
|
-
.dir
|
|
5089
|
-
: "asc" /* ASCENDING */;
|
|
5090
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5091
|
-
}
|
|
4875
|
+
iter = this.data.getIterator();
|
|
5092
4876
|
}
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
*/
|
|
5099
|
-
function queryToTarget(query) {
|
|
5100
|
-
const queryImpl = debugCast(query);
|
|
5101
|
-
if (!queryImpl.memoizedTarget) {
|
|
5102
|
-
if (queryImpl.limitType === "F" /* First */) {
|
|
5103
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
5104
|
-
}
|
|
5105
|
-
else {
|
|
5106
|
-
// Flip the orderBy directions since we want the last results
|
|
5107
|
-
const orderBys = [];
|
|
5108
|
-
for (const orderBy of queryOrderBy(queryImpl)) {
|
|
5109
|
-
const dir = orderBy.dir === "desc" /* DESCENDING */
|
|
5110
|
-
? "asc" /* ASCENDING */
|
|
5111
|
-
: "desc" /* DESCENDING */;
|
|
5112
|
-
orderBys.push(new OrderBy(orderBy.field, dir));
|
|
4877
|
+
while (iter.hasNext()) {
|
|
4878
|
+
const elem = iter.getNext();
|
|
4879
|
+
const result = cb(elem.key);
|
|
4880
|
+
if (!result) {
|
|
4881
|
+
return;
|
|
5113
4882
|
}
|
|
5114
|
-
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5115
|
-
const startAt = queryImpl.endAt
|
|
5116
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
5117
|
-
: null;
|
|
5118
|
-
const endAt = queryImpl.startAt
|
|
5119
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
5120
|
-
: null;
|
|
5121
|
-
// Now return as a LimitType.First query.
|
|
5122
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5123
4883
|
}
|
|
5124
4884
|
}
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5130
|
-
}
|
|
5131
|
-
function queryWithAddedOrderBy(query, orderBy) {
|
|
5132
|
-
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5133
|
-
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5134
|
-
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5135
|
-
}
|
|
5136
|
-
function queryWithLimit(query, limit, limitType) {
|
|
5137
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5138
|
-
}
|
|
5139
|
-
function queryWithStartAt(query, bound) {
|
|
5140
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5141
|
-
}
|
|
5142
|
-
function queryWithEndAt(query, bound) {
|
|
5143
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5144
|
-
}
|
|
5145
|
-
function queryEquals(left, right) {
|
|
5146
|
-
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5147
|
-
left.limitType === right.limitType);
|
|
5148
|
-
}
|
|
5149
|
-
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5150
|
-
// example, use as a dictionary key, but the implementation is subject to
|
|
5151
|
-
// collisions. Make it collision-free.
|
|
5152
|
-
function canonifyQuery(query) {
|
|
5153
|
-
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5154
|
-
}
|
|
5155
|
-
function stringifyQuery(query) {
|
|
5156
|
-
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5157
|
-
}
|
|
5158
|
-
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5159
|
-
function queryMatches(query, doc) {
|
|
5160
|
-
return (doc.isFoundDocument() &&
|
|
5161
|
-
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5162
|
-
queryMatchesOrderBy(query, doc) &&
|
|
5163
|
-
queryMatchesFilters(query, doc) &&
|
|
5164
|
-
queryMatchesBounds(query, doc));
|
|
5165
|
-
}
|
|
5166
|
-
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5167
|
-
const docPath = doc.key.path;
|
|
5168
|
-
if (query.collectionGroup !== null) {
|
|
5169
|
-
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5170
|
-
// Group queries rooted at a document path yet.
|
|
5171
|
-
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5172
|
-
query.path.isPrefixOf(docPath));
|
|
4885
|
+
/** Finds the least element greater than or equal to `elem`. */
|
|
4886
|
+
firstAfterOrEqual(elem) {
|
|
4887
|
+
const iter = this.data.getIteratorFrom(elem);
|
|
4888
|
+
return iter.hasNext() ? iter.getNext().key : null;
|
|
5173
4889
|
}
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
return query.path.isEqual(docPath);
|
|
4890
|
+
getIterator() {
|
|
4891
|
+
return new SortedSetIterator(this.data.getIterator());
|
|
5177
4892
|
}
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
return query.path.isImmediateParentOf(docPath);
|
|
4893
|
+
getIteratorFrom(key) {
|
|
4894
|
+
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5181
4895
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
*/
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5191
|
-
return false;
|
|
4896
|
+
/** Inserts or updates an element */
|
|
4897
|
+
add(elem) {
|
|
4898
|
+
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
4899
|
+
}
|
|
4900
|
+
/** Deletes an element */
|
|
4901
|
+
delete(elem) {
|
|
4902
|
+
if (!this.has(elem)) {
|
|
4903
|
+
return this;
|
|
5192
4904
|
}
|
|
4905
|
+
return this.copy(this.data.remove(elem));
|
|
5193
4906
|
}
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
4907
|
+
isEmpty() {
|
|
4908
|
+
return this.data.isEmpty();
|
|
4909
|
+
}
|
|
4910
|
+
unionWith(other) {
|
|
4911
|
+
let result = this;
|
|
4912
|
+
// Make sure `result` always refers to the larger one of the two sets.
|
|
4913
|
+
if (result.size < other.size) {
|
|
4914
|
+
result = other;
|
|
4915
|
+
other = this;
|
|
4916
|
+
}
|
|
4917
|
+
other.forEach(elem => {
|
|
4918
|
+
result = result.add(elem);
|
|
4919
|
+
});
|
|
4920
|
+
return result;
|
|
4921
|
+
}
|
|
4922
|
+
isEqual(other) {
|
|
4923
|
+
if (!(other instanceof SortedSet)) {
|
|
4924
|
+
return false;
|
|
4925
|
+
}
|
|
4926
|
+
if (this.size !== other.size) {
|
|
5199
4927
|
return false;
|
|
5200
4928
|
}
|
|
4929
|
+
const thisIt = this.data.getIterator();
|
|
4930
|
+
const otherIt = other.data.getIterator();
|
|
4931
|
+
while (thisIt.hasNext()) {
|
|
4932
|
+
const thisElem = thisIt.getNext().key;
|
|
4933
|
+
const otherElem = otherIt.getNext().key;
|
|
4934
|
+
if (this.comparator(thisElem, otherElem) !== 0) {
|
|
4935
|
+
return false;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
return true;
|
|
4939
|
+
}
|
|
4940
|
+
toArray() {
|
|
4941
|
+
const res = [];
|
|
4942
|
+
this.forEach(targetId => {
|
|
4943
|
+
res.push(targetId);
|
|
4944
|
+
});
|
|
4945
|
+
return res;
|
|
4946
|
+
}
|
|
4947
|
+
toString() {
|
|
4948
|
+
const result = [];
|
|
4949
|
+
this.forEach(elem => result.push(elem));
|
|
4950
|
+
return 'SortedSet(' + result.toString() + ')';
|
|
4951
|
+
}
|
|
4952
|
+
copy(data) {
|
|
4953
|
+
const result = new SortedSet(this.comparator);
|
|
4954
|
+
result.data = data;
|
|
4955
|
+
return result;
|
|
5201
4956
|
}
|
|
5202
|
-
return true;
|
|
5203
4957
|
}
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5208
|
-
return false;
|
|
4958
|
+
class SortedSetIterator {
|
|
4959
|
+
constructor(iter) {
|
|
4960
|
+
this.iter = iter;
|
|
5209
4961
|
}
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
4962
|
+
getNext() {
|
|
4963
|
+
return this.iter.getNext().key;
|
|
4964
|
+
}
|
|
4965
|
+
hasNext() {
|
|
4966
|
+
return this.iter.hasNext();
|
|
5213
4967
|
}
|
|
5214
|
-
return true;
|
|
5215
4968
|
}
|
|
5216
4969
|
/**
|
|
5217
|
-
*
|
|
4970
|
+
* Compares two sorted sets for equality using their natural ordering. The
|
|
4971
|
+
* method computes the intersection and invokes `onAdd` for every element that
|
|
4972
|
+
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
4973
|
+
* `before` but missing from `after`.
|
|
5218
4974
|
*
|
|
5219
|
-
*
|
|
5220
|
-
*
|
|
4975
|
+
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
4976
|
+
* n), where n is the size of the two lists.
|
|
4977
|
+
*
|
|
4978
|
+
* @param before - The elements that exist in the original set.
|
|
4979
|
+
* @param after - The elements to diff against the original set.
|
|
4980
|
+
* @param comparator - The comparator for the elements in before and after.
|
|
4981
|
+
* @param onAdd - A function to invoke for every element that is part of `
|
|
4982
|
+
* after` but not `before`.
|
|
4983
|
+
* @param onRemove - A function to invoke for every element that is part of
|
|
4984
|
+
* `before` but not `after`.
|
|
5221
4985
|
*/
|
|
5222
|
-
function
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
4986
|
+
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
4987
|
+
const beforeIt = before.getIterator();
|
|
4988
|
+
const afterIt = after.getIterator();
|
|
4989
|
+
let beforeValue = advanceIterator(beforeIt);
|
|
4990
|
+
let afterValue = advanceIterator(afterIt);
|
|
4991
|
+
// Walk through the two sets at the same time, using the ordering defined by
|
|
4992
|
+
// `comparator`.
|
|
4993
|
+
while (beforeValue || afterValue) {
|
|
4994
|
+
let added = false;
|
|
4995
|
+
let removed = false;
|
|
4996
|
+
if (beforeValue && afterValue) {
|
|
4997
|
+
const cmp = comparator(beforeValue, afterValue);
|
|
4998
|
+
if (cmp < 0) {
|
|
4999
|
+
// The element was removed if the next element in our ordered
|
|
5000
|
+
// walkthrough is only in `before`.
|
|
5001
|
+
removed = true;
|
|
5002
|
+
}
|
|
5003
|
+
else if (cmp > 0) {
|
|
5004
|
+
// The element was added if the next element in our ordered walkthrough
|
|
5005
|
+
// is only in `after`.
|
|
5006
|
+
added = true;
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
else if (beforeValue != null) {
|
|
5010
|
+
removed = true;
|
|
5011
|
+
}
|
|
5012
|
+
else {
|
|
5013
|
+
added = true;
|
|
5014
|
+
}
|
|
5015
|
+
if (added) {
|
|
5016
|
+
onAdd(afterValue);
|
|
5017
|
+
afterValue = advanceIterator(afterIt);
|
|
5018
|
+
}
|
|
5019
|
+
else if (removed) {
|
|
5020
|
+
onRemove(beforeValue);
|
|
5021
|
+
beforeValue = advanceIterator(beforeIt);
|
|
5022
|
+
}
|
|
5023
|
+
else {
|
|
5024
|
+
beforeValue = advanceIterator(beforeIt);
|
|
5025
|
+
afterValue = advanceIterator(afterIt);
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5227
5028
|
}
|
|
5228
5029
|
/**
|
|
5229
|
-
* Returns
|
|
5230
|
-
* based on the Query's ordering constraint.
|
|
5030
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
5231
5031
|
*/
|
|
5232
|
-
function
|
|
5233
|
-
return (
|
|
5234
|
-
let comparedOnKeyField = false;
|
|
5235
|
-
for (const orderBy of queryOrderBy(query)) {
|
|
5236
|
-
const comp = compareDocs(orderBy, d1, d2);
|
|
5237
|
-
if (comp !== 0) {
|
|
5238
|
-
return comp;
|
|
5239
|
-
}
|
|
5240
|
-
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5241
|
-
}
|
|
5242
|
-
return 0;
|
|
5243
|
-
};
|
|
5244
|
-
}
|
|
5245
|
-
function compareDocs(orderBy, d1, d2) {
|
|
5246
|
-
const comparison = orderBy.field.isKeyField()
|
|
5247
|
-
? DocumentKey.comparator(d1.key, d2.key)
|
|
5248
|
-
: compareDocumentsByField(orderBy.field, d1, d2);
|
|
5249
|
-
switch (orderBy.dir) {
|
|
5250
|
-
case "asc" /* ASCENDING */:
|
|
5251
|
-
return comparison;
|
|
5252
|
-
case "desc" /* DESCENDING */:
|
|
5253
|
-
return -1 * comparison;
|
|
5254
|
-
default:
|
|
5255
|
-
return fail();
|
|
5256
|
-
}
|
|
5032
|
+
function advanceIterator(it) {
|
|
5033
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5257
5034
|
}
|
|
5258
5035
|
|
|
5259
5036
|
/**
|
|
5260
5037
|
* @license
|
|
5261
|
-
* Copyright
|
|
5038
|
+
* Copyright 2019 Google LLC
|
|
5262
5039
|
*
|
|
5263
5040
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5264
5041
|
* you may not use this file except in compliance with the License.
|
|
@@ -5272,555 +5049,961 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5272
5049
|
* See the License for the specific language governing permissions and
|
|
5273
5050
|
* limitations under the License.
|
|
5274
5051
|
*/
|
|
5052
|
+
// Visible for testing
|
|
5053
|
+
class TargetImpl {
|
|
5054
|
+
constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
5055
|
+
this.path = path;
|
|
5056
|
+
this.collectionGroup = collectionGroup;
|
|
5057
|
+
this.orderBy = orderBy;
|
|
5058
|
+
this.filters = filters;
|
|
5059
|
+
this.limit = limit;
|
|
5060
|
+
this.startAt = startAt;
|
|
5061
|
+
this.endAt = endAt;
|
|
5062
|
+
this.memoizedCanonicalId = null;
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5275
5065
|
/**
|
|
5276
|
-
*
|
|
5277
|
-
*
|
|
5278
|
-
*
|
|
5279
|
-
*
|
|
5066
|
+
* Initializes a Target with a path and optional additional query constraints.
|
|
5067
|
+
* Path must currently be empty if this is a collection group query.
|
|
5068
|
+
*
|
|
5069
|
+
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
5070
|
+
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
5071
|
+
* property.
|
|
5280
5072
|
*/
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
*/
|
|
5291
|
-
this.inner = {};
|
|
5292
|
-
/** The number of entries stored in the map */
|
|
5293
|
-
this.innerSize = 0;
|
|
5294
|
-
}
|
|
5295
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
5296
|
-
get(key) {
|
|
5297
|
-
const id = this.mapKeyFn(key);
|
|
5298
|
-
const matches = this.inner[id];
|
|
5299
|
-
if (matches === undefined) {
|
|
5300
|
-
return undefined;
|
|
5073
|
+
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
5074
|
+
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
5075
|
+
}
|
|
5076
|
+
function canonifyTarget(target) {
|
|
5077
|
+
const targetImpl = debugCast(target);
|
|
5078
|
+
if (targetImpl.memoizedCanonicalId === null) {
|
|
5079
|
+
let str = targetImpl.path.canonicalString();
|
|
5080
|
+
if (targetImpl.collectionGroup !== null) {
|
|
5081
|
+
str += '|cg:' + targetImpl.collectionGroup;
|
|
5301
5082
|
}
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5083
|
+
str += '|f:';
|
|
5084
|
+
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
5085
|
+
str += '|ob:';
|
|
5086
|
+
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
5087
|
+
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
5088
|
+
str += '|l:';
|
|
5089
|
+
str += targetImpl.limit;
|
|
5306
5090
|
}
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
}
|
|
5312
|
-
/** Put this key and value in the map. */
|
|
5313
|
-
set(key, value) {
|
|
5314
|
-
const id = this.mapKeyFn(key);
|
|
5315
|
-
const matches = this.inner[id];
|
|
5316
|
-
if (matches === undefined) {
|
|
5317
|
-
this.inner[id] = [[key, value]];
|
|
5318
|
-
this.innerSize++;
|
|
5319
|
-
return;
|
|
5091
|
+
if (targetImpl.startAt) {
|
|
5092
|
+
str += '|lb:';
|
|
5093
|
+
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
5094
|
+
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
5320
5095
|
}
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
return;
|
|
5326
|
-
}
|
|
5096
|
+
if (targetImpl.endAt) {
|
|
5097
|
+
str += '|ub:';
|
|
5098
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
5099
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5327
5100
|
}
|
|
5328
|
-
|
|
5329
|
-
this.innerSize++;
|
|
5101
|
+
targetImpl.memoizedCanonicalId = str;
|
|
5330
5102
|
}
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5103
|
+
return targetImpl.memoizedCanonicalId;
|
|
5104
|
+
}
|
|
5105
|
+
function stringifyTarget(target) {
|
|
5106
|
+
let str = target.path.canonicalString();
|
|
5107
|
+
if (target.collectionGroup !== null) {
|
|
5108
|
+
str += ' collectionGroup=' + target.collectionGroup;
|
|
5109
|
+
}
|
|
5110
|
+
if (target.filters.length > 0) {
|
|
5111
|
+
str += `, filters: [${target.filters
|
|
5112
|
+
.map(f => stringifyFilter(f))
|
|
5113
|
+
.join(', ')}]`;
|
|
5114
|
+
}
|
|
5115
|
+
if (!isNullOrUndefined(target.limit)) {
|
|
5116
|
+
str += ', limit: ' + target.limit;
|
|
5117
|
+
}
|
|
5118
|
+
if (target.orderBy.length > 0) {
|
|
5119
|
+
str += `, orderBy: [${target.orderBy
|
|
5120
|
+
.map(o => stringifyOrderBy(o))
|
|
5121
|
+
.join(', ')}]`;
|
|
5122
|
+
}
|
|
5123
|
+
if (target.startAt) {
|
|
5124
|
+
str += ', startAt: ';
|
|
5125
|
+
str += target.startAt.inclusive ? 'b:' : 'a:';
|
|
5126
|
+
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
5127
|
+
}
|
|
5128
|
+
if (target.endAt) {
|
|
5129
|
+
str += ', endAt: ';
|
|
5130
|
+
str += target.endAt.inclusive ? 'a:' : 'b:';
|
|
5131
|
+
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5132
|
+
}
|
|
5133
|
+
return `Target(${str})`;
|
|
5134
|
+
}
|
|
5135
|
+
function targetEquals(left, right) {
|
|
5136
|
+
if (left.limit !== right.limit) {
|
|
5137
|
+
return false;
|
|
5138
|
+
}
|
|
5139
|
+
if (left.orderBy.length !== right.orderBy.length) {
|
|
5140
|
+
return false;
|
|
5141
|
+
}
|
|
5142
|
+
for (let i = 0; i < left.orderBy.length; i++) {
|
|
5143
|
+
if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
|
|
5338
5144
|
return false;
|
|
5339
5145
|
}
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
if (matches.length === 1) {
|
|
5343
|
-
delete this.inner[id];
|
|
5344
|
-
}
|
|
5345
|
-
else {
|
|
5346
|
-
matches.splice(i, 1);
|
|
5347
|
-
}
|
|
5348
|
-
this.innerSize--;
|
|
5349
|
-
return true;
|
|
5350
|
-
}
|
|
5351
|
-
}
|
|
5146
|
+
}
|
|
5147
|
+
if (left.filters.length !== right.filters.length) {
|
|
5352
5148
|
return false;
|
|
5353
5149
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
}
|
|
5359
|
-
});
|
|
5150
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
5151
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
5152
|
+
return false;
|
|
5153
|
+
}
|
|
5360
5154
|
}
|
|
5361
|
-
|
|
5362
|
-
return
|
|
5155
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
5156
|
+
return false;
|
|
5363
5157
|
}
|
|
5364
|
-
|
|
5365
|
-
return
|
|
5158
|
+
if (!left.path.isEqual(right.path)) {
|
|
5159
|
+
return false;
|
|
5366
5160
|
}
|
|
5367
|
-
|
|
5368
|
-
|
|
5161
|
+
if (!boundEquals(left.startAt, right.startAt)) {
|
|
5162
|
+
return false;
|
|
5163
|
+
}
|
|
5164
|
+
return boundEquals(left.endAt, right.endAt);
|
|
5165
|
+
}
|
|
5166
|
+
function targetIsDocumentTarget(target) {
|
|
5167
|
+
return (DocumentKey.isDocumentKey(target.path) &&
|
|
5168
|
+
target.collectionGroup === null &&
|
|
5169
|
+
target.filters.length === 0);
|
|
5170
|
+
}
|
|
5171
|
+
/** Returns the field filters that target the given field path. */
|
|
5172
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
5173
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
5174
|
+
}
|
|
5369
5175
|
/**
|
|
5370
|
-
*
|
|
5371
|
-
*
|
|
5372
|
-
*
|
|
5373
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5374
|
-
* you may not use this file except in compliance with the License.
|
|
5375
|
-
* You may obtain a copy of the License at
|
|
5376
|
-
*
|
|
5377
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5378
|
-
*
|
|
5379
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
5380
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5381
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5382
|
-
* See the License for the specific language governing permissions and
|
|
5383
|
-
* limitations under the License.
|
|
5176
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
5177
|
+
* filters. Returns `null` if there are no such filters.
|
|
5384
5178
|
*/
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
this.comparator = comparator;
|
|
5390
|
-
this.root = root ? root : LLRBNode.EMPTY;
|
|
5179
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
5180
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
5181
|
+
if (segment === undefined) {
|
|
5182
|
+
return null;
|
|
5391
5183
|
}
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5184
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5185
|
+
switch (fieldFilter.op) {
|
|
5186
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
5187
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
5188
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
5189
|
+
return [fieldFilter.value];
|
|
5190
|
+
// Remaining filters are not array filters.
|
|
5191
|
+
}
|
|
5397
5192
|
}
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
indexOf(key) {
|
|
5424
|
-
// Number of nodes that were pruned when descending right
|
|
5425
|
-
let prunedNodes = 0;
|
|
5426
|
-
let node = this.root;
|
|
5427
|
-
while (!node.isEmpty()) {
|
|
5428
|
-
const cmp = this.comparator(key, node.key);
|
|
5429
|
-
if (cmp === 0) {
|
|
5430
|
-
return prunedNodes + node.left.size;
|
|
5431
|
-
}
|
|
5432
|
-
else if (cmp < 0) {
|
|
5433
|
-
node = node.left;
|
|
5434
|
-
}
|
|
5435
|
-
else {
|
|
5436
|
-
// Count all nodes left of the node plus the node itself
|
|
5437
|
-
prunedNodes += node.left.size + 1;
|
|
5438
|
-
node = node.right;
|
|
5193
|
+
return null;
|
|
5194
|
+
}
|
|
5195
|
+
/**
|
|
5196
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
5197
|
+
* `null` if there are no such filters.
|
|
5198
|
+
*/
|
|
5199
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
5200
|
+
const values = new Map();
|
|
5201
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5202
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5203
|
+
switch (fieldFilter.op) {
|
|
5204
|
+
case "==" /* EQUAL */:
|
|
5205
|
+
case "in" /* IN */:
|
|
5206
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
5207
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
5208
|
+
// `value != 'ab'`).
|
|
5209
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
5210
|
+
break;
|
|
5211
|
+
case "not-in" /* NOT_IN */:
|
|
5212
|
+
case "!=" /* NOT_EQUAL */:
|
|
5213
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
5214
|
+
// segments and hence we can return early here.
|
|
5215
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
5216
|
+
return Array.from(values.values());
|
|
5217
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
5439
5218
|
}
|
|
5440
5219
|
}
|
|
5441
|
-
// Node not found
|
|
5442
|
-
return -1;
|
|
5443
|
-
}
|
|
5444
|
-
isEmpty() {
|
|
5445
|
-
return this.root.isEmpty();
|
|
5446
|
-
}
|
|
5447
|
-
// Returns the total number of nodes in the map.
|
|
5448
|
-
get size() {
|
|
5449
|
-
return this.root.size;
|
|
5450
|
-
}
|
|
5451
|
-
// Returns the minimum key in the map.
|
|
5452
|
-
minKey() {
|
|
5453
|
-
return this.root.minKey();
|
|
5454
|
-
}
|
|
5455
|
-
// Returns the maximum key in the map.
|
|
5456
|
-
maxKey() {
|
|
5457
|
-
return this.root.maxKey();
|
|
5458
|
-
}
|
|
5459
|
-
// Traverses the map in key order and calls the specified action function
|
|
5460
|
-
// for each key/value pair. If action returns true, traversal is aborted.
|
|
5461
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5462
|
-
// value returned by action.
|
|
5463
|
-
inorderTraversal(action) {
|
|
5464
|
-
return this.root.inorderTraversal(action);
|
|
5465
|
-
}
|
|
5466
|
-
forEach(fn) {
|
|
5467
|
-
this.inorderTraversal((k, v) => {
|
|
5468
|
-
fn(k, v);
|
|
5469
|
-
return false;
|
|
5470
|
-
});
|
|
5471
|
-
}
|
|
5472
|
-
toString() {
|
|
5473
|
-
const descriptions = [];
|
|
5474
|
-
this.inorderTraversal((k, v) => {
|
|
5475
|
-
descriptions.push(`${k}:${v}`);
|
|
5476
|
-
return false;
|
|
5477
|
-
});
|
|
5478
|
-
return `{${descriptions.join(', ')}}`;
|
|
5479
|
-
}
|
|
5480
|
-
// Traverses the map in reverse key order and calls the specified action
|
|
5481
|
-
// function for each key/value pair. If action returns true, traversal is
|
|
5482
|
-
// aborted.
|
|
5483
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5484
|
-
// value returned by action.
|
|
5485
|
-
reverseTraversal(action) {
|
|
5486
|
-
return this.root.reverseTraversal(action);
|
|
5487
|
-
}
|
|
5488
|
-
// Returns an iterator over the SortedMap.
|
|
5489
|
-
getIterator() {
|
|
5490
|
-
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
5491
|
-
}
|
|
5492
|
-
getIteratorFrom(key) {
|
|
5493
|
-
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
5494
5220
|
}
|
|
5495
|
-
|
|
5496
|
-
|
|
5221
|
+
return null;
|
|
5222
|
+
}
|
|
5223
|
+
/**
|
|
5224
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
5225
|
+
* scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
|
|
5226
|
+
* exists.
|
|
5227
|
+
*/
|
|
5228
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
5229
|
+
const values = [];
|
|
5230
|
+
let inclusive = true;
|
|
5231
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
5232
|
+
// startAt.
|
|
5233
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5234
|
+
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
5235
|
+
? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
|
|
5236
|
+
: targetGetDescendingBound(target, segment.fieldPath, target.startAt);
|
|
5237
|
+
values.push(segmentBound.value);
|
|
5238
|
+
inclusive && (inclusive = segmentBound.inclusive);
|
|
5497
5239
|
}
|
|
5498
|
-
|
|
5499
|
-
|
|
5240
|
+
return new Bound(values, inclusive);
|
|
5241
|
+
}
|
|
5242
|
+
/**
|
|
5243
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
5244
|
+
* when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
|
|
5245
|
+
* upper bound exists.
|
|
5246
|
+
*/
|
|
5247
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
5248
|
+
const values = [];
|
|
5249
|
+
let inclusive = true;
|
|
5250
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
5251
|
+
// endAt.
|
|
5252
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5253
|
+
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
5254
|
+
? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
|
|
5255
|
+
: targetGetAscendingBound(target, segment.fieldPath, target.endAt);
|
|
5256
|
+
values.push(segmentBound.value);
|
|
5257
|
+
inclusive && (inclusive = segmentBound.inclusive);
|
|
5500
5258
|
}
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5259
|
+
return new Bound(values, inclusive);
|
|
5260
|
+
}
|
|
5261
|
+
/**
|
|
5262
|
+
* Returns the value to use as the lower bound for ascending index segment at
|
|
5263
|
+
* the provided `fieldPath` (or the upper bound for an descending segment).
|
|
5264
|
+
*/
|
|
5265
|
+
function targetGetAscendingBound(target, fieldPath, bound) {
|
|
5266
|
+
let value = MIN_VALUE;
|
|
5267
|
+
let inclusive = true;
|
|
5268
|
+
// Process all filters to find a value for the current field segment
|
|
5269
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
5270
|
+
let filterValue = MIN_VALUE;
|
|
5271
|
+
let filterInclusive = true;
|
|
5272
|
+
switch (fieldFilter.op) {
|
|
5273
|
+
case "<" /* LESS_THAN */:
|
|
5274
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5275
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
5276
|
+
break;
|
|
5277
|
+
case "==" /* EQUAL */:
|
|
5278
|
+
case "in" /* IN */:
|
|
5279
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5280
|
+
filterValue = fieldFilter.value;
|
|
5281
|
+
break;
|
|
5282
|
+
case ">" /* GREATER_THAN */:
|
|
5283
|
+
filterValue = fieldFilter.value;
|
|
5284
|
+
filterInclusive = false;
|
|
5285
|
+
break;
|
|
5286
|
+
case "!=" /* NOT_EQUAL */:
|
|
5287
|
+
case "not-in" /* NOT_IN */:
|
|
5288
|
+
filterValue = MIN_VALUE;
|
|
5289
|
+
break;
|
|
5290
|
+
// Remaining filters cannot be used as lower bounds.
|
|
5291
|
+
}
|
|
5292
|
+
if (lowerBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) < 0) {
|
|
5293
|
+
value = filterValue;
|
|
5294
|
+
inclusive = filterInclusive;
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
// If there is an additional bound, compare the values against the existing
|
|
5298
|
+
// range to see if we can narrow the scope.
|
|
5299
|
+
if (bound !== null) {
|
|
5300
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5301
|
+
const orderBy = target.orderBy[i];
|
|
5302
|
+
if (orderBy.field.isEqual(fieldPath)) {
|
|
5303
|
+
const cursorValue = bound.position[i];
|
|
5304
|
+
if (lowerBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) < 0) {
|
|
5305
|
+
value = cursorValue;
|
|
5306
|
+
inclusive = bound.inclusive;
|
|
5521
5307
|
}
|
|
5522
|
-
}
|
|
5523
|
-
else if (cmp === 0) {
|
|
5524
|
-
// This node is exactly equal to our start key. Push it on the stack,
|
|
5525
|
-
// but stop iterating;
|
|
5526
|
-
this.nodeStack.push(node);
|
|
5527
5308
|
break;
|
|
5528
5309
|
}
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
return { value, inclusive };
|
|
5313
|
+
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Returns the value to use as the upper bound for ascending index segment at
|
|
5316
|
+
* the provided `fieldPath` (or the lower bound for a descending segment).
|
|
5317
|
+
*/
|
|
5318
|
+
function targetGetDescendingBound(target, fieldPath, bound) {
|
|
5319
|
+
let value = MAX_VALUE;
|
|
5320
|
+
let inclusive = true;
|
|
5321
|
+
// Process all filters to find a value for the current field segment
|
|
5322
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
5323
|
+
let filterValue = MAX_VALUE;
|
|
5324
|
+
let filterInclusive = true;
|
|
5325
|
+
switch (fieldFilter.op) {
|
|
5326
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5327
|
+
case ">" /* GREATER_THAN */:
|
|
5328
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
5329
|
+
filterInclusive = false;
|
|
5330
|
+
break;
|
|
5331
|
+
case "==" /* EQUAL */:
|
|
5332
|
+
case "in" /* IN */:
|
|
5333
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5334
|
+
filterValue = fieldFilter.value;
|
|
5335
|
+
break;
|
|
5336
|
+
case "<" /* LESS_THAN */:
|
|
5337
|
+
filterValue = fieldFilter.value;
|
|
5338
|
+
filterInclusive = false;
|
|
5339
|
+
break;
|
|
5340
|
+
case "!=" /* NOT_EQUAL */:
|
|
5341
|
+
case "not-in" /* NOT_IN */:
|
|
5342
|
+
filterValue = MAX_VALUE;
|
|
5343
|
+
break;
|
|
5344
|
+
// Remaining filters cannot be used as upper bounds.
|
|
5345
|
+
}
|
|
5346
|
+
if (upperBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) > 0) {
|
|
5347
|
+
value = filterValue;
|
|
5348
|
+
inclusive = filterInclusive;
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
// If there is an additional bound, compare the values against the existing
|
|
5352
|
+
// range to see if we can narrow the scope.
|
|
5353
|
+
if (bound !== null) {
|
|
5354
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5355
|
+
const orderBy = target.orderBy[i];
|
|
5356
|
+
if (orderBy.field.isEqual(fieldPath)) {
|
|
5357
|
+
const cursorValue = bound.position[i];
|
|
5358
|
+
if (upperBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) > 0) {
|
|
5359
|
+
value = cursorValue;
|
|
5360
|
+
inclusive = bound.inclusive;
|
|
5538
5361
|
}
|
|
5362
|
+
break;
|
|
5539
5363
|
}
|
|
5540
5364
|
}
|
|
5541
5365
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5366
|
+
return { value, inclusive };
|
|
5367
|
+
}
|
|
5368
|
+
/** Returns the number of segments of a perfect index for this target. */
|
|
5369
|
+
function targetGetSegmentCount(target) {
|
|
5370
|
+
let fields = new SortedSet(FieldPath$1.comparator);
|
|
5371
|
+
let hasArraySegment = false;
|
|
5372
|
+
for (const filter of target.filters) {
|
|
5373
|
+
// TODO(orquery): Use the flattened filters here
|
|
5374
|
+
const fieldFilter = filter;
|
|
5375
|
+
// __name__ is not an explicit segment of any index, so we don't need to
|
|
5376
|
+
// count it.
|
|
5377
|
+
if (fieldFilter.field.isKeyField()) {
|
|
5378
|
+
continue;
|
|
5379
|
+
}
|
|
5380
|
+
// ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.
|
|
5381
|
+
// For instance, it is possible to have an index for "a ARRAY a ASC". Even
|
|
5382
|
+
// though these are on the same field, they should be counted as two
|
|
5383
|
+
// separate segments in an index.
|
|
5384
|
+
if (fieldFilter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
5385
|
+
fieldFilter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5386
|
+
hasArraySegment = true;
|
|
5551
5387
|
}
|
|
5552
5388
|
else {
|
|
5553
|
-
|
|
5554
|
-
while (!node.isEmpty()) {
|
|
5555
|
-
this.nodeStack.push(node);
|
|
5556
|
-
node = node.left;
|
|
5557
|
-
}
|
|
5389
|
+
fields = fields.add(fieldFilter.field);
|
|
5558
5390
|
}
|
|
5559
|
-
return result;
|
|
5560
5391
|
}
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
return null;
|
|
5392
|
+
for (const orderBy of target.orderBy) {
|
|
5393
|
+
// __name__ is not an explicit segment of any index, so we don't need to
|
|
5394
|
+
// count it.
|
|
5395
|
+
if (!orderBy.field.isKeyField()) {
|
|
5396
|
+
fields = fields.add(orderBy.field);
|
|
5567
5397
|
}
|
|
5568
|
-
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
5569
|
-
return { key: node.key, value: node.value };
|
|
5570
5398
|
}
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
class
|
|
5574
|
-
|
|
5575
|
-
|
|
5399
|
+
return fields.size + (hasArraySegment ? 1 : 0);
|
|
5400
|
+
}
|
|
5401
|
+
class Filter {
|
|
5402
|
+
}
|
|
5403
|
+
class FieldFilter extends Filter {
|
|
5404
|
+
constructor(field, op, value) {
|
|
5405
|
+
super();
|
|
5406
|
+
this.field = field;
|
|
5407
|
+
this.op = op;
|
|
5576
5408
|
this.value = value;
|
|
5577
|
-
this.color = color != null ? color : LLRBNode.RED;
|
|
5578
|
-
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
5579
|
-
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
5580
|
-
this.size = this.left.size + 1 + this.right.size;
|
|
5581
5409
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
inorderTraversal(action) {
|
|
5594
|
-
return (this.left.inorderTraversal(action) ||
|
|
5595
|
-
action(this.key, this.value) ||
|
|
5596
|
-
this.right.inorderTraversal(action));
|
|
5597
|
-
}
|
|
5598
|
-
// Traverses the tree in reverse key order and calls the specified action
|
|
5599
|
-
// function for each node. If action returns true, traversal is aborted.
|
|
5600
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5601
|
-
// value returned by action.
|
|
5602
|
-
reverseTraversal(action) {
|
|
5603
|
-
return (this.right.reverseTraversal(action) ||
|
|
5604
|
-
action(this.key, this.value) ||
|
|
5605
|
-
this.left.reverseTraversal(action));
|
|
5606
|
-
}
|
|
5607
|
-
// Returns the minimum node in the tree.
|
|
5608
|
-
min() {
|
|
5609
|
-
if (this.left.isEmpty()) {
|
|
5610
|
-
return this;
|
|
5611
|
-
}
|
|
5612
|
-
else {
|
|
5613
|
-
return this.left.min();
|
|
5410
|
+
/**
|
|
5411
|
+
* Creates a filter based on the provided arguments.
|
|
5412
|
+
*/
|
|
5413
|
+
static create(field, op, value) {
|
|
5414
|
+
if (field.isKeyField()) {
|
|
5415
|
+
if (op === "in" /* IN */ || op === "not-in" /* NOT_IN */) {
|
|
5416
|
+
return this.createKeyFieldInFilter(field, op, value);
|
|
5417
|
+
}
|
|
5418
|
+
else {
|
|
5419
|
+
return new KeyFieldFilter(field, op, value);
|
|
5420
|
+
}
|
|
5614
5421
|
}
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
minKey() {
|
|
5618
|
-
return this.min().key;
|
|
5619
|
-
}
|
|
5620
|
-
// Returns the maximum key in the tree.
|
|
5621
|
-
maxKey() {
|
|
5622
|
-
if (this.right.isEmpty()) {
|
|
5623
|
-
return this.key;
|
|
5422
|
+
else if (op === "array-contains" /* ARRAY_CONTAINS */) {
|
|
5423
|
+
return new ArrayContainsFilter(field, value);
|
|
5624
5424
|
}
|
|
5625
|
-
else {
|
|
5626
|
-
return
|
|
5425
|
+
else if (op === "in" /* IN */) {
|
|
5426
|
+
return new InFilter(field, value);
|
|
5627
5427
|
}
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
insert(key, value, comparator) {
|
|
5631
|
-
let n = this;
|
|
5632
|
-
const cmp = comparator(key, n.key);
|
|
5633
|
-
if (cmp < 0) {
|
|
5634
|
-
n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
|
|
5428
|
+
else if (op === "not-in" /* NOT_IN */) {
|
|
5429
|
+
return new NotInFilter(field, value);
|
|
5635
5430
|
}
|
|
5636
|
-
else if (
|
|
5637
|
-
|
|
5431
|
+
else if (op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5432
|
+
return new ArrayContainsAnyFilter(field, value);
|
|
5638
5433
|
}
|
|
5639
5434
|
else {
|
|
5640
|
-
|
|
5435
|
+
return new FieldFilter(field, op, value);
|
|
5641
5436
|
}
|
|
5642
|
-
return n.fixUp();
|
|
5643
5437
|
}
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
let n = this;
|
|
5649
|
-
if (!n.left.isRed() && !n.left.left.isRed()) {
|
|
5650
|
-
n = n.moveRedLeft();
|
|
5651
|
-
}
|
|
5652
|
-
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
5653
|
-
return n.fixUp();
|
|
5438
|
+
static createKeyFieldInFilter(field, op, value) {
|
|
5439
|
+
return op === "in" /* IN */
|
|
5440
|
+
? new KeyFieldInFilter(field, value)
|
|
5441
|
+
: new KeyFieldNotInFilter(field, value);
|
|
5654
5442
|
}
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
n = n.moveRedLeft();
|
|
5662
|
-
}
|
|
5663
|
-
n = n.copy(null, null, null, n.left.remove(key, comparator), null);
|
|
5443
|
+
matches(doc) {
|
|
5444
|
+
const other = doc.data.field(this.field);
|
|
5445
|
+
// Types do not have to match in NOT_EQUAL filters.
|
|
5446
|
+
if (this.op === "!=" /* NOT_EQUAL */) {
|
|
5447
|
+
return (other !== null &&
|
|
5448
|
+
this.matchesComparison(valueCompare(other, this.value)));
|
|
5664
5449
|
}
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5450
|
+
// Only compare types with matching backend order (such as double and int).
|
|
5451
|
+
return (other !== null &&
|
|
5452
|
+
typeOrder(this.value) === typeOrder(other) &&
|
|
5453
|
+
this.matchesComparison(valueCompare(other, this.value)));
|
|
5454
|
+
}
|
|
5455
|
+
matchesComparison(comparison) {
|
|
5456
|
+
switch (this.op) {
|
|
5457
|
+
case "<" /* LESS_THAN */:
|
|
5458
|
+
return comparison < 0;
|
|
5459
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5460
|
+
return comparison <= 0;
|
|
5461
|
+
case "==" /* EQUAL */:
|
|
5462
|
+
return comparison === 0;
|
|
5463
|
+
case "!=" /* NOT_EQUAL */:
|
|
5464
|
+
return comparison !== 0;
|
|
5465
|
+
case ">" /* GREATER_THAN */:
|
|
5466
|
+
return comparison > 0;
|
|
5467
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5468
|
+
return comparison >= 0;
|
|
5469
|
+
default:
|
|
5470
|
+
return fail();
|
|
5682
5471
|
}
|
|
5683
|
-
return n.fixUp();
|
|
5684
5472
|
}
|
|
5685
|
-
|
|
5686
|
-
return
|
|
5473
|
+
isInequality() {
|
|
5474
|
+
return ([
|
|
5475
|
+
"<" /* LESS_THAN */,
|
|
5476
|
+
"<=" /* LESS_THAN_OR_EQUAL */,
|
|
5477
|
+
">" /* GREATER_THAN */,
|
|
5478
|
+
">=" /* GREATER_THAN_OR_EQUAL */,
|
|
5479
|
+
"!=" /* NOT_EQUAL */,
|
|
5480
|
+
"not-in" /* NOT_IN */
|
|
5481
|
+
].indexOf(this.op) >= 0);
|
|
5687
5482
|
}
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5483
|
+
}
|
|
5484
|
+
function canonifyFilter(filter) {
|
|
5485
|
+
// TODO(b/29183165): Technically, this won't be unique if two values have
|
|
5486
|
+
// the same description, such as the int 3 and the string "3". So we should
|
|
5487
|
+
// add the types in here somehow, too.
|
|
5488
|
+
return (filter.field.canonicalString() +
|
|
5489
|
+
filter.op.toString() +
|
|
5490
|
+
canonicalId(filter.value));
|
|
5491
|
+
}
|
|
5492
|
+
function filterEquals(f1, f2) {
|
|
5493
|
+
return (f1.op === f2.op &&
|
|
5494
|
+
f1.field.isEqual(f2.field) &&
|
|
5495
|
+
valueEquals(f1.value, f2.value));
|
|
5496
|
+
}
|
|
5497
|
+
/** Returns a debug description for `filter`. */
|
|
5498
|
+
function stringifyFilter(filter) {
|
|
5499
|
+
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
5500
|
+
}
|
|
5501
|
+
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
5502
|
+
class KeyFieldFilter extends FieldFilter {
|
|
5503
|
+
constructor(field, op, value) {
|
|
5504
|
+
super(field, op, value);
|
|
5505
|
+
this.key = DocumentKey.fromName(value.referenceValue);
|
|
5701
5506
|
}
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
n = n.copy(null, null, null, null, n.right.rotateRight());
|
|
5706
|
-
n = n.rotateLeft();
|
|
5707
|
-
n = n.colorFlip();
|
|
5708
|
-
}
|
|
5709
|
-
return n;
|
|
5507
|
+
matches(doc) {
|
|
5508
|
+
const comparison = DocumentKey.comparator(doc.key, this.key);
|
|
5509
|
+
return this.matchesComparison(comparison);
|
|
5710
5510
|
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
return n;
|
|
5511
|
+
}
|
|
5512
|
+
/** Filter that matches on key fields within an array. */
|
|
5513
|
+
class KeyFieldInFilter extends FieldFilter {
|
|
5514
|
+
constructor(field, value) {
|
|
5515
|
+
super(field, "in" /* IN */, value);
|
|
5516
|
+
this.keys = extractDocumentKeysFromArrayValue("in" /* IN */, value);
|
|
5718
5517
|
}
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5518
|
+
matches(doc) {
|
|
5519
|
+
return this.keys.some(key => key.isEqual(doc.key));
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
/** Filter that matches on key fields not present within an array. */
|
|
5523
|
+
class KeyFieldNotInFilter extends FieldFilter {
|
|
5524
|
+
constructor(field, value) {
|
|
5525
|
+
super(field, "not-in" /* NOT_IN */, value);
|
|
5526
|
+
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
5527
|
+
}
|
|
5528
|
+
matches(doc) {
|
|
5529
|
+
return !this.keys.some(key => key.isEqual(doc.key));
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
function extractDocumentKeysFromArrayValue(op, value) {
|
|
5533
|
+
var _a;
|
|
5534
|
+
return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
|
|
5535
|
+
return DocumentKey.fromName(v.referenceValue);
|
|
5536
|
+
});
|
|
5537
|
+
}
|
|
5538
|
+
/** A Filter that implements the array-contains operator. */
|
|
5539
|
+
class ArrayContainsFilter extends FieldFilter {
|
|
5540
|
+
constructor(field, value) {
|
|
5541
|
+
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
5542
|
+
}
|
|
5543
|
+
matches(doc) {
|
|
5544
|
+
const other = doc.data.field(this.field);
|
|
5545
|
+
return isArray(other) && arrayValueContains(other.arrayValue, this.value);
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
/** A Filter that implements the IN operator. */
|
|
5549
|
+
class InFilter extends FieldFilter {
|
|
5550
|
+
constructor(field, value) {
|
|
5551
|
+
super(field, "in" /* IN */, value);
|
|
5552
|
+
}
|
|
5553
|
+
matches(doc) {
|
|
5554
|
+
const other = doc.data.field(this.field);
|
|
5555
|
+
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
/** A Filter that implements the not-in operator. */
|
|
5559
|
+
class NotInFilter extends FieldFilter {
|
|
5560
|
+
constructor(field, value) {
|
|
5561
|
+
super(field, "not-in" /* NOT_IN */, value);
|
|
5562
|
+
}
|
|
5563
|
+
matches(doc) {
|
|
5564
|
+
if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
|
|
5565
|
+
return false;
|
|
5566
|
+
}
|
|
5567
|
+
const other = doc.data.field(this.field);
|
|
5568
|
+
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
/** A Filter that implements the array-contains-any operator. */
|
|
5572
|
+
class ArrayContainsAnyFilter extends FieldFilter {
|
|
5573
|
+
constructor(field, value) {
|
|
5574
|
+
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
5575
|
+
}
|
|
5576
|
+
matches(doc) {
|
|
5577
|
+
const other = doc.data.field(this.field);
|
|
5578
|
+
if (!isArray(other) || !other.arrayValue.values) {
|
|
5579
|
+
return false;
|
|
5580
|
+
}
|
|
5581
|
+
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
/**
|
|
5585
|
+
* Represents a bound of a query.
|
|
5586
|
+
*
|
|
5587
|
+
* The bound is specified with the given components representing a position and
|
|
5588
|
+
* whether it's just before or just after the position (relative to whatever the
|
|
5589
|
+
* query order is).
|
|
5590
|
+
*
|
|
5591
|
+
* The position represents a logical index position for a query. It's a prefix
|
|
5592
|
+
* of values for the (potentially implicit) order by clauses of a query.
|
|
5593
|
+
*
|
|
5594
|
+
* Bound provides a function to determine whether a document comes before or
|
|
5595
|
+
* after a bound. This is influenced by whether the position is just before or
|
|
5596
|
+
* just after the provided values.
|
|
5597
|
+
*/
|
|
5598
|
+
class Bound {
|
|
5599
|
+
constructor(position, inclusive) {
|
|
5600
|
+
this.position = position;
|
|
5601
|
+
this.inclusive = inclusive;
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
5606
|
+
*/
|
|
5607
|
+
class OrderBy {
|
|
5608
|
+
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
5609
|
+
this.field = field;
|
|
5610
|
+
this.dir = dir;
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
function canonifyOrderBy(orderBy) {
|
|
5614
|
+
// TODO(b/29183165): Make this collision robust.
|
|
5615
|
+
return orderBy.field.canonicalString() + orderBy.dir;
|
|
5616
|
+
}
|
|
5617
|
+
function stringifyOrderBy(orderBy) {
|
|
5618
|
+
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
5619
|
+
}
|
|
5620
|
+
function orderByEquals(left, right) {
|
|
5621
|
+
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
5622
|
+
}
|
|
5623
|
+
function boundCompareToDocument(bound, orderBy, doc) {
|
|
5624
|
+
let comparison = 0;
|
|
5625
|
+
for (let i = 0; i < bound.position.length; i++) {
|
|
5626
|
+
const orderByComponent = orderBy[i];
|
|
5627
|
+
const component = bound.position[i];
|
|
5628
|
+
if (orderByComponent.field.isKeyField()) {
|
|
5629
|
+
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
5630
|
+
}
|
|
5631
|
+
else {
|
|
5632
|
+
const docValue = doc.data.field(orderByComponent.field);
|
|
5633
|
+
comparison = valueCompare(component, docValue);
|
|
5634
|
+
}
|
|
5635
|
+
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
5636
|
+
comparison = comparison * -1;
|
|
5637
|
+
}
|
|
5638
|
+
if (comparison !== 0) {
|
|
5639
|
+
break;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
return comparison;
|
|
5643
|
+
}
|
|
5644
|
+
/**
|
|
5645
|
+
* Returns true if a document sorts after a bound using the provided sort
|
|
5646
|
+
* order.
|
|
5647
|
+
*/
|
|
5648
|
+
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
5649
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5650
|
+
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
5651
|
+
}
|
|
5652
|
+
/**
|
|
5653
|
+
* Returns true if a document sorts before a bound using the provided sort
|
|
5654
|
+
* order.
|
|
5655
|
+
*/
|
|
5656
|
+
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
5657
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5658
|
+
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
5659
|
+
}
|
|
5660
|
+
function boundEquals(left, right) {
|
|
5661
|
+
if (left === null) {
|
|
5662
|
+
return right === null;
|
|
5663
|
+
}
|
|
5664
|
+
else if (right === null) {
|
|
5665
|
+
return false;
|
|
5666
|
+
}
|
|
5667
|
+
if (left.inclusive !== right.inclusive ||
|
|
5668
|
+
left.position.length !== right.position.length) {
|
|
5669
|
+
return false;
|
|
5670
|
+
}
|
|
5671
|
+
for (let i = 0; i < left.position.length; i++) {
|
|
5672
|
+
const leftPosition = left.position[i];
|
|
5673
|
+
const rightPosition = right.position[i];
|
|
5674
|
+
if (!valueEquals(leftPosition, rightPosition)) {
|
|
5675
|
+
return false;
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
return true;
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
/**
|
|
5682
|
+
* @license
|
|
5683
|
+
* Copyright 2017 Google LLC
|
|
5684
|
+
*
|
|
5685
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5686
|
+
* you may not use this file except in compliance with the License.
|
|
5687
|
+
* You may obtain a copy of the License at
|
|
5688
|
+
*
|
|
5689
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5690
|
+
*
|
|
5691
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5692
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5693
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5694
|
+
* See the License for the specific language governing permissions and
|
|
5695
|
+
* limitations under the License.
|
|
5696
|
+
*/
|
|
5697
|
+
/**
|
|
5698
|
+
* Query encapsulates all the query attributes we support in the SDK. It can
|
|
5699
|
+
* be run against the LocalStore, as well as be converted to a `Target` to
|
|
5700
|
+
* query the RemoteStore results.
|
|
5701
|
+
*
|
|
5702
|
+
* Visible for testing.
|
|
5703
|
+
*/
|
|
5704
|
+
class QueryImpl {
|
|
5705
|
+
/**
|
|
5706
|
+
* Initializes a Query with a path and optional additional query constraints.
|
|
5707
|
+
* Path must currently be empty if this is a collection group query.
|
|
5708
|
+
*/
|
|
5709
|
+
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
5710
|
+
this.path = path;
|
|
5711
|
+
this.collectionGroup = collectionGroup;
|
|
5712
|
+
this.explicitOrderBy = explicitOrderBy;
|
|
5713
|
+
this.filters = filters;
|
|
5714
|
+
this.limit = limit;
|
|
5715
|
+
this.limitType = limitType;
|
|
5716
|
+
this.startAt = startAt;
|
|
5717
|
+
this.endAt = endAt;
|
|
5718
|
+
this.memoizedOrderBy = null;
|
|
5719
|
+
// The corresponding `Target` of this `Query` instance.
|
|
5720
|
+
this.memoizedTarget = null;
|
|
5721
|
+
if (this.startAt) ;
|
|
5722
|
+
if (this.endAt) ;
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
/** Creates a new Query instance with the options provided. */
|
|
5726
|
+
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
5727
|
+
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
5728
|
+
}
|
|
5729
|
+
/** Creates a new Query for a query that matches all documents at `path` */
|
|
5730
|
+
function newQueryForPath(path) {
|
|
5731
|
+
return new QueryImpl(path);
|
|
5732
|
+
}
|
|
5733
|
+
/**
|
|
5734
|
+
* Helper to convert a collection group query into a collection query at a
|
|
5735
|
+
* specific path. This is used when executing collection group queries, since
|
|
5736
|
+
* we have to split the query into a set of collection queries at multiple
|
|
5737
|
+
* paths.
|
|
5738
|
+
*/
|
|
5739
|
+
function asCollectionQueryAtPath(query, path) {
|
|
5740
|
+
return new QueryImpl(path,
|
|
5741
|
+
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5742
|
+
}
|
|
5743
|
+
/**
|
|
5744
|
+
* Returns true if this query does not specify any query constraints that
|
|
5745
|
+
* could remove results.
|
|
5746
|
+
*/
|
|
5747
|
+
function queryMatchesAllDocuments(query) {
|
|
5748
|
+
return (query.filters.length === 0 &&
|
|
5749
|
+
query.limit === null &&
|
|
5750
|
+
query.startAt == null &&
|
|
5751
|
+
query.endAt == null &&
|
|
5752
|
+
(query.explicitOrderBy.length === 0 ||
|
|
5753
|
+
(query.explicitOrderBy.length === 1 &&
|
|
5754
|
+
query.explicitOrderBy[0].field.isKeyField())));
|
|
5755
|
+
}
|
|
5756
|
+
function getFirstOrderByField(query) {
|
|
5757
|
+
return query.explicitOrderBy.length > 0
|
|
5758
|
+
? query.explicitOrderBy[0].field
|
|
5759
|
+
: null;
|
|
5760
|
+
}
|
|
5761
|
+
function getInequalityFilterField(query) {
|
|
5762
|
+
for (const filter of query.filters) {
|
|
5763
|
+
if (filter.isInequality()) {
|
|
5764
|
+
return filter.field;
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
return null;
|
|
5768
|
+
}
|
|
5769
|
+
/**
|
|
5770
|
+
* Checks if any of the provided Operators are included in the query and
|
|
5771
|
+
* returns the first one that is, or null if none are.
|
|
5772
|
+
*/
|
|
5773
|
+
function findFilterOperator(query, operators) {
|
|
5774
|
+
for (const filter of query.filters) {
|
|
5775
|
+
if (operators.indexOf(filter.op) >= 0) {
|
|
5776
|
+
return filter.op;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return null;
|
|
5780
|
+
}
|
|
5781
|
+
/**
|
|
5782
|
+
* Creates a new Query for a collection group query that matches all documents
|
|
5783
|
+
* within the provided collection group.
|
|
5784
|
+
*/
|
|
5785
|
+
function newQueryForCollectionGroup(collectionId) {
|
|
5786
|
+
return new QueryImpl(ResourcePath.emptyPath(), collectionId);
|
|
5787
|
+
}
|
|
5788
|
+
/**
|
|
5789
|
+
* Returns whether the query matches a single document by path (rather than a
|
|
5790
|
+
* collection).
|
|
5791
|
+
*/
|
|
5792
|
+
function isDocumentQuery$1(query) {
|
|
5793
|
+
return (DocumentKey.isDocumentKey(query.path) &&
|
|
5794
|
+
query.collectionGroup === null &&
|
|
5795
|
+
query.filters.length === 0);
|
|
5796
|
+
}
|
|
5797
|
+
/**
|
|
5798
|
+
* Returns whether the query matches a collection group rather than a specific
|
|
5799
|
+
* collection.
|
|
5800
|
+
*/
|
|
5801
|
+
function isCollectionGroupQuery(query) {
|
|
5802
|
+
return query.collectionGroup !== null;
|
|
5803
|
+
}
|
|
5804
|
+
/**
|
|
5805
|
+
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5806
|
+
* which can be different from the order by constraints the user provided (e.g.
|
|
5807
|
+
* the SDK and backend always orders by `__name__`).
|
|
5808
|
+
*/
|
|
5809
|
+
function queryOrderBy(query) {
|
|
5810
|
+
const queryImpl = debugCast(query);
|
|
5811
|
+
if (queryImpl.memoizedOrderBy === null) {
|
|
5812
|
+
queryImpl.memoizedOrderBy = [];
|
|
5813
|
+
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5814
|
+
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5815
|
+
if (inequalityField !== null && firstOrderByField === null) {
|
|
5816
|
+
// In order to implicitly add key ordering, we must also add the
|
|
5817
|
+
// inequality filter field for it to be a valid query.
|
|
5818
|
+
// Note that the default inequality field and key ordering is ascending.
|
|
5819
|
+
if (!inequalityField.isKeyField()) {
|
|
5820
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
5821
|
+
}
|
|
5822
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
5823
|
+
}
|
|
5824
|
+
else {
|
|
5825
|
+
let foundKeyOrdering = false;
|
|
5826
|
+
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5827
|
+
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5828
|
+
if (orderBy.field.isKeyField()) {
|
|
5829
|
+
foundKeyOrdering = true;
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
if (!foundKeyOrdering) {
|
|
5833
|
+
// The order of the implicit key ordering always matches the last
|
|
5834
|
+
// explicit order by
|
|
5835
|
+
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5836
|
+
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5837
|
+
.dir
|
|
5838
|
+
: "asc" /* ASCENDING */;
|
|
5839
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
return queryImpl.memoizedOrderBy;
|
|
5844
|
+
}
|
|
5845
|
+
/**
|
|
5846
|
+
* Converts this `Query` instance to it's corresponding `Target` representation.
|
|
5847
|
+
*/
|
|
5848
|
+
function queryToTarget(query) {
|
|
5849
|
+
const queryImpl = debugCast(query);
|
|
5850
|
+
if (!queryImpl.memoizedTarget) {
|
|
5851
|
+
if (queryImpl.limitType === "F" /* First */) {
|
|
5852
|
+
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
5853
|
+
}
|
|
5854
|
+
else {
|
|
5855
|
+
// Flip the orderBy directions since we want the last results
|
|
5856
|
+
const orderBys = [];
|
|
5857
|
+
for (const orderBy of queryOrderBy(queryImpl)) {
|
|
5858
|
+
const dir = orderBy.dir === "desc" /* DESCENDING */
|
|
5859
|
+
? "asc" /* ASCENDING */
|
|
5860
|
+
: "desc" /* DESCENDING */;
|
|
5861
|
+
orderBys.push(new OrderBy(orderBy.field, dir));
|
|
5862
|
+
}
|
|
5863
|
+
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5864
|
+
const startAt = queryImpl.endAt
|
|
5865
|
+
? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)
|
|
5866
|
+
: null;
|
|
5867
|
+
const endAt = queryImpl.startAt
|
|
5868
|
+
? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)
|
|
5869
|
+
: null;
|
|
5870
|
+
// Now return as a LimitType.First query.
|
|
5871
|
+
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
return queryImpl.memoizedTarget;
|
|
5875
|
+
}
|
|
5876
|
+
function queryWithAddedFilter(query, filter) {
|
|
5877
|
+
const newFilters = query.filters.concat([filter]);
|
|
5878
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5879
|
+
}
|
|
5880
|
+
function queryWithAddedOrderBy(query, orderBy) {
|
|
5881
|
+
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5882
|
+
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5883
|
+
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5884
|
+
}
|
|
5885
|
+
function queryWithLimit(query, limit, limitType) {
|
|
5886
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5887
|
+
}
|
|
5888
|
+
function queryWithStartAt(query, bound) {
|
|
5889
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5890
|
+
}
|
|
5891
|
+
function queryWithEndAt(query, bound) {
|
|
5892
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5893
|
+
}
|
|
5894
|
+
function queryEquals(left, right) {
|
|
5895
|
+
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5896
|
+
left.limitType === right.limitType);
|
|
5897
|
+
}
|
|
5898
|
+
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5899
|
+
// example, use as a dictionary key, but the implementation is subject to
|
|
5900
|
+
// collisions. Make it collision-free.
|
|
5901
|
+
function canonifyQuery(query) {
|
|
5902
|
+
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5903
|
+
}
|
|
5904
|
+
function stringifyQuery(query) {
|
|
5905
|
+
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5906
|
+
}
|
|
5907
|
+
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5908
|
+
function queryMatches(query, doc) {
|
|
5909
|
+
return (doc.isFoundDocument() &&
|
|
5910
|
+
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5911
|
+
queryMatchesOrderBy(query, doc) &&
|
|
5912
|
+
queryMatchesFilters(query, doc) &&
|
|
5913
|
+
queryMatchesBounds(query, doc));
|
|
5914
|
+
}
|
|
5915
|
+
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5916
|
+
const docPath = doc.key.path;
|
|
5917
|
+
if (query.collectionGroup !== null) {
|
|
5918
|
+
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5919
|
+
// Group queries rooted at a document path yet.
|
|
5920
|
+
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5921
|
+
query.path.isPrefixOf(docPath));
|
|
5722
5922
|
}
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
return
|
|
5923
|
+
else if (DocumentKey.isDocumentKey(query.path)) {
|
|
5924
|
+
// exact match for document queries
|
|
5925
|
+
return query.path.isEqual(docPath);
|
|
5726
5926
|
}
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
return this.copy(null, null, !this.color, left, right);
|
|
5927
|
+
else {
|
|
5928
|
+
// shallow ancestor queries by default
|
|
5929
|
+
return query.path.isImmediateParentOf(docPath);
|
|
5731
5930
|
}
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5931
|
+
}
|
|
5932
|
+
/**
|
|
5933
|
+
* A document must have a value for every ordering clause in order to show up
|
|
5934
|
+
* in the results.
|
|
5935
|
+
*/
|
|
5936
|
+
function queryMatchesOrderBy(query, doc) {
|
|
5937
|
+
for (const orderBy of query.explicitOrderBy) {
|
|
5938
|
+
// order by key always matches
|
|
5939
|
+
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5739
5940
|
return false;
|
|
5740
5941
|
}
|
|
5741
5942
|
}
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
if (this.right.isRed()) {
|
|
5749
|
-
throw fail();
|
|
5750
|
-
}
|
|
5751
|
-
const blackDepth = this.left.check();
|
|
5752
|
-
if (blackDepth !== this.right.check()) {
|
|
5753
|
-
throw fail();
|
|
5754
|
-
}
|
|
5755
|
-
else {
|
|
5756
|
-
return blackDepth + (this.isRed() ? 0 : 1);
|
|
5943
|
+
return true;
|
|
5944
|
+
}
|
|
5945
|
+
function queryMatchesFilters(query, doc) {
|
|
5946
|
+
for (const filter of query.filters) {
|
|
5947
|
+
if (!filter.matches(doc)) {
|
|
5948
|
+
return false;
|
|
5757
5949
|
}
|
|
5758
5950
|
}
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
5766
|
-
class LLRBEmptyNode {
|
|
5767
|
-
constructor() {
|
|
5768
|
-
this.size = 0;
|
|
5769
|
-
}
|
|
5770
|
-
get key() {
|
|
5771
|
-
throw fail();
|
|
5772
|
-
}
|
|
5773
|
-
get value() {
|
|
5774
|
-
throw fail();
|
|
5775
|
-
}
|
|
5776
|
-
get color() {
|
|
5777
|
-
throw fail();
|
|
5778
|
-
}
|
|
5779
|
-
get left() {
|
|
5780
|
-
throw fail();
|
|
5781
|
-
}
|
|
5782
|
-
get right() {
|
|
5783
|
-
throw fail();
|
|
5784
|
-
}
|
|
5785
|
-
// Returns a copy of the current node.
|
|
5786
|
-
copy(key, value, color, left, right) {
|
|
5787
|
-
return this;
|
|
5788
|
-
}
|
|
5789
|
-
// Returns a copy of the tree, with the specified key/value added.
|
|
5790
|
-
insert(key, value, comparator) {
|
|
5791
|
-
return new LLRBNode(key, value);
|
|
5792
|
-
}
|
|
5793
|
-
// Returns a copy of the tree, with the specified key removed.
|
|
5794
|
-
remove(key, comparator) {
|
|
5795
|
-
return this;
|
|
5796
|
-
}
|
|
5797
|
-
isEmpty() {
|
|
5798
|
-
return true;
|
|
5799
|
-
}
|
|
5800
|
-
inorderTraversal(action) {
|
|
5801
|
-
return false;
|
|
5802
|
-
}
|
|
5803
|
-
reverseTraversal(action) {
|
|
5951
|
+
return true;
|
|
5952
|
+
}
|
|
5953
|
+
/** Makes sure a document is within the bounds, if provided. */
|
|
5954
|
+
function queryMatchesBounds(query, doc) {
|
|
5955
|
+
if (query.startAt &&
|
|
5956
|
+
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5804
5957
|
return false;
|
|
5805
5958
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
}
|
|
5809
|
-
maxKey() {
|
|
5810
|
-
return null;
|
|
5811
|
-
}
|
|
5812
|
-
isRed() {
|
|
5959
|
+
if (query.endAt &&
|
|
5960
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
5813
5961
|
return false;
|
|
5814
5962
|
}
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5963
|
+
return true;
|
|
5964
|
+
}
|
|
5965
|
+
/**
|
|
5966
|
+
* Returns the collection group that this query targets.
|
|
5967
|
+
*
|
|
5968
|
+
* PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
|
|
5969
|
+
* synchronization for query results.
|
|
5970
|
+
*/
|
|
5971
|
+
function queryCollectionGroup(query) {
|
|
5972
|
+
return (query.collectionGroup ||
|
|
5973
|
+
(query.path.length % 2 === 1
|
|
5974
|
+
? query.path.lastSegment()
|
|
5975
|
+
: query.path.get(query.path.length - 2)));
|
|
5976
|
+
}
|
|
5977
|
+
/**
|
|
5978
|
+
* Returns a new comparator function that can be used to compare two documents
|
|
5979
|
+
* based on the Query's ordering constraint.
|
|
5980
|
+
*/
|
|
5981
|
+
function newQueryComparator(query) {
|
|
5982
|
+
return (d1, d2) => {
|
|
5983
|
+
let comparedOnKeyField = false;
|
|
5984
|
+
for (const orderBy of queryOrderBy(query)) {
|
|
5985
|
+
const comp = compareDocs(orderBy, d1, d2);
|
|
5986
|
+
if (comp !== 0) {
|
|
5987
|
+
return comp;
|
|
5988
|
+
}
|
|
5989
|
+
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5990
|
+
}
|
|
5820
5991
|
return 0;
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5994
|
+
function compareDocs(orderBy, d1, d2) {
|
|
5995
|
+
const comparison = orderBy.field.isKeyField()
|
|
5996
|
+
? DocumentKey.comparator(d1.key, d2.key)
|
|
5997
|
+
: compareDocumentsByField(orderBy.field, d1, d2);
|
|
5998
|
+
switch (orderBy.dir) {
|
|
5999
|
+
case "asc" /* ASCENDING */:
|
|
6000
|
+
return comparison;
|
|
6001
|
+
case "desc" /* DESCENDING */:
|
|
6002
|
+
return -1 * comparison;
|
|
6003
|
+
default:
|
|
6004
|
+
return fail();
|
|
5821
6005
|
}
|
|
5822
|
-
}
|
|
5823
|
-
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
6006
|
+
}
|
|
5824
6007
|
|
|
5825
6008
|
/**
|
|
5826
6009
|
* @license
|
|
@@ -5839,218 +6022,97 @@ LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
|
5839
6022
|
* limitations under the License.
|
|
5840
6023
|
*/
|
|
5841
6024
|
/**
|
|
5842
|
-
*
|
|
5843
|
-
*
|
|
5844
|
-
*
|
|
5845
|
-
*
|
|
5846
|
-
* be equal!
|
|
6025
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
6026
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
6027
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
6028
|
+
* automatically handles collisions of keys.
|
|
5847
6029
|
*/
|
|
5848
|
-
class
|
|
5849
|
-
constructor(
|
|
5850
|
-
this.
|
|
5851
|
-
this.
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
/** Iterates elements in order defined by "comparator" */
|
|
5869
|
-
forEach(cb) {
|
|
5870
|
-
this.data.inorderTraversal((k, v) => {
|
|
5871
|
-
cb(k);
|
|
5872
|
-
return false;
|
|
5873
|
-
});
|
|
5874
|
-
}
|
|
5875
|
-
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
5876
|
-
forEachInRange(range, cb) {
|
|
5877
|
-
const iter = this.data.getIteratorFrom(range[0]);
|
|
5878
|
-
while (iter.hasNext()) {
|
|
5879
|
-
const elem = iter.getNext();
|
|
5880
|
-
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
5881
|
-
return;
|
|
5882
|
-
}
|
|
5883
|
-
cb(elem.key);
|
|
5884
|
-
}
|
|
5885
|
-
}
|
|
5886
|
-
/**
|
|
5887
|
-
* Iterates over `elem`s such that: start <= elem until false is returned.
|
|
5888
|
-
*/
|
|
5889
|
-
forEachWhile(cb, start) {
|
|
5890
|
-
let iter;
|
|
5891
|
-
if (start !== undefined) {
|
|
5892
|
-
iter = this.data.getIteratorFrom(start);
|
|
5893
|
-
}
|
|
5894
|
-
else {
|
|
5895
|
-
iter = this.data.getIterator();
|
|
6030
|
+
class ObjectMap {
|
|
6031
|
+
constructor(mapKeyFn, equalsFn) {
|
|
6032
|
+
this.mapKeyFn = mapKeyFn;
|
|
6033
|
+
this.equalsFn = equalsFn;
|
|
6034
|
+
/**
|
|
6035
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
6036
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
6037
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
6038
|
+
* constant time lookups in practice.
|
|
6039
|
+
*/
|
|
6040
|
+
this.inner = {};
|
|
6041
|
+
/** The number of entries stored in the map */
|
|
6042
|
+
this.innerSize = 0;
|
|
6043
|
+
}
|
|
6044
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
6045
|
+
get(key) {
|
|
6046
|
+
const id = this.mapKeyFn(key);
|
|
6047
|
+
const matches = this.inner[id];
|
|
6048
|
+
if (matches === undefined) {
|
|
6049
|
+
return undefined;
|
|
5896
6050
|
}
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
if (!result) {
|
|
5901
|
-
return;
|
|
6051
|
+
for (const [otherKey, value] of matches) {
|
|
6052
|
+
if (this.equalsFn(otherKey, key)) {
|
|
6053
|
+
return value;
|
|
5902
6054
|
}
|
|
5903
6055
|
}
|
|
6056
|
+
return undefined;
|
|
5904
6057
|
}
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
const iter = this.data.getIteratorFrom(elem);
|
|
5908
|
-
return iter.hasNext() ? iter.getNext().key : null;
|
|
5909
|
-
}
|
|
5910
|
-
getIterator() {
|
|
5911
|
-
return new SortedSetIterator(this.data.getIterator());
|
|
5912
|
-
}
|
|
5913
|
-
getIteratorFrom(key) {
|
|
5914
|
-
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5915
|
-
}
|
|
5916
|
-
/** Inserts or updates an element */
|
|
5917
|
-
add(elem) {
|
|
5918
|
-
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
6058
|
+
has(key) {
|
|
6059
|
+
return this.get(key) !== undefined;
|
|
5919
6060
|
}
|
|
5920
|
-
/**
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
6061
|
+
/** Put this key and value in the map. */
|
|
6062
|
+
set(key, value) {
|
|
6063
|
+
const id = this.mapKeyFn(key);
|
|
6064
|
+
const matches = this.inner[id];
|
|
6065
|
+
if (matches === undefined) {
|
|
6066
|
+
this.inner[id] = [[key, value]];
|
|
6067
|
+
this.innerSize++;
|
|
6068
|
+
return;
|
|
5924
6069
|
}
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
let result = this;
|
|
5932
|
-
// Make sure `result` always refers to the larger one of the two sets.
|
|
5933
|
-
if (result.size < other.size) {
|
|
5934
|
-
result = other;
|
|
5935
|
-
other = this;
|
|
6070
|
+
for (let i = 0; i < matches.length; i++) {
|
|
6071
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
6072
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
6073
|
+
matches[i] = [key, value];
|
|
6074
|
+
return;
|
|
6075
|
+
}
|
|
5936
6076
|
}
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
});
|
|
5940
|
-
return result;
|
|
6077
|
+
matches.push([key, value]);
|
|
6078
|
+
this.innerSize++;
|
|
5941
6079
|
}
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
6080
|
+
/**
|
|
6081
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
6082
|
+
*/
|
|
6083
|
+
delete(key) {
|
|
6084
|
+
const id = this.mapKeyFn(key);
|
|
6085
|
+
const matches = this.inner[id];
|
|
6086
|
+
if (matches === undefined) {
|
|
5947
6087
|
return false;
|
|
5948
6088
|
}
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6089
|
+
for (let i = 0; i < matches.length; i++) {
|
|
6090
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
6091
|
+
if (matches.length === 1) {
|
|
6092
|
+
delete this.inner[id];
|
|
6093
|
+
}
|
|
6094
|
+
else {
|
|
6095
|
+
matches.splice(i, 1);
|
|
6096
|
+
}
|
|
6097
|
+
this.innerSize--;
|
|
6098
|
+
return true;
|
|
5956
6099
|
}
|
|
5957
6100
|
}
|
|
5958
|
-
return
|
|
6101
|
+
return false;
|
|
5959
6102
|
}
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
6103
|
+
forEach(fn) {
|
|
6104
|
+
forEach(this.inner, (_, entries) => {
|
|
6105
|
+
for (const [k, v] of entries) {
|
|
6106
|
+
fn(k, v);
|
|
6107
|
+
}
|
|
5964
6108
|
});
|
|
5965
|
-
return res;
|
|
5966
|
-
}
|
|
5967
|
-
toString() {
|
|
5968
|
-
const result = [];
|
|
5969
|
-
this.forEach(elem => result.push(elem));
|
|
5970
|
-
return 'SortedSet(' + result.toString() + ')';
|
|
5971
|
-
}
|
|
5972
|
-
copy(data) {
|
|
5973
|
-
const result = new SortedSet(this.comparator);
|
|
5974
|
-
result.data = data;
|
|
5975
|
-
return result;
|
|
5976
|
-
}
|
|
5977
|
-
}
|
|
5978
|
-
class SortedSetIterator {
|
|
5979
|
-
constructor(iter) {
|
|
5980
|
-
this.iter = iter;
|
|
5981
6109
|
}
|
|
5982
|
-
|
|
5983
|
-
return this.
|
|
5984
|
-
}
|
|
5985
|
-
hasNext() {
|
|
5986
|
-
return this.iter.hasNext();
|
|
6110
|
+
isEmpty() {
|
|
6111
|
+
return isEmpty(this.inner);
|
|
5987
6112
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
* Compares two sorted sets for equality using their natural ordering. The
|
|
5991
|
-
* method computes the intersection and invokes `onAdd` for every element that
|
|
5992
|
-
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
5993
|
-
* `before` but missing from `after`.
|
|
5994
|
-
*
|
|
5995
|
-
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
5996
|
-
* n), where n is the size of the two lists.
|
|
5997
|
-
*
|
|
5998
|
-
* @param before - The elements that exist in the original set.
|
|
5999
|
-
* @param after - The elements to diff against the original set.
|
|
6000
|
-
* @param comparator - The comparator for the elements in before and after.
|
|
6001
|
-
* @param onAdd - A function to invoke for every element that is part of `
|
|
6002
|
-
* after` but not `before`.
|
|
6003
|
-
* @param onRemove - A function to invoke for every element that is part of
|
|
6004
|
-
* `before` but not `after`.
|
|
6005
|
-
*/
|
|
6006
|
-
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
6007
|
-
const beforeIt = before.getIterator();
|
|
6008
|
-
const afterIt = after.getIterator();
|
|
6009
|
-
let beforeValue = advanceIterator(beforeIt);
|
|
6010
|
-
let afterValue = advanceIterator(afterIt);
|
|
6011
|
-
// Walk through the two sets at the same time, using the ordering defined by
|
|
6012
|
-
// `comparator`.
|
|
6013
|
-
while (beforeValue || afterValue) {
|
|
6014
|
-
let added = false;
|
|
6015
|
-
let removed = false;
|
|
6016
|
-
if (beforeValue && afterValue) {
|
|
6017
|
-
const cmp = comparator(beforeValue, afterValue);
|
|
6018
|
-
if (cmp < 0) {
|
|
6019
|
-
// The element was removed if the next element in our ordered
|
|
6020
|
-
// walkthrough is only in `before`.
|
|
6021
|
-
removed = true;
|
|
6022
|
-
}
|
|
6023
|
-
else if (cmp > 0) {
|
|
6024
|
-
// The element was added if the next element in our ordered walkthrough
|
|
6025
|
-
// is only in `after`.
|
|
6026
|
-
added = true;
|
|
6027
|
-
}
|
|
6028
|
-
}
|
|
6029
|
-
else if (beforeValue != null) {
|
|
6030
|
-
removed = true;
|
|
6031
|
-
}
|
|
6032
|
-
else {
|
|
6033
|
-
added = true;
|
|
6034
|
-
}
|
|
6035
|
-
if (added) {
|
|
6036
|
-
onAdd(afterValue);
|
|
6037
|
-
afterValue = advanceIterator(afterIt);
|
|
6038
|
-
}
|
|
6039
|
-
else if (removed) {
|
|
6040
|
-
onRemove(beforeValue);
|
|
6041
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6042
|
-
}
|
|
6043
|
-
else {
|
|
6044
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6045
|
-
afterValue = advanceIterator(afterIt);
|
|
6046
|
-
}
|
|
6113
|
+
size() {
|
|
6114
|
+
return this.innerSize;
|
|
6047
6115
|
}
|
|
6048
|
-
}
|
|
6049
|
-
/**
|
|
6050
|
-
* Returns the next element from the iterator or `undefined` if none available.
|
|
6051
|
-
*/
|
|
6052
|
-
function advanceIterator(it) {
|
|
6053
|
-
return it.hasNext() ? it.getNext() : undefined;
|
|
6054
6116
|
}
|
|
6055
6117
|
|
|
6056
6118
|
/**
|
|
@@ -6074,8 +6136,12 @@ function mutableDocumentMap() {
|
|
|
6074
6136
|
return EMPTY_MUTABLE_DOCUMENT_MAP;
|
|
6075
6137
|
}
|
|
6076
6138
|
const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
6077
|
-
function documentMap() {
|
|
6078
|
-
|
|
6139
|
+
function documentMap(...docs) {
|
|
6140
|
+
let map = EMPTY_DOCUMENT_MAP;
|
|
6141
|
+
for (const doc of docs) {
|
|
6142
|
+
map = map.insert(doc.key, doc);
|
|
6143
|
+
}
|
|
6144
|
+
return map;
|
|
6079
6145
|
}
|
|
6080
6146
|
function newOverlayMap() {
|
|
6081
6147
|
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
@@ -9968,9 +10034,9 @@ class MemoryIndexManager {
|
|
|
9968
10034
|
// Field indices are not supported with memory persistence.
|
|
9969
10035
|
return PersistencePromise.resolve(null);
|
|
9970
10036
|
}
|
|
9971
|
-
|
|
10037
|
+
getIndexType(transaction, target) {
|
|
9972
10038
|
// Field indices are not supported with memory persistence.
|
|
9973
|
-
return PersistencePromise.resolve(
|
|
10039
|
+
return PersistencePromise.resolve(0 /* NONE */);
|
|
9974
10040
|
}
|
|
9975
10041
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9976
10042
|
// Field indices are not supported with memory persistence.
|
|
@@ -9980,6 +10046,9 @@ class MemoryIndexManager {
|
|
|
9980
10046
|
// Field indices are not supported with memory persistence.
|
|
9981
10047
|
return PersistencePromise.resolve(null);
|
|
9982
10048
|
}
|
|
10049
|
+
getMinOffset(transaction, target) {
|
|
10050
|
+
return PersistencePromise.resolve(IndexOffset.min());
|
|
10051
|
+
}
|
|
9983
10052
|
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
9984
10053
|
// Field indices are not supported with memory persistence.
|
|
9985
10054
|
return PersistencePromise.resolve();
|
|
@@ -10046,8 +10115,9 @@ const EMPTY_VALUE = new Uint8Array(0);
|
|
|
10046
10115
|
* data as it supports multi-tab access.
|
|
10047
10116
|
*/
|
|
10048
10117
|
class IndexedDbIndexManager {
|
|
10049
|
-
constructor(user) {
|
|
10118
|
+
constructor(user, databaseId) {
|
|
10050
10119
|
this.user = user;
|
|
10120
|
+
this.databaseId = databaseId;
|
|
10051
10121
|
/**
|
|
10052
10122
|
* An in-memory copy of the index entries we've already written since the SDK
|
|
10053
10123
|
* launched. Used to avoid re-writing the same entry repeatedly.
|
|
@@ -10143,7 +10213,8 @@ class IndexedDbIndexManager {
|
|
|
10143
10213
|
return PersistencePromise.resolve(null);
|
|
10144
10214
|
}
|
|
10145
10215
|
else {
|
|
10146
|
-
let
|
|
10216
|
+
let existingKeys = documentKeySet();
|
|
10217
|
+
const result = [];
|
|
10147
10218
|
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10148
10219
|
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10149
10220
|
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
@@ -10153,13 +10224,17 @@ class IndexedDbIndexManager {
|
|
|
10153
10224
|
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10154
10225
|
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10155
10226
|
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10156
|
-
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded,
|
|
10227
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
|
|
10157
10228
|
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10158
10229
|
return indexEntries
|
|
10159
10230
|
.loadFirst(indexRange, target.limit)
|
|
10160
10231
|
.next(entries => {
|
|
10161
10232
|
entries.forEach(entry => {
|
|
10162
|
-
|
|
10233
|
+
const documentKey = DocumentKey.fromSegments(entry.documentKey);
|
|
10234
|
+
if (!existingKeys.has(documentKey)) {
|
|
10235
|
+
existingKeys = existingKeys.add(documentKey);
|
|
10236
|
+
result.push(documentKey);
|
|
10237
|
+
}
|
|
10163
10238
|
});
|
|
10164
10239
|
});
|
|
10165
10240
|
});
|
|
@@ -10187,22 +10262,18 @@ class IndexedDbIndexManager {
|
|
|
10187
10262
|
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10188
10263
|
// combined with the values from the query bounds.
|
|
10189
10264
|
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10190
|
-
Math.max(lowerBounds
|
|
10265
|
+
Math.max(lowerBounds.length, upperBounds.length);
|
|
10191
10266
|
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10192
10267
|
const indexRanges = [];
|
|
10193
10268
|
for (let i = 0; i < totalScans; ++i) {
|
|
10194
10269
|
const arrayValue = arrayValues
|
|
10195
10270
|
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10196
10271
|
: EMPTY_VALUE;
|
|
10197
|
-
const lowerBound = lowerBounds
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
: this.generateEmptyBound(indexId + 1);
|
|
10203
|
-
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10204
|
-
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10205
|
-
/* inclusive= */ true))));
|
|
10272
|
+
const lowerBound = this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive);
|
|
10273
|
+
const upperBound = this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive);
|
|
10274
|
+
const notInBound = notInValues.map(notIn => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10275
|
+
/* inclusive= */ true));
|
|
10276
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInBound));
|
|
10206
10277
|
}
|
|
10207
10278
|
return indexRanges;
|
|
10208
10279
|
}
|
|
@@ -10216,25 +10287,38 @@ class IndexedDbIndexManager {
|
|
|
10216
10287
|
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10217
10288
|
return inclusive ? entry.successor() : entry;
|
|
10218
10289
|
}
|
|
10219
|
-
/**
|
|
10220
|
-
* Generates an empty bound that scopes the index scan to the current index
|
|
10221
|
-
* and user.
|
|
10222
|
-
*/
|
|
10223
|
-
generateEmptyBound(indexId) {
|
|
10224
|
-
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10225
|
-
}
|
|
10226
10290
|
getFieldIndex(transaction, target) {
|
|
10227
10291
|
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10228
10292
|
const collectionGroup = target.collectionGroup != null
|
|
10229
10293
|
? target.collectionGroup
|
|
10230
10294
|
: target.path.lastSegment();
|
|
10231
10295
|
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10296
|
+
// Return the index with the most number of segments.
|
|
10297
|
+
let index = null;
|
|
10298
|
+
for (const candidate of indexes) {
|
|
10299
|
+
const matches = targetIndexMatcher.servedByIndex(candidate);
|
|
10300
|
+
if (matches &&
|
|
10301
|
+
(!index || candidate.fields.length > index.fields.length)) {
|
|
10302
|
+
index = candidate;
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
return index;
|
|
10236
10306
|
});
|
|
10237
10307
|
}
|
|
10308
|
+
getIndexType(transaction, target) {
|
|
10309
|
+
let indexType = 2 /* FULL */;
|
|
10310
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
|
|
10311
|
+
return this.getFieldIndex(transaction, target).next(index => {
|
|
10312
|
+
if (!index) {
|
|
10313
|
+
indexType = 0 /* NONE */;
|
|
10314
|
+
}
|
|
10315
|
+
else if (indexType !== 0 /* NONE */ &&
|
|
10316
|
+
index.fields.length < targetGetSegmentCount(target)) {
|
|
10317
|
+
indexType = 1 /* PARTIAL */;
|
|
10318
|
+
}
|
|
10319
|
+
});
|
|
10320
|
+
}).next(() => indexType);
|
|
10321
|
+
}
|
|
10238
10322
|
/**
|
|
10239
10323
|
* Returns the byte encoded form of the directional values in the field index.
|
|
10240
10324
|
* Returns `null` if the document does not have all fields specified in the
|
|
@@ -10258,6 +10342,15 @@ class IndexedDbIndexManager {
|
|
|
10258
10342
|
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10259
10343
|
return encoder.encodedBytes();
|
|
10260
10344
|
}
|
|
10345
|
+
/**
|
|
10346
|
+
* Returns an encoded form of the document key that sorts based on the key
|
|
10347
|
+
* ordering of the field index.
|
|
10348
|
+
*/
|
|
10349
|
+
encodeDirectionalKey(fieldIndex, documentKey) {
|
|
10350
|
+
const encoder = new IndexByteEncoder();
|
|
10351
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
|
|
10352
|
+
return encoder.encodedBytes();
|
|
10353
|
+
}
|
|
10261
10354
|
/**
|
|
10262
10355
|
* Encodes the given field values according to the specification in `target`.
|
|
10263
10356
|
* For IN queries, a list of possible values is returned.
|
|
@@ -10288,9 +10381,6 @@ class IndexedDbIndexManager {
|
|
|
10288
10381
|
* queries, a list of possible values is returned.
|
|
10289
10382
|
*/
|
|
10290
10383
|
encodeBound(fieldIndex, target, bound) {
|
|
10291
|
-
if (bound == null) {
|
|
10292
|
-
return null;
|
|
10293
|
-
}
|
|
10294
10384
|
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10295
10385
|
}
|
|
10296
10386
|
/** Returns the byte representation for the provided encoders. */
|
|
@@ -10381,7 +10471,7 @@ class IndexedDbIndexManager {
|
|
|
10381
10471
|
return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
|
|
10382
10472
|
const newEntries = this.computeIndexEntries(doc, fieldIndex);
|
|
10383
10473
|
if (!existingEntries.isEqual(newEntries)) {
|
|
10384
|
-
return this.updateEntries(transaction, doc, existingEntries, newEntries);
|
|
10474
|
+
return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
|
|
10385
10475
|
}
|
|
10386
10476
|
return PersistencePromise.resolve();
|
|
10387
10477
|
});
|
|
@@ -10389,24 +10479,26 @@ class IndexedDbIndexManager {
|
|
|
10389
10479
|
});
|
|
10390
10480
|
});
|
|
10391
10481
|
}
|
|
10392
|
-
addIndexEntry(transaction, document, indexEntry) {
|
|
10482
|
+
addIndexEntry(transaction, document, fieldIndex, indexEntry) {
|
|
10393
10483
|
const indexEntries = indexEntriesStore(transaction);
|
|
10394
10484
|
return indexEntries.put({
|
|
10395
10485
|
indexId: indexEntry.indexId,
|
|
10396
10486
|
uid: this.uid,
|
|
10397
10487
|
arrayValue: indexEntry.arrayValue,
|
|
10398
10488
|
directionalValue: indexEntry.directionalValue,
|
|
10399
|
-
|
|
10489
|
+
orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
|
|
10490
|
+
documentKey: document.key.path.toArray()
|
|
10400
10491
|
});
|
|
10401
10492
|
}
|
|
10402
|
-
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10493
|
+
deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
|
|
10403
10494
|
const indexEntries = indexEntriesStore(transaction);
|
|
10404
10495
|
return indexEntries.delete([
|
|
10405
10496
|
indexEntry.indexId,
|
|
10406
10497
|
this.uid,
|
|
10407
10498
|
indexEntry.arrayValue,
|
|
10408
10499
|
indexEntry.directionalValue,
|
|
10409
|
-
|
|
10500
|
+
this.encodeDirectionalKey(fieldIndex, document.key),
|
|
10501
|
+
document.key.path.toArray()
|
|
10410
10502
|
]);
|
|
10411
10503
|
}
|
|
10412
10504
|
getExistingIndexEntries(transaction, documentKey, fieldIndex) {
|
|
@@ -10418,7 +10510,7 @@ class IndexedDbIndexManager {
|
|
|
10418
10510
|
range: IDBKeyRange.only([
|
|
10419
10511
|
fieldIndex.indexId,
|
|
10420
10512
|
this.uid,
|
|
10421
|
-
|
|
10513
|
+
this.encodeDirectionalKey(fieldIndex, documentKey)
|
|
10422
10514
|
])
|
|
10423
10515
|
}, (_, entry) => {
|
|
10424
10516
|
results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
|
|
@@ -10451,15 +10543,15 @@ class IndexedDbIndexManager {
|
|
|
10451
10543
|
* that are no longer referenced in `newEntries` and adding all newly added
|
|
10452
10544
|
* entries.
|
|
10453
10545
|
*/
|
|
10454
|
-
updateEntries(transaction, document, existingEntries, newEntries) {
|
|
10546
|
+
updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
|
|
10455
10547
|
logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
|
|
10456
10548
|
const promises = [];
|
|
10457
10549
|
diffSortedSets(existingEntries, newEntries, indexEntryComparator,
|
|
10458
10550
|
/* onAdd= */ entry => {
|
|
10459
|
-
promises.push(this.addIndexEntry(transaction, document, entry));
|
|
10551
|
+
promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
|
|
10460
10552
|
},
|
|
10461
10553
|
/* onRemove= */ entry => {
|
|
10462
|
-
promises.push(this.deleteIndexEntry(transaction, document, entry));
|
|
10554
|
+
promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
|
|
10463
10555
|
});
|
|
10464
10556
|
return PersistencePromise.waitFor(promises);
|
|
10465
10557
|
}
|
|
@@ -10483,7 +10575,7 @@ class IndexedDbIndexManager {
|
|
|
10483
10575
|
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10484
10576
|
*/
|
|
10485
10577
|
createRange(lower, upper, notInValues) {
|
|
10486
|
-
// The
|
|
10578
|
+
// The notIn values need to be sorted and unique so that we can return a
|
|
10487
10579
|
// sorted set of non-overlapping ranges.
|
|
10488
10580
|
notInValues = notInValues
|
|
10489
10581
|
.sort((l, r) => indexEntryComparator(l, r))
|
|
@@ -10516,17 +10608,33 @@ class IndexedDbIndexManager {
|
|
|
10516
10608
|
this.uid,
|
|
10517
10609
|
bounds[i].arrayValue,
|
|
10518
10610
|
bounds[i].directionalValue,
|
|
10519
|
-
|
|
10611
|
+
EMPTY_VALUE,
|
|
10612
|
+
[]
|
|
10520
10613
|
], [
|
|
10521
10614
|
bounds[i + 1].indexId,
|
|
10522
10615
|
this.uid,
|
|
10523
10616
|
bounds[i + 1].arrayValue,
|
|
10524
10617
|
bounds[i + 1].directionalValue,
|
|
10525
|
-
|
|
10618
|
+
EMPTY_VALUE,
|
|
10619
|
+
[]
|
|
10526
10620
|
]));
|
|
10527
10621
|
}
|
|
10528
10622
|
return ranges;
|
|
10529
10623
|
}
|
|
10624
|
+
getMinOffset(transaction, target) {
|
|
10625
|
+
let offset;
|
|
10626
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
|
|
10627
|
+
return this.getFieldIndex(transaction, target).next(index => {
|
|
10628
|
+
if (!index) {
|
|
10629
|
+
offset = IndexOffset.min();
|
|
10630
|
+
}
|
|
10631
|
+
else if (!offset ||
|
|
10632
|
+
indexOffsetComparator(index.indexState.offset, offset) < 0) {
|
|
10633
|
+
offset = index.indexState.offset;
|
|
10634
|
+
}
|
|
10635
|
+
});
|
|
10636
|
+
}).next(() => offset);
|
|
10637
|
+
}
|
|
10530
10638
|
}
|
|
10531
10639
|
/**
|
|
10532
10640
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -12276,12 +12384,31 @@ function dbCollectionGroupKey(collectionGroup, offset) {
|
|
|
12276
12384
|
}
|
|
12277
12385
|
/**
|
|
12278
12386
|
* Comparator that compares document keys according to the primary key sorting
|
|
12279
|
-
* used by the `DbRemoteDocumentDocument` store (by
|
|
12280
|
-
* document ID).
|
|
12387
|
+
* used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
|
|
12388
|
+
* and then document ID).
|
|
12389
|
+
*
|
|
12390
|
+
* Visible for testing.
|
|
12281
12391
|
*/
|
|
12282
12392
|
function dbKeyComparator(l, r) {
|
|
12283
|
-
const
|
|
12284
|
-
|
|
12393
|
+
const left = l.path.toArray();
|
|
12394
|
+
const right = r.path.toArray();
|
|
12395
|
+
// The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
|
|
12396
|
+
let cmp = 0;
|
|
12397
|
+
for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
|
|
12398
|
+
cmp = primitiveComparator(left[i], right[i]);
|
|
12399
|
+
if (cmp) {
|
|
12400
|
+
return cmp;
|
|
12401
|
+
}
|
|
12402
|
+
}
|
|
12403
|
+
cmp = primitiveComparator(left.length, right.length);
|
|
12404
|
+
if (cmp) {
|
|
12405
|
+
return cmp;
|
|
12406
|
+
}
|
|
12407
|
+
cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
|
|
12408
|
+
if (cmp) {
|
|
12409
|
+
return cmp;
|
|
12410
|
+
}
|
|
12411
|
+
return primitiveComparator(left[left.length - 1], right[right.length - 1]);
|
|
12285
12412
|
}
|
|
12286
12413
|
/**
|
|
12287
12414
|
* Schema Version for the Web client:
|
|
@@ -13182,7 +13309,7 @@ class IndexedDbPersistence {
|
|
|
13182
13309
|
return this.remoteDocumentCache;
|
|
13183
13310
|
}
|
|
13184
13311
|
getIndexManager(user) {
|
|
13185
|
-
return new IndexedDbIndexManager(user);
|
|
13312
|
+
return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
|
|
13186
13313
|
}
|
|
13187
13314
|
getDocumentOverlayCache(user) {
|
|
13188
13315
|
return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
|
|
@@ -13678,7 +13805,7 @@ class LocalStoreImpl {
|
|
|
13678
13805
|
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13679
13806
|
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13680
13807
|
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
13681
|
-
this.queryEngine.
|
|
13808
|
+
this.queryEngine.initialize(this.localDocuments, this.indexManager);
|
|
13682
13809
|
}
|
|
13683
13810
|
collectGarbage(garbageCollector) {
|
|
13684
13811
|
return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
|
|
@@ -15420,10 +15547,30 @@ class MemoryEagerDelegate {
|
|
|
15420
15547
|
* limitations under the License.
|
|
15421
15548
|
*/
|
|
15422
15549
|
/**
|
|
15423
|
-
*
|
|
15424
|
-
*
|
|
15425
|
-
*
|
|
15426
|
-
*
|
|
15550
|
+
* The Firestore query engine.
|
|
15551
|
+
*
|
|
15552
|
+
* Firestore queries can be executed in three modes. The Query Engine determines
|
|
15553
|
+
* what mode to use based on what data is persisted. The mode only determines
|
|
15554
|
+
* the runtime complexity of the query - the result set is equivalent across all
|
|
15555
|
+
* implementations.
|
|
15556
|
+
*
|
|
15557
|
+
* The Query engine will use indexed-based execution if a user has configured
|
|
15558
|
+
* any index that can be used to execute query (via `setIndexConfiguration()`).
|
|
15559
|
+
* Otherwise, the engine will try to optimize the query by re-using a previously
|
|
15560
|
+
* persisted query result. If that is not possible, the query will be executed
|
|
15561
|
+
* via a full collection scan.
|
|
15562
|
+
*
|
|
15563
|
+
* Index-based execution is the default when available. The query engine
|
|
15564
|
+
* supports partial indexed execution and merges the result from the index
|
|
15565
|
+
* lookup with documents that have not yet been indexed. The index evaluation
|
|
15566
|
+
* matches the backend's format and as such, the SDK can use indexing for all
|
|
15567
|
+
* queries that the backend supports.
|
|
15568
|
+
*
|
|
15569
|
+
* If no index exists, the query engine tries to take advantage of the target
|
|
15570
|
+
* document mapping in the TargetCache. These mappings exists for all queries
|
|
15571
|
+
* that have been synced with the backend at least once and allow the query
|
|
15572
|
+
* engine to only read documents that previously matched a query plus any
|
|
15573
|
+
* documents that were edited after the query was last listened to.
|
|
15427
15574
|
*
|
|
15428
15575
|
* There are some cases when this optimization is not guaranteed to produce
|
|
15429
15576
|
* the same results as full collection scans. In these cases, query
|
|
@@ -15438,16 +15585,41 @@ class MemoryEagerDelegate {
|
|
|
15438
15585
|
* - Queries that have never been CURRENT or free of limbo documents.
|
|
15439
15586
|
*/
|
|
15440
15587
|
class QueryEngine {
|
|
15588
|
+
constructor() {
|
|
15589
|
+
this.initialized = false;
|
|
15590
|
+
}
|
|
15441
15591
|
/** Sets the document view to query against. */
|
|
15442
|
-
|
|
15592
|
+
initialize(localDocuments, indexManager) {
|
|
15443
15593
|
this.localDocumentsView = localDocuments;
|
|
15594
|
+
this.indexManager = indexManager;
|
|
15595
|
+
this.initialized = true;
|
|
15444
15596
|
}
|
|
15445
15597
|
/** Returns all local documents matching the specified query. */
|
|
15446
15598
|
getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15599
|
+
return this.performQueryUsingIndex(transaction, query)
|
|
15600
|
+
.next(result => result
|
|
15601
|
+
? result
|
|
15602
|
+
: this.performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion))
|
|
15603
|
+
.next(result => result ? result : this.executeFullCollectionScan(transaction, query));
|
|
15604
|
+
}
|
|
15605
|
+
/**
|
|
15606
|
+
* Performs an indexed query that evaluates the query based on a collection's
|
|
15607
|
+
* persisted index values. Returns `null` if an index is not available.
|
|
15608
|
+
*/
|
|
15609
|
+
performQueryUsingIndex(transaction, query) {
|
|
15610
|
+
{
|
|
15611
|
+
return PersistencePromise.resolve(null);
|
|
15612
|
+
}
|
|
15613
|
+
}
|
|
15614
|
+
/**
|
|
15615
|
+
* Performs a query based on the target's persisted query mapping. Returns
|
|
15616
|
+
* `null` if the mapping is not available or cannot be used.
|
|
15617
|
+
*/
|
|
15618
|
+
performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion) {
|
|
15619
|
+
if (queryMatchesAllDocuments(query)) {
|
|
15620
|
+
// Queries that match all documents don't benefit from using
|
|
15621
|
+
// key-based lookups. It is more efficient to scan all documents in a
|
|
15622
|
+
// collection, rather than to perform individual lookups.
|
|
15451
15623
|
return this.executeFullCollectionScan(transaction, query);
|
|
15452
15624
|
}
|
|
15453
15625
|
// Queries that have never seen a snapshot without limbo free documents
|
|
@@ -15457,8 +15629,7 @@ class QueryEngine {
|
|
|
15457
15629
|
}
|
|
15458
15630
|
return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
|
|
15459
15631
|
const previousResults = this.applyQuery(query, documents);
|
|
15460
|
-
if ((
|
|
15461
|
-
this.needsRefill(query.limitType, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15632
|
+
if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15462
15633
|
return this.executeFullCollectionScan(transaction, query);
|
|
15463
15634
|
}
|
|
15464
15635
|
if (getLogLevel() <= logger.LogLevel.DEBUG) {
|
|
@@ -15466,15 +15637,7 @@ class QueryEngine {
|
|
|
15466
15637
|
}
|
|
15467
15638
|
// Retrieve all results for documents that were updated since the last
|
|
15468
15639
|
// limbo-document free remote snapshot.
|
|
15469
|
-
return this.
|
|
15470
|
-
// We merge `previousResults` into `updateResults`, since
|
|
15471
|
-
// `updateResults` is already a DocumentMap. If a document is
|
|
15472
|
-
// contained in both lists, then its contents are the same.
|
|
15473
|
-
previousResults.forEach(doc => {
|
|
15474
|
-
updatedResults = updatedResults.insert(doc.key, doc);
|
|
15475
|
-
});
|
|
15476
|
-
return updatedResults;
|
|
15477
|
-
});
|
|
15640
|
+
return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
|
|
15478
15641
|
});
|
|
15479
15642
|
}
|
|
15480
15643
|
/** Applies the query filter and sorting to the provided documents. */
|
|
@@ -15493,6 +15656,7 @@ class QueryEngine {
|
|
|
15493
15656
|
* Determines if a limit query needs to be refilled from cache, making it
|
|
15494
15657
|
* ineligible for index-free execution.
|
|
15495
15658
|
*
|
|
15659
|
+
* @param query The query.
|
|
15496
15660
|
* @param sortedPreviousResults - The documents that matched the query when it
|
|
15497
15661
|
* was last synchronized, sorted by the query's comparator.
|
|
15498
15662
|
* @param remoteKeys - The document keys that matched the query at the last
|
|
@@ -15500,10 +15664,14 @@ class QueryEngine {
|
|
|
15500
15664
|
* @param limboFreeSnapshotVersion - The version of the snapshot when the
|
|
15501
15665
|
* query was last synchronized.
|
|
15502
15666
|
*/
|
|
15503
|
-
needsRefill(
|
|
15504
|
-
|
|
15505
|
-
|
|
15667
|
+
needsRefill(query, sortedPreviousResults, remoteKeys, limboFreeSnapshotVersion) {
|
|
15668
|
+
if (query.limit === null) {
|
|
15669
|
+
// Queries without limits do not need to be refilled.
|
|
15670
|
+
return false;
|
|
15671
|
+
}
|
|
15506
15672
|
if (remoteKeys.size !== sortedPreviousResults.size) {
|
|
15673
|
+
// The query needs to be refilled if a previously matching document no
|
|
15674
|
+
// longer matches.
|
|
15507
15675
|
return true;
|
|
15508
15676
|
}
|
|
15509
15677
|
// Limit queries are not eligible for index-free query execution if there is
|
|
@@ -15514,7 +15682,7 @@ class QueryEngine {
|
|
|
15514
15682
|
// the boundary of the limit itself did not change and documents from cache
|
|
15515
15683
|
// will continue to be "rejected" by this boundary. Therefore, we can ignore
|
|
15516
15684
|
// any modifications that don't affect the last document.
|
|
15517
|
-
const docAtLimitEdge = limitType === "F" /* First */
|
|
15685
|
+
const docAtLimitEdge = query.limitType === "F" /* First */
|
|
15518
15686
|
? sortedPreviousResults.last()
|
|
15519
15687
|
: sortedPreviousResults.first();
|
|
15520
15688
|
if (!docAtLimitEdge) {
|
|
@@ -15530,6 +15698,22 @@ class QueryEngine {
|
|
|
15530
15698
|
}
|
|
15531
15699
|
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15532
15700
|
}
|
|
15701
|
+
/**
|
|
15702
|
+
* Combines the results from an indexed execution with the remaining documents
|
|
15703
|
+
* that have not yet been indexed.
|
|
15704
|
+
*/
|
|
15705
|
+
appendRemainingResults(transaction, indexedResults, query, offset) {
|
|
15706
|
+
// Retrieve all results for documents that were updated since the offset.
|
|
15707
|
+
return this.localDocumentsView
|
|
15708
|
+
.getDocumentsMatchingQuery(transaction, query, offset)
|
|
15709
|
+
.next(remainingResults => {
|
|
15710
|
+
// Merge with existing results
|
|
15711
|
+
indexedResults.forEach(d => {
|
|
15712
|
+
remainingResults = remainingResults.insert(d.key, d);
|
|
15713
|
+
});
|
|
15714
|
+
return remainingResults;
|
|
15715
|
+
});
|
|
15716
|
+
}
|
|
15533
15717
|
}
|
|
15534
15718
|
|
|
15535
15719
|
/**
|
|
@@ -21789,10 +21973,12 @@ class View {
|
|
|
21789
21973
|
//
|
|
21790
21974
|
// Note that this should never get used in a refill (when previousChanges is
|
|
21791
21975
|
// set), because there will only be adds -- no deletes or updates.
|
|
21792
|
-
const lastDocInLimit =
|
|
21976
|
+
const lastDocInLimit = this.query.limitType === "F" /* First */ &&
|
|
21977
|
+
oldDocumentSet.size === this.query.limit
|
|
21793
21978
|
? oldDocumentSet.last()
|
|
21794
21979
|
: null;
|
|
21795
|
-
const firstDocInLimit =
|
|
21980
|
+
const firstDocInLimit = this.query.limitType === "L" /* Last */ &&
|
|
21981
|
+
oldDocumentSet.size === this.query.limit
|
|
21796
21982
|
? oldDocumentSet.first()
|
|
21797
21983
|
: null;
|
|
21798
21984
|
docChanges.inorderTraversal((key, entry) => {
|
|
@@ -21865,9 +22051,9 @@ class View {
|
|
|
21865
22051
|
}
|
|
21866
22052
|
});
|
|
21867
22053
|
// Drop documents out to meet limit/limitToLast requirement.
|
|
21868
|
-
if (
|
|
22054
|
+
if (this.query.limit !== null) {
|
|
21869
22055
|
while (newDocumentSet.size > this.query.limit) {
|
|
21870
|
-
const oldDoc =
|
|
22056
|
+
const oldDoc = this.query.limitType === "F" /* First */
|
|
21871
22057
|
? newDocumentSet.last()
|
|
21872
22058
|
: newDocumentSet.first();
|
|
21873
22059
|
newDocumentSet = newDocumentSet.delete(oldDoc.key);
|
|
@@ -26998,7 +27184,8 @@ function snapshotEqual(left, right) {
|
|
|
26998
27184
|
* limitations under the License.
|
|
26999
27185
|
*/
|
|
27000
27186
|
function validateHasExplicitOrderByForLimitToLast(query) {
|
|
27001
|
-
if (
|
|
27187
|
+
if (query.limitType === "L" /* Last */ &&
|
|
27188
|
+
query.explicitOrderBy.length === 0) {
|
|
27002
27189
|
throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
|
|
27003
27190
|
}
|
|
27004
27191
|
}
|