@firebase/firestore 3.4.8 → 3.4.9
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/lite/index.d.ts +1 -0
- package/dist/firestore/src/api/transaction.d.ts +2 -1
- package/dist/firestore/src/api/transaction_options.d.ts +17 -0
- package/dist/firestore/src/api.d.ts +1 -0
- package/dist/firestore/src/core/firestore_client.d.ts +2 -1
- package/dist/firestore/src/core/query.d.ts +2 -4
- package/dist/firestore/src/core/target.d.ts +6 -4
- package/dist/firestore/src/core/transaction_options.d.ts +25 -0
- package/dist/firestore/src/core/transaction_runner.d.ts +3 -2
- package/dist/firestore/src/lite-api/transaction.d.ts +2 -1
- package/dist/firestore/src/lite-api/transaction_options.d.ts +23 -0
- package/dist/firestore/src/local/index_manager.d.ts +24 -3
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +4 -7
- package/dist/firestore/src/local/indexeddb_schema.d.ts +1 -0
- package/dist/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/firestore/src/local/memory_index_manager.d.ts +3 -2
- package/dist/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/firestore/src/model/collections.d.ts +1 -1
- 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 +2 -2
- package/dist/index.d.ts +24 -1
- package/dist/index.esm2017.js +4108 -3992
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4198 -4087
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1711 -1565
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1711 -1565
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4100 -3984
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +50 -4
- package/dist/lite/firestore/lite/index.d.ts +1 -0
- package/dist/lite/firestore/src/api/transaction.d.ts +2 -1
- package/dist/lite/firestore/src/api/transaction_options.d.ts +17 -0
- package/dist/lite/firestore/src/api.d.ts +1 -0
- package/dist/lite/firestore/src/core/firestore_client.d.ts +2 -1
- 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/core/transaction_options.d.ts +25 -0
- package/dist/lite/firestore/src/core/transaction_runner.d.ts +3 -2
- package/dist/lite/firestore/src/lite-api/transaction.d.ts +2 -1
- package/dist/lite/firestore/src/lite-api/transaction_options.d.ts +23 -0
- package/dist/lite/firestore/src/local/index_manager.d.ts +24 -3
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +4 -7
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +1 -0
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +3 -2
- package/dist/lite/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/firestore/src/model/collections.d.ts +1 -1
- 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 +2 -2
- package/dist/lite/index.browser.esm2017.js +341 -312
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +299 -291
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.d.ts +24 -1
- package/dist/lite/index.node.cjs.js +56 -15
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +56 -15
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +1227 -1198
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/internal.d.ts +25 -1
- package/dist/lite/packages/firestore/dist/lite/index.browser.esm2017.d.ts +28 -28
- package/dist/lite/packages/firestore/lite/index.d.ts +1 -0
- package/dist/lite/packages/firestore/src/api/transaction.d.ts +2 -1
- package/dist/lite/packages/firestore/src/api/transaction_options.d.ts +17 -0
- package/dist/lite/packages/firestore/src/api.d.ts +1 -0
- package/dist/lite/packages/firestore/src/core/firestore_client.d.ts +2 -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/core/transaction_options.d.ts +25 -0
- package/dist/lite/packages/firestore/src/core/transaction_runner.d.ts +3 -2
- package/dist/lite/packages/firestore/src/lite-api/transaction.d.ts +2 -1
- package/dist/lite/packages/firestore/src/lite-api/transaction_options.d.ts +23 -0
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +24 -3
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +4 -7
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +1 -0
- 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 +3 -2
- package/dist/lite/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/packages/firestore/src/model/collections.d.ts +1 -1
- 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 +2 -2
- package/dist/lite/private.d.ts +25 -1
- package/dist/packages/firestore/dist/index.esm2017.d.ts +155 -154
- package/dist/packages/firestore/lite/index.d.ts +1 -0
- package/dist/packages/firestore/src/api/transaction.d.ts +2 -1
- package/dist/packages/firestore/src/api/transaction_options.d.ts +17 -0
- package/dist/packages/firestore/src/api.d.ts +1 -0
- package/dist/packages/firestore/src/core/firestore_client.d.ts +2 -1
- 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/core/transaction_options.d.ts +25 -0
- package/dist/packages/firestore/src/core/transaction_runner.d.ts +3 -2
- package/dist/packages/firestore/src/lite-api/transaction.d.ts +2 -1
- package/dist/packages/firestore/src/lite-api/transaction_options.d.ts +23 -0
- package/dist/packages/firestore/src/local/index_manager.d.ts +24 -3
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +4 -7
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +1 -0
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +3 -2
- package/dist/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/packages/firestore/src/model/collections.d.ts +1 -1
- 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 +2 -2
- package/dist/private.d.ts +50 -4
- package/package.json +7 -7
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.9";
|
|
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.
|
|
89
|
+
const version = "9.8.0";
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @license
|
|
@@ -3346,7 +3346,7 @@ function typeOrder(value) {
|
|
|
3346
3346
|
return 4 /* ServerTimestampValue */;
|
|
3347
3347
|
}
|
|
3348
3348
|
else if (isMaxValue(value)) {
|
|
3349
|
-
return
|
|
3349
|
+
return 9007199254740991 /* MaxValue */;
|
|
3350
3350
|
}
|
|
3351
3351
|
return 10 /* ObjectValue */;
|
|
3352
3352
|
}
|
|
@@ -3558,6 +3558,15 @@ function compareArrays(left, right) {
|
|
|
3558
3558
|
return primitiveComparator(leftArray.length, rightArray.length);
|
|
3559
3559
|
}
|
|
3560
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
|
+
}
|
|
3561
3570
|
const leftMap = left.fields || {};
|
|
3562
3571
|
const leftKeys = Object.keys(leftMap);
|
|
3563
3572
|
const rightMap = right.fields || {};
|
|
@@ -3805,27 +3814,31 @@ function valuesGetUpperBound(value) {
|
|
|
3805
3814
|
return fail();
|
|
3806
3815
|
}
|
|
3807
3816
|
}
|
|
3808
|
-
function
|
|
3809
|
-
|
|
3810
|
-
|
|
3817
|
+
function lowerBoundCompare(left, right) {
|
|
3818
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3819
|
+
if (cmp !== 0) {
|
|
3820
|
+
return cmp;
|
|
3811
3821
|
}
|
|
3812
|
-
|
|
3813
|
-
return
|
|
3822
|
+
if (left.inclusive && !right.inclusive) {
|
|
3823
|
+
return -1;
|
|
3814
3824
|
}
|
|
3815
|
-
else {
|
|
3816
|
-
return
|
|
3825
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3826
|
+
return 1;
|
|
3817
3827
|
}
|
|
3828
|
+
return 0;
|
|
3818
3829
|
}
|
|
3819
|
-
function
|
|
3820
|
-
|
|
3821
|
-
|
|
3830
|
+
function upperBoundCompare(left, right) {
|
|
3831
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3832
|
+
if (cmp !== 0) {
|
|
3833
|
+
return cmp;
|
|
3822
3834
|
}
|
|
3823
|
-
|
|
3824
|
-
return
|
|
3835
|
+
if (left.inclusive && !right.inclusive) {
|
|
3836
|
+
return 1;
|
|
3825
3837
|
}
|
|
3826
|
-
else {
|
|
3827
|
-
return
|
|
3838
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3839
|
+
return -1;
|
|
3828
3840
|
}
|
|
3841
|
+
return 0;
|
|
3829
3842
|
}
|
|
3830
3843
|
|
|
3831
3844
|
/**
|
|
@@ -4335,7 +4348,7 @@ function indexOffsetComparator(left, right) {
|
|
|
4335
4348
|
|
|
4336
4349
|
/**
|
|
4337
4350
|
* @license
|
|
4338
|
-
* Copyright
|
|
4351
|
+
* Copyright 2017 Google LLC
|
|
4339
4352
|
*
|
|
4340
4353
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4341
4354
|
* you may not use this file except in compliance with the License.
|
|
@@ -4349,609 +4362,445 @@ function indexOffsetComparator(left, right) {
|
|
|
4349
4362
|
* See the License for the specific language governing permissions and
|
|
4350
4363
|
* limitations under the License.
|
|
4351
4364
|
*/
|
|
4352
|
-
//
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
this.
|
|
4357
|
-
this.
|
|
4358
|
-
this.filters = filters;
|
|
4359
|
-
this.limit = limit;
|
|
4360
|
-
this.startAt = startAt;
|
|
4361
|
-
this.endAt = endAt;
|
|
4362
|
-
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;
|
|
4363
4371
|
}
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
4370
|
-
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
4371
|
-
* property.
|
|
4372
|
-
*/
|
|
4373
|
-
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
4374
|
-
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
4375
|
-
}
|
|
4376
|
-
function canonifyTarget(target) {
|
|
4377
|
-
const targetImpl = debugCast(target);
|
|
4378
|
-
if (targetImpl.memoizedCanonicalId === null) {
|
|
4379
|
-
let str = targetImpl.path.canonicalString();
|
|
4380
|
-
if (targetImpl.collectionGroup !== null) {
|
|
4381
|
-
str += '|cg:' + targetImpl.collectionGroup;
|
|
4382
|
-
}
|
|
4383
|
-
str += '|f:';
|
|
4384
|
-
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4385
|
-
str += '|ob:';
|
|
4386
|
-
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4387
|
-
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4388
|
-
str += '|l:';
|
|
4389
|
-
str += targetImpl.limit;
|
|
4390
|
-
}
|
|
4391
|
-
if (targetImpl.startAt) {
|
|
4392
|
-
str += '|lb:';
|
|
4393
|
-
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4394
|
-
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4395
|
-
}
|
|
4396
|
-
if (targetImpl.endAt) {
|
|
4397
|
-
str += '|ub:';
|
|
4398
|
-
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4399
|
-
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4400
|
-
}
|
|
4401
|
-
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));
|
|
4402
4377
|
}
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
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));
|
|
4409
4383
|
}
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
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;
|
|
4414
4400
|
}
|
|
4415
|
-
if
|
|
4416
|
-
|
|
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;
|
|
4417
4423
|
}
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
.map(o => stringifyOrderBy(o))
|
|
4421
|
-
.join(', ')}]`;
|
|
4424
|
+
isEmpty() {
|
|
4425
|
+
return this.root.isEmpty();
|
|
4422
4426
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
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;
|
|
4427
4430
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4431
|
+
// Returns the minimum key in the map.
|
|
4432
|
+
minKey() {
|
|
4433
|
+
return this.root.minKey();
|
|
4432
4434
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
if (left.limit !== right.limit) {
|
|
4437
|
-
return false;
|
|
4435
|
+
// Returns the maximum key in the map.
|
|
4436
|
+
maxKey() {
|
|
4437
|
+
return this.root.maxKey();
|
|
4438
4438
|
}
|
|
4439
|
-
|
|
4440
|
-
|
|
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);
|
|
4441
4445
|
}
|
|
4442
|
-
|
|
4443
|
-
|
|
4446
|
+
forEach(fn) {
|
|
4447
|
+
this.inorderTraversal((k, v) => {
|
|
4448
|
+
fn(k, v);
|
|
4444
4449
|
return false;
|
|
4445
|
-
}
|
|
4446
|
-
}
|
|
4447
|
-
if (left.filters.length !== right.filters.length) {
|
|
4448
|
-
return false;
|
|
4450
|
+
});
|
|
4449
4451
|
}
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
+
toString() {
|
|
4453
|
+
const descriptions = [];
|
|
4454
|
+
this.inorderTraversal((k, v) => {
|
|
4455
|
+
descriptions.push(`${k}:${v}`);
|
|
4452
4456
|
return false;
|
|
4453
|
-
}
|
|
4457
|
+
});
|
|
4458
|
+
return `{${descriptions.join(', ')}}`;
|
|
4454
4459
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
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);
|
|
4457
4467
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4468
|
+
// Returns an iterator over the SortedMap.
|
|
4469
|
+
getIterator() {
|
|
4470
|
+
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
4460
4471
|
}
|
|
4461
|
-
|
|
4462
|
-
return false;
|
|
4472
|
+
getIteratorFrom(key) {
|
|
4473
|
+
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
4463
4474
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
function targetIsDocumentTarget(target) {
|
|
4467
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4468
|
-
target.collectionGroup === null &&
|
|
4469
|
-
target.filters.length === 0);
|
|
4470
|
-
}
|
|
4471
|
-
/** Returns the field filters that target the given field path. */
|
|
4472
|
-
function targetGetFieldFiltersForPath(target, path) {
|
|
4473
|
-
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4474
|
-
}
|
|
4475
|
-
/**
|
|
4476
|
-
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4477
|
-
* filters. Returns `null` if there are no such filters.
|
|
4478
|
-
*/
|
|
4479
|
-
function targetGetArrayValues(target, fieldIndex) {
|
|
4480
|
-
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4481
|
-
if (segment === undefined) {
|
|
4482
|
-
return null;
|
|
4475
|
+
getReverseIterator() {
|
|
4476
|
+
return new SortedMapIterator(this.root, null, this.comparator, true);
|
|
4483
4477
|
}
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4487
|
-
return fieldFilter.value.arrayValue.values || [];
|
|
4488
|
-
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4489
|
-
return [fieldFilter.value];
|
|
4490
|
-
// Remaining filters are not array filters.
|
|
4491
|
-
}
|
|
4478
|
+
getReverseIteratorFrom(key) {
|
|
4479
|
+
return new SortedMapIterator(this.root, key, this.comparator, true);
|
|
4492
4480
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
//
|
|
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;
|
|
4493
|
+
}
|
|
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;
|
|
4518
|
+
}
|
|
4518
4519
|
}
|
|
4519
4520
|
}
|
|
4520
4521
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
let inclusive = true;
|
|
4531
|
-
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4532
|
-
// startAt.
|
|
4533
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4534
|
-
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
4535
|
-
? targetGetLowerBoundForField(target, segment.fieldPath, target.startAt)
|
|
4536
|
-
: targetGetUpperBoundForField(target, segment.fieldPath, target.startAt);
|
|
4537
|
-
if (!segmentBound.value) {
|
|
4538
|
-
// No lower bound exists
|
|
4539
|
-
return null;
|
|
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;
|
|
4530
|
+
}
|
|
4540
4531
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4532
|
+
else {
|
|
4533
|
+
node = node.right;
|
|
4534
|
+
while (!node.isEmpty()) {
|
|
4535
|
+
this.nodeStack.push(node);
|
|
4536
|
+
node = node.left;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
return result;
|
|
4543
4540
|
}
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
* upper bound exists.
|
|
4550
|
-
*/
|
|
4551
|
-
function targetGetUpperBound(target, fieldIndex) {
|
|
4552
|
-
const values = [];
|
|
4553
|
-
let inclusive = true;
|
|
4554
|
-
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4555
|
-
// endAt.
|
|
4556
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4557
|
-
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
4558
|
-
? targetGetUpperBoundForField(target, segment.fieldPath, target.endAt)
|
|
4559
|
-
: targetGetLowerBoundForField(target, segment.fieldPath, target.endAt);
|
|
4560
|
-
if (!segmentBound.value) {
|
|
4561
|
-
// No upper bound exists
|
|
4541
|
+
hasNext() {
|
|
4542
|
+
return this.nodeStack.length > 0;
|
|
4543
|
+
}
|
|
4544
|
+
peek() {
|
|
4545
|
+
if (this.nodeStack.length === 0) {
|
|
4562
4546
|
return null;
|
|
4563
4547
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4548
|
+
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
4549
|
+
return { key: node.key, value: node.value };
|
|
4566
4550
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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;
|
|
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;
|
|
4561
|
+
}
|
|
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;
|
|
4599
4591
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
inclusive = filterInclusive;
|
|
4592
|
+
else {
|
|
4593
|
+
return this.left.min();
|
|
4603
4594
|
}
|
|
4604
4595
|
}
|
|
4605
|
-
//
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4609
|
-
const orderBy = target.orderBy[i];
|
|
4610
|
-
if (orderBy.field.isEqual(fieldPath)) {
|
|
4611
|
-
const cursorValue = bound.position[i];
|
|
4612
|
-
if (valuesMax(value, cursorValue) === cursorValue) {
|
|
4613
|
-
value = cursorValue;
|
|
4614
|
-
inclusive = bound.inclusive;
|
|
4615
|
-
}
|
|
4616
|
-
break;
|
|
4617
|
-
}
|
|
4618
|
-
}
|
|
4596
|
+
// Returns the maximum key in the tree.
|
|
4597
|
+
minKey() {
|
|
4598
|
+
return this.min().key;
|
|
4619
4599
|
}
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
* the provided `fieldPath` (or the lower bound for an descending segment).
|
|
4625
|
-
*/
|
|
4626
|
-
function targetGetUpperBoundForField(target, fieldPath, bound) {
|
|
4627
|
-
let value = undefined;
|
|
4628
|
-
let inclusive = true;
|
|
4629
|
-
// Process all filters to find a value for the current field segment
|
|
4630
|
-
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
4631
|
-
let filterValue = undefined;
|
|
4632
|
-
let filterInclusive = true;
|
|
4633
|
-
switch (fieldFilter.op) {
|
|
4634
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4635
|
-
case ">" /* GREATER_THAN */:
|
|
4636
|
-
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4637
|
-
filterInclusive = false;
|
|
4638
|
-
break;
|
|
4639
|
-
case "==" /* EQUAL */:
|
|
4640
|
-
case "in" /* IN */:
|
|
4641
|
-
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4642
|
-
filterValue = fieldFilter.value;
|
|
4643
|
-
break;
|
|
4644
|
-
case "<" /* LESS_THAN */:
|
|
4645
|
-
filterValue = fieldFilter.value;
|
|
4646
|
-
filterInclusive = false;
|
|
4647
|
-
break;
|
|
4648
|
-
case "!=" /* NOT_EQUAL */:
|
|
4649
|
-
case "not-in" /* NOT_IN */:
|
|
4650
|
-
filterValue = MAX_VALUE;
|
|
4651
|
-
break;
|
|
4652
|
-
// Remaining filters cannot be used as upper bounds.
|
|
4600
|
+
// Returns the maximum key in the tree.
|
|
4601
|
+
maxKey() {
|
|
4602
|
+
if (this.right.isEmpty()) {
|
|
4603
|
+
return this.key;
|
|
4653
4604
|
}
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
inclusive = filterInclusive;
|
|
4605
|
+
else {
|
|
4606
|
+
return this.right.maxKey();
|
|
4657
4607
|
}
|
|
4658
4608
|
}
|
|
4659
|
-
//
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
inclusive = bound.inclusive;
|
|
4669
|
-
}
|
|
4670
|
-
break;
|
|
4671
|
-
}
|
|
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);
|
|
4672
4618
|
}
|
|
4619
|
+
else {
|
|
4620
|
+
n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
|
|
4621
|
+
}
|
|
4622
|
+
return n.fixUp();
|
|
4673
4623
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4624
|
+
removeMin() {
|
|
4625
|
+
if (this.left.isEmpty()) {
|
|
4626
|
+
return LLRBNode.EMPTY;
|
|
4627
|
+
}
|
|
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();
|
|
4684
4634
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
if (
|
|
4690
|
-
if (
|
|
4691
|
-
|
|
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();
|
|
4692
4642
|
}
|
|
4693
|
-
|
|
4694
|
-
|
|
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
|
+
}
|
|
4695
4660
|
}
|
|
4661
|
+
n = n.copy(null, null, null, null, n.right.remove(key, comparator));
|
|
4696
4662
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4663
|
+
return n.fixUp();
|
|
4664
|
+
}
|
|
4665
|
+
isRed() {
|
|
4666
|
+
return this.color;
|
|
4667
|
+
}
|
|
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();
|
|
4699
4673
|
}
|
|
4700
|
-
|
|
4701
|
-
|
|
4674
|
+
if (n.left.isRed() && n.left.left.isRed()) {
|
|
4675
|
+
n = n.rotateRight();
|
|
4702
4676
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4677
|
+
if (n.left.isRed() && n.right.isRed()) {
|
|
4678
|
+
n = n.colorFlip();
|
|
4705
4679
|
}
|
|
4706
|
-
|
|
4707
|
-
|
|
4680
|
+
return n;
|
|
4681
|
+
}
|
|
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();
|
|
4708
4688
|
}
|
|
4709
|
-
|
|
4710
|
-
|
|
4689
|
+
return n;
|
|
4690
|
+
}
|
|
4691
|
+
moveRedRight() {
|
|
4692
|
+
let n = this.colorFlip();
|
|
4693
|
+
if (n.left.left.isRed()) {
|
|
4694
|
+
n = n.rotateRight();
|
|
4695
|
+
n = n.colorFlip();
|
|
4711
4696
|
}
|
|
4697
|
+
return n;
|
|
4712
4698
|
}
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
: new KeyFieldNotInFilter(field, value);
|
|
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);
|
|
4717
4702
|
}
|
|
4718
|
-
|
|
4719
|
-
const
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
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);
|
|
4706
|
+
}
|
|
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);
|
|
4711
|
+
}
|
|
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;
|
|
4724
4720
|
}
|
|
4725
|
-
// Only compare types with matching backend order (such as double and int).
|
|
4726
|
-
return (other !== null &&
|
|
4727
|
-
typeOrder(this.value) === typeOrder(other) &&
|
|
4728
|
-
this.matchesComparison(valueCompare(other, this.value)));
|
|
4729
4721
|
}
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
return fail();
|
|
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);
|
|
4746
4737
|
}
|
|
4747
4738
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
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;
|
|
4757
4749
|
}
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
// TODO(b/29183165): Technically, this won't be unique if two values have
|
|
4761
|
-
// the same description, such as the int 3 and the string "3". So we should
|
|
4762
|
-
// add the types in here somehow, too.
|
|
4763
|
-
return (filter.field.canonicalString() +
|
|
4764
|
-
filter.op.toString() +
|
|
4765
|
-
canonicalId(filter.value));
|
|
4766
|
-
}
|
|
4767
|
-
function filterEquals(f1, f2) {
|
|
4768
|
-
return (f1.op === f2.op &&
|
|
4769
|
-
f1.field.isEqual(f2.field) &&
|
|
4770
|
-
valueEquals(f1.value, f2.value));
|
|
4771
|
-
}
|
|
4772
|
-
/** Returns a debug description for `filter`. */
|
|
4773
|
-
function stringifyFilter(filter) {
|
|
4774
|
-
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
4775
|
-
}
|
|
4776
|
-
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
4777
|
-
class KeyFieldFilter extends FieldFilter {
|
|
4778
|
-
constructor(field, op, value) {
|
|
4779
|
-
super(field, op, value);
|
|
4780
|
-
this.key = DocumentKey.fromName(value.referenceValue);
|
|
4750
|
+
get key() {
|
|
4751
|
+
throw fail();
|
|
4781
4752
|
}
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
return this.matchesComparison(comparison);
|
|
4753
|
+
get value() {
|
|
4754
|
+
throw fail();
|
|
4785
4755
|
}
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
class KeyFieldInFilter extends FieldFilter {
|
|
4789
|
-
constructor(field, value) {
|
|
4790
|
-
super(field, "in" /* IN */, value);
|
|
4791
|
-
this.keys = extractDocumentKeysFromArrayValue("in" /* IN */, value);
|
|
4756
|
+
get color() {
|
|
4757
|
+
throw fail();
|
|
4792
4758
|
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4759
|
+
get left() {
|
|
4760
|
+
throw fail();
|
|
4795
4761
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
class KeyFieldNotInFilter extends FieldFilter {
|
|
4799
|
-
constructor(field, value) {
|
|
4800
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4801
|
-
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
4762
|
+
get right() {
|
|
4763
|
+
throw fail();
|
|
4802
4764
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4765
|
+
// Returns a copy of the current node.
|
|
4766
|
+
copy(key, value, color, left, right) {
|
|
4767
|
+
return this;
|
|
4805
4768
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
|
|
4810
|
-
return DocumentKey.fromName(v.referenceValue);
|
|
4811
|
-
});
|
|
4812
|
-
}
|
|
4813
|
-
/** A Filter that implements the array-contains operator. */
|
|
4814
|
-
class ArrayContainsFilter extends FieldFilter {
|
|
4815
|
-
constructor(field, value) {
|
|
4816
|
-
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
4769
|
+
// Returns a copy of the tree, with the specified key/value added.
|
|
4770
|
+
insert(key, value, comparator) {
|
|
4771
|
+
return new LLRBNode(key, value);
|
|
4817
4772
|
}
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
return
|
|
4773
|
+
// Returns a copy of the tree, with the specified key removed.
|
|
4774
|
+
remove(key, comparator) {
|
|
4775
|
+
return this;
|
|
4821
4776
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
class InFilter extends FieldFilter {
|
|
4825
|
-
constructor(field, value) {
|
|
4826
|
-
super(field, "in" /* IN */, value);
|
|
4777
|
+
isEmpty() {
|
|
4778
|
+
return true;
|
|
4827
4779
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
/** A Filter that implements the not-in operator. */
|
|
4834
|
-
class NotInFilter extends FieldFilter {
|
|
4835
|
-
constructor(field, value) {
|
|
4836
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4837
|
-
}
|
|
4838
|
-
matches(doc) {
|
|
4839
|
-
if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
|
|
4840
|
-
return false;
|
|
4841
|
-
}
|
|
4842
|
-
const other = doc.data.field(this.field);
|
|
4843
|
-
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
4844
|
-
}
|
|
4845
|
-
}
|
|
4846
|
-
/** A Filter that implements the array-contains-any operator. */
|
|
4847
|
-
class ArrayContainsAnyFilter extends FieldFilter {
|
|
4848
|
-
constructor(field, value) {
|
|
4849
|
-
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
4850
|
-
}
|
|
4851
|
-
matches(doc) {
|
|
4852
|
-
const other = doc.data.field(this.field);
|
|
4853
|
-
if (!isArray(other) || !other.arrayValue.values) {
|
|
4854
|
-
return false;
|
|
4855
|
-
}
|
|
4856
|
-
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
4857
|
-
}
|
|
4858
|
-
}
|
|
4859
|
-
/**
|
|
4860
|
-
* Represents a bound of a query.
|
|
4861
|
-
*
|
|
4862
|
-
* The bound is specified with the given components representing a position and
|
|
4863
|
-
* whether it's just before or just after the position (relative to whatever the
|
|
4864
|
-
* query order is).
|
|
4865
|
-
*
|
|
4866
|
-
* The position represents a logical index position for a query. It's a prefix
|
|
4867
|
-
* of values for the (potentially implicit) order by clauses of a query.
|
|
4868
|
-
*
|
|
4869
|
-
* Bound provides a function to determine whether a document comes before or
|
|
4870
|
-
* after a bound. This is influenced by whether the position is just before or
|
|
4871
|
-
* just after the provided values.
|
|
4872
|
-
*/
|
|
4873
|
-
class Bound {
|
|
4874
|
-
constructor(position, inclusive) {
|
|
4875
|
-
this.position = position;
|
|
4876
|
-
this.inclusive = inclusive;
|
|
4780
|
+
inorderTraversal(action) {
|
|
4781
|
+
return false;
|
|
4877
4782
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
4881
|
-
*/
|
|
4882
|
-
class OrderBy {
|
|
4883
|
-
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
4884
|
-
this.field = field;
|
|
4885
|
-
this.dir = dir;
|
|
4783
|
+
reverseTraversal(action) {
|
|
4784
|
+
return false;
|
|
4886
4785
|
}
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
// TODO(b/29183165): Make this collision robust.
|
|
4890
|
-
return orderBy.field.canonicalString() + orderBy.dir;
|
|
4891
|
-
}
|
|
4892
|
-
function stringifyOrderBy(orderBy) {
|
|
4893
|
-
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
4894
|
-
}
|
|
4895
|
-
function orderByEquals(left, right) {
|
|
4896
|
-
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4897
|
-
}
|
|
4898
|
-
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4899
|
-
let comparison = 0;
|
|
4900
|
-
for (let i = 0; i < bound.position.length; i++) {
|
|
4901
|
-
const orderByComponent = orderBy[i];
|
|
4902
|
-
const component = bound.position[i];
|
|
4903
|
-
if (orderByComponent.field.isKeyField()) {
|
|
4904
|
-
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
4905
|
-
}
|
|
4906
|
-
else {
|
|
4907
|
-
const docValue = doc.data.field(orderByComponent.field);
|
|
4908
|
-
comparison = valueCompare(component, docValue);
|
|
4909
|
-
}
|
|
4910
|
-
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
4911
|
-
comparison = comparison * -1;
|
|
4912
|
-
}
|
|
4913
|
-
if (comparison !== 0) {
|
|
4914
|
-
break;
|
|
4915
|
-
}
|
|
4786
|
+
minKey() {
|
|
4787
|
+
return null;
|
|
4916
4788
|
}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
/**
|
|
4920
|
-
* Returns true if a document sorts after a bound using the provided sort
|
|
4921
|
-
* order.
|
|
4922
|
-
*/
|
|
4923
|
-
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
4924
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4925
|
-
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
4926
|
-
}
|
|
4927
|
-
/**
|
|
4928
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4929
|
-
* order.
|
|
4930
|
-
*/
|
|
4931
|
-
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
4932
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4933
|
-
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4934
|
-
}
|
|
4935
|
-
function boundEquals(left, right) {
|
|
4936
|
-
if (left === null) {
|
|
4937
|
-
return right === null;
|
|
4789
|
+
maxKey() {
|
|
4790
|
+
return null;
|
|
4938
4791
|
}
|
|
4939
|
-
|
|
4792
|
+
isRed() {
|
|
4940
4793
|
return false;
|
|
4941
4794
|
}
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
return
|
|
4795
|
+
// For testing.
|
|
4796
|
+
checkMaxDepth() {
|
|
4797
|
+
return true;
|
|
4945
4798
|
}
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
const rightPosition = right.position[i];
|
|
4949
|
-
if (!valueEquals(leftPosition, rightPosition)) {
|
|
4950
|
-
return false;
|
|
4951
|
-
}
|
|
4799
|
+
check() {
|
|
4800
|
+
return 0;
|
|
4952
4801
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4802
|
+
} // end LLRBEmptyNode
|
|
4803
|
+
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
4955
4804
|
|
|
4956
4805
|
/**
|
|
4957
4806
|
* @license
|
|
@@ -4970,325 +4819,223 @@ function boundEquals(left, right) {
|
|
|
4970
4819
|
* limitations under the License.
|
|
4971
4820
|
*/
|
|
4972
4821
|
/**
|
|
4973
|
-
*
|
|
4974
|
-
*
|
|
4975
|
-
* query the RemoteStore results.
|
|
4822
|
+
* SortedSet is an immutable (copy-on-write) collection that holds elements
|
|
4823
|
+
* in order specified by the provided comparator.
|
|
4976
4824
|
*
|
|
4977
|
-
*
|
|
4825
|
+
* NOTE: if provided comparator returns 0 for two elements, we consider them to
|
|
4826
|
+
* be equal!
|
|
4978
4827
|
*/
|
|
4979
|
-
class
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
*/
|
|
4984
|
-
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
4985
|
-
this.path = path;
|
|
4986
|
-
this.collectionGroup = collectionGroup;
|
|
4987
|
-
this.explicitOrderBy = explicitOrderBy;
|
|
4988
|
-
this.filters = filters;
|
|
4989
|
-
this.limit = limit;
|
|
4990
|
-
this.limitType = limitType;
|
|
4991
|
-
this.startAt = startAt;
|
|
4992
|
-
this.endAt = endAt;
|
|
4993
|
-
this.memoizedOrderBy = null;
|
|
4994
|
-
// The corresponding `Target` of this `Query` instance.
|
|
4995
|
-
this.memoizedTarget = null;
|
|
4996
|
-
if (this.startAt) ;
|
|
4997
|
-
if (this.endAt) ;
|
|
4828
|
+
class SortedSet {
|
|
4829
|
+
constructor(comparator) {
|
|
4830
|
+
this.comparator = comparator;
|
|
4831
|
+
this.data = new SortedMap(this.comparator);
|
|
4998
4832
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
5002
|
-
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
5003
|
-
}
|
|
5004
|
-
/** Creates a new Query for a query that matches all documents at `path` */
|
|
5005
|
-
function newQueryForPath(path) {
|
|
5006
|
-
return new QueryImpl(path);
|
|
5007
|
-
}
|
|
5008
|
-
/**
|
|
5009
|
-
* Helper to convert a collection group query into a collection query at a
|
|
5010
|
-
* specific path. This is used when executing collection group queries, since
|
|
5011
|
-
* we have to split the query into a set of collection queries at multiple
|
|
5012
|
-
* paths.
|
|
5013
|
-
*/
|
|
5014
|
-
function asCollectionQueryAtPath(query, path) {
|
|
5015
|
-
return new QueryImpl(path,
|
|
5016
|
-
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5017
|
-
}
|
|
5018
|
-
/**
|
|
5019
|
-
* Returns true if this query does not specify any query constraints that
|
|
5020
|
-
* could remove results.
|
|
5021
|
-
*/
|
|
5022
|
-
function matchesAllDocuments(query) {
|
|
5023
|
-
return (query.filters.length === 0 &&
|
|
5024
|
-
query.limit === null &&
|
|
5025
|
-
query.startAt == null &&
|
|
5026
|
-
query.endAt == null &&
|
|
5027
|
-
(query.explicitOrderBy.length === 0 ||
|
|
5028
|
-
(query.explicitOrderBy.length === 1 &&
|
|
5029
|
-
query.explicitOrderBy[0].field.isKeyField())));
|
|
5030
|
-
}
|
|
5031
|
-
function hasLimitToFirst(query) {
|
|
5032
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "F" /* First */;
|
|
5033
|
-
}
|
|
5034
|
-
function hasLimitToLast(query) {
|
|
5035
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "L" /* Last */;
|
|
5036
|
-
}
|
|
5037
|
-
function getFirstOrderByField(query) {
|
|
5038
|
-
return query.explicitOrderBy.length > 0
|
|
5039
|
-
? query.explicitOrderBy[0].field
|
|
5040
|
-
: null;
|
|
5041
|
-
}
|
|
5042
|
-
function getInequalityFilterField(query) {
|
|
5043
|
-
for (const filter of query.filters) {
|
|
5044
|
-
if (filter.isInequality()) {
|
|
5045
|
-
return filter.field;
|
|
5046
|
-
}
|
|
4833
|
+
has(elem) {
|
|
4834
|
+
return this.data.get(elem) !== null;
|
|
5047
4835
|
}
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
4836
|
+
first() {
|
|
4837
|
+
return this.data.minKey();
|
|
4838
|
+
}
|
|
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;
|
|
4862
|
+
}
|
|
4863
|
+
cb(elem.key);
|
|
5058
4864
|
}
|
|
5059
4865
|
}
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
return new QueryImpl(ResourcePath.emptyPath(), collectionId);
|
|
5068
|
-
}
|
|
5069
|
-
/**
|
|
5070
|
-
* Returns whether the query matches a single document by path (rather than a
|
|
5071
|
-
* collection).
|
|
5072
|
-
*/
|
|
5073
|
-
function isDocumentQuery$1(query) {
|
|
5074
|
-
return (DocumentKey.isDocumentKey(query.path) &&
|
|
5075
|
-
query.collectionGroup === null &&
|
|
5076
|
-
query.filters.length === 0);
|
|
5077
|
-
}
|
|
5078
|
-
/**
|
|
5079
|
-
* Returns whether the query matches a collection group rather than a specific
|
|
5080
|
-
* collection.
|
|
5081
|
-
*/
|
|
5082
|
-
function isCollectionGroupQuery(query) {
|
|
5083
|
-
return query.collectionGroup !== null;
|
|
5084
|
-
}
|
|
5085
|
-
/**
|
|
5086
|
-
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5087
|
-
* which can be different from the order by constraints the user provided (e.g.
|
|
5088
|
-
* the SDK and backend always orders by `__name__`).
|
|
5089
|
-
*/
|
|
5090
|
-
function queryOrderBy(query) {
|
|
5091
|
-
const queryImpl = debugCast(query);
|
|
5092
|
-
if (queryImpl.memoizedOrderBy === null) {
|
|
5093
|
-
queryImpl.memoizedOrderBy = [];
|
|
5094
|
-
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5095
|
-
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5096
|
-
if (inequalityField !== null && firstOrderByField === null) {
|
|
5097
|
-
// In order to implicitly add key ordering, we must also add the
|
|
5098
|
-
// inequality filter field for it to be a valid query.
|
|
5099
|
-
// Note that the default inequality field and key ordering is ascending.
|
|
5100
|
-
if (!inequalityField.isKeyField()) {
|
|
5101
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
5102
|
-
}
|
|
5103
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
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);
|
|
5104
4873
|
}
|
|
5105
4874
|
else {
|
|
5106
|
-
|
|
5107
|
-
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5108
|
-
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5109
|
-
if (orderBy.field.isKeyField()) {
|
|
5110
|
-
foundKeyOrdering = true;
|
|
5111
|
-
}
|
|
5112
|
-
}
|
|
5113
|
-
if (!foundKeyOrdering) {
|
|
5114
|
-
// The order of the implicit key ordering always matches the last
|
|
5115
|
-
// explicit order by
|
|
5116
|
-
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5117
|
-
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5118
|
-
.dir
|
|
5119
|
-
: "asc" /* ASCENDING */;
|
|
5120
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5121
|
-
}
|
|
5122
|
-
}
|
|
5123
|
-
}
|
|
5124
|
-
return queryImpl.memoizedOrderBy;
|
|
5125
|
-
}
|
|
5126
|
-
/**
|
|
5127
|
-
* Converts this `Query` instance to it's corresponding `Target` representation.
|
|
5128
|
-
*/
|
|
5129
|
-
function queryToTarget(query) {
|
|
5130
|
-
const queryImpl = debugCast(query);
|
|
5131
|
-
if (!queryImpl.memoizedTarget) {
|
|
5132
|
-
if (queryImpl.limitType === "F" /* First */) {
|
|
5133
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
4875
|
+
iter = this.data.getIterator();
|
|
5134
4876
|
}
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
const
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
? "asc" /* ASCENDING */
|
|
5141
|
-
: "desc" /* DESCENDING */;
|
|
5142
|
-
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;
|
|
5143
4882
|
}
|
|
5144
|
-
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5145
|
-
const startAt = queryImpl.endAt
|
|
5146
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
5147
|
-
: null;
|
|
5148
|
-
const endAt = queryImpl.startAt
|
|
5149
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
5150
|
-
: null;
|
|
5151
|
-
// Now return as a LimitType.First query.
|
|
5152
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5153
4883
|
}
|
|
5154
4884
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5160
|
-
}
|
|
5161
|
-
function queryWithAddedOrderBy(query, orderBy) {
|
|
5162
|
-
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5163
|
-
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5164
|
-
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5165
|
-
}
|
|
5166
|
-
function queryWithLimit(query, limit, limitType) {
|
|
5167
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5168
|
-
}
|
|
5169
|
-
function queryWithStartAt(query, bound) {
|
|
5170
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5171
|
-
}
|
|
5172
|
-
function queryWithEndAt(query, bound) {
|
|
5173
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5174
|
-
}
|
|
5175
|
-
function queryEquals(left, right) {
|
|
5176
|
-
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5177
|
-
left.limitType === right.limitType);
|
|
5178
|
-
}
|
|
5179
|
-
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5180
|
-
// example, use as a dictionary key, but the implementation is subject to
|
|
5181
|
-
// collisions. Make it collision-free.
|
|
5182
|
-
function canonifyQuery(query) {
|
|
5183
|
-
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5184
|
-
}
|
|
5185
|
-
function stringifyQuery(query) {
|
|
5186
|
-
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5187
|
-
}
|
|
5188
|
-
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5189
|
-
function queryMatches(query, doc) {
|
|
5190
|
-
return (doc.isFoundDocument() &&
|
|
5191
|
-
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5192
|
-
queryMatchesOrderBy(query, doc) &&
|
|
5193
|
-
queryMatchesFilters(query, doc) &&
|
|
5194
|
-
queryMatchesBounds(query, doc));
|
|
5195
|
-
}
|
|
5196
|
-
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5197
|
-
const docPath = doc.key.path;
|
|
5198
|
-
if (query.collectionGroup !== null) {
|
|
5199
|
-
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5200
|
-
// Group queries rooted at a document path yet.
|
|
5201
|
-
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5202
|
-
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;
|
|
5203
4889
|
}
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
return query.path.isEqual(docPath);
|
|
4890
|
+
getIterator() {
|
|
4891
|
+
return new SortedSetIterator(this.data.getIterator());
|
|
5207
4892
|
}
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
return query.path.isImmediateParentOf(docPath);
|
|
4893
|
+
getIteratorFrom(key) {
|
|
4894
|
+
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5211
4895
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
*/
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5221
|
-
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;
|
|
5222
4904
|
}
|
|
4905
|
+
return this.copy(this.data.remove(elem));
|
|
5223
4906
|
}
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
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)) {
|
|
5229
4924
|
return false;
|
|
5230
4925
|
}
|
|
4926
|
+
if (this.size !== other.size) {
|
|
4927
|
+
return false;
|
|
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;
|
|
5231
4956
|
}
|
|
5232
|
-
return true;
|
|
5233
4957
|
}
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5238
|
-
return false;
|
|
4958
|
+
class SortedSetIterator {
|
|
4959
|
+
constructor(iter) {
|
|
4960
|
+
this.iter = iter;
|
|
5239
4961
|
}
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
4962
|
+
getNext() {
|
|
4963
|
+
return this.iter.getNext().key;
|
|
4964
|
+
}
|
|
4965
|
+
hasNext() {
|
|
4966
|
+
return this.iter.hasNext();
|
|
5243
4967
|
}
|
|
5244
|
-
return true;
|
|
5245
4968
|
}
|
|
5246
4969
|
/**
|
|
5247
|
-
*
|
|
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`.
|
|
5248
4974
|
*
|
|
5249
|
-
*
|
|
5250
|
-
*
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
* Returns a new comparator function that can be used to compare two documents
|
|
5260
|
-
* based on the Query's ordering constraint.
|
|
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`.
|
|
5261
4985
|
*/
|
|
5262
|
-
function
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
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;
|
|
5269
5007
|
}
|
|
5270
|
-
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5271
5008
|
}
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
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
|
+
}
|
|
5286
5027
|
}
|
|
5028
|
+
}
|
|
5029
|
+
/**
|
|
5030
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
5031
|
+
*/
|
|
5032
|
+
function advanceIterator(it) {
|
|
5033
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5287
5034
|
}
|
|
5288
5035
|
|
|
5289
5036
|
/**
|
|
5290
5037
|
* @license
|
|
5291
|
-
* Copyright
|
|
5038
|
+
* Copyright 2019 Google LLC
|
|
5292
5039
|
*
|
|
5293
5040
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5294
5041
|
* you may not use this file except in compliance with the License.
|
|
@@ -5302,555 +5049,961 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5302
5049
|
* See the License for the specific language governing permissions and
|
|
5303
5050
|
* limitations under the License.
|
|
5304
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
|
+
}
|
|
5305
5065
|
/**
|
|
5306
|
-
*
|
|
5307
|
-
*
|
|
5308
|
-
*
|
|
5309
|
-
*
|
|
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.
|
|
5310
5072
|
*/
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
*/
|
|
5321
|
-
this.inner = {};
|
|
5322
|
-
/** The number of entries stored in the map */
|
|
5323
|
-
this.innerSize = 0;
|
|
5324
|
-
}
|
|
5325
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
5326
|
-
get(key) {
|
|
5327
|
-
const id = this.mapKeyFn(key);
|
|
5328
|
-
const matches = this.inner[id];
|
|
5329
|
-
if (matches === undefined) {
|
|
5330
|
-
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;
|
|
5331
5082
|
}
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
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;
|
|
5336
5090
|
}
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
}
|
|
5342
|
-
/** Put this key and value in the map. */
|
|
5343
|
-
set(key, value) {
|
|
5344
|
-
const id = this.mapKeyFn(key);
|
|
5345
|
-
const matches = this.inner[id];
|
|
5346
|
-
if (matches === undefined) {
|
|
5347
|
-
this.inner[id] = [[key, value]];
|
|
5348
|
-
this.innerSize++;
|
|
5349
|
-
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(',');
|
|
5350
5095
|
}
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
return;
|
|
5356
|
-
}
|
|
5096
|
+
if (targetImpl.endAt) {
|
|
5097
|
+
str += '|ub:';
|
|
5098
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
5099
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5357
5100
|
}
|
|
5358
|
-
|
|
5359
|
-
this.innerSize++;
|
|
5101
|
+
targetImpl.memoizedCanonicalId = str;
|
|
5360
5102
|
}
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
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])) {
|
|
5368
5144
|
return false;
|
|
5369
5145
|
}
|
|
5370
|
-
for (let i = 0; i < matches.length; i++) {
|
|
5371
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
5372
|
-
if (matches.length === 1) {
|
|
5373
|
-
delete this.inner[id];
|
|
5374
|
-
}
|
|
5375
|
-
else {
|
|
5376
|
-
matches.splice(i, 1);
|
|
5377
|
-
}
|
|
5378
|
-
this.innerSize--;
|
|
5379
|
-
return true;
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
return false;
|
|
5383
5146
|
}
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
for (const [k, v] of entries) {
|
|
5387
|
-
fn(k, v);
|
|
5388
|
-
}
|
|
5389
|
-
});
|
|
5147
|
+
if (left.filters.length !== right.filters.length) {
|
|
5148
|
+
return false;
|
|
5390
5149
|
}
|
|
5391
|
-
|
|
5392
|
-
|
|
5150
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
5151
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
5152
|
+
return false;
|
|
5153
|
+
}
|
|
5393
5154
|
}
|
|
5394
|
-
|
|
5395
|
-
return
|
|
5155
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
5156
|
+
return false;
|
|
5396
5157
|
}
|
|
5397
|
-
|
|
5398
|
-
|
|
5158
|
+
if (!left.path.isEqual(right.path)) {
|
|
5159
|
+
return false;
|
|
5160
|
+
}
|
|
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
|
+
}
|
|
5399
5175
|
/**
|
|
5400
|
-
*
|
|
5401
|
-
*
|
|
5402
|
-
*
|
|
5403
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5404
|
-
* you may not use this file except in compliance with the License.
|
|
5405
|
-
* You may obtain a copy of the License at
|
|
5406
|
-
*
|
|
5407
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5408
|
-
*
|
|
5409
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
5410
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5411
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5412
|
-
* See the License for the specific language governing permissions and
|
|
5413
|
-
* 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.
|
|
5414
5178
|
*/
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
this.comparator = comparator;
|
|
5420
|
-
this.root = root ? root : LLRBNode.EMPTY;
|
|
5421
|
-
}
|
|
5422
|
-
// Returns a copy of the map, with the specified key/value added or replaced.
|
|
5423
|
-
insert(key, value) {
|
|
5424
|
-
return new SortedMap(this.comparator, this.root
|
|
5425
|
-
.insert(key, value, this.comparator)
|
|
5426
|
-
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
5179
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
5180
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
5181
|
+
if (segment === undefined) {
|
|
5182
|
+
return null;
|
|
5427
5183
|
}
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
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
|
+
}
|
|
5433
5192
|
}
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
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.
|
|
5444
5218
|
}
|
|
5445
|
-
|
|
5446
|
-
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
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);
|
|
5239
|
+
}
|
|
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);
|
|
5258
|
+
}
|
|
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;
|
|
5307
|
+
}
|
|
5308
|
+
break;
|
|
5447
5309
|
}
|
|
5448
5310
|
}
|
|
5449
|
-
return null;
|
|
5450
5311
|
}
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
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;
|
|
5361
|
+
}
|
|
5362
|
+
break;
|
|
5461
5363
|
}
|
|
5462
|
-
|
|
5463
|
-
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
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;
|
|
5387
|
+
}
|
|
5388
|
+
else {
|
|
5389
|
+
fields = fields.add(fieldFilter.field);
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
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);
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
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;
|
|
5408
|
+
this.value = value;
|
|
5409
|
+
}
|
|
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);
|
|
5464
5417
|
}
|
|
5465
5418
|
else {
|
|
5466
|
-
|
|
5467
|
-
prunedNodes += node.left.size + 1;
|
|
5468
|
-
node = node.right;
|
|
5419
|
+
return new KeyFieldFilter(field, op, value);
|
|
5469
5420
|
}
|
|
5470
5421
|
}
|
|
5471
|
-
|
|
5472
|
-
|
|
5422
|
+
else if (op === "array-contains" /* ARRAY_CONTAINS */) {
|
|
5423
|
+
return new ArrayContainsFilter(field, value);
|
|
5424
|
+
}
|
|
5425
|
+
else if (op === "in" /* IN */) {
|
|
5426
|
+
return new InFilter(field, value);
|
|
5427
|
+
}
|
|
5428
|
+
else if (op === "not-in" /* NOT_IN */) {
|
|
5429
|
+
return new NotInFilter(field, value);
|
|
5430
|
+
}
|
|
5431
|
+
else if (op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5432
|
+
return new ArrayContainsAnyFilter(field, value);
|
|
5433
|
+
}
|
|
5434
|
+
else {
|
|
5435
|
+
return new FieldFilter(field, op, value);
|
|
5436
|
+
}
|
|
5473
5437
|
}
|
|
5474
|
-
|
|
5475
|
-
return
|
|
5438
|
+
static createKeyFieldInFilter(field, op, value) {
|
|
5439
|
+
return op === "in" /* IN */
|
|
5440
|
+
? new KeyFieldInFilter(field, value)
|
|
5441
|
+
: new KeyFieldNotInFilter(field, value);
|
|
5476
5442
|
}
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
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)));
|
|
5449
|
+
}
|
|
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)));
|
|
5480
5454
|
}
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
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();
|
|
5471
|
+
}
|
|
5484
5472
|
}
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
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);
|
|
5488
5482
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
//
|
|
5492
|
-
//
|
|
5493
|
-
|
|
5494
|
-
|
|
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);
|
|
5495
5506
|
}
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
return false;
|
|
5500
|
-
});
|
|
5507
|
+
matches(doc) {
|
|
5508
|
+
const comparison = DocumentKey.comparator(doc.key, this.key);
|
|
5509
|
+
return this.matchesComparison(comparison);
|
|
5501
5510
|
}
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
return `{${descriptions.join(', ')}}`;
|
|
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);
|
|
5509
5517
|
}
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
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);
|
|
5517
5552
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
return
|
|
5553
|
+
matches(doc) {
|
|
5554
|
+
const other = doc.data.field(this.field);
|
|
5555
|
+
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
5521
5556
|
}
|
|
5522
|
-
|
|
5523
|
-
|
|
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);
|
|
5524
5562
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
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);
|
|
5527
5569
|
}
|
|
5528
|
-
|
|
5529
|
-
|
|
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);
|
|
5530
5575
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
this.isReverse = isReverse;
|
|
5536
|
-
this.nodeStack = [];
|
|
5537
|
-
let cmp = 1;
|
|
5538
|
-
while (!node.isEmpty()) {
|
|
5539
|
-
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5540
|
-
// flip the comparison if we're going in reverse
|
|
5541
|
-
if (startKey && isReverse) {
|
|
5542
|
-
cmp *= -1;
|
|
5543
|
-
}
|
|
5544
|
-
if (cmp < 0) {
|
|
5545
|
-
// This node is less than our start key. ignore it
|
|
5546
|
-
if (this.isReverse) {
|
|
5547
|
-
node = node.left;
|
|
5548
|
-
}
|
|
5549
|
-
else {
|
|
5550
|
-
node = node.right;
|
|
5551
|
-
}
|
|
5552
|
-
}
|
|
5553
|
-
else if (cmp === 0) {
|
|
5554
|
-
// This node is exactly equal to our start key. Push it on the stack,
|
|
5555
|
-
// but stop iterating;
|
|
5556
|
-
this.nodeStack.push(node);
|
|
5557
|
-
break;
|
|
5558
|
-
}
|
|
5559
|
-
else {
|
|
5560
|
-
// This node is greater than our start key, add it to the stack and move
|
|
5561
|
-
// to the next one
|
|
5562
|
-
this.nodeStack.push(node);
|
|
5563
|
-
if (this.isReverse) {
|
|
5564
|
-
node = node.right;
|
|
5565
|
-
}
|
|
5566
|
-
else {
|
|
5567
|
-
node = node.left;
|
|
5568
|
-
}
|
|
5569
|
-
}
|
|
5576
|
+
matches(doc) {
|
|
5577
|
+
const other = doc.data.field(this.field);
|
|
5578
|
+
if (!isArray(other) || !other.arrayValue.values) {
|
|
5579
|
+
return false;
|
|
5570
5580
|
}
|
|
5581
|
+
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
5571
5582
|
}
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
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);
|
|
5581
5630
|
}
|
|
5582
5631
|
else {
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
this.nodeStack.push(node);
|
|
5586
|
-
node = node.left;
|
|
5587
|
-
}
|
|
5632
|
+
const docValue = doc.data.field(orderByComponent.field);
|
|
5633
|
+
comparison = valueCompare(component, docValue);
|
|
5588
5634
|
}
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
peek() {
|
|
5595
|
-
if (this.nodeStack.length === 0) {
|
|
5596
|
-
return null;
|
|
5635
|
+
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
5636
|
+
comparison = comparison * -1;
|
|
5637
|
+
}
|
|
5638
|
+
if (comparison !== 0) {
|
|
5639
|
+
break;
|
|
5597
5640
|
}
|
|
5598
|
-
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
5599
|
-
return { key: node.key, value: node.value };
|
|
5600
|
-
}
|
|
5601
|
-
} // end SortedMapIterator
|
|
5602
|
-
// Represents a node in a Left-leaning Red-Black tree.
|
|
5603
|
-
class LLRBNode {
|
|
5604
|
-
constructor(key, value, color, left, right) {
|
|
5605
|
-
this.key = key;
|
|
5606
|
-
this.value = value;
|
|
5607
|
-
this.color = color != null ? color : LLRBNode.RED;
|
|
5608
|
-
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
5609
|
-
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
5610
|
-
this.size = this.left.size + 1 + this.right.size;
|
|
5611
5641
|
}
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
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;
|
|
5615
5663
|
}
|
|
5616
|
-
|
|
5664
|
+
else if (right === null) {
|
|
5617
5665
|
return false;
|
|
5618
5666
|
}
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
// value returned by action.
|
|
5623
|
-
inorderTraversal(action) {
|
|
5624
|
-
return (this.left.inorderTraversal(action) ||
|
|
5625
|
-
action(this.key, this.value) ||
|
|
5626
|
-
this.right.inorderTraversal(action));
|
|
5627
|
-
}
|
|
5628
|
-
// Traverses the tree in reverse key order and calls the specified action
|
|
5629
|
-
// function for each node. If action returns true, traversal is aborted.
|
|
5630
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5631
|
-
// value returned by action.
|
|
5632
|
-
reverseTraversal(action) {
|
|
5633
|
-
return (this.right.reverseTraversal(action) ||
|
|
5634
|
-
action(this.key, this.value) ||
|
|
5635
|
-
this.left.reverseTraversal(action));
|
|
5667
|
+
if (left.inclusive !== right.inclusive ||
|
|
5668
|
+
left.position.length !== right.position.length) {
|
|
5669
|
+
return false;
|
|
5636
5670
|
}
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
else {
|
|
5643
|
-
return this.left.min();
|
|
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;
|
|
5644
5676
|
}
|
|
5645
5677
|
}
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
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) ;
|
|
5658
5723
|
}
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
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;
|
|
5671
5765
|
}
|
|
5672
|
-
return n.fixUp();
|
|
5673
5766
|
}
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
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;
|
|
5681
5777
|
}
|
|
5682
|
-
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
5683
|
-
return n.fixUp();
|
|
5684
5778
|
}
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
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));
|
|
5692
5821
|
}
|
|
5693
|
-
|
|
5822
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
5694
5823
|
}
|
|
5695
5824
|
else {
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
}
|
|
5702
|
-
if (comparator(key, n.key) === 0) {
|
|
5703
|
-
if (n.right.isEmpty()) {
|
|
5704
|
-
return LLRBNode.EMPTY;
|
|
5705
|
-
}
|
|
5706
|
-
else {
|
|
5707
|
-
smallest = n.right.min();
|
|
5708
|
-
n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
|
|
5825
|
+
let foundKeyOrdering = false;
|
|
5826
|
+
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5827
|
+
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5828
|
+
if (orderBy.field.isKeyField()) {
|
|
5829
|
+
foundKeyOrdering = true;
|
|
5709
5830
|
}
|
|
5710
5831
|
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
let n = this;
|
|
5721
|
-
if (n.right.isRed() && !n.left.isRed()) {
|
|
5722
|
-
n = n.rotateLeft();
|
|
5723
|
-
}
|
|
5724
|
-
if (n.left.isRed() && n.left.left.isRed()) {
|
|
5725
|
-
n = n.rotateRight();
|
|
5726
|
-
}
|
|
5727
|
-
if (n.left.isRed() && n.right.isRed()) {
|
|
5728
|
-
n = n.colorFlip();
|
|
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
|
+
}
|
|
5729
5841
|
}
|
|
5730
|
-
return n;
|
|
5731
5842
|
}
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
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);
|
|
5738
5853
|
}
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
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);
|
|
5746
5872
|
}
|
|
5747
|
-
return n;
|
|
5748
5873
|
}
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
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));
|
|
5752
5922
|
}
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
return
|
|
5923
|
+
else if (DocumentKey.isDocumentKey(query.path)) {
|
|
5924
|
+
// exact match for document queries
|
|
5925
|
+
return query.path.isEqual(docPath);
|
|
5756
5926
|
}
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
return this.copy(null, null, !this.color, left, right);
|
|
5927
|
+
else {
|
|
5928
|
+
// shallow ancestor queries by default
|
|
5929
|
+
return query.path.isImmediateParentOf(docPath);
|
|
5761
5930
|
}
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
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) {
|
|
5769
5940
|
return false;
|
|
5770
5941
|
}
|
|
5771
5942
|
}
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
if (this.right.isRed()) {
|
|
5779
|
-
throw fail();
|
|
5780
|
-
}
|
|
5781
|
-
const blackDepth = this.left.check();
|
|
5782
|
-
if (blackDepth !== this.right.check()) {
|
|
5783
|
-
throw fail();
|
|
5784
|
-
}
|
|
5785
|
-
else {
|
|
5786
|
-
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;
|
|
5787
5949
|
}
|
|
5788
5950
|
}
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
5796
|
-
class LLRBEmptyNode {
|
|
5797
|
-
constructor() {
|
|
5798
|
-
this.size = 0;
|
|
5799
|
-
}
|
|
5800
|
-
get key() {
|
|
5801
|
-
throw fail();
|
|
5802
|
-
}
|
|
5803
|
-
get value() {
|
|
5804
|
-
throw fail();
|
|
5805
|
-
}
|
|
5806
|
-
get color() {
|
|
5807
|
-
throw fail();
|
|
5808
|
-
}
|
|
5809
|
-
get left() {
|
|
5810
|
-
throw fail();
|
|
5811
|
-
}
|
|
5812
|
-
get right() {
|
|
5813
|
-
throw fail();
|
|
5814
|
-
}
|
|
5815
|
-
// Returns a copy of the current node.
|
|
5816
|
-
copy(key, value, color, left, right) {
|
|
5817
|
-
return this;
|
|
5818
|
-
}
|
|
5819
|
-
// Returns a copy of the tree, with the specified key/value added.
|
|
5820
|
-
insert(key, value, comparator) {
|
|
5821
|
-
return new LLRBNode(key, value);
|
|
5822
|
-
}
|
|
5823
|
-
// Returns a copy of the tree, with the specified key removed.
|
|
5824
|
-
remove(key, comparator) {
|
|
5825
|
-
return this;
|
|
5826
|
-
}
|
|
5827
|
-
isEmpty() {
|
|
5828
|
-
return true;
|
|
5829
|
-
}
|
|
5830
|
-
inorderTraversal(action) {
|
|
5831
|
-
return false;
|
|
5832
|
-
}
|
|
5833
|
-
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)) {
|
|
5834
5957
|
return false;
|
|
5835
5958
|
}
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
}
|
|
5839
|
-
maxKey() {
|
|
5840
|
-
return null;
|
|
5841
|
-
}
|
|
5842
|
-
isRed() {
|
|
5959
|
+
if (query.endAt &&
|
|
5960
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
5843
5961
|
return false;
|
|
5844
5962
|
}
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
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
|
+
}
|
|
5850
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();
|
|
5851
6005
|
}
|
|
5852
|
-
}
|
|
5853
|
-
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
6006
|
+
}
|
|
5854
6007
|
|
|
5855
6008
|
/**
|
|
5856
6009
|
* @license
|
|
@@ -5869,218 +6022,97 @@ LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
|
5869
6022
|
* limitations under the License.
|
|
5870
6023
|
*/
|
|
5871
6024
|
/**
|
|
5872
|
-
*
|
|
5873
|
-
*
|
|
5874
|
-
*
|
|
5875
|
-
*
|
|
5876
|
-
* 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.
|
|
5877
6029
|
*/
|
|
5878
|
-
class
|
|
5879
|
-
constructor(
|
|
5880
|
-
this.
|
|
5881
|
-
this.
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
}
|
|
5892
|
-
get size() {
|
|
5893
|
-
return this.data.size;
|
|
5894
|
-
}
|
|
5895
|
-
indexOf(elem) {
|
|
5896
|
-
return this.data.indexOf(elem);
|
|
5897
|
-
}
|
|
5898
|
-
/** Iterates elements in order defined by "comparator" */
|
|
5899
|
-
forEach(cb) {
|
|
5900
|
-
this.data.inorderTraversal((k, v) => {
|
|
5901
|
-
cb(k);
|
|
5902
|
-
return false;
|
|
5903
|
-
});
|
|
5904
|
-
}
|
|
5905
|
-
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
5906
|
-
forEachInRange(range, cb) {
|
|
5907
|
-
const iter = this.data.getIteratorFrom(range[0]);
|
|
5908
|
-
while (iter.hasNext()) {
|
|
5909
|
-
const elem = iter.getNext();
|
|
5910
|
-
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
5911
|
-
return;
|
|
5912
|
-
}
|
|
5913
|
-
cb(elem.key);
|
|
5914
|
-
}
|
|
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;
|
|
5915
6043
|
}
|
|
5916
|
-
/**
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
iter = this.data.getIteratorFrom(start);
|
|
5923
|
-
}
|
|
5924
|
-
else {
|
|
5925
|
-
iter = this.data.getIterator();
|
|
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;
|
|
5926
6050
|
}
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
if (!result) {
|
|
5931
|
-
return;
|
|
6051
|
+
for (const [otherKey, value] of matches) {
|
|
6052
|
+
if (this.equalsFn(otherKey, key)) {
|
|
6053
|
+
return value;
|
|
5932
6054
|
}
|
|
5933
6055
|
}
|
|
6056
|
+
return undefined;
|
|
5934
6057
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
const iter = this.data.getIteratorFrom(elem);
|
|
5938
|
-
return iter.hasNext() ? iter.getNext().key : null;
|
|
5939
|
-
}
|
|
5940
|
-
getIterator() {
|
|
5941
|
-
return new SortedSetIterator(this.data.getIterator());
|
|
5942
|
-
}
|
|
5943
|
-
getIteratorFrom(key) {
|
|
5944
|
-
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5945
|
-
}
|
|
5946
|
-
/** Inserts or updates an element */
|
|
5947
|
-
add(elem) {
|
|
5948
|
-
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
6058
|
+
has(key) {
|
|
6059
|
+
return this.get(key) !== undefined;
|
|
5949
6060
|
}
|
|
5950
|
-
/**
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
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;
|
|
5954
6069
|
}
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
let result = this;
|
|
5962
|
-
// Make sure `result` always refers to the larger one of the two sets.
|
|
5963
|
-
if (result.size < other.size) {
|
|
5964
|
-
result = other;
|
|
5965
|
-
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
|
+
}
|
|
5966
6076
|
}
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
});
|
|
5970
|
-
return result;
|
|
6077
|
+
matches.push([key, value]);
|
|
6078
|
+
this.innerSize++;
|
|
5971
6079
|
}
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
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) {
|
|
5977
6087
|
return false;
|
|
5978
6088
|
}
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
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;
|
|
5986
6099
|
}
|
|
5987
6100
|
}
|
|
5988
|
-
return
|
|
6101
|
+
return false;
|
|
5989
6102
|
}
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
6103
|
+
forEach(fn) {
|
|
6104
|
+
forEach(this.inner, (_, entries) => {
|
|
6105
|
+
for (const [k, v] of entries) {
|
|
6106
|
+
fn(k, v);
|
|
6107
|
+
}
|
|
5994
6108
|
});
|
|
5995
|
-
return res;
|
|
5996
|
-
}
|
|
5997
|
-
toString() {
|
|
5998
|
-
const result = [];
|
|
5999
|
-
this.forEach(elem => result.push(elem));
|
|
6000
|
-
return 'SortedSet(' + result.toString() + ')';
|
|
6001
|
-
}
|
|
6002
|
-
copy(data) {
|
|
6003
|
-
const result = new SortedSet(this.comparator);
|
|
6004
|
-
result.data = data;
|
|
6005
|
-
return result;
|
|
6006
|
-
}
|
|
6007
|
-
}
|
|
6008
|
-
class SortedSetIterator {
|
|
6009
|
-
constructor(iter) {
|
|
6010
|
-
this.iter = iter;
|
|
6011
6109
|
}
|
|
6012
|
-
|
|
6013
|
-
return this.
|
|
6014
|
-
}
|
|
6015
|
-
hasNext() {
|
|
6016
|
-
return this.iter.hasNext();
|
|
6110
|
+
isEmpty() {
|
|
6111
|
+
return isEmpty(this.inner);
|
|
6017
6112
|
}
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
* Compares two sorted sets for equality using their natural ordering. The
|
|
6021
|
-
* method computes the intersection and invokes `onAdd` for every element that
|
|
6022
|
-
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
6023
|
-
* `before` but missing from `after`.
|
|
6024
|
-
*
|
|
6025
|
-
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
6026
|
-
* n), where n is the size of the two lists.
|
|
6027
|
-
*
|
|
6028
|
-
* @param before - The elements that exist in the original set.
|
|
6029
|
-
* @param after - The elements to diff against the original set.
|
|
6030
|
-
* @param comparator - The comparator for the elements in before and after.
|
|
6031
|
-
* @param onAdd - A function to invoke for every element that is part of `
|
|
6032
|
-
* after` but not `before`.
|
|
6033
|
-
* @param onRemove - A function to invoke for every element that is part of
|
|
6034
|
-
* `before` but not `after`.
|
|
6035
|
-
*/
|
|
6036
|
-
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
6037
|
-
const beforeIt = before.getIterator();
|
|
6038
|
-
const afterIt = after.getIterator();
|
|
6039
|
-
let beforeValue = advanceIterator(beforeIt);
|
|
6040
|
-
let afterValue = advanceIterator(afterIt);
|
|
6041
|
-
// Walk through the two sets at the same time, using the ordering defined by
|
|
6042
|
-
// `comparator`.
|
|
6043
|
-
while (beforeValue || afterValue) {
|
|
6044
|
-
let added = false;
|
|
6045
|
-
let removed = false;
|
|
6046
|
-
if (beforeValue && afterValue) {
|
|
6047
|
-
const cmp = comparator(beforeValue, afterValue);
|
|
6048
|
-
if (cmp < 0) {
|
|
6049
|
-
// The element was removed if the next element in our ordered
|
|
6050
|
-
// walkthrough is only in `before`.
|
|
6051
|
-
removed = true;
|
|
6052
|
-
}
|
|
6053
|
-
else if (cmp > 0) {
|
|
6054
|
-
// The element was added if the next element in our ordered walkthrough
|
|
6055
|
-
// is only in `after`.
|
|
6056
|
-
added = true;
|
|
6057
|
-
}
|
|
6058
|
-
}
|
|
6059
|
-
else if (beforeValue != null) {
|
|
6060
|
-
removed = true;
|
|
6061
|
-
}
|
|
6062
|
-
else {
|
|
6063
|
-
added = true;
|
|
6064
|
-
}
|
|
6065
|
-
if (added) {
|
|
6066
|
-
onAdd(afterValue);
|
|
6067
|
-
afterValue = advanceIterator(afterIt);
|
|
6068
|
-
}
|
|
6069
|
-
else if (removed) {
|
|
6070
|
-
onRemove(beforeValue);
|
|
6071
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6072
|
-
}
|
|
6073
|
-
else {
|
|
6074
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6075
|
-
afterValue = advanceIterator(afterIt);
|
|
6076
|
-
}
|
|
6113
|
+
size() {
|
|
6114
|
+
return this.innerSize;
|
|
6077
6115
|
}
|
|
6078
|
-
}
|
|
6079
|
-
/**
|
|
6080
|
-
* Returns the next element from the iterator or `undefined` if none available.
|
|
6081
|
-
*/
|
|
6082
|
-
function advanceIterator(it) {
|
|
6083
|
-
return it.hasNext() ? it.getNext() : undefined;
|
|
6084
6116
|
}
|
|
6085
6117
|
|
|
6086
6118
|
/**
|
|
@@ -6104,8 +6136,12 @@ function mutableDocumentMap() {
|
|
|
6104
6136
|
return EMPTY_MUTABLE_DOCUMENT_MAP;
|
|
6105
6137
|
}
|
|
6106
6138
|
const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
6107
|
-
function documentMap() {
|
|
6108
|
-
|
|
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;
|
|
6109
6145
|
}
|
|
6110
6146
|
function newOverlayMap() {
|
|
6111
6147
|
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
@@ -9998,9 +10034,9 @@ class MemoryIndexManager {
|
|
|
9998
10034
|
// Field indices are not supported with memory persistence.
|
|
9999
10035
|
return PersistencePromise.resolve(null);
|
|
10000
10036
|
}
|
|
10001
|
-
|
|
10037
|
+
getIndexType(transaction, target) {
|
|
10002
10038
|
// Field indices are not supported with memory persistence.
|
|
10003
|
-
return PersistencePromise.resolve(
|
|
10039
|
+
return PersistencePromise.resolve(0 /* NONE */);
|
|
10004
10040
|
}
|
|
10005
10041
|
getFieldIndexes(transaction, collectionGroup) {
|
|
10006
10042
|
// Field indices are not supported with memory persistence.
|
|
@@ -10010,6 +10046,9 @@ class MemoryIndexManager {
|
|
|
10010
10046
|
// Field indices are not supported with memory persistence.
|
|
10011
10047
|
return PersistencePromise.resolve(null);
|
|
10012
10048
|
}
|
|
10049
|
+
getMinOffset(transaction, target) {
|
|
10050
|
+
return PersistencePromise.resolve(IndexOffset.min());
|
|
10051
|
+
}
|
|
10013
10052
|
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10014
10053
|
// Field indices are not supported with memory persistence.
|
|
10015
10054
|
return PersistencePromise.resolve();
|
|
@@ -10185,7 +10224,7 @@ class IndexedDbIndexManager {
|
|
|
10185
10224
|
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10186
10225
|
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10187
10226
|
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10188
|
-
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded,
|
|
10227
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
|
|
10189
10228
|
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10190
10229
|
return indexEntries
|
|
10191
10230
|
.loadFirst(indexRange, target.limit)
|
|
@@ -10223,22 +10262,18 @@ class IndexedDbIndexManager {
|
|
|
10223
10262
|
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10224
10263
|
// combined with the values from the query bounds.
|
|
10225
10264
|
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10226
|
-
Math.max(lowerBounds
|
|
10265
|
+
Math.max(lowerBounds.length, upperBounds.length);
|
|
10227
10266
|
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10228
10267
|
const indexRanges = [];
|
|
10229
10268
|
for (let i = 0; i < totalScans; ++i) {
|
|
10230
10269
|
const arrayValue = arrayValues
|
|
10231
10270
|
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10232
10271
|
: EMPTY_VALUE;
|
|
10233
|
-
const lowerBound = lowerBounds
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
: this.generateEmptyBound(indexId + 1);
|
|
10239
|
-
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10240
|
-
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10241
|
-
/* 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));
|
|
10242
10277
|
}
|
|
10243
10278
|
return indexRanges;
|
|
10244
10279
|
}
|
|
@@ -10252,25 +10287,38 @@ class IndexedDbIndexManager {
|
|
|
10252
10287
|
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10253
10288
|
return inclusive ? entry.successor() : entry;
|
|
10254
10289
|
}
|
|
10255
|
-
/**
|
|
10256
|
-
* Generates an empty bound that scopes the index scan to the current index
|
|
10257
|
-
* and user.
|
|
10258
|
-
*/
|
|
10259
|
-
generateEmptyBound(indexId) {
|
|
10260
|
-
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10261
|
-
}
|
|
10262
10290
|
getFieldIndex(transaction, target) {
|
|
10263
10291
|
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10264
10292
|
const collectionGroup = target.collectionGroup != null
|
|
10265
10293
|
? target.collectionGroup
|
|
10266
10294
|
: target.path.lastSegment();
|
|
10267
10295
|
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
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;
|
|
10272
10306
|
});
|
|
10273
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
|
+
}
|
|
10274
10322
|
/**
|
|
10275
10323
|
* Returns the byte encoded form of the directional values in the field index.
|
|
10276
10324
|
* Returns `null` if the document does not have all fields specified in the
|
|
@@ -10333,9 +10381,6 @@ class IndexedDbIndexManager {
|
|
|
10333
10381
|
* queries, a list of possible values is returned.
|
|
10334
10382
|
*/
|
|
10335
10383
|
encodeBound(fieldIndex, target, bound) {
|
|
10336
|
-
if (bound == null) {
|
|
10337
|
-
return null;
|
|
10338
|
-
}
|
|
10339
10384
|
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10340
10385
|
}
|
|
10341
10386
|
/** Returns the byte representation for the provided encoders. */
|
|
@@ -10530,7 +10575,7 @@ class IndexedDbIndexManager {
|
|
|
10530
10575
|
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10531
10576
|
*/
|
|
10532
10577
|
createRange(lower, upper, notInValues) {
|
|
10533
|
-
// The
|
|
10578
|
+
// The notIn values need to be sorted and unique so that we can return a
|
|
10534
10579
|
// sorted set of non-overlapping ranges.
|
|
10535
10580
|
notInValues = notInValues
|
|
10536
10581
|
.sort((l, r) => indexEntryComparator(l, r))
|
|
@@ -10576,6 +10621,20 @@ class IndexedDbIndexManager {
|
|
|
10576
10621
|
}
|
|
10577
10622
|
return ranges;
|
|
10578
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
|
+
}
|
|
10579
10638
|
}
|
|
10580
10639
|
/**
|
|
10581
10640
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -13746,7 +13805,7 @@ class LocalStoreImpl {
|
|
|
13746
13805
|
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13747
13806
|
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13748
13807
|
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
13749
|
-
this.queryEngine.
|
|
13808
|
+
this.queryEngine.initialize(this.localDocuments, this.indexManager);
|
|
13750
13809
|
}
|
|
13751
13810
|
collectGarbage(garbageCollector) {
|
|
13752
13811
|
return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
|
|
@@ -15488,10 +15547,30 @@ class MemoryEagerDelegate {
|
|
|
15488
15547
|
* limitations under the License.
|
|
15489
15548
|
*/
|
|
15490
15549
|
/**
|
|
15491
|
-
*
|
|
15492
|
-
*
|
|
15493
|
-
*
|
|
15494
|
-
*
|
|
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.
|
|
15495
15574
|
*
|
|
15496
15575
|
* There are some cases when this optimization is not guaranteed to produce
|
|
15497
15576
|
* the same results as full collection scans. In these cases, query
|
|
@@ -15506,16 +15585,41 @@ class MemoryEagerDelegate {
|
|
|
15506
15585
|
* - Queries that have never been CURRENT or free of limbo documents.
|
|
15507
15586
|
*/
|
|
15508
15587
|
class QueryEngine {
|
|
15588
|
+
constructor() {
|
|
15589
|
+
this.initialized = false;
|
|
15590
|
+
}
|
|
15509
15591
|
/** Sets the document view to query against. */
|
|
15510
|
-
|
|
15592
|
+
initialize(localDocuments, indexManager) {
|
|
15511
15593
|
this.localDocumentsView = localDocuments;
|
|
15594
|
+
this.indexManager = indexManager;
|
|
15595
|
+
this.initialized = true;
|
|
15512
15596
|
}
|
|
15513
15597
|
/** Returns all local documents matching the specified query. */
|
|
15514
15598
|
getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
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.
|
|
15519
15623
|
return this.executeFullCollectionScan(transaction, query);
|
|
15520
15624
|
}
|
|
15521
15625
|
// Queries that have never seen a snapshot without limbo free documents
|
|
@@ -15525,8 +15629,7 @@ class QueryEngine {
|
|
|
15525
15629
|
}
|
|
15526
15630
|
return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
|
|
15527
15631
|
const previousResults = this.applyQuery(query, documents);
|
|
15528
|
-
if ((
|
|
15529
|
-
this.needsRefill(query.limitType, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15632
|
+
if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15530
15633
|
return this.executeFullCollectionScan(transaction, query);
|
|
15531
15634
|
}
|
|
15532
15635
|
if (getLogLevel() <= logger.LogLevel.DEBUG) {
|
|
@@ -15534,15 +15637,7 @@ class QueryEngine {
|
|
|
15534
15637
|
}
|
|
15535
15638
|
// Retrieve all results for documents that were updated since the last
|
|
15536
15639
|
// limbo-document free remote snapshot.
|
|
15537
|
-
return this.
|
|
15538
|
-
// We merge `previousResults` into `updateResults`, since
|
|
15539
|
-
// `updateResults` is already a DocumentMap. If a document is
|
|
15540
|
-
// contained in both lists, then its contents are the same.
|
|
15541
|
-
previousResults.forEach(doc => {
|
|
15542
|
-
updatedResults = updatedResults.insert(doc.key, doc);
|
|
15543
|
-
});
|
|
15544
|
-
return updatedResults;
|
|
15545
|
-
});
|
|
15640
|
+
return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
|
|
15546
15641
|
});
|
|
15547
15642
|
}
|
|
15548
15643
|
/** Applies the query filter and sorting to the provided documents. */
|
|
@@ -15561,6 +15656,7 @@ class QueryEngine {
|
|
|
15561
15656
|
* Determines if a limit query needs to be refilled from cache, making it
|
|
15562
15657
|
* ineligible for index-free execution.
|
|
15563
15658
|
*
|
|
15659
|
+
* @param query The query.
|
|
15564
15660
|
* @param sortedPreviousResults - The documents that matched the query when it
|
|
15565
15661
|
* was last synchronized, sorted by the query's comparator.
|
|
15566
15662
|
* @param remoteKeys - The document keys that matched the query at the last
|
|
@@ -15568,10 +15664,14 @@ class QueryEngine {
|
|
|
15568
15664
|
* @param limboFreeSnapshotVersion - The version of the snapshot when the
|
|
15569
15665
|
* query was last synchronized.
|
|
15570
15666
|
*/
|
|
15571
|
-
needsRefill(
|
|
15572
|
-
|
|
15573
|
-
|
|
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
|
+
}
|
|
15574
15672
|
if (remoteKeys.size !== sortedPreviousResults.size) {
|
|
15673
|
+
// The query needs to be refilled if a previously matching document no
|
|
15674
|
+
// longer matches.
|
|
15575
15675
|
return true;
|
|
15576
15676
|
}
|
|
15577
15677
|
// Limit queries are not eligible for index-free query execution if there is
|
|
@@ -15582,7 +15682,7 @@ class QueryEngine {
|
|
|
15582
15682
|
// the boundary of the limit itself did not change and documents from cache
|
|
15583
15683
|
// will continue to be "rejected" by this boundary. Therefore, we can ignore
|
|
15584
15684
|
// any modifications that don't affect the last document.
|
|
15585
|
-
const docAtLimitEdge = limitType === "F" /* First */
|
|
15685
|
+
const docAtLimitEdge = query.limitType === "F" /* First */
|
|
15586
15686
|
? sortedPreviousResults.last()
|
|
15587
15687
|
: sortedPreviousResults.first();
|
|
15588
15688
|
if (!docAtLimitEdge) {
|
|
@@ -15598,6 +15698,22 @@ class QueryEngine {
|
|
|
15598
15698
|
}
|
|
15599
15699
|
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15600
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
|
+
}
|
|
15601
15717
|
}
|
|
15602
15718
|
|
|
15603
15719
|
/**
|
|
@@ -21857,10 +21973,12 @@ class View {
|
|
|
21857
21973
|
//
|
|
21858
21974
|
// Note that this should never get used in a refill (when previousChanges is
|
|
21859
21975
|
// set), because there will only be adds -- no deletes or updates.
|
|
21860
|
-
const lastDocInLimit =
|
|
21976
|
+
const lastDocInLimit = this.query.limitType === "F" /* First */ &&
|
|
21977
|
+
oldDocumentSet.size === this.query.limit
|
|
21861
21978
|
? oldDocumentSet.last()
|
|
21862
21979
|
: null;
|
|
21863
|
-
const firstDocInLimit =
|
|
21980
|
+
const firstDocInLimit = this.query.limitType === "L" /* Last */ &&
|
|
21981
|
+
oldDocumentSet.size === this.query.limit
|
|
21864
21982
|
? oldDocumentSet.first()
|
|
21865
21983
|
: null;
|
|
21866
21984
|
docChanges.inorderTraversal((key, entry) => {
|
|
@@ -21933,9 +22051,9 @@ class View {
|
|
|
21933
22051
|
}
|
|
21934
22052
|
});
|
|
21935
22053
|
// Drop documents out to meet limit/limitToLast requirement.
|
|
21936
|
-
if (
|
|
22054
|
+
if (this.query.limit !== null) {
|
|
21937
22055
|
while (newDocumentSet.size > this.query.limit) {
|
|
21938
|
-
const oldDoc =
|
|
22056
|
+
const oldDoc = this.query.limitType === "F" /* First */
|
|
21939
22057
|
? newDocumentSet.last()
|
|
21940
22058
|
: newDocumentSet.first();
|
|
21941
22059
|
newDocumentSet = newDocumentSet.delete(oldDoc.key);
|
|
@@ -23936,18 +24054,18 @@ class Transaction$2 {
|
|
|
23936
24054
|
* See the License for the specific language governing permissions and
|
|
23937
24055
|
* limitations under the License.
|
|
23938
24056
|
*/
|
|
23939
|
-
const DEFAULT_MAX_ATTEMPTS_COUNT = 5;
|
|
23940
24057
|
/**
|
|
23941
24058
|
* TransactionRunner encapsulates the logic needed to run and retry transactions
|
|
23942
24059
|
* with backoff.
|
|
23943
24060
|
*/
|
|
23944
24061
|
class TransactionRunner {
|
|
23945
|
-
constructor(asyncQueue, datastore, updateFunction, deferred) {
|
|
24062
|
+
constructor(asyncQueue, datastore, options, updateFunction, deferred) {
|
|
23946
24063
|
this.asyncQueue = asyncQueue;
|
|
23947
24064
|
this.datastore = datastore;
|
|
24065
|
+
this.options = options;
|
|
23948
24066
|
this.updateFunction = updateFunction;
|
|
23949
24067
|
this.deferred = deferred;
|
|
23950
|
-
this.attemptsRemaining =
|
|
24068
|
+
this.attemptsRemaining = options.maxAttempts;
|
|
23951
24069
|
this.backoff = new ExponentialBackoff(this.asyncQueue, "transaction_retry" /* TransactionRetry */);
|
|
23952
24070
|
}
|
|
23953
24071
|
/** Runs the transaction and sets the result on deferred. */
|
|
@@ -24305,11 +24423,11 @@ function firestoreClientAddSnapshotsInSyncListener(client, observer) {
|
|
|
24305
24423
|
* been committed. For this reason, it is required that all reads are
|
|
24306
24424
|
* performed before any writes. Transactions must be performed while online.
|
|
24307
24425
|
*/
|
|
24308
|
-
function firestoreClientTransaction(client, updateFunction) {
|
|
24426
|
+
function firestoreClientTransaction(client, updateFunction, options) {
|
|
24309
24427
|
const deferred = new Deferred();
|
|
24310
24428
|
client.asyncQueue.enqueueAndForget(async () => {
|
|
24311
24429
|
const datastore = await getDatastore(client);
|
|
24312
|
-
new TransactionRunner(client.asyncQueue, datastore, updateFunction, deferred).run();
|
|
24430
|
+
new TransactionRunner(client.asyncQueue, datastore, options, updateFunction, deferred).run();
|
|
24313
24431
|
});
|
|
24314
24432
|
return deferred.promise;
|
|
24315
24433
|
}
|
|
@@ -27066,7 +27184,8 @@ function snapshotEqual(left, right) {
|
|
|
27066
27184
|
* limitations under the License.
|
|
27067
27185
|
*/
|
|
27068
27186
|
function validateHasExplicitOrderByForLimitToLast(query) {
|
|
27069
|
-
if (
|
|
27187
|
+
if (query.limitType === "L" /* Last */ &&
|
|
27188
|
+
query.explicitOrderBy.length === 0) {
|
|
27070
27189
|
throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
|
|
27071
27190
|
}
|
|
27072
27191
|
}
|
|
@@ -27508,6 +27627,31 @@ function validateOrderByAndInequalityMatch(baseQuery, inequality, orderBy) {
|
|
|
27508
27627
|
}
|
|
27509
27628
|
}
|
|
27510
27629
|
|
|
27630
|
+
/**
|
|
27631
|
+
* @license
|
|
27632
|
+
* Copyright 2022 Google LLC
|
|
27633
|
+
*
|
|
27634
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
27635
|
+
* you may not use this file except in compliance with the License.
|
|
27636
|
+
* You may obtain a copy of the License at
|
|
27637
|
+
*
|
|
27638
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
27639
|
+
*
|
|
27640
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
27641
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
27642
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
27643
|
+
* See the License for the specific language governing permissions and
|
|
27644
|
+
* limitations under the License.
|
|
27645
|
+
*/
|
|
27646
|
+
const DEFAULT_TRANSACTION_OPTIONS = {
|
|
27647
|
+
maxAttempts: 5
|
|
27648
|
+
};
|
|
27649
|
+
function validateTransactionOptions(options) {
|
|
27650
|
+
if (options.maxAttempts < 1) {
|
|
27651
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Max attempts must be at least 1');
|
|
27652
|
+
}
|
|
27653
|
+
}
|
|
27654
|
+
|
|
27511
27655
|
/**
|
|
27512
27656
|
* @license
|
|
27513
27657
|
* Copyright 2020 Google LLC
|
|
@@ -28229,10 +28373,12 @@ class Transaction extends Transaction$1 {
|
|
|
28229
28373
|
* `updateFunction `is returned here. Otherwise, if the transaction failed, a
|
|
28230
28374
|
* rejected promise with the corresponding failure error is returned.
|
|
28231
28375
|
*/
|
|
28232
|
-
function runTransaction(firestore, updateFunction) {
|
|
28376
|
+
function runTransaction(firestore, updateFunction, options) {
|
|
28233
28377
|
firestore = cast(firestore, Firestore);
|
|
28378
|
+
const optionsWithDefaults = Object.assign(Object.assign({}, DEFAULT_TRANSACTION_OPTIONS), options);
|
|
28379
|
+
validateTransactionOptions(optionsWithDefaults);
|
|
28234
28380
|
const client = ensureFirestoreConfigured(firestore);
|
|
28235
|
-
return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)));
|
|
28381
|
+
return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)), optionsWithDefaults);
|
|
28236
28382
|
}
|
|
28237
28383
|
|
|
28238
28384
|
/**
|