@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.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
|
|
|
8
8
|
import * as protoLoader from '@grpc/proto-loader';
|
|
9
9
|
|
|
10
10
|
const name = "@firebase/firestore";
|
|
11
|
-
const version$1 = "3.4.
|
|
11
|
+
const version$1 = "3.4.9";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
61
61
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
62
62
|
User.MOCK_USER = new User('mock-user');
|
|
63
63
|
|
|
64
|
-
const version = "9.
|
|
64
|
+
const version = "9.8.0";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @license
|
|
@@ -3321,7 +3321,7 @@ function typeOrder(value) {
|
|
|
3321
3321
|
return 4 /* ServerTimestampValue */;
|
|
3322
3322
|
}
|
|
3323
3323
|
else if (isMaxValue(value)) {
|
|
3324
|
-
return
|
|
3324
|
+
return 9007199254740991 /* MaxValue */;
|
|
3325
3325
|
}
|
|
3326
3326
|
return 10 /* ObjectValue */;
|
|
3327
3327
|
}
|
|
@@ -3533,6 +3533,15 @@ function compareArrays(left, right) {
|
|
|
3533
3533
|
return primitiveComparator(leftArray.length, rightArray.length);
|
|
3534
3534
|
}
|
|
3535
3535
|
function compareMaps(left, right) {
|
|
3536
|
+
if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
|
|
3537
|
+
return 0;
|
|
3538
|
+
}
|
|
3539
|
+
else if (left === MAX_VALUE.mapValue) {
|
|
3540
|
+
return 1;
|
|
3541
|
+
}
|
|
3542
|
+
else if (right === MAX_VALUE.mapValue) {
|
|
3543
|
+
return -1;
|
|
3544
|
+
}
|
|
3536
3545
|
const leftMap = left.fields || {};
|
|
3537
3546
|
const leftKeys = Object.keys(leftMap);
|
|
3538
3547
|
const rightMap = right.fields || {};
|
|
@@ -3780,27 +3789,31 @@ function valuesGetUpperBound(value) {
|
|
|
3780
3789
|
return fail();
|
|
3781
3790
|
}
|
|
3782
3791
|
}
|
|
3783
|
-
function
|
|
3784
|
-
|
|
3785
|
-
|
|
3792
|
+
function lowerBoundCompare(left, right) {
|
|
3793
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3794
|
+
if (cmp !== 0) {
|
|
3795
|
+
return cmp;
|
|
3786
3796
|
}
|
|
3787
|
-
|
|
3788
|
-
return
|
|
3797
|
+
if (left.inclusive && !right.inclusive) {
|
|
3798
|
+
return -1;
|
|
3789
3799
|
}
|
|
3790
|
-
else {
|
|
3791
|
-
return
|
|
3800
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3801
|
+
return 1;
|
|
3792
3802
|
}
|
|
3803
|
+
return 0;
|
|
3793
3804
|
}
|
|
3794
|
-
function
|
|
3795
|
-
|
|
3796
|
-
|
|
3805
|
+
function upperBoundCompare(left, right) {
|
|
3806
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3807
|
+
if (cmp !== 0) {
|
|
3808
|
+
return cmp;
|
|
3797
3809
|
}
|
|
3798
|
-
|
|
3799
|
-
return
|
|
3810
|
+
if (left.inclusive && !right.inclusive) {
|
|
3811
|
+
return 1;
|
|
3800
3812
|
}
|
|
3801
|
-
else {
|
|
3802
|
-
return
|
|
3813
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3814
|
+
return -1;
|
|
3803
3815
|
}
|
|
3816
|
+
return 0;
|
|
3804
3817
|
}
|
|
3805
3818
|
|
|
3806
3819
|
/**
|
|
@@ -4310,7 +4323,7 @@ function indexOffsetComparator(left, right) {
|
|
|
4310
4323
|
|
|
4311
4324
|
/**
|
|
4312
4325
|
* @license
|
|
4313
|
-
* Copyright
|
|
4326
|
+
* Copyright 2017 Google LLC
|
|
4314
4327
|
*
|
|
4315
4328
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4316
4329
|
* you may not use this file except in compliance with the License.
|
|
@@ -4324,609 +4337,445 @@ function indexOffsetComparator(left, right) {
|
|
|
4324
4337
|
* See the License for the specific language governing permissions and
|
|
4325
4338
|
* limitations under the License.
|
|
4326
4339
|
*/
|
|
4327
|
-
//
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
this.
|
|
4332
|
-
this.
|
|
4333
|
-
this.filters = filters;
|
|
4334
|
-
this.limit = limit;
|
|
4335
|
-
this.startAt = startAt;
|
|
4336
|
-
this.endAt = endAt;
|
|
4337
|
-
this.memoizedCanonicalId = null;
|
|
4340
|
+
// An immutable sorted map implementation, based on a Left-leaning Red-Black
|
|
4341
|
+
// tree.
|
|
4342
|
+
class SortedMap {
|
|
4343
|
+
constructor(comparator, root) {
|
|
4344
|
+
this.comparator = comparator;
|
|
4345
|
+
this.root = root ? root : LLRBNode.EMPTY;
|
|
4338
4346
|
}
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
4345
|
-
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
4346
|
-
* property.
|
|
4347
|
-
*/
|
|
4348
|
-
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
4349
|
-
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
4350
|
-
}
|
|
4351
|
-
function canonifyTarget(target) {
|
|
4352
|
-
const targetImpl = debugCast(target);
|
|
4353
|
-
if (targetImpl.memoizedCanonicalId === null) {
|
|
4354
|
-
let str = targetImpl.path.canonicalString();
|
|
4355
|
-
if (targetImpl.collectionGroup !== null) {
|
|
4356
|
-
str += '|cg:' + targetImpl.collectionGroup;
|
|
4357
|
-
}
|
|
4358
|
-
str += '|f:';
|
|
4359
|
-
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4360
|
-
str += '|ob:';
|
|
4361
|
-
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4362
|
-
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4363
|
-
str += '|l:';
|
|
4364
|
-
str += targetImpl.limit;
|
|
4365
|
-
}
|
|
4366
|
-
if (targetImpl.startAt) {
|
|
4367
|
-
str += '|lb:';
|
|
4368
|
-
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4369
|
-
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4370
|
-
}
|
|
4371
|
-
if (targetImpl.endAt) {
|
|
4372
|
-
str += '|ub:';
|
|
4373
|
-
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4374
|
-
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4375
|
-
}
|
|
4376
|
-
targetImpl.memoizedCanonicalId = str;
|
|
4347
|
+
// Returns a copy of the map, with the specified key/value added or replaced.
|
|
4348
|
+
insert(key, value) {
|
|
4349
|
+
return new SortedMap(this.comparator, this.root
|
|
4350
|
+
.insert(key, value, this.comparator)
|
|
4351
|
+
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
4377
4352
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
str += ' collectionGroup=' + target.collectionGroup;
|
|
4353
|
+
// Returns a copy of the map, with the specified key removed.
|
|
4354
|
+
remove(key) {
|
|
4355
|
+
return new SortedMap(this.comparator, this.root
|
|
4356
|
+
.remove(key, this.comparator)
|
|
4357
|
+
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
4384
4358
|
}
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4359
|
+
// Returns the value of the node with the given key, or null.
|
|
4360
|
+
get(key) {
|
|
4361
|
+
let node = this.root;
|
|
4362
|
+
while (!node.isEmpty()) {
|
|
4363
|
+
const cmp = this.comparator(key, node.key);
|
|
4364
|
+
if (cmp === 0) {
|
|
4365
|
+
return node.value;
|
|
4366
|
+
}
|
|
4367
|
+
else if (cmp < 0) {
|
|
4368
|
+
node = node.left;
|
|
4369
|
+
}
|
|
4370
|
+
else if (cmp > 0) {
|
|
4371
|
+
node = node.right;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
return null;
|
|
4389
4375
|
}
|
|
4390
|
-
if
|
|
4391
|
-
|
|
4376
|
+
// Returns the index of the element in this sorted map, or -1 if it doesn't
|
|
4377
|
+
// exist.
|
|
4378
|
+
indexOf(key) {
|
|
4379
|
+
// Number of nodes that were pruned when descending right
|
|
4380
|
+
let prunedNodes = 0;
|
|
4381
|
+
let node = this.root;
|
|
4382
|
+
while (!node.isEmpty()) {
|
|
4383
|
+
const cmp = this.comparator(key, node.key);
|
|
4384
|
+
if (cmp === 0) {
|
|
4385
|
+
return prunedNodes + node.left.size;
|
|
4386
|
+
}
|
|
4387
|
+
else if (cmp < 0) {
|
|
4388
|
+
node = node.left;
|
|
4389
|
+
}
|
|
4390
|
+
else {
|
|
4391
|
+
// Count all nodes left of the node plus the node itself
|
|
4392
|
+
prunedNodes += node.left.size + 1;
|
|
4393
|
+
node = node.right;
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
// Node not found
|
|
4397
|
+
return -1;
|
|
4392
4398
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
.map(o => stringifyOrderBy(o))
|
|
4396
|
-
.join(', ')}]`;
|
|
4399
|
+
isEmpty() {
|
|
4400
|
+
return this.root.isEmpty();
|
|
4397
4401
|
}
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4402
|
+
// Returns the total number of nodes in the map.
|
|
4403
|
+
get size() {
|
|
4404
|
+
return this.root.size;
|
|
4402
4405
|
}
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4406
|
+
// Returns the minimum key in the map.
|
|
4407
|
+
minKey() {
|
|
4408
|
+
return this.root.minKey();
|
|
4407
4409
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
if (left.limit !== right.limit) {
|
|
4412
|
-
return false;
|
|
4410
|
+
// Returns the maximum key in the map.
|
|
4411
|
+
maxKey() {
|
|
4412
|
+
return this.root.maxKey();
|
|
4413
4413
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4414
|
+
// Traverses the map in key order and calls the specified action function
|
|
4415
|
+
// for each key/value pair. If action returns true, traversal is aborted.
|
|
4416
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4417
|
+
// value returned by action.
|
|
4418
|
+
inorderTraversal(action) {
|
|
4419
|
+
return this.root.inorderTraversal(action);
|
|
4416
4420
|
}
|
|
4417
|
-
|
|
4418
|
-
|
|
4421
|
+
forEach(fn) {
|
|
4422
|
+
this.inorderTraversal((k, v) => {
|
|
4423
|
+
fn(k, v);
|
|
4419
4424
|
return false;
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
|
-
if (left.filters.length !== right.filters.length) {
|
|
4423
|
-
return false;
|
|
4425
|
+
});
|
|
4424
4426
|
}
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
+
toString() {
|
|
4428
|
+
const descriptions = [];
|
|
4429
|
+
this.inorderTraversal((k, v) => {
|
|
4430
|
+
descriptions.push(`${k}:${v}`);
|
|
4427
4431
|
return false;
|
|
4428
|
-
}
|
|
4432
|
+
});
|
|
4433
|
+
return `{${descriptions.join(', ')}}`;
|
|
4429
4434
|
}
|
|
4430
|
-
|
|
4431
|
-
|
|
4435
|
+
// Traverses the map in reverse key order and calls the specified action
|
|
4436
|
+
// function for each key/value pair. If action returns true, traversal is
|
|
4437
|
+
// aborted.
|
|
4438
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4439
|
+
// value returned by action.
|
|
4440
|
+
reverseTraversal(action) {
|
|
4441
|
+
return this.root.reverseTraversal(action);
|
|
4432
4442
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4443
|
+
// Returns an iterator over the SortedMap.
|
|
4444
|
+
getIterator() {
|
|
4445
|
+
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
4435
4446
|
}
|
|
4436
|
-
|
|
4437
|
-
return false;
|
|
4447
|
+
getIteratorFrom(key) {
|
|
4448
|
+
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
4438
4449
|
}
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
function targetIsDocumentTarget(target) {
|
|
4442
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4443
|
-
target.collectionGroup === null &&
|
|
4444
|
-
target.filters.length === 0);
|
|
4445
|
-
}
|
|
4446
|
-
/** Returns the field filters that target the given field path. */
|
|
4447
|
-
function targetGetFieldFiltersForPath(target, path) {
|
|
4448
|
-
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4449
|
-
}
|
|
4450
|
-
/**
|
|
4451
|
-
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4452
|
-
* filters. Returns `null` if there are no such filters.
|
|
4453
|
-
*/
|
|
4454
|
-
function targetGetArrayValues(target, fieldIndex) {
|
|
4455
|
-
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4456
|
-
if (segment === undefined) {
|
|
4457
|
-
return null;
|
|
4450
|
+
getReverseIterator() {
|
|
4451
|
+
return new SortedMapIterator(this.root, null, this.comparator, true);
|
|
4458
4452
|
}
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4462
|
-
return fieldFilter.value.arrayValue.values || [];
|
|
4463
|
-
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4464
|
-
return [fieldFilter.value];
|
|
4465
|
-
// Remaining filters are not array filters.
|
|
4466
|
-
}
|
|
4453
|
+
getReverseIteratorFrom(key) {
|
|
4454
|
+
return new SortedMapIterator(this.root, key, this.comparator, true);
|
|
4467
4455
|
}
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
//
|
|
4456
|
+
} // end SortedMap
|
|
4457
|
+
// An iterator over an LLRBNode.
|
|
4458
|
+
class SortedMapIterator {
|
|
4459
|
+
constructor(node, startKey, comparator, isReverse) {
|
|
4460
|
+
this.isReverse = isReverse;
|
|
4461
|
+
this.nodeStack = [];
|
|
4462
|
+
let cmp = 1;
|
|
4463
|
+
while (!node.isEmpty()) {
|
|
4464
|
+
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
4465
|
+
// flip the comparison if we're going in reverse
|
|
4466
|
+
if (startKey && isReverse) {
|
|
4467
|
+
cmp *= -1;
|
|
4468
|
+
}
|
|
4469
|
+
if (cmp < 0) {
|
|
4470
|
+
// This node is less than our start key. ignore it
|
|
4471
|
+
if (this.isReverse) {
|
|
4472
|
+
node = node.left;
|
|
4473
|
+
}
|
|
4474
|
+
else {
|
|
4475
|
+
node = node.right;
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
else if (cmp === 0) {
|
|
4479
|
+
// This node is exactly equal to our start key. Push it on the stack,
|
|
4480
|
+
// but stop iterating;
|
|
4481
|
+
this.nodeStack.push(node);
|
|
4482
|
+
break;
|
|
4483
|
+
}
|
|
4484
|
+
else {
|
|
4485
|
+
// This node is greater than our start key, add it to the stack and move
|
|
4486
|
+
// to the next one
|
|
4487
|
+
this.nodeStack.push(node);
|
|
4488
|
+
if (this.isReverse) {
|
|
4489
|
+
node = node.right;
|
|
4490
|
+
}
|
|
4491
|
+
else {
|
|
4492
|
+
node = node.left;
|
|
4493
|
+
}
|
|
4493
4494
|
}
|
|
4494
4495
|
}
|
|
4495
4496
|
}
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
let inclusive = true;
|
|
4506
|
-
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
4507
|
-
// startAt.
|
|
4508
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4509
|
-
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
4510
|
-
? targetGetLowerBoundForField(target, segment.fieldPath, target.startAt)
|
|
4511
|
-
: targetGetUpperBoundForField(target, segment.fieldPath, target.startAt);
|
|
4512
|
-
if (!segmentBound.value) {
|
|
4513
|
-
// No lower bound exists
|
|
4514
|
-
return null;
|
|
4497
|
+
getNext() {
|
|
4498
|
+
let node = this.nodeStack.pop();
|
|
4499
|
+
const result = { key: node.key, value: node.value };
|
|
4500
|
+
if (this.isReverse) {
|
|
4501
|
+
node = node.left;
|
|
4502
|
+
while (!node.isEmpty()) {
|
|
4503
|
+
this.nodeStack.push(node);
|
|
4504
|
+
node = node.right;
|
|
4505
|
+
}
|
|
4515
4506
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4507
|
+
else {
|
|
4508
|
+
node = node.right;
|
|
4509
|
+
while (!node.isEmpty()) {
|
|
4510
|
+
this.nodeStack.push(node);
|
|
4511
|
+
node = node.left;
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
return result;
|
|
4518
4515
|
}
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
* upper bound exists.
|
|
4525
|
-
*/
|
|
4526
|
-
function targetGetUpperBound(target, fieldIndex) {
|
|
4527
|
-
const values = [];
|
|
4528
|
-
let inclusive = true;
|
|
4529
|
-
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4530
|
-
// endAt.
|
|
4531
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4532
|
-
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
4533
|
-
? targetGetUpperBoundForField(target, segment.fieldPath, target.endAt)
|
|
4534
|
-
: targetGetLowerBoundForField(target, segment.fieldPath, target.endAt);
|
|
4535
|
-
if (!segmentBound.value) {
|
|
4536
|
-
// No upper bound exists
|
|
4516
|
+
hasNext() {
|
|
4517
|
+
return this.nodeStack.length > 0;
|
|
4518
|
+
}
|
|
4519
|
+
peek() {
|
|
4520
|
+
if (this.nodeStack.length === 0) {
|
|
4537
4521
|
return null;
|
|
4538
4522
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
4523
|
+
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
4524
|
+
return { key: node.key, value: node.value };
|
|
4541
4525
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4526
|
+
} // end SortedMapIterator
|
|
4527
|
+
// Represents a node in a Left-leaning Red-Black tree.
|
|
4528
|
+
class LLRBNode {
|
|
4529
|
+
constructor(key, value, color, left, right) {
|
|
4530
|
+
this.key = key;
|
|
4531
|
+
this.value = value;
|
|
4532
|
+
this.color = color != null ? color : LLRBNode.RED;
|
|
4533
|
+
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
4534
|
+
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
4535
|
+
this.size = this.left.size + 1 + this.right.size;
|
|
4536
|
+
}
|
|
4537
|
+
// Returns a copy of the current node, optionally replacing pieces of it.
|
|
4538
|
+
copy(key, value, color, left, right) {
|
|
4539
|
+
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);
|
|
4540
|
+
}
|
|
4541
|
+
isEmpty() {
|
|
4542
|
+
return false;
|
|
4543
|
+
}
|
|
4544
|
+
// Traverses the tree in key order and calls the specified action function
|
|
4545
|
+
// for each node. If action returns true, traversal is aborted.
|
|
4546
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4547
|
+
// value returned by action.
|
|
4548
|
+
inorderTraversal(action) {
|
|
4549
|
+
return (this.left.inorderTraversal(action) ||
|
|
4550
|
+
action(this.key, this.value) ||
|
|
4551
|
+
this.right.inorderTraversal(action));
|
|
4552
|
+
}
|
|
4553
|
+
// Traverses the tree in reverse key order and calls the specified action
|
|
4554
|
+
// function for each node. If action returns true, traversal is aborted.
|
|
4555
|
+
// Returns the first truthy value returned by action, or the last falsey
|
|
4556
|
+
// value returned by action.
|
|
4557
|
+
reverseTraversal(action) {
|
|
4558
|
+
return (this.right.reverseTraversal(action) ||
|
|
4559
|
+
action(this.key, this.value) ||
|
|
4560
|
+
this.left.reverseTraversal(action));
|
|
4561
|
+
}
|
|
4562
|
+
// Returns the minimum node in the tree.
|
|
4563
|
+
min() {
|
|
4564
|
+
if (this.left.isEmpty()) {
|
|
4565
|
+
return this;
|
|
4574
4566
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
inclusive = filterInclusive;
|
|
4567
|
+
else {
|
|
4568
|
+
return this.left.min();
|
|
4578
4569
|
}
|
|
4579
4570
|
}
|
|
4580
|
-
//
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4584
|
-
const orderBy = target.orderBy[i];
|
|
4585
|
-
if (orderBy.field.isEqual(fieldPath)) {
|
|
4586
|
-
const cursorValue = bound.position[i];
|
|
4587
|
-
if (valuesMax(value, cursorValue) === cursorValue) {
|
|
4588
|
-
value = cursorValue;
|
|
4589
|
-
inclusive = bound.inclusive;
|
|
4590
|
-
}
|
|
4591
|
-
break;
|
|
4592
|
-
}
|
|
4593
|
-
}
|
|
4571
|
+
// Returns the maximum key in the tree.
|
|
4572
|
+
minKey() {
|
|
4573
|
+
return this.min().key;
|
|
4594
4574
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
* the provided `fieldPath` (or the lower bound for an descending segment).
|
|
4600
|
-
*/
|
|
4601
|
-
function targetGetUpperBoundForField(target, fieldPath, bound) {
|
|
4602
|
-
let value = undefined;
|
|
4603
|
-
let inclusive = true;
|
|
4604
|
-
// Process all filters to find a value for the current field segment
|
|
4605
|
-
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
4606
|
-
let filterValue = undefined;
|
|
4607
|
-
let filterInclusive = true;
|
|
4608
|
-
switch (fieldFilter.op) {
|
|
4609
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4610
|
-
case ">" /* GREATER_THAN */:
|
|
4611
|
-
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4612
|
-
filterInclusive = false;
|
|
4613
|
-
break;
|
|
4614
|
-
case "==" /* EQUAL */:
|
|
4615
|
-
case "in" /* IN */:
|
|
4616
|
-
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4617
|
-
filterValue = fieldFilter.value;
|
|
4618
|
-
break;
|
|
4619
|
-
case "<" /* LESS_THAN */:
|
|
4620
|
-
filterValue = fieldFilter.value;
|
|
4621
|
-
filterInclusive = false;
|
|
4622
|
-
break;
|
|
4623
|
-
case "!=" /* NOT_EQUAL */:
|
|
4624
|
-
case "not-in" /* NOT_IN */:
|
|
4625
|
-
filterValue = MAX_VALUE;
|
|
4626
|
-
break;
|
|
4627
|
-
// Remaining filters cannot be used as upper bounds.
|
|
4575
|
+
// Returns the maximum key in the tree.
|
|
4576
|
+
maxKey() {
|
|
4577
|
+
if (this.right.isEmpty()) {
|
|
4578
|
+
return this.key;
|
|
4628
4579
|
}
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
inclusive = filterInclusive;
|
|
4580
|
+
else {
|
|
4581
|
+
return this.right.maxKey();
|
|
4632
4582
|
}
|
|
4633
4583
|
}
|
|
4634
|
-
//
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
inclusive = bound.inclusive;
|
|
4644
|
-
}
|
|
4645
|
-
break;
|
|
4646
|
-
}
|
|
4584
|
+
// Returns new tree, with the key/value added.
|
|
4585
|
+
insert(key, value, comparator) {
|
|
4586
|
+
let n = this;
|
|
4587
|
+
const cmp = comparator(key, n.key);
|
|
4588
|
+
if (cmp < 0) {
|
|
4589
|
+
n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
|
|
4590
|
+
}
|
|
4591
|
+
else if (cmp === 0) {
|
|
4592
|
+
n = n.copy(null, value, null, null, null);
|
|
4647
4593
|
}
|
|
4594
|
+
else {
|
|
4595
|
+
n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
|
|
4596
|
+
}
|
|
4597
|
+
return n.fixUp();
|
|
4648
4598
|
}
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
}
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4599
|
+
removeMin() {
|
|
4600
|
+
if (this.left.isEmpty()) {
|
|
4601
|
+
return LLRBNode.EMPTY;
|
|
4602
|
+
}
|
|
4603
|
+
let n = this;
|
|
4604
|
+
if (!n.left.isRed() && !n.left.left.isRed()) {
|
|
4605
|
+
n = n.moveRedLeft();
|
|
4606
|
+
}
|
|
4607
|
+
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
4608
|
+
return n.fixUp();
|
|
4659
4609
|
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
if (
|
|
4665
|
-
if (
|
|
4666
|
-
|
|
4610
|
+
// Returns new tree, with the specified item removed.
|
|
4611
|
+
remove(key, comparator) {
|
|
4612
|
+
let smallest;
|
|
4613
|
+
let n = this;
|
|
4614
|
+
if (comparator(key, n.key) < 0) {
|
|
4615
|
+
if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {
|
|
4616
|
+
n = n.moveRedLeft();
|
|
4667
4617
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4618
|
+
n = n.copy(null, null, null, n.left.remove(key, comparator), null);
|
|
4619
|
+
}
|
|
4620
|
+
else {
|
|
4621
|
+
if (n.left.isRed()) {
|
|
4622
|
+
n = n.rotateRight();
|
|
4623
|
+
}
|
|
4624
|
+
if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {
|
|
4625
|
+
n = n.moveRedRight();
|
|
4626
|
+
}
|
|
4627
|
+
if (comparator(key, n.key) === 0) {
|
|
4628
|
+
if (n.right.isEmpty()) {
|
|
4629
|
+
return LLRBNode.EMPTY;
|
|
4630
|
+
}
|
|
4631
|
+
else {
|
|
4632
|
+
smallest = n.right.min();
|
|
4633
|
+
n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
|
|
4634
|
+
}
|
|
4670
4635
|
}
|
|
4636
|
+
n = n.copy(null, null, null, null, n.right.remove(key, comparator));
|
|
4671
4637
|
}
|
|
4672
|
-
|
|
4673
|
-
|
|
4638
|
+
return n.fixUp();
|
|
4639
|
+
}
|
|
4640
|
+
isRed() {
|
|
4641
|
+
return this.color;
|
|
4642
|
+
}
|
|
4643
|
+
// Returns new tree after performing any needed rotations.
|
|
4644
|
+
fixUp() {
|
|
4645
|
+
let n = this;
|
|
4646
|
+
if (n.right.isRed() && !n.left.isRed()) {
|
|
4647
|
+
n = n.rotateLeft();
|
|
4674
4648
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4649
|
+
if (n.left.isRed() && n.left.left.isRed()) {
|
|
4650
|
+
n = n.rotateRight();
|
|
4677
4651
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4652
|
+
if (n.left.isRed() && n.right.isRed()) {
|
|
4653
|
+
n = n.colorFlip();
|
|
4680
4654
|
}
|
|
4681
|
-
|
|
4682
|
-
|
|
4655
|
+
return n;
|
|
4656
|
+
}
|
|
4657
|
+
moveRedLeft() {
|
|
4658
|
+
let n = this.colorFlip();
|
|
4659
|
+
if (n.right.left.isRed()) {
|
|
4660
|
+
n = n.copy(null, null, null, null, n.right.rotateRight());
|
|
4661
|
+
n = n.rotateLeft();
|
|
4662
|
+
n = n.colorFlip();
|
|
4683
4663
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4664
|
+
return n;
|
|
4665
|
+
}
|
|
4666
|
+
moveRedRight() {
|
|
4667
|
+
let n = this.colorFlip();
|
|
4668
|
+
if (n.left.left.isRed()) {
|
|
4669
|
+
n = n.rotateRight();
|
|
4670
|
+
n = n.colorFlip();
|
|
4686
4671
|
}
|
|
4672
|
+
return n;
|
|
4687
4673
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
: new KeyFieldNotInFilter(field, value);
|
|
4674
|
+
rotateLeft() {
|
|
4675
|
+
const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);
|
|
4676
|
+
return this.right.copy(null, null, this.color, nl, null);
|
|
4692
4677
|
}
|
|
4693
|
-
|
|
4694
|
-
const
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4678
|
+
rotateRight() {
|
|
4679
|
+
const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);
|
|
4680
|
+
return this.left.copy(null, null, this.color, null, nr);
|
|
4681
|
+
}
|
|
4682
|
+
colorFlip() {
|
|
4683
|
+
const left = this.left.copy(null, null, !this.left.color, null, null);
|
|
4684
|
+
const right = this.right.copy(null, null, !this.right.color, null, null);
|
|
4685
|
+
return this.copy(null, null, !this.color, left, right);
|
|
4686
|
+
}
|
|
4687
|
+
// For testing.
|
|
4688
|
+
checkMaxDepth() {
|
|
4689
|
+
const blackDepth = this.check();
|
|
4690
|
+
if (Math.pow(2.0, blackDepth) <= this.size + 1) {
|
|
4691
|
+
return true;
|
|
4692
|
+
}
|
|
4693
|
+
else {
|
|
4694
|
+
return false;
|
|
4699
4695
|
}
|
|
4700
|
-
// Only compare types with matching backend order (such as double and int).
|
|
4701
|
-
return (other !== null &&
|
|
4702
|
-
typeOrder(this.value) === typeOrder(other) &&
|
|
4703
|
-
this.matchesComparison(valueCompare(other, this.value)));
|
|
4704
4696
|
}
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
return fail();
|
|
4697
|
+
// In a balanced RB tree, the black-depth (number of black nodes) from root to
|
|
4698
|
+
// leaves is equal on both sides. This function verifies that or asserts.
|
|
4699
|
+
check() {
|
|
4700
|
+
if (this.isRed() && this.left.isRed()) {
|
|
4701
|
+
throw fail();
|
|
4702
|
+
}
|
|
4703
|
+
if (this.right.isRed()) {
|
|
4704
|
+
throw fail();
|
|
4705
|
+
}
|
|
4706
|
+
const blackDepth = this.left.check();
|
|
4707
|
+
if (blackDepth !== this.right.check()) {
|
|
4708
|
+
throw fail();
|
|
4709
|
+
}
|
|
4710
|
+
else {
|
|
4711
|
+
return blackDepth + (this.isRed() ? 0 : 1);
|
|
4721
4712
|
}
|
|
4722
4713
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4714
|
+
} // end LLRBNode
|
|
4715
|
+
// Empty node is shared between all LLRB trees.
|
|
4716
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4717
|
+
LLRBNode.EMPTY = null;
|
|
4718
|
+
LLRBNode.RED = true;
|
|
4719
|
+
LLRBNode.BLACK = false;
|
|
4720
|
+
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
4721
|
+
class LLRBEmptyNode {
|
|
4722
|
+
constructor() {
|
|
4723
|
+
this.size = 0;
|
|
4732
4724
|
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
// TODO(b/29183165): Technically, this won't be unique if two values have
|
|
4736
|
-
// the same description, such as the int 3 and the string "3". So we should
|
|
4737
|
-
// add the types in here somehow, too.
|
|
4738
|
-
return (filter.field.canonicalString() +
|
|
4739
|
-
filter.op.toString() +
|
|
4740
|
-
canonicalId(filter.value));
|
|
4741
|
-
}
|
|
4742
|
-
function filterEquals(f1, f2) {
|
|
4743
|
-
return (f1.op === f2.op &&
|
|
4744
|
-
f1.field.isEqual(f2.field) &&
|
|
4745
|
-
valueEquals(f1.value, f2.value));
|
|
4746
|
-
}
|
|
4747
|
-
/** Returns a debug description for `filter`. */
|
|
4748
|
-
function stringifyFilter(filter) {
|
|
4749
|
-
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
4750
|
-
}
|
|
4751
|
-
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
4752
|
-
class KeyFieldFilter extends FieldFilter {
|
|
4753
|
-
constructor(field, op, value) {
|
|
4754
|
-
super(field, op, value);
|
|
4755
|
-
this.key = DocumentKey.fromName(value.referenceValue);
|
|
4725
|
+
get key() {
|
|
4726
|
+
throw fail();
|
|
4756
4727
|
}
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
return this.matchesComparison(comparison);
|
|
4728
|
+
get value() {
|
|
4729
|
+
throw fail();
|
|
4760
4730
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
class KeyFieldInFilter extends FieldFilter {
|
|
4764
|
-
constructor(field, value) {
|
|
4765
|
-
super(field, "in" /* IN */, value);
|
|
4766
|
-
this.keys = extractDocumentKeysFromArrayValue("in" /* IN */, value);
|
|
4731
|
+
get color() {
|
|
4732
|
+
throw fail();
|
|
4767
4733
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4734
|
+
get left() {
|
|
4735
|
+
throw fail();
|
|
4770
4736
|
}
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
class KeyFieldNotInFilter extends FieldFilter {
|
|
4774
|
-
constructor(field, value) {
|
|
4775
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4776
|
-
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
4737
|
+
get right() {
|
|
4738
|
+
throw fail();
|
|
4777
4739
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4740
|
+
// Returns a copy of the current node.
|
|
4741
|
+
copy(key, value, color, left, right) {
|
|
4742
|
+
return this;
|
|
4780
4743
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
|
|
4785
|
-
return DocumentKey.fromName(v.referenceValue);
|
|
4786
|
-
});
|
|
4787
|
-
}
|
|
4788
|
-
/** A Filter that implements the array-contains operator. */
|
|
4789
|
-
class ArrayContainsFilter extends FieldFilter {
|
|
4790
|
-
constructor(field, value) {
|
|
4791
|
-
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
4744
|
+
// Returns a copy of the tree, with the specified key/value added.
|
|
4745
|
+
insert(key, value, comparator) {
|
|
4746
|
+
return new LLRBNode(key, value);
|
|
4792
4747
|
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
return
|
|
4748
|
+
// Returns a copy of the tree, with the specified key removed.
|
|
4749
|
+
remove(key, comparator) {
|
|
4750
|
+
return this;
|
|
4796
4751
|
}
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
class InFilter extends FieldFilter {
|
|
4800
|
-
constructor(field, value) {
|
|
4801
|
-
super(field, "in" /* IN */, value);
|
|
4752
|
+
isEmpty() {
|
|
4753
|
+
return true;
|
|
4802
4754
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
4806
|
-
}
|
|
4807
|
-
}
|
|
4808
|
-
/** A Filter that implements the not-in operator. */
|
|
4809
|
-
class NotInFilter extends FieldFilter {
|
|
4810
|
-
constructor(field, value) {
|
|
4811
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4812
|
-
}
|
|
4813
|
-
matches(doc) {
|
|
4814
|
-
if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
|
|
4815
|
-
return false;
|
|
4816
|
-
}
|
|
4817
|
-
const other = doc.data.field(this.field);
|
|
4818
|
-
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
4819
|
-
}
|
|
4820
|
-
}
|
|
4821
|
-
/** A Filter that implements the array-contains-any operator. */
|
|
4822
|
-
class ArrayContainsAnyFilter extends FieldFilter {
|
|
4823
|
-
constructor(field, value) {
|
|
4824
|
-
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
4825
|
-
}
|
|
4826
|
-
matches(doc) {
|
|
4827
|
-
const other = doc.data.field(this.field);
|
|
4828
|
-
if (!isArray(other) || !other.arrayValue.values) {
|
|
4829
|
-
return false;
|
|
4830
|
-
}
|
|
4831
|
-
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
/**
|
|
4835
|
-
* Represents a bound of a query.
|
|
4836
|
-
*
|
|
4837
|
-
* The bound is specified with the given components representing a position and
|
|
4838
|
-
* whether it's just before or just after the position (relative to whatever the
|
|
4839
|
-
* query order is).
|
|
4840
|
-
*
|
|
4841
|
-
* The position represents a logical index position for a query. It's a prefix
|
|
4842
|
-
* of values for the (potentially implicit) order by clauses of a query.
|
|
4843
|
-
*
|
|
4844
|
-
* Bound provides a function to determine whether a document comes before or
|
|
4845
|
-
* after a bound. This is influenced by whether the position is just before or
|
|
4846
|
-
* just after the provided values.
|
|
4847
|
-
*/
|
|
4848
|
-
class Bound {
|
|
4849
|
-
constructor(position, inclusive) {
|
|
4850
|
-
this.position = position;
|
|
4851
|
-
this.inclusive = inclusive;
|
|
4755
|
+
inorderTraversal(action) {
|
|
4756
|
+
return false;
|
|
4852
4757
|
}
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
4856
|
-
*/
|
|
4857
|
-
class OrderBy {
|
|
4858
|
-
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
4859
|
-
this.field = field;
|
|
4860
|
-
this.dir = dir;
|
|
4758
|
+
reverseTraversal(action) {
|
|
4759
|
+
return false;
|
|
4861
4760
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
// TODO(b/29183165): Make this collision robust.
|
|
4865
|
-
return orderBy.field.canonicalString() + orderBy.dir;
|
|
4866
|
-
}
|
|
4867
|
-
function stringifyOrderBy(orderBy) {
|
|
4868
|
-
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
4869
|
-
}
|
|
4870
|
-
function orderByEquals(left, right) {
|
|
4871
|
-
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4872
|
-
}
|
|
4873
|
-
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4874
|
-
let comparison = 0;
|
|
4875
|
-
for (let i = 0; i < bound.position.length; i++) {
|
|
4876
|
-
const orderByComponent = orderBy[i];
|
|
4877
|
-
const component = bound.position[i];
|
|
4878
|
-
if (orderByComponent.field.isKeyField()) {
|
|
4879
|
-
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
4880
|
-
}
|
|
4881
|
-
else {
|
|
4882
|
-
const docValue = doc.data.field(orderByComponent.field);
|
|
4883
|
-
comparison = valueCompare(component, docValue);
|
|
4884
|
-
}
|
|
4885
|
-
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
4886
|
-
comparison = comparison * -1;
|
|
4887
|
-
}
|
|
4888
|
-
if (comparison !== 0) {
|
|
4889
|
-
break;
|
|
4890
|
-
}
|
|
4761
|
+
minKey() {
|
|
4762
|
+
return null;
|
|
4891
4763
|
}
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
/**
|
|
4895
|
-
* Returns true if a document sorts after a bound using the provided sort
|
|
4896
|
-
* order.
|
|
4897
|
-
*/
|
|
4898
|
-
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
4899
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4900
|
-
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
4901
|
-
}
|
|
4902
|
-
/**
|
|
4903
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4904
|
-
* order.
|
|
4905
|
-
*/
|
|
4906
|
-
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
4907
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4908
|
-
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4909
|
-
}
|
|
4910
|
-
function boundEquals(left, right) {
|
|
4911
|
-
if (left === null) {
|
|
4912
|
-
return right === null;
|
|
4764
|
+
maxKey() {
|
|
4765
|
+
return null;
|
|
4913
4766
|
}
|
|
4914
|
-
|
|
4767
|
+
isRed() {
|
|
4915
4768
|
return false;
|
|
4916
4769
|
}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
return
|
|
4770
|
+
// For testing.
|
|
4771
|
+
checkMaxDepth() {
|
|
4772
|
+
return true;
|
|
4920
4773
|
}
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
const rightPosition = right.position[i];
|
|
4924
|
-
if (!valueEquals(leftPosition, rightPosition)) {
|
|
4925
|
-
return false;
|
|
4926
|
-
}
|
|
4774
|
+
check() {
|
|
4775
|
+
return 0;
|
|
4927
4776
|
}
|
|
4928
|
-
|
|
4929
|
-
|
|
4777
|
+
} // end LLRBEmptyNode
|
|
4778
|
+
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
4930
4779
|
|
|
4931
4780
|
/**
|
|
4932
4781
|
* @license
|
|
@@ -4945,325 +4794,223 @@ function boundEquals(left, right) {
|
|
|
4945
4794
|
* limitations under the License.
|
|
4946
4795
|
*/
|
|
4947
4796
|
/**
|
|
4948
|
-
*
|
|
4949
|
-
*
|
|
4950
|
-
* query the RemoteStore results.
|
|
4797
|
+
* SortedSet is an immutable (copy-on-write) collection that holds elements
|
|
4798
|
+
* in order specified by the provided comparator.
|
|
4951
4799
|
*
|
|
4952
|
-
*
|
|
4800
|
+
* NOTE: if provided comparator returns 0 for two elements, we consider them to
|
|
4801
|
+
* be equal!
|
|
4953
4802
|
*/
|
|
4954
|
-
class
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
*/
|
|
4959
|
-
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
4960
|
-
this.path = path;
|
|
4961
|
-
this.collectionGroup = collectionGroup;
|
|
4962
|
-
this.explicitOrderBy = explicitOrderBy;
|
|
4963
|
-
this.filters = filters;
|
|
4964
|
-
this.limit = limit;
|
|
4965
|
-
this.limitType = limitType;
|
|
4966
|
-
this.startAt = startAt;
|
|
4967
|
-
this.endAt = endAt;
|
|
4968
|
-
this.memoizedOrderBy = null;
|
|
4969
|
-
// The corresponding `Target` of this `Query` instance.
|
|
4970
|
-
this.memoizedTarget = null;
|
|
4971
|
-
if (this.startAt) ;
|
|
4972
|
-
if (this.endAt) ;
|
|
4803
|
+
class SortedSet {
|
|
4804
|
+
constructor(comparator) {
|
|
4805
|
+
this.comparator = comparator;
|
|
4806
|
+
this.data = new SortedMap(this.comparator);
|
|
4973
4807
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
4977
|
-
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
4978
|
-
}
|
|
4979
|
-
/** Creates a new Query for a query that matches all documents at `path` */
|
|
4980
|
-
function newQueryForPath(path) {
|
|
4981
|
-
return new QueryImpl(path);
|
|
4982
|
-
}
|
|
4983
|
-
/**
|
|
4984
|
-
* Helper to convert a collection group query into a collection query at a
|
|
4985
|
-
* specific path. This is used when executing collection group queries, since
|
|
4986
|
-
* we have to split the query into a set of collection queries at multiple
|
|
4987
|
-
* paths.
|
|
4988
|
-
*/
|
|
4989
|
-
function asCollectionQueryAtPath(query, path) {
|
|
4990
|
-
return new QueryImpl(path,
|
|
4991
|
-
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
4992
|
-
}
|
|
4993
|
-
/**
|
|
4994
|
-
* Returns true if this query does not specify any query constraints that
|
|
4995
|
-
* could remove results.
|
|
4996
|
-
*/
|
|
4997
|
-
function matchesAllDocuments(query) {
|
|
4998
|
-
return (query.filters.length === 0 &&
|
|
4999
|
-
query.limit === null &&
|
|
5000
|
-
query.startAt == null &&
|
|
5001
|
-
query.endAt == null &&
|
|
5002
|
-
(query.explicitOrderBy.length === 0 ||
|
|
5003
|
-
(query.explicitOrderBy.length === 1 &&
|
|
5004
|
-
query.explicitOrderBy[0].field.isKeyField())));
|
|
5005
|
-
}
|
|
5006
|
-
function hasLimitToFirst(query) {
|
|
5007
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "F" /* First */;
|
|
5008
|
-
}
|
|
5009
|
-
function hasLimitToLast(query) {
|
|
5010
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "L" /* Last */;
|
|
5011
|
-
}
|
|
5012
|
-
function getFirstOrderByField(query) {
|
|
5013
|
-
return query.explicitOrderBy.length > 0
|
|
5014
|
-
? query.explicitOrderBy[0].field
|
|
5015
|
-
: null;
|
|
5016
|
-
}
|
|
5017
|
-
function getInequalityFilterField(query) {
|
|
5018
|
-
for (const filter of query.filters) {
|
|
5019
|
-
if (filter.isInequality()) {
|
|
5020
|
-
return filter.field;
|
|
5021
|
-
}
|
|
4808
|
+
has(elem) {
|
|
4809
|
+
return this.data.get(elem) !== null;
|
|
5022
4810
|
}
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
4811
|
+
first() {
|
|
4812
|
+
return this.data.minKey();
|
|
4813
|
+
}
|
|
4814
|
+
last() {
|
|
4815
|
+
return this.data.maxKey();
|
|
4816
|
+
}
|
|
4817
|
+
get size() {
|
|
4818
|
+
return this.data.size;
|
|
4819
|
+
}
|
|
4820
|
+
indexOf(elem) {
|
|
4821
|
+
return this.data.indexOf(elem);
|
|
4822
|
+
}
|
|
4823
|
+
/** Iterates elements in order defined by "comparator" */
|
|
4824
|
+
forEach(cb) {
|
|
4825
|
+
this.data.inorderTraversal((k, v) => {
|
|
4826
|
+
cb(k);
|
|
4827
|
+
return false;
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
4831
|
+
forEachInRange(range, cb) {
|
|
4832
|
+
const iter = this.data.getIteratorFrom(range[0]);
|
|
4833
|
+
while (iter.hasNext()) {
|
|
4834
|
+
const elem = iter.getNext();
|
|
4835
|
+
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
cb(elem.key);
|
|
5033
4839
|
}
|
|
5034
4840
|
}
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
return new QueryImpl(ResourcePath.emptyPath(), collectionId);
|
|
5043
|
-
}
|
|
5044
|
-
/**
|
|
5045
|
-
* Returns whether the query matches a single document by path (rather than a
|
|
5046
|
-
* collection).
|
|
5047
|
-
*/
|
|
5048
|
-
function isDocumentQuery$1(query) {
|
|
5049
|
-
return (DocumentKey.isDocumentKey(query.path) &&
|
|
5050
|
-
query.collectionGroup === null &&
|
|
5051
|
-
query.filters.length === 0);
|
|
5052
|
-
}
|
|
5053
|
-
/**
|
|
5054
|
-
* Returns whether the query matches a collection group rather than a specific
|
|
5055
|
-
* collection.
|
|
5056
|
-
*/
|
|
5057
|
-
function isCollectionGroupQuery(query) {
|
|
5058
|
-
return query.collectionGroup !== null;
|
|
5059
|
-
}
|
|
5060
|
-
/**
|
|
5061
|
-
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5062
|
-
* which can be different from the order by constraints the user provided (e.g.
|
|
5063
|
-
* the SDK and backend always orders by `__name__`).
|
|
5064
|
-
*/
|
|
5065
|
-
function queryOrderBy(query) {
|
|
5066
|
-
const queryImpl = debugCast(query);
|
|
5067
|
-
if (queryImpl.memoizedOrderBy === null) {
|
|
5068
|
-
queryImpl.memoizedOrderBy = [];
|
|
5069
|
-
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5070
|
-
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5071
|
-
if (inequalityField !== null && firstOrderByField === null) {
|
|
5072
|
-
// In order to implicitly add key ordering, we must also add the
|
|
5073
|
-
// inequality filter field for it to be a valid query.
|
|
5074
|
-
// Note that the default inequality field and key ordering is ascending.
|
|
5075
|
-
if (!inequalityField.isKeyField()) {
|
|
5076
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
5077
|
-
}
|
|
5078
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
4841
|
+
/**
|
|
4842
|
+
* Iterates over `elem`s such that: start <= elem until false is returned.
|
|
4843
|
+
*/
|
|
4844
|
+
forEachWhile(cb, start) {
|
|
4845
|
+
let iter;
|
|
4846
|
+
if (start !== undefined) {
|
|
4847
|
+
iter = this.data.getIteratorFrom(start);
|
|
5079
4848
|
}
|
|
5080
4849
|
else {
|
|
5081
|
-
|
|
5082
|
-
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5083
|
-
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5084
|
-
if (orderBy.field.isKeyField()) {
|
|
5085
|
-
foundKeyOrdering = true;
|
|
5086
|
-
}
|
|
5087
|
-
}
|
|
5088
|
-
if (!foundKeyOrdering) {
|
|
5089
|
-
// The order of the implicit key ordering always matches the last
|
|
5090
|
-
// explicit order by
|
|
5091
|
-
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5092
|
-
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5093
|
-
.dir
|
|
5094
|
-
: "asc" /* ASCENDING */;
|
|
5095
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5096
|
-
}
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
return queryImpl.memoizedOrderBy;
|
|
5100
|
-
}
|
|
5101
|
-
/**
|
|
5102
|
-
* Converts this `Query` instance to it's corresponding `Target` representation.
|
|
5103
|
-
*/
|
|
5104
|
-
function queryToTarget(query) {
|
|
5105
|
-
const queryImpl = debugCast(query);
|
|
5106
|
-
if (!queryImpl.memoizedTarget) {
|
|
5107
|
-
if (queryImpl.limitType === "F" /* First */) {
|
|
5108
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
4850
|
+
iter = this.data.getIterator();
|
|
5109
4851
|
}
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
const
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
? "asc" /* ASCENDING */
|
|
5116
|
-
: "desc" /* DESCENDING */;
|
|
5117
|
-
orderBys.push(new OrderBy(orderBy.field, dir));
|
|
4852
|
+
while (iter.hasNext()) {
|
|
4853
|
+
const elem = iter.getNext();
|
|
4854
|
+
const result = cb(elem.key);
|
|
4855
|
+
if (!result) {
|
|
4856
|
+
return;
|
|
5118
4857
|
}
|
|
5119
|
-
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5120
|
-
const startAt = queryImpl.endAt
|
|
5121
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
5122
|
-
: null;
|
|
5123
|
-
const endAt = queryImpl.startAt
|
|
5124
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
5125
|
-
: null;
|
|
5126
|
-
// Now return as a LimitType.First query.
|
|
5127
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5128
4858
|
}
|
|
5129
4859
|
}
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5135
|
-
}
|
|
5136
|
-
function queryWithAddedOrderBy(query, orderBy) {
|
|
5137
|
-
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5138
|
-
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5139
|
-
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5140
|
-
}
|
|
5141
|
-
function queryWithLimit(query, limit, limitType) {
|
|
5142
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5143
|
-
}
|
|
5144
|
-
function queryWithStartAt(query, bound) {
|
|
5145
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5146
|
-
}
|
|
5147
|
-
function queryWithEndAt(query, bound) {
|
|
5148
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5149
|
-
}
|
|
5150
|
-
function queryEquals(left, right) {
|
|
5151
|
-
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5152
|
-
left.limitType === right.limitType);
|
|
5153
|
-
}
|
|
5154
|
-
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5155
|
-
// example, use as a dictionary key, but the implementation is subject to
|
|
5156
|
-
// collisions. Make it collision-free.
|
|
5157
|
-
function canonifyQuery(query) {
|
|
5158
|
-
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5159
|
-
}
|
|
5160
|
-
function stringifyQuery(query) {
|
|
5161
|
-
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5162
|
-
}
|
|
5163
|
-
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5164
|
-
function queryMatches(query, doc) {
|
|
5165
|
-
return (doc.isFoundDocument() &&
|
|
5166
|
-
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5167
|
-
queryMatchesOrderBy(query, doc) &&
|
|
5168
|
-
queryMatchesFilters(query, doc) &&
|
|
5169
|
-
queryMatchesBounds(query, doc));
|
|
5170
|
-
}
|
|
5171
|
-
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5172
|
-
const docPath = doc.key.path;
|
|
5173
|
-
if (query.collectionGroup !== null) {
|
|
5174
|
-
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5175
|
-
// Group queries rooted at a document path yet.
|
|
5176
|
-
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5177
|
-
query.path.isPrefixOf(docPath));
|
|
4860
|
+
/** Finds the least element greater than or equal to `elem`. */
|
|
4861
|
+
firstAfterOrEqual(elem) {
|
|
4862
|
+
const iter = this.data.getIteratorFrom(elem);
|
|
4863
|
+
return iter.hasNext() ? iter.getNext().key : null;
|
|
5178
4864
|
}
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
return query.path.isEqual(docPath);
|
|
4865
|
+
getIterator() {
|
|
4866
|
+
return new SortedSetIterator(this.data.getIterator());
|
|
5182
4867
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
return query.path.isImmediateParentOf(docPath);
|
|
4868
|
+
getIteratorFrom(key) {
|
|
4869
|
+
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5186
4870
|
}
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
*/
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5196
|
-
return false;
|
|
4871
|
+
/** Inserts or updates an element */
|
|
4872
|
+
add(elem) {
|
|
4873
|
+
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
4874
|
+
}
|
|
4875
|
+
/** Deletes an element */
|
|
4876
|
+
delete(elem) {
|
|
4877
|
+
if (!this.has(elem)) {
|
|
4878
|
+
return this;
|
|
5197
4879
|
}
|
|
4880
|
+
return this.copy(this.data.remove(elem));
|
|
5198
4881
|
}
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
4882
|
+
isEmpty() {
|
|
4883
|
+
return this.data.isEmpty();
|
|
4884
|
+
}
|
|
4885
|
+
unionWith(other) {
|
|
4886
|
+
let result = this;
|
|
4887
|
+
// Make sure `result` always refers to the larger one of the two sets.
|
|
4888
|
+
if (result.size < other.size) {
|
|
4889
|
+
result = other;
|
|
4890
|
+
other = this;
|
|
4891
|
+
}
|
|
4892
|
+
other.forEach(elem => {
|
|
4893
|
+
result = result.add(elem);
|
|
4894
|
+
});
|
|
4895
|
+
return result;
|
|
4896
|
+
}
|
|
4897
|
+
isEqual(other) {
|
|
4898
|
+
if (!(other instanceof SortedSet)) {
|
|
5204
4899
|
return false;
|
|
5205
4900
|
}
|
|
4901
|
+
if (this.size !== other.size) {
|
|
4902
|
+
return false;
|
|
4903
|
+
}
|
|
4904
|
+
const thisIt = this.data.getIterator();
|
|
4905
|
+
const otherIt = other.data.getIterator();
|
|
4906
|
+
while (thisIt.hasNext()) {
|
|
4907
|
+
const thisElem = thisIt.getNext().key;
|
|
4908
|
+
const otherElem = otherIt.getNext().key;
|
|
4909
|
+
if (this.comparator(thisElem, otherElem) !== 0) {
|
|
4910
|
+
return false;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
return true;
|
|
4914
|
+
}
|
|
4915
|
+
toArray() {
|
|
4916
|
+
const res = [];
|
|
4917
|
+
this.forEach(targetId => {
|
|
4918
|
+
res.push(targetId);
|
|
4919
|
+
});
|
|
4920
|
+
return res;
|
|
4921
|
+
}
|
|
4922
|
+
toString() {
|
|
4923
|
+
const result = [];
|
|
4924
|
+
this.forEach(elem => result.push(elem));
|
|
4925
|
+
return 'SortedSet(' + result.toString() + ')';
|
|
4926
|
+
}
|
|
4927
|
+
copy(data) {
|
|
4928
|
+
const result = new SortedSet(this.comparator);
|
|
4929
|
+
result.data = data;
|
|
4930
|
+
return result;
|
|
5206
4931
|
}
|
|
5207
|
-
return true;
|
|
5208
4932
|
}
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5213
|
-
return false;
|
|
4933
|
+
class SortedSetIterator {
|
|
4934
|
+
constructor(iter) {
|
|
4935
|
+
this.iter = iter;
|
|
5214
4936
|
}
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
4937
|
+
getNext() {
|
|
4938
|
+
return this.iter.getNext().key;
|
|
4939
|
+
}
|
|
4940
|
+
hasNext() {
|
|
4941
|
+
return this.iter.hasNext();
|
|
5218
4942
|
}
|
|
5219
|
-
return true;
|
|
5220
4943
|
}
|
|
5221
4944
|
/**
|
|
5222
|
-
*
|
|
4945
|
+
* Compares two sorted sets for equality using their natural ordering. The
|
|
4946
|
+
* method computes the intersection and invokes `onAdd` for every element that
|
|
4947
|
+
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
4948
|
+
* `before` but missing from `after`.
|
|
5223
4949
|
*
|
|
5224
|
-
*
|
|
5225
|
-
*
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
* Returns a new comparator function that can be used to compare two documents
|
|
5235
|
-
* based on the Query's ordering constraint.
|
|
4950
|
+
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
4951
|
+
* n), where n is the size of the two lists.
|
|
4952
|
+
*
|
|
4953
|
+
* @param before - The elements that exist in the original set.
|
|
4954
|
+
* @param after - The elements to diff against the original set.
|
|
4955
|
+
* @param comparator - The comparator for the elements in before and after.
|
|
4956
|
+
* @param onAdd - A function to invoke for every element that is part of `
|
|
4957
|
+
* after` but not `before`.
|
|
4958
|
+
* @param onRemove - A function to invoke for every element that is part of
|
|
4959
|
+
* `before` but not `after`.
|
|
5236
4960
|
*/
|
|
5237
|
-
function
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
4961
|
+
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
4962
|
+
const beforeIt = before.getIterator();
|
|
4963
|
+
const afterIt = after.getIterator();
|
|
4964
|
+
let beforeValue = advanceIterator(beforeIt);
|
|
4965
|
+
let afterValue = advanceIterator(afterIt);
|
|
4966
|
+
// Walk through the two sets at the same time, using the ordering defined by
|
|
4967
|
+
// `comparator`.
|
|
4968
|
+
while (beforeValue || afterValue) {
|
|
4969
|
+
let added = false;
|
|
4970
|
+
let removed = false;
|
|
4971
|
+
if (beforeValue && afterValue) {
|
|
4972
|
+
const cmp = comparator(beforeValue, afterValue);
|
|
4973
|
+
if (cmp < 0) {
|
|
4974
|
+
// The element was removed if the next element in our ordered
|
|
4975
|
+
// walkthrough is only in `before`.
|
|
4976
|
+
removed = true;
|
|
4977
|
+
}
|
|
4978
|
+
else if (cmp > 0) {
|
|
4979
|
+
// The element was added if the next element in our ordered walkthrough
|
|
4980
|
+
// is only in `after`.
|
|
4981
|
+
added = true;
|
|
5244
4982
|
}
|
|
5245
|
-
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5246
4983
|
}
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
4984
|
+
else if (beforeValue != null) {
|
|
4985
|
+
removed = true;
|
|
4986
|
+
}
|
|
4987
|
+
else {
|
|
4988
|
+
added = true;
|
|
4989
|
+
}
|
|
4990
|
+
if (added) {
|
|
4991
|
+
onAdd(afterValue);
|
|
4992
|
+
afterValue = advanceIterator(afterIt);
|
|
4993
|
+
}
|
|
4994
|
+
else if (removed) {
|
|
4995
|
+
onRemove(beforeValue);
|
|
4996
|
+
beforeValue = advanceIterator(beforeIt);
|
|
4997
|
+
}
|
|
4998
|
+
else {
|
|
4999
|
+
beforeValue = advanceIterator(beforeIt);
|
|
5000
|
+
afterValue = advanceIterator(afterIt);
|
|
5001
|
+
}
|
|
5261
5002
|
}
|
|
5003
|
+
}
|
|
5004
|
+
/**
|
|
5005
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
5006
|
+
*/
|
|
5007
|
+
function advanceIterator(it) {
|
|
5008
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5262
5009
|
}
|
|
5263
5010
|
|
|
5264
5011
|
/**
|
|
5265
5012
|
* @license
|
|
5266
|
-
* Copyright
|
|
5013
|
+
* Copyright 2019 Google LLC
|
|
5267
5014
|
*
|
|
5268
5015
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5269
5016
|
* you may not use this file except in compliance with the License.
|
|
@@ -5277,555 +5024,961 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5277
5024
|
* See the License for the specific language governing permissions and
|
|
5278
5025
|
* limitations under the License.
|
|
5279
5026
|
*/
|
|
5027
|
+
// Visible for testing
|
|
5028
|
+
class TargetImpl {
|
|
5029
|
+
constructor(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
5030
|
+
this.path = path;
|
|
5031
|
+
this.collectionGroup = collectionGroup;
|
|
5032
|
+
this.orderBy = orderBy;
|
|
5033
|
+
this.filters = filters;
|
|
5034
|
+
this.limit = limit;
|
|
5035
|
+
this.startAt = startAt;
|
|
5036
|
+
this.endAt = endAt;
|
|
5037
|
+
this.memoizedCanonicalId = null;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5280
5040
|
/**
|
|
5281
|
-
*
|
|
5282
|
-
*
|
|
5283
|
-
*
|
|
5284
|
-
*
|
|
5041
|
+
* Initializes a Target with a path and optional additional query constraints.
|
|
5042
|
+
* Path must currently be empty if this is a collection group query.
|
|
5043
|
+
*
|
|
5044
|
+
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
5045
|
+
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
5046
|
+
* property.
|
|
5285
5047
|
*/
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
*/
|
|
5296
|
-
this.inner = {};
|
|
5297
|
-
/** The number of entries stored in the map */
|
|
5298
|
-
this.innerSize = 0;
|
|
5299
|
-
}
|
|
5300
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
5301
|
-
get(key) {
|
|
5302
|
-
const id = this.mapKeyFn(key);
|
|
5303
|
-
const matches = this.inner[id];
|
|
5304
|
-
if (matches === undefined) {
|
|
5305
|
-
return undefined;
|
|
5048
|
+
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
5049
|
+
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
5050
|
+
}
|
|
5051
|
+
function canonifyTarget(target) {
|
|
5052
|
+
const targetImpl = debugCast(target);
|
|
5053
|
+
if (targetImpl.memoizedCanonicalId === null) {
|
|
5054
|
+
let str = targetImpl.path.canonicalString();
|
|
5055
|
+
if (targetImpl.collectionGroup !== null) {
|
|
5056
|
+
str += '|cg:' + targetImpl.collectionGroup;
|
|
5306
5057
|
}
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5058
|
+
str += '|f:';
|
|
5059
|
+
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
5060
|
+
str += '|ob:';
|
|
5061
|
+
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
5062
|
+
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
5063
|
+
str += '|l:';
|
|
5064
|
+
str += targetImpl.limit;
|
|
5311
5065
|
}
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
}
|
|
5317
|
-
/** Put this key and value in the map. */
|
|
5318
|
-
set(key, value) {
|
|
5319
|
-
const id = this.mapKeyFn(key);
|
|
5320
|
-
const matches = this.inner[id];
|
|
5321
|
-
if (matches === undefined) {
|
|
5322
|
-
this.inner[id] = [[key, value]];
|
|
5323
|
-
this.innerSize++;
|
|
5324
|
-
return;
|
|
5066
|
+
if (targetImpl.startAt) {
|
|
5067
|
+
str += '|lb:';
|
|
5068
|
+
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
5069
|
+
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
5325
5070
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
return;
|
|
5331
|
-
}
|
|
5071
|
+
if (targetImpl.endAt) {
|
|
5072
|
+
str += '|ub:';
|
|
5073
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
5074
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5332
5075
|
}
|
|
5333
|
-
|
|
5334
|
-
this.innerSize++;
|
|
5076
|
+
targetImpl.memoizedCanonicalId = str;
|
|
5335
5077
|
}
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5078
|
+
return targetImpl.memoizedCanonicalId;
|
|
5079
|
+
}
|
|
5080
|
+
function stringifyTarget(target) {
|
|
5081
|
+
let str = target.path.canonicalString();
|
|
5082
|
+
if (target.collectionGroup !== null) {
|
|
5083
|
+
str += ' collectionGroup=' + target.collectionGroup;
|
|
5084
|
+
}
|
|
5085
|
+
if (target.filters.length > 0) {
|
|
5086
|
+
str += `, filters: [${target.filters
|
|
5087
|
+
.map(f => stringifyFilter(f))
|
|
5088
|
+
.join(', ')}]`;
|
|
5089
|
+
}
|
|
5090
|
+
if (!isNullOrUndefined(target.limit)) {
|
|
5091
|
+
str += ', limit: ' + target.limit;
|
|
5092
|
+
}
|
|
5093
|
+
if (target.orderBy.length > 0) {
|
|
5094
|
+
str += `, orderBy: [${target.orderBy
|
|
5095
|
+
.map(o => stringifyOrderBy(o))
|
|
5096
|
+
.join(', ')}]`;
|
|
5097
|
+
}
|
|
5098
|
+
if (target.startAt) {
|
|
5099
|
+
str += ', startAt: ';
|
|
5100
|
+
str += target.startAt.inclusive ? 'b:' : 'a:';
|
|
5101
|
+
str += target.startAt.position.map(p => canonicalId(p)).join(',');
|
|
5102
|
+
}
|
|
5103
|
+
if (target.endAt) {
|
|
5104
|
+
str += ', endAt: ';
|
|
5105
|
+
str += target.endAt.inclusive ? 'a:' : 'b:';
|
|
5106
|
+
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5107
|
+
}
|
|
5108
|
+
return `Target(${str})`;
|
|
5109
|
+
}
|
|
5110
|
+
function targetEquals(left, right) {
|
|
5111
|
+
if (left.limit !== right.limit) {
|
|
5112
|
+
return false;
|
|
5113
|
+
}
|
|
5114
|
+
if (left.orderBy.length !== right.orderBy.length) {
|
|
5115
|
+
return false;
|
|
5116
|
+
}
|
|
5117
|
+
for (let i = 0; i < left.orderBy.length; i++) {
|
|
5118
|
+
if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
|
|
5343
5119
|
return false;
|
|
5344
5120
|
}
|
|
5345
|
-
for (let i = 0; i < matches.length; i++) {
|
|
5346
|
-
if (this.equalsFn(matches[i][0], key)) {
|
|
5347
|
-
if (matches.length === 1) {
|
|
5348
|
-
delete this.inner[id];
|
|
5349
|
-
}
|
|
5350
|
-
else {
|
|
5351
|
-
matches.splice(i, 1);
|
|
5352
|
-
}
|
|
5353
|
-
this.innerSize--;
|
|
5354
|
-
return true;
|
|
5355
|
-
}
|
|
5356
|
-
}
|
|
5357
|
-
return false;
|
|
5358
5121
|
}
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
for (const [k, v] of entries) {
|
|
5362
|
-
fn(k, v);
|
|
5363
|
-
}
|
|
5364
|
-
});
|
|
5122
|
+
if (left.filters.length !== right.filters.length) {
|
|
5123
|
+
return false;
|
|
5365
5124
|
}
|
|
5366
|
-
|
|
5367
|
-
|
|
5125
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
5126
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
5127
|
+
return false;
|
|
5128
|
+
}
|
|
5368
5129
|
}
|
|
5369
|
-
|
|
5370
|
-
return
|
|
5130
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
5131
|
+
return false;
|
|
5371
5132
|
}
|
|
5372
|
-
|
|
5373
|
-
|
|
5133
|
+
if (!left.path.isEqual(right.path)) {
|
|
5134
|
+
return false;
|
|
5135
|
+
}
|
|
5136
|
+
if (!boundEquals(left.startAt, right.startAt)) {
|
|
5137
|
+
return false;
|
|
5138
|
+
}
|
|
5139
|
+
return boundEquals(left.endAt, right.endAt);
|
|
5140
|
+
}
|
|
5141
|
+
function targetIsDocumentTarget(target) {
|
|
5142
|
+
return (DocumentKey.isDocumentKey(target.path) &&
|
|
5143
|
+
target.collectionGroup === null &&
|
|
5144
|
+
target.filters.length === 0);
|
|
5145
|
+
}
|
|
5146
|
+
/** Returns the field filters that target the given field path. */
|
|
5147
|
+
function targetGetFieldFiltersForPath(target, path) {
|
|
5148
|
+
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
5149
|
+
}
|
|
5374
5150
|
/**
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
5378
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5379
|
-
* you may not use this file except in compliance with the License.
|
|
5380
|
-
* You may obtain a copy of the License at
|
|
5381
|
-
*
|
|
5382
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5383
|
-
*
|
|
5384
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
5385
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5386
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5387
|
-
* See the License for the specific language governing permissions and
|
|
5388
|
-
* limitations under the License.
|
|
5151
|
+
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
5152
|
+
* filters. Returns `null` if there are no such filters.
|
|
5389
5153
|
*/
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
this.comparator = comparator;
|
|
5395
|
-
this.root = root ? root : LLRBNode.EMPTY;
|
|
5396
|
-
}
|
|
5397
|
-
// Returns a copy of the map, with the specified key/value added or replaced.
|
|
5398
|
-
insert(key, value) {
|
|
5399
|
-
return new SortedMap(this.comparator, this.root
|
|
5400
|
-
.insert(key, value, this.comparator)
|
|
5401
|
-
.copy(null, null, LLRBNode.BLACK, null, null));
|
|
5154
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
5155
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
5156
|
+
if (segment === undefined) {
|
|
5157
|
+
return null;
|
|
5402
5158
|
}
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5159
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5160
|
+
switch (fieldFilter.op) {
|
|
5161
|
+
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
5162
|
+
return fieldFilter.value.arrayValue.values || [];
|
|
5163
|
+
case "array-contains" /* ARRAY_CONTAINS */:
|
|
5164
|
+
return [fieldFilter.value];
|
|
5165
|
+
// Remaining filters are not array filters.
|
|
5166
|
+
}
|
|
5408
5167
|
}
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5168
|
+
return null;
|
|
5169
|
+
}
|
|
5170
|
+
/**
|
|
5171
|
+
* Returns the list of values that are used in != or NOT_IN filters. Returns
|
|
5172
|
+
* `null` if there are no such filters.
|
|
5173
|
+
*/
|
|
5174
|
+
function targetGetNotInValues(target, fieldIndex) {
|
|
5175
|
+
const values = new Map();
|
|
5176
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5177
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
5178
|
+
switch (fieldFilter.op) {
|
|
5179
|
+
case "==" /* EQUAL */:
|
|
5180
|
+
case "in" /* IN */:
|
|
5181
|
+
// Encode equality prefix, which is encoded in the index value before
|
|
5182
|
+
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
5183
|
+
// `value != 'ab'`).
|
|
5184
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
5185
|
+
break;
|
|
5186
|
+
case "not-in" /* NOT_IN */:
|
|
5187
|
+
case "!=" /* NOT_EQUAL */:
|
|
5188
|
+
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
5189
|
+
// segments and hence we can return early here.
|
|
5190
|
+
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
5191
|
+
return Array.from(values.values());
|
|
5192
|
+
// Remaining filters cannot be used as notIn bounds.
|
|
5419
5193
|
}
|
|
5420
|
-
|
|
5421
|
-
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
return null;
|
|
5197
|
+
}
|
|
5198
|
+
/**
|
|
5199
|
+
* Returns a lower bound of field values that can be used as a starting point to
|
|
5200
|
+
* scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
|
|
5201
|
+
* exists.
|
|
5202
|
+
*/
|
|
5203
|
+
function targetGetLowerBound(target, fieldIndex) {
|
|
5204
|
+
const values = [];
|
|
5205
|
+
let inclusive = true;
|
|
5206
|
+
// For each segment, retrieve a lower bound if there is a suitable filter or
|
|
5207
|
+
// startAt.
|
|
5208
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5209
|
+
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
5210
|
+
? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
|
|
5211
|
+
: targetGetDescendingBound(target, segment.fieldPath, target.startAt);
|
|
5212
|
+
values.push(segmentBound.value);
|
|
5213
|
+
inclusive && (inclusive = segmentBound.inclusive);
|
|
5214
|
+
}
|
|
5215
|
+
return new Bound(values, inclusive);
|
|
5216
|
+
}
|
|
5217
|
+
/**
|
|
5218
|
+
* Returns an upper bound of field values that can be used as an ending point
|
|
5219
|
+
* when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
|
|
5220
|
+
* upper bound exists.
|
|
5221
|
+
*/
|
|
5222
|
+
function targetGetUpperBound(target, fieldIndex) {
|
|
5223
|
+
const values = [];
|
|
5224
|
+
let inclusive = true;
|
|
5225
|
+
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
5226
|
+
// endAt.
|
|
5227
|
+
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
5228
|
+
const segmentBound = segment.kind === 0 /* ASCENDING */
|
|
5229
|
+
? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
|
|
5230
|
+
: targetGetAscendingBound(target, segment.fieldPath, target.endAt);
|
|
5231
|
+
values.push(segmentBound.value);
|
|
5232
|
+
inclusive && (inclusive = segmentBound.inclusive);
|
|
5233
|
+
}
|
|
5234
|
+
return new Bound(values, inclusive);
|
|
5235
|
+
}
|
|
5236
|
+
/**
|
|
5237
|
+
* Returns the value to use as the lower bound for ascending index segment at
|
|
5238
|
+
* the provided `fieldPath` (or the upper bound for an descending segment).
|
|
5239
|
+
*/
|
|
5240
|
+
function targetGetAscendingBound(target, fieldPath, bound) {
|
|
5241
|
+
let value = MIN_VALUE;
|
|
5242
|
+
let inclusive = true;
|
|
5243
|
+
// Process all filters to find a value for the current field segment
|
|
5244
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
5245
|
+
let filterValue = MIN_VALUE;
|
|
5246
|
+
let filterInclusive = true;
|
|
5247
|
+
switch (fieldFilter.op) {
|
|
5248
|
+
case "<" /* LESS_THAN */:
|
|
5249
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5250
|
+
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
5251
|
+
break;
|
|
5252
|
+
case "==" /* EQUAL */:
|
|
5253
|
+
case "in" /* IN */:
|
|
5254
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5255
|
+
filterValue = fieldFilter.value;
|
|
5256
|
+
break;
|
|
5257
|
+
case ">" /* GREATER_THAN */:
|
|
5258
|
+
filterValue = fieldFilter.value;
|
|
5259
|
+
filterInclusive = false;
|
|
5260
|
+
break;
|
|
5261
|
+
case "!=" /* NOT_EQUAL */:
|
|
5262
|
+
case "not-in" /* NOT_IN */:
|
|
5263
|
+
filterValue = MIN_VALUE;
|
|
5264
|
+
break;
|
|
5265
|
+
// Remaining filters cannot be used as lower bounds.
|
|
5266
|
+
}
|
|
5267
|
+
if (lowerBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) < 0) {
|
|
5268
|
+
value = filterValue;
|
|
5269
|
+
inclusive = filterInclusive;
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
// If there is an additional bound, compare the values against the existing
|
|
5273
|
+
// range to see if we can narrow the scope.
|
|
5274
|
+
if (bound !== null) {
|
|
5275
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5276
|
+
const orderBy = target.orderBy[i];
|
|
5277
|
+
if (orderBy.field.isEqual(fieldPath)) {
|
|
5278
|
+
const cursorValue = bound.position[i];
|
|
5279
|
+
if (lowerBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) < 0) {
|
|
5280
|
+
value = cursorValue;
|
|
5281
|
+
inclusive = bound.inclusive;
|
|
5282
|
+
}
|
|
5283
|
+
break;
|
|
5422
5284
|
}
|
|
5423
5285
|
}
|
|
5424
|
-
return null;
|
|
5425
5286
|
}
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5287
|
+
return { value, inclusive };
|
|
5288
|
+
}
|
|
5289
|
+
/**
|
|
5290
|
+
* Returns the value to use as the upper bound for ascending index segment at
|
|
5291
|
+
* the provided `fieldPath` (or the lower bound for a descending segment).
|
|
5292
|
+
*/
|
|
5293
|
+
function targetGetDescendingBound(target, fieldPath, bound) {
|
|
5294
|
+
let value = MAX_VALUE;
|
|
5295
|
+
let inclusive = true;
|
|
5296
|
+
// Process all filters to find a value for the current field segment
|
|
5297
|
+
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
|
|
5298
|
+
let filterValue = MAX_VALUE;
|
|
5299
|
+
let filterInclusive = true;
|
|
5300
|
+
switch (fieldFilter.op) {
|
|
5301
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5302
|
+
case ">" /* GREATER_THAN */:
|
|
5303
|
+
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
5304
|
+
filterInclusive = false;
|
|
5305
|
+
break;
|
|
5306
|
+
case "==" /* EQUAL */:
|
|
5307
|
+
case "in" /* IN */:
|
|
5308
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5309
|
+
filterValue = fieldFilter.value;
|
|
5310
|
+
break;
|
|
5311
|
+
case "<" /* LESS_THAN */:
|
|
5312
|
+
filterValue = fieldFilter.value;
|
|
5313
|
+
filterInclusive = false;
|
|
5314
|
+
break;
|
|
5315
|
+
case "!=" /* NOT_EQUAL */:
|
|
5316
|
+
case "not-in" /* NOT_IN */:
|
|
5317
|
+
filterValue = MAX_VALUE;
|
|
5318
|
+
break;
|
|
5319
|
+
// Remaining filters cannot be used as upper bounds.
|
|
5320
|
+
}
|
|
5321
|
+
if (upperBoundCompare({ value, inclusive }, { value: filterValue, inclusive: filterInclusive }) > 0) {
|
|
5322
|
+
value = filterValue;
|
|
5323
|
+
inclusive = filterInclusive;
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
// If there is an additional bound, compare the values against the existing
|
|
5327
|
+
// range to see if we can narrow the scope.
|
|
5328
|
+
if (bound !== null) {
|
|
5329
|
+
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
5330
|
+
const orderBy = target.orderBy[i];
|
|
5331
|
+
if (orderBy.field.isEqual(fieldPath)) {
|
|
5332
|
+
const cursorValue = bound.position[i];
|
|
5333
|
+
if (upperBoundCompare({ value, inclusive }, { value: cursorValue, inclusive: bound.inclusive }) > 0) {
|
|
5334
|
+
value = cursorValue;
|
|
5335
|
+
inclusive = bound.inclusive;
|
|
5336
|
+
}
|
|
5337
|
+
break;
|
|
5436
5338
|
}
|
|
5437
|
-
|
|
5438
|
-
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
return { value, inclusive };
|
|
5342
|
+
}
|
|
5343
|
+
/** Returns the number of segments of a perfect index for this target. */
|
|
5344
|
+
function targetGetSegmentCount(target) {
|
|
5345
|
+
let fields = new SortedSet(FieldPath$1.comparator);
|
|
5346
|
+
let hasArraySegment = false;
|
|
5347
|
+
for (const filter of target.filters) {
|
|
5348
|
+
// TODO(orquery): Use the flattened filters here
|
|
5349
|
+
const fieldFilter = filter;
|
|
5350
|
+
// __name__ is not an explicit segment of any index, so we don't need to
|
|
5351
|
+
// count it.
|
|
5352
|
+
if (fieldFilter.field.isKeyField()) {
|
|
5353
|
+
continue;
|
|
5354
|
+
}
|
|
5355
|
+
// ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.
|
|
5356
|
+
// For instance, it is possible to have an index for "a ARRAY a ASC". Even
|
|
5357
|
+
// though these are on the same field, they should be counted as two
|
|
5358
|
+
// separate segments in an index.
|
|
5359
|
+
if (fieldFilter.op === "array-contains" /* ARRAY_CONTAINS */ ||
|
|
5360
|
+
fieldFilter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5361
|
+
hasArraySegment = true;
|
|
5362
|
+
}
|
|
5363
|
+
else {
|
|
5364
|
+
fields = fields.add(fieldFilter.field);
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
for (const orderBy of target.orderBy) {
|
|
5368
|
+
// __name__ is not an explicit segment of any index, so we don't need to
|
|
5369
|
+
// count it.
|
|
5370
|
+
if (!orderBy.field.isKeyField()) {
|
|
5371
|
+
fields = fields.add(orderBy.field);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
return fields.size + (hasArraySegment ? 1 : 0);
|
|
5375
|
+
}
|
|
5376
|
+
class Filter {
|
|
5377
|
+
}
|
|
5378
|
+
class FieldFilter extends Filter {
|
|
5379
|
+
constructor(field, op, value) {
|
|
5380
|
+
super();
|
|
5381
|
+
this.field = field;
|
|
5382
|
+
this.op = op;
|
|
5383
|
+
this.value = value;
|
|
5384
|
+
}
|
|
5385
|
+
/**
|
|
5386
|
+
* Creates a filter based on the provided arguments.
|
|
5387
|
+
*/
|
|
5388
|
+
static create(field, op, value) {
|
|
5389
|
+
if (field.isKeyField()) {
|
|
5390
|
+
if (op === "in" /* IN */ || op === "not-in" /* NOT_IN */) {
|
|
5391
|
+
return this.createKeyFieldInFilter(field, op, value);
|
|
5439
5392
|
}
|
|
5440
5393
|
else {
|
|
5441
|
-
|
|
5442
|
-
prunedNodes += node.left.size + 1;
|
|
5443
|
-
node = node.right;
|
|
5394
|
+
return new KeyFieldFilter(field, op, value);
|
|
5444
5395
|
}
|
|
5445
5396
|
}
|
|
5446
|
-
|
|
5447
|
-
|
|
5397
|
+
else if (op === "array-contains" /* ARRAY_CONTAINS */) {
|
|
5398
|
+
return new ArrayContainsFilter(field, value);
|
|
5399
|
+
}
|
|
5400
|
+
else if (op === "in" /* IN */) {
|
|
5401
|
+
return new InFilter(field, value);
|
|
5402
|
+
}
|
|
5403
|
+
else if (op === "not-in" /* NOT_IN */) {
|
|
5404
|
+
return new NotInFilter(field, value);
|
|
5405
|
+
}
|
|
5406
|
+
else if (op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5407
|
+
return new ArrayContainsAnyFilter(field, value);
|
|
5408
|
+
}
|
|
5409
|
+
else {
|
|
5410
|
+
return new FieldFilter(field, op, value);
|
|
5411
|
+
}
|
|
5448
5412
|
}
|
|
5449
|
-
|
|
5450
|
-
return
|
|
5413
|
+
static createKeyFieldInFilter(field, op, value) {
|
|
5414
|
+
return op === "in" /* IN */
|
|
5415
|
+
? new KeyFieldInFilter(field, value)
|
|
5416
|
+
: new KeyFieldNotInFilter(field, value);
|
|
5451
5417
|
}
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5418
|
+
matches(doc) {
|
|
5419
|
+
const other = doc.data.field(this.field);
|
|
5420
|
+
// Types do not have to match in NOT_EQUAL filters.
|
|
5421
|
+
if (this.op === "!=" /* NOT_EQUAL */) {
|
|
5422
|
+
return (other !== null &&
|
|
5423
|
+
this.matchesComparison(valueCompare(other, this.value)));
|
|
5424
|
+
}
|
|
5425
|
+
// Only compare types with matching backend order (such as double and int).
|
|
5426
|
+
return (other !== null &&
|
|
5427
|
+
typeOrder(this.value) === typeOrder(other) &&
|
|
5428
|
+
this.matchesComparison(valueCompare(other, this.value)));
|
|
5455
5429
|
}
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5430
|
+
matchesComparison(comparison) {
|
|
5431
|
+
switch (this.op) {
|
|
5432
|
+
case "<" /* LESS_THAN */:
|
|
5433
|
+
return comparison < 0;
|
|
5434
|
+
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
5435
|
+
return comparison <= 0;
|
|
5436
|
+
case "==" /* EQUAL */:
|
|
5437
|
+
return comparison === 0;
|
|
5438
|
+
case "!=" /* NOT_EQUAL */:
|
|
5439
|
+
return comparison !== 0;
|
|
5440
|
+
case ">" /* GREATER_THAN */:
|
|
5441
|
+
return comparison > 0;
|
|
5442
|
+
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
5443
|
+
return comparison >= 0;
|
|
5444
|
+
default:
|
|
5445
|
+
return fail();
|
|
5446
|
+
}
|
|
5459
5447
|
}
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5448
|
+
isInequality() {
|
|
5449
|
+
return ([
|
|
5450
|
+
"<" /* LESS_THAN */,
|
|
5451
|
+
"<=" /* LESS_THAN_OR_EQUAL */,
|
|
5452
|
+
">" /* GREATER_THAN */,
|
|
5453
|
+
">=" /* GREATER_THAN_OR_EQUAL */,
|
|
5454
|
+
"!=" /* NOT_EQUAL */,
|
|
5455
|
+
"not-in" /* NOT_IN */
|
|
5456
|
+
].indexOf(this.op) >= 0);
|
|
5463
5457
|
}
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
//
|
|
5467
|
-
//
|
|
5468
|
-
|
|
5469
|
-
|
|
5458
|
+
}
|
|
5459
|
+
function canonifyFilter(filter) {
|
|
5460
|
+
// TODO(b/29183165): Technically, this won't be unique if two values have
|
|
5461
|
+
// the same description, such as the int 3 and the string "3". So we should
|
|
5462
|
+
// add the types in here somehow, too.
|
|
5463
|
+
return (filter.field.canonicalString() +
|
|
5464
|
+
filter.op.toString() +
|
|
5465
|
+
canonicalId(filter.value));
|
|
5466
|
+
}
|
|
5467
|
+
function filterEquals(f1, f2) {
|
|
5468
|
+
return (f1.op === f2.op &&
|
|
5469
|
+
f1.field.isEqual(f2.field) &&
|
|
5470
|
+
valueEquals(f1.value, f2.value));
|
|
5471
|
+
}
|
|
5472
|
+
/** Returns a debug description for `filter`. */
|
|
5473
|
+
function stringifyFilter(filter) {
|
|
5474
|
+
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
5475
|
+
}
|
|
5476
|
+
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
5477
|
+
class KeyFieldFilter extends FieldFilter {
|
|
5478
|
+
constructor(field, op, value) {
|
|
5479
|
+
super(field, op, value);
|
|
5480
|
+
this.key = DocumentKey.fromName(value.referenceValue);
|
|
5470
5481
|
}
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
return false;
|
|
5475
|
-
});
|
|
5482
|
+
matches(doc) {
|
|
5483
|
+
const comparison = DocumentKey.comparator(doc.key, this.key);
|
|
5484
|
+
return this.matchesComparison(comparison);
|
|
5476
5485
|
}
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
return `{${descriptions.join(', ')}}`;
|
|
5486
|
+
}
|
|
5487
|
+
/** Filter that matches on key fields within an array. */
|
|
5488
|
+
class KeyFieldInFilter extends FieldFilter {
|
|
5489
|
+
constructor(field, value) {
|
|
5490
|
+
super(field, "in" /* IN */, value);
|
|
5491
|
+
this.keys = extractDocumentKeysFromArrayValue("in" /* IN */, value);
|
|
5484
5492
|
}
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5493
|
+
matches(doc) {
|
|
5494
|
+
return this.keys.some(key => key.isEqual(doc.key));
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
/** Filter that matches on key fields not present within an array. */
|
|
5498
|
+
class KeyFieldNotInFilter extends FieldFilter {
|
|
5499
|
+
constructor(field, value) {
|
|
5500
|
+
super(field, "not-in" /* NOT_IN */, value);
|
|
5501
|
+
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
5502
|
+
}
|
|
5503
|
+
matches(doc) {
|
|
5504
|
+
return !this.keys.some(key => key.isEqual(doc.key));
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
function extractDocumentKeysFromArrayValue(op, value) {
|
|
5508
|
+
var _a;
|
|
5509
|
+
return (((_a = value.arrayValue) === null || _a === void 0 ? void 0 : _a.values) || []).map(v => {
|
|
5510
|
+
return DocumentKey.fromName(v.referenceValue);
|
|
5511
|
+
});
|
|
5512
|
+
}
|
|
5513
|
+
/** A Filter that implements the array-contains operator. */
|
|
5514
|
+
class ArrayContainsFilter extends FieldFilter {
|
|
5515
|
+
constructor(field, value) {
|
|
5516
|
+
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
5517
|
+
}
|
|
5518
|
+
matches(doc) {
|
|
5519
|
+
const other = doc.data.field(this.field);
|
|
5520
|
+
return isArray(other) && arrayValueContains(other.arrayValue, this.value);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
/** A Filter that implements the IN operator. */
|
|
5524
|
+
class InFilter extends FieldFilter {
|
|
5525
|
+
constructor(field, value) {
|
|
5526
|
+
super(field, "in" /* IN */, value);
|
|
5492
5527
|
}
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
return
|
|
5528
|
+
matches(doc) {
|
|
5529
|
+
const other = doc.data.field(this.field);
|
|
5530
|
+
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
5496
5531
|
}
|
|
5497
|
-
|
|
5498
|
-
|
|
5532
|
+
}
|
|
5533
|
+
/** A Filter that implements the not-in operator. */
|
|
5534
|
+
class NotInFilter extends FieldFilter {
|
|
5535
|
+
constructor(field, value) {
|
|
5536
|
+
super(field, "not-in" /* NOT_IN */, value);
|
|
5499
5537
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5538
|
+
matches(doc) {
|
|
5539
|
+
if (arrayValueContains(this.value.arrayValue, { nullValue: 'NULL_VALUE' })) {
|
|
5540
|
+
return false;
|
|
5541
|
+
}
|
|
5542
|
+
const other = doc.data.field(this.field);
|
|
5543
|
+
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
5502
5544
|
}
|
|
5503
|
-
|
|
5504
|
-
|
|
5545
|
+
}
|
|
5546
|
+
/** A Filter that implements the array-contains-any operator. */
|
|
5547
|
+
class ArrayContainsAnyFilter extends FieldFilter {
|
|
5548
|
+
constructor(field, value) {
|
|
5549
|
+
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
5505
5550
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
this.isReverse = isReverse;
|
|
5511
|
-
this.nodeStack = [];
|
|
5512
|
-
let cmp = 1;
|
|
5513
|
-
while (!node.isEmpty()) {
|
|
5514
|
-
cmp = startKey ? comparator(node.key, startKey) : 1;
|
|
5515
|
-
// flip the comparison if we're going in reverse
|
|
5516
|
-
if (startKey && isReverse) {
|
|
5517
|
-
cmp *= -1;
|
|
5518
|
-
}
|
|
5519
|
-
if (cmp < 0) {
|
|
5520
|
-
// This node is less than our start key. ignore it
|
|
5521
|
-
if (this.isReverse) {
|
|
5522
|
-
node = node.left;
|
|
5523
|
-
}
|
|
5524
|
-
else {
|
|
5525
|
-
node = node.right;
|
|
5526
|
-
}
|
|
5527
|
-
}
|
|
5528
|
-
else if (cmp === 0) {
|
|
5529
|
-
// This node is exactly equal to our start key. Push it on the stack,
|
|
5530
|
-
// but stop iterating;
|
|
5531
|
-
this.nodeStack.push(node);
|
|
5532
|
-
break;
|
|
5533
|
-
}
|
|
5534
|
-
else {
|
|
5535
|
-
// This node is greater than our start key, add it to the stack and move
|
|
5536
|
-
// to the next one
|
|
5537
|
-
this.nodeStack.push(node);
|
|
5538
|
-
if (this.isReverse) {
|
|
5539
|
-
node = node.right;
|
|
5540
|
-
}
|
|
5541
|
-
else {
|
|
5542
|
-
node = node.left;
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5551
|
+
matches(doc) {
|
|
5552
|
+
const other = doc.data.field(this.field);
|
|
5553
|
+
if (!isArray(other) || !other.arrayValue.values) {
|
|
5554
|
+
return false;
|
|
5545
5555
|
}
|
|
5556
|
+
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
5546
5557
|
}
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5558
|
+
}
|
|
5559
|
+
/**
|
|
5560
|
+
* Represents a bound of a query.
|
|
5561
|
+
*
|
|
5562
|
+
* The bound is specified with the given components representing a position and
|
|
5563
|
+
* whether it's just before or just after the position (relative to whatever the
|
|
5564
|
+
* query order is).
|
|
5565
|
+
*
|
|
5566
|
+
* The position represents a logical index position for a query. It's a prefix
|
|
5567
|
+
* of values for the (potentially implicit) order by clauses of a query.
|
|
5568
|
+
*
|
|
5569
|
+
* Bound provides a function to determine whether a document comes before or
|
|
5570
|
+
* after a bound. This is influenced by whether the position is just before or
|
|
5571
|
+
* just after the provided values.
|
|
5572
|
+
*/
|
|
5573
|
+
class Bound {
|
|
5574
|
+
constructor(position, inclusive) {
|
|
5575
|
+
this.position = position;
|
|
5576
|
+
this.inclusive = inclusive;
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
/**
|
|
5580
|
+
* An ordering on a field, in some Direction. Direction defaults to ASCENDING.
|
|
5581
|
+
*/
|
|
5582
|
+
class OrderBy {
|
|
5583
|
+
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
5584
|
+
this.field = field;
|
|
5585
|
+
this.dir = dir;
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
function canonifyOrderBy(orderBy) {
|
|
5589
|
+
// TODO(b/29183165): Make this collision robust.
|
|
5590
|
+
return orderBy.field.canonicalString() + orderBy.dir;
|
|
5591
|
+
}
|
|
5592
|
+
function stringifyOrderBy(orderBy) {
|
|
5593
|
+
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
5594
|
+
}
|
|
5595
|
+
function orderByEquals(left, right) {
|
|
5596
|
+
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
5597
|
+
}
|
|
5598
|
+
function boundCompareToDocument(bound, orderBy, doc) {
|
|
5599
|
+
let comparison = 0;
|
|
5600
|
+
for (let i = 0; i < bound.position.length; i++) {
|
|
5601
|
+
const orderByComponent = orderBy[i];
|
|
5602
|
+
const component = bound.position[i];
|
|
5603
|
+
if (orderByComponent.field.isKeyField()) {
|
|
5604
|
+
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
5556
5605
|
}
|
|
5557
5606
|
else {
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
this.nodeStack.push(node);
|
|
5561
|
-
node = node.left;
|
|
5562
|
-
}
|
|
5607
|
+
const docValue = doc.data.field(orderByComponent.field);
|
|
5608
|
+
comparison = valueCompare(component, docValue);
|
|
5563
5609
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
peek() {
|
|
5570
|
-
if (this.nodeStack.length === 0) {
|
|
5571
|
-
return null;
|
|
5610
|
+
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
5611
|
+
comparison = comparison * -1;
|
|
5612
|
+
}
|
|
5613
|
+
if (comparison !== 0) {
|
|
5614
|
+
break;
|
|
5572
5615
|
}
|
|
5573
|
-
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
5574
|
-
return { key: node.key, value: node.value };
|
|
5575
|
-
}
|
|
5576
|
-
} // end SortedMapIterator
|
|
5577
|
-
// Represents a node in a Left-leaning Red-Black tree.
|
|
5578
|
-
class LLRBNode {
|
|
5579
|
-
constructor(key, value, color, left, right) {
|
|
5580
|
-
this.key = key;
|
|
5581
|
-
this.value = value;
|
|
5582
|
-
this.color = color != null ? color : LLRBNode.RED;
|
|
5583
|
-
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
5584
|
-
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
5585
|
-
this.size = this.left.size + 1 + this.right.size;
|
|
5586
5616
|
}
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5617
|
+
return comparison;
|
|
5618
|
+
}
|
|
5619
|
+
/**
|
|
5620
|
+
* Returns true if a document sorts after a bound using the provided sort
|
|
5621
|
+
* order.
|
|
5622
|
+
*/
|
|
5623
|
+
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
5624
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5625
|
+
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
5626
|
+
}
|
|
5627
|
+
/**
|
|
5628
|
+
* Returns true if a document sorts before a bound using the provided sort
|
|
5629
|
+
* order.
|
|
5630
|
+
*/
|
|
5631
|
+
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
5632
|
+
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
5633
|
+
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
5634
|
+
}
|
|
5635
|
+
function boundEquals(left, right) {
|
|
5636
|
+
if (left === null) {
|
|
5637
|
+
return right === null;
|
|
5590
5638
|
}
|
|
5591
|
-
|
|
5639
|
+
else if (right === null) {
|
|
5592
5640
|
return false;
|
|
5593
5641
|
}
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
// value returned by action.
|
|
5598
|
-
inorderTraversal(action) {
|
|
5599
|
-
return (this.left.inorderTraversal(action) ||
|
|
5600
|
-
action(this.key, this.value) ||
|
|
5601
|
-
this.right.inorderTraversal(action));
|
|
5602
|
-
}
|
|
5603
|
-
// Traverses the tree in reverse key order and calls the specified action
|
|
5604
|
-
// function for each node. If action returns true, traversal is aborted.
|
|
5605
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5606
|
-
// value returned by action.
|
|
5607
|
-
reverseTraversal(action) {
|
|
5608
|
-
return (this.right.reverseTraversal(action) ||
|
|
5609
|
-
action(this.key, this.value) ||
|
|
5610
|
-
this.left.reverseTraversal(action));
|
|
5642
|
+
if (left.inclusive !== right.inclusive ||
|
|
5643
|
+
left.position.length !== right.position.length) {
|
|
5644
|
+
return false;
|
|
5611
5645
|
}
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
else {
|
|
5618
|
-
return this.left.min();
|
|
5646
|
+
for (let i = 0; i < left.position.length; i++) {
|
|
5647
|
+
const leftPosition = left.position[i];
|
|
5648
|
+
const rightPosition = right.position[i];
|
|
5649
|
+
if (!valueEquals(leftPosition, rightPosition)) {
|
|
5650
|
+
return false;
|
|
5619
5651
|
}
|
|
5620
5652
|
}
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5653
|
+
return true;
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
/**
|
|
5657
|
+
* @license
|
|
5658
|
+
* Copyright 2017 Google LLC
|
|
5659
|
+
*
|
|
5660
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5661
|
+
* you may not use this file except in compliance with the License.
|
|
5662
|
+
* You may obtain a copy of the License at
|
|
5663
|
+
*
|
|
5664
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5665
|
+
*
|
|
5666
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
5667
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5668
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5669
|
+
* See the License for the specific language governing permissions and
|
|
5670
|
+
* limitations under the License.
|
|
5671
|
+
*/
|
|
5672
|
+
/**
|
|
5673
|
+
* Query encapsulates all the query attributes we support in the SDK. It can
|
|
5674
|
+
* be run against the LocalStore, as well as be converted to a `Target` to
|
|
5675
|
+
* query the RemoteStore results.
|
|
5676
|
+
*
|
|
5677
|
+
* Visible for testing.
|
|
5678
|
+
*/
|
|
5679
|
+
class QueryImpl {
|
|
5680
|
+
/**
|
|
5681
|
+
* Initializes a Query with a path and optional additional query constraints.
|
|
5682
|
+
* Path must currently be empty if this is a collection group query.
|
|
5683
|
+
*/
|
|
5684
|
+
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
5685
|
+
this.path = path;
|
|
5686
|
+
this.collectionGroup = collectionGroup;
|
|
5687
|
+
this.explicitOrderBy = explicitOrderBy;
|
|
5688
|
+
this.filters = filters;
|
|
5689
|
+
this.limit = limit;
|
|
5690
|
+
this.limitType = limitType;
|
|
5691
|
+
this.startAt = startAt;
|
|
5692
|
+
this.endAt = endAt;
|
|
5693
|
+
this.memoizedOrderBy = null;
|
|
5694
|
+
// The corresponding `Target` of this `Query` instance.
|
|
5695
|
+
this.memoizedTarget = null;
|
|
5696
|
+
if (this.startAt) ;
|
|
5697
|
+
if (this.endAt) ;
|
|
5633
5698
|
}
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5699
|
+
}
|
|
5700
|
+
/** Creates a new Query instance with the options provided. */
|
|
5701
|
+
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
5702
|
+
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
5703
|
+
}
|
|
5704
|
+
/** Creates a new Query for a query that matches all documents at `path` */
|
|
5705
|
+
function newQueryForPath(path) {
|
|
5706
|
+
return new QueryImpl(path);
|
|
5707
|
+
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Helper to convert a collection group query into a collection query at a
|
|
5710
|
+
* specific path. This is used when executing collection group queries, since
|
|
5711
|
+
* we have to split the query into a set of collection queries at multiple
|
|
5712
|
+
* paths.
|
|
5713
|
+
*/
|
|
5714
|
+
function asCollectionQueryAtPath(query, path) {
|
|
5715
|
+
return new QueryImpl(path,
|
|
5716
|
+
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5717
|
+
}
|
|
5718
|
+
/**
|
|
5719
|
+
* Returns true if this query does not specify any query constraints that
|
|
5720
|
+
* could remove results.
|
|
5721
|
+
*/
|
|
5722
|
+
function queryMatchesAllDocuments(query) {
|
|
5723
|
+
return (query.filters.length === 0 &&
|
|
5724
|
+
query.limit === null &&
|
|
5725
|
+
query.startAt == null &&
|
|
5726
|
+
query.endAt == null &&
|
|
5727
|
+
(query.explicitOrderBy.length === 0 ||
|
|
5728
|
+
(query.explicitOrderBy.length === 1 &&
|
|
5729
|
+
query.explicitOrderBy[0].field.isKeyField())));
|
|
5730
|
+
}
|
|
5731
|
+
function getFirstOrderByField(query) {
|
|
5732
|
+
return query.explicitOrderBy.length > 0
|
|
5733
|
+
? query.explicitOrderBy[0].field
|
|
5734
|
+
: null;
|
|
5735
|
+
}
|
|
5736
|
+
function getInequalityFilterField(query) {
|
|
5737
|
+
for (const filter of query.filters) {
|
|
5738
|
+
if (filter.isInequality()) {
|
|
5739
|
+
return filter.field;
|
|
5646
5740
|
}
|
|
5647
|
-
return n.fixUp();
|
|
5648
5741
|
}
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5742
|
+
return null;
|
|
5743
|
+
}
|
|
5744
|
+
/**
|
|
5745
|
+
* Checks if any of the provided Operators are included in the query and
|
|
5746
|
+
* returns the first one that is, or null if none are.
|
|
5747
|
+
*/
|
|
5748
|
+
function findFilterOperator(query, operators) {
|
|
5749
|
+
for (const filter of query.filters) {
|
|
5750
|
+
if (operators.indexOf(filter.op) >= 0) {
|
|
5751
|
+
return filter.op;
|
|
5656
5752
|
}
|
|
5657
|
-
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
5658
|
-
return n.fixUp();
|
|
5659
5753
|
}
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5754
|
+
return null;
|
|
5755
|
+
}
|
|
5756
|
+
/**
|
|
5757
|
+
* Creates a new Query for a collection group query that matches all documents
|
|
5758
|
+
* within the provided collection group.
|
|
5759
|
+
*/
|
|
5760
|
+
function newQueryForCollectionGroup(collectionId) {
|
|
5761
|
+
return new QueryImpl(ResourcePath.emptyPath(), collectionId);
|
|
5762
|
+
}
|
|
5763
|
+
/**
|
|
5764
|
+
* Returns whether the query matches a single document by path (rather than a
|
|
5765
|
+
* collection).
|
|
5766
|
+
*/
|
|
5767
|
+
function isDocumentQuery$1(query) {
|
|
5768
|
+
return (DocumentKey.isDocumentKey(query.path) &&
|
|
5769
|
+
query.collectionGroup === null &&
|
|
5770
|
+
query.filters.length === 0);
|
|
5771
|
+
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Returns whether the query matches a collection group rather than a specific
|
|
5774
|
+
* collection.
|
|
5775
|
+
*/
|
|
5776
|
+
function isCollectionGroupQuery(query) {
|
|
5777
|
+
return query.collectionGroup !== null;
|
|
5778
|
+
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5781
|
+
* which can be different from the order by constraints the user provided (e.g.
|
|
5782
|
+
* the SDK and backend always orders by `__name__`).
|
|
5783
|
+
*/
|
|
5784
|
+
function queryOrderBy(query) {
|
|
5785
|
+
const queryImpl = debugCast(query);
|
|
5786
|
+
if (queryImpl.memoizedOrderBy === null) {
|
|
5787
|
+
queryImpl.memoizedOrderBy = [];
|
|
5788
|
+
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5789
|
+
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5790
|
+
if (inequalityField !== null && firstOrderByField === null) {
|
|
5791
|
+
// In order to implicitly add key ordering, we must also add the
|
|
5792
|
+
// inequality filter field for it to be a valid query.
|
|
5793
|
+
// Note that the default inequality field and key ordering is ascending.
|
|
5794
|
+
if (!inequalityField.isKeyField()) {
|
|
5795
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
5667
5796
|
}
|
|
5668
|
-
|
|
5797
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
5669
5798
|
}
|
|
5670
5799
|
else {
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
}
|
|
5677
|
-
if (comparator(key, n.key) === 0) {
|
|
5678
|
-
if (n.right.isEmpty()) {
|
|
5679
|
-
return LLRBNode.EMPTY;
|
|
5680
|
-
}
|
|
5681
|
-
else {
|
|
5682
|
-
smallest = n.right.min();
|
|
5683
|
-
n = n.copy(smallest.key, smallest.value, null, null, n.right.removeMin());
|
|
5800
|
+
let foundKeyOrdering = false;
|
|
5801
|
+
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5802
|
+
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5803
|
+
if (orderBy.field.isKeyField()) {
|
|
5804
|
+
foundKeyOrdering = true;
|
|
5684
5805
|
}
|
|
5685
5806
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
let n = this;
|
|
5696
|
-
if (n.right.isRed() && !n.left.isRed()) {
|
|
5697
|
-
n = n.rotateLeft();
|
|
5698
|
-
}
|
|
5699
|
-
if (n.left.isRed() && n.left.left.isRed()) {
|
|
5700
|
-
n = n.rotateRight();
|
|
5701
|
-
}
|
|
5702
|
-
if (n.left.isRed() && n.right.isRed()) {
|
|
5703
|
-
n = n.colorFlip();
|
|
5807
|
+
if (!foundKeyOrdering) {
|
|
5808
|
+
// The order of the implicit key ordering always matches the last
|
|
5809
|
+
// explicit order by
|
|
5810
|
+
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5811
|
+
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5812
|
+
.dir
|
|
5813
|
+
: "asc" /* ASCENDING */;
|
|
5814
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5815
|
+
}
|
|
5704
5816
|
}
|
|
5705
|
-
return n;
|
|
5706
5817
|
}
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5818
|
+
return queryImpl.memoizedOrderBy;
|
|
5819
|
+
}
|
|
5820
|
+
/**
|
|
5821
|
+
* Converts this `Query` instance to it's corresponding `Target` representation.
|
|
5822
|
+
*/
|
|
5823
|
+
function queryToTarget(query) {
|
|
5824
|
+
const queryImpl = debugCast(query);
|
|
5825
|
+
if (!queryImpl.memoizedTarget) {
|
|
5826
|
+
if (queryImpl.limitType === "F" /* First */) {
|
|
5827
|
+
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
5713
5828
|
}
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5829
|
+
else {
|
|
5830
|
+
// Flip the orderBy directions since we want the last results
|
|
5831
|
+
const orderBys = [];
|
|
5832
|
+
for (const orderBy of queryOrderBy(queryImpl)) {
|
|
5833
|
+
const dir = orderBy.dir === "desc" /* DESCENDING */
|
|
5834
|
+
? "asc" /* ASCENDING */
|
|
5835
|
+
: "desc" /* DESCENDING */;
|
|
5836
|
+
orderBys.push(new OrderBy(orderBy.field, dir));
|
|
5837
|
+
}
|
|
5838
|
+
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5839
|
+
const startAt = queryImpl.endAt
|
|
5840
|
+
? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)
|
|
5841
|
+
: null;
|
|
5842
|
+
const endAt = queryImpl.startAt
|
|
5843
|
+
? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)
|
|
5844
|
+
: null;
|
|
5845
|
+
// Now return as a LimitType.First query.
|
|
5846
|
+
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5721
5847
|
}
|
|
5722
|
-
return n;
|
|
5723
5848
|
}
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5849
|
+
return queryImpl.memoizedTarget;
|
|
5850
|
+
}
|
|
5851
|
+
function queryWithAddedFilter(query, filter) {
|
|
5852
|
+
const newFilters = query.filters.concat([filter]);
|
|
5853
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5854
|
+
}
|
|
5855
|
+
function queryWithAddedOrderBy(query, orderBy) {
|
|
5856
|
+
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5857
|
+
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5858
|
+
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5859
|
+
}
|
|
5860
|
+
function queryWithLimit(query, limit, limitType) {
|
|
5861
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5862
|
+
}
|
|
5863
|
+
function queryWithStartAt(query, bound) {
|
|
5864
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5865
|
+
}
|
|
5866
|
+
function queryWithEndAt(query, bound) {
|
|
5867
|
+
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5868
|
+
}
|
|
5869
|
+
function queryEquals(left, right) {
|
|
5870
|
+
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5871
|
+
left.limitType === right.limitType);
|
|
5872
|
+
}
|
|
5873
|
+
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5874
|
+
// example, use as a dictionary key, but the implementation is subject to
|
|
5875
|
+
// collisions. Make it collision-free.
|
|
5876
|
+
function canonifyQuery(query) {
|
|
5877
|
+
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5878
|
+
}
|
|
5879
|
+
function stringifyQuery(query) {
|
|
5880
|
+
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5881
|
+
}
|
|
5882
|
+
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5883
|
+
function queryMatches(query, doc) {
|
|
5884
|
+
return (doc.isFoundDocument() &&
|
|
5885
|
+
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5886
|
+
queryMatchesOrderBy(query, doc) &&
|
|
5887
|
+
queryMatchesFilters(query, doc) &&
|
|
5888
|
+
queryMatchesBounds(query, doc));
|
|
5889
|
+
}
|
|
5890
|
+
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5891
|
+
const docPath = doc.key.path;
|
|
5892
|
+
if (query.collectionGroup !== null) {
|
|
5893
|
+
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5894
|
+
// Group queries rooted at a document path yet.
|
|
5895
|
+
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5896
|
+
query.path.isPrefixOf(docPath));
|
|
5727
5897
|
}
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
return
|
|
5898
|
+
else if (DocumentKey.isDocumentKey(query.path)) {
|
|
5899
|
+
// exact match for document queries
|
|
5900
|
+
return query.path.isEqual(docPath);
|
|
5731
5901
|
}
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
return this.copy(null, null, !this.color, left, right);
|
|
5902
|
+
else {
|
|
5903
|
+
// shallow ancestor queries by default
|
|
5904
|
+
return query.path.isImmediateParentOf(docPath);
|
|
5736
5905
|
}
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5906
|
+
}
|
|
5907
|
+
/**
|
|
5908
|
+
* A document must have a value for every ordering clause in order to show up
|
|
5909
|
+
* in the results.
|
|
5910
|
+
*/
|
|
5911
|
+
function queryMatchesOrderBy(query, doc) {
|
|
5912
|
+
for (const orderBy of query.explicitOrderBy) {
|
|
5913
|
+
// order by key always matches
|
|
5914
|
+
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5744
5915
|
return false;
|
|
5745
5916
|
}
|
|
5746
5917
|
}
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
if (this.right.isRed()) {
|
|
5754
|
-
throw fail();
|
|
5755
|
-
}
|
|
5756
|
-
const blackDepth = this.left.check();
|
|
5757
|
-
if (blackDepth !== this.right.check()) {
|
|
5758
|
-
throw fail();
|
|
5759
|
-
}
|
|
5760
|
-
else {
|
|
5761
|
-
return blackDepth + (this.isRed() ? 0 : 1);
|
|
5918
|
+
return true;
|
|
5919
|
+
}
|
|
5920
|
+
function queryMatchesFilters(query, doc) {
|
|
5921
|
+
for (const filter of query.filters) {
|
|
5922
|
+
if (!filter.matches(doc)) {
|
|
5923
|
+
return false;
|
|
5762
5924
|
}
|
|
5763
5925
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
5771
|
-
class LLRBEmptyNode {
|
|
5772
|
-
constructor() {
|
|
5773
|
-
this.size = 0;
|
|
5774
|
-
}
|
|
5775
|
-
get key() {
|
|
5776
|
-
throw fail();
|
|
5777
|
-
}
|
|
5778
|
-
get value() {
|
|
5779
|
-
throw fail();
|
|
5780
|
-
}
|
|
5781
|
-
get color() {
|
|
5782
|
-
throw fail();
|
|
5783
|
-
}
|
|
5784
|
-
get left() {
|
|
5785
|
-
throw fail();
|
|
5786
|
-
}
|
|
5787
|
-
get right() {
|
|
5788
|
-
throw fail();
|
|
5789
|
-
}
|
|
5790
|
-
// Returns a copy of the current node.
|
|
5791
|
-
copy(key, value, color, left, right) {
|
|
5792
|
-
return this;
|
|
5793
|
-
}
|
|
5794
|
-
// Returns a copy of the tree, with the specified key/value added.
|
|
5795
|
-
insert(key, value, comparator) {
|
|
5796
|
-
return new LLRBNode(key, value);
|
|
5797
|
-
}
|
|
5798
|
-
// Returns a copy of the tree, with the specified key removed.
|
|
5799
|
-
remove(key, comparator) {
|
|
5800
|
-
return this;
|
|
5801
|
-
}
|
|
5802
|
-
isEmpty() {
|
|
5803
|
-
return true;
|
|
5804
|
-
}
|
|
5805
|
-
inorderTraversal(action) {
|
|
5806
|
-
return false;
|
|
5807
|
-
}
|
|
5808
|
-
reverseTraversal(action) {
|
|
5926
|
+
return true;
|
|
5927
|
+
}
|
|
5928
|
+
/** Makes sure a document is within the bounds, if provided. */
|
|
5929
|
+
function queryMatchesBounds(query, doc) {
|
|
5930
|
+
if (query.startAt &&
|
|
5931
|
+
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5809
5932
|
return false;
|
|
5810
5933
|
}
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
}
|
|
5814
|
-
maxKey() {
|
|
5815
|
-
return null;
|
|
5816
|
-
}
|
|
5817
|
-
isRed() {
|
|
5934
|
+
if (query.endAt &&
|
|
5935
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
5818
5936
|
return false;
|
|
5819
5937
|
}
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5938
|
+
return true;
|
|
5939
|
+
}
|
|
5940
|
+
/**
|
|
5941
|
+
* Returns the collection group that this query targets.
|
|
5942
|
+
*
|
|
5943
|
+
* PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
|
|
5944
|
+
* synchronization for query results.
|
|
5945
|
+
*/
|
|
5946
|
+
function queryCollectionGroup(query) {
|
|
5947
|
+
return (query.collectionGroup ||
|
|
5948
|
+
(query.path.length % 2 === 1
|
|
5949
|
+
? query.path.lastSegment()
|
|
5950
|
+
: query.path.get(query.path.length - 2)));
|
|
5951
|
+
}
|
|
5952
|
+
/**
|
|
5953
|
+
* Returns a new comparator function that can be used to compare two documents
|
|
5954
|
+
* based on the Query's ordering constraint.
|
|
5955
|
+
*/
|
|
5956
|
+
function newQueryComparator(query) {
|
|
5957
|
+
return (d1, d2) => {
|
|
5958
|
+
let comparedOnKeyField = false;
|
|
5959
|
+
for (const orderBy of queryOrderBy(query)) {
|
|
5960
|
+
const comp = compareDocs(orderBy, d1, d2);
|
|
5961
|
+
if (comp !== 0) {
|
|
5962
|
+
return comp;
|
|
5963
|
+
}
|
|
5964
|
+
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5965
|
+
}
|
|
5825
5966
|
return 0;
|
|
5967
|
+
};
|
|
5968
|
+
}
|
|
5969
|
+
function compareDocs(orderBy, d1, d2) {
|
|
5970
|
+
const comparison = orderBy.field.isKeyField()
|
|
5971
|
+
? DocumentKey.comparator(d1.key, d2.key)
|
|
5972
|
+
: compareDocumentsByField(orderBy.field, d1, d2);
|
|
5973
|
+
switch (orderBy.dir) {
|
|
5974
|
+
case "asc" /* ASCENDING */:
|
|
5975
|
+
return comparison;
|
|
5976
|
+
case "desc" /* DESCENDING */:
|
|
5977
|
+
return -1 * comparison;
|
|
5978
|
+
default:
|
|
5979
|
+
return fail();
|
|
5826
5980
|
}
|
|
5827
|
-
}
|
|
5828
|
-
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
5981
|
+
}
|
|
5829
5982
|
|
|
5830
5983
|
/**
|
|
5831
5984
|
* @license
|
|
@@ -5844,218 +5997,97 @@ LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
|
5844
5997
|
* limitations under the License.
|
|
5845
5998
|
*/
|
|
5846
5999
|
/**
|
|
5847
|
-
*
|
|
5848
|
-
*
|
|
5849
|
-
*
|
|
5850
|
-
*
|
|
5851
|
-
* be equal!
|
|
6000
|
+
* A map implementation that uses objects as keys. Objects must have an
|
|
6001
|
+
* associated equals function and must be immutable. Entries in the map are
|
|
6002
|
+
* stored together with the key being produced from the mapKeyFn. This map
|
|
6003
|
+
* automatically handles collisions of keys.
|
|
5852
6004
|
*/
|
|
5853
|
-
class
|
|
5854
|
-
constructor(
|
|
5855
|
-
this.
|
|
5856
|
-
this.
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
}
|
|
5867
|
-
get size() {
|
|
5868
|
-
return this.data.size;
|
|
5869
|
-
}
|
|
5870
|
-
indexOf(elem) {
|
|
5871
|
-
return this.data.indexOf(elem);
|
|
5872
|
-
}
|
|
5873
|
-
/** Iterates elements in order defined by "comparator" */
|
|
5874
|
-
forEach(cb) {
|
|
5875
|
-
this.data.inorderTraversal((k, v) => {
|
|
5876
|
-
cb(k);
|
|
5877
|
-
return false;
|
|
5878
|
-
});
|
|
5879
|
-
}
|
|
5880
|
-
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
5881
|
-
forEachInRange(range, cb) {
|
|
5882
|
-
const iter = this.data.getIteratorFrom(range[0]);
|
|
5883
|
-
while (iter.hasNext()) {
|
|
5884
|
-
const elem = iter.getNext();
|
|
5885
|
-
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
5886
|
-
return;
|
|
5887
|
-
}
|
|
5888
|
-
cb(elem.key);
|
|
5889
|
-
}
|
|
6005
|
+
class ObjectMap {
|
|
6006
|
+
constructor(mapKeyFn, equalsFn) {
|
|
6007
|
+
this.mapKeyFn = mapKeyFn;
|
|
6008
|
+
this.equalsFn = equalsFn;
|
|
6009
|
+
/**
|
|
6010
|
+
* The inner map for a key/value pair. Due to the possibility of collisions we
|
|
6011
|
+
* keep a list of entries that we do a linear search through to find an actual
|
|
6012
|
+
* match. Note that collisions should be rare, so we still expect near
|
|
6013
|
+
* constant time lookups in practice.
|
|
6014
|
+
*/
|
|
6015
|
+
this.inner = {};
|
|
6016
|
+
/** The number of entries stored in the map */
|
|
6017
|
+
this.innerSize = 0;
|
|
5890
6018
|
}
|
|
5891
|
-
/**
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
iter = this.data.getIteratorFrom(start);
|
|
5898
|
-
}
|
|
5899
|
-
else {
|
|
5900
|
-
iter = this.data.getIterator();
|
|
6019
|
+
/** Get a value for this key, or undefined if it does not exist. */
|
|
6020
|
+
get(key) {
|
|
6021
|
+
const id = this.mapKeyFn(key);
|
|
6022
|
+
const matches = this.inner[id];
|
|
6023
|
+
if (matches === undefined) {
|
|
6024
|
+
return undefined;
|
|
5901
6025
|
}
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
if (!result) {
|
|
5906
|
-
return;
|
|
6026
|
+
for (const [otherKey, value] of matches) {
|
|
6027
|
+
if (this.equalsFn(otherKey, key)) {
|
|
6028
|
+
return value;
|
|
5907
6029
|
}
|
|
5908
6030
|
}
|
|
6031
|
+
return undefined;
|
|
5909
6032
|
}
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
const iter = this.data.getIteratorFrom(elem);
|
|
5913
|
-
return iter.hasNext() ? iter.getNext().key : null;
|
|
5914
|
-
}
|
|
5915
|
-
getIterator() {
|
|
5916
|
-
return new SortedSetIterator(this.data.getIterator());
|
|
5917
|
-
}
|
|
5918
|
-
getIteratorFrom(key) {
|
|
5919
|
-
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5920
|
-
}
|
|
5921
|
-
/** Inserts or updates an element */
|
|
5922
|
-
add(elem) {
|
|
5923
|
-
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
6033
|
+
has(key) {
|
|
6034
|
+
return this.get(key) !== undefined;
|
|
5924
6035
|
}
|
|
5925
|
-
/**
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6036
|
+
/** Put this key and value in the map. */
|
|
6037
|
+
set(key, value) {
|
|
6038
|
+
const id = this.mapKeyFn(key);
|
|
6039
|
+
const matches = this.inner[id];
|
|
6040
|
+
if (matches === undefined) {
|
|
6041
|
+
this.inner[id] = [[key, value]];
|
|
6042
|
+
this.innerSize++;
|
|
6043
|
+
return;
|
|
5929
6044
|
}
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
let result = this;
|
|
5937
|
-
// Make sure `result` always refers to the larger one of the two sets.
|
|
5938
|
-
if (result.size < other.size) {
|
|
5939
|
-
result = other;
|
|
5940
|
-
other = this;
|
|
6045
|
+
for (let i = 0; i < matches.length; i++) {
|
|
6046
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
6047
|
+
// This is updating an existing entry and does not increase `innerSize`.
|
|
6048
|
+
matches[i] = [key, value];
|
|
6049
|
+
return;
|
|
6050
|
+
}
|
|
5941
6051
|
}
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
});
|
|
5945
|
-
return result;
|
|
6052
|
+
matches.push([key, value]);
|
|
6053
|
+
this.innerSize++;
|
|
5946
6054
|
}
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
6055
|
+
/**
|
|
6056
|
+
* Remove this key from the map. Returns a boolean if anything was deleted.
|
|
6057
|
+
*/
|
|
6058
|
+
delete(key) {
|
|
6059
|
+
const id = this.mapKeyFn(key);
|
|
6060
|
+
const matches = this.inner[id];
|
|
6061
|
+
if (matches === undefined) {
|
|
5952
6062
|
return false;
|
|
5953
6063
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
6064
|
+
for (let i = 0; i < matches.length; i++) {
|
|
6065
|
+
if (this.equalsFn(matches[i][0], key)) {
|
|
6066
|
+
if (matches.length === 1) {
|
|
6067
|
+
delete this.inner[id];
|
|
6068
|
+
}
|
|
6069
|
+
else {
|
|
6070
|
+
matches.splice(i, 1);
|
|
6071
|
+
}
|
|
6072
|
+
this.innerSize--;
|
|
6073
|
+
return true;
|
|
5961
6074
|
}
|
|
5962
6075
|
}
|
|
5963
|
-
return
|
|
6076
|
+
return false;
|
|
5964
6077
|
}
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6078
|
+
forEach(fn) {
|
|
6079
|
+
forEach(this.inner, (_, entries) => {
|
|
6080
|
+
for (const [k, v] of entries) {
|
|
6081
|
+
fn(k, v);
|
|
6082
|
+
}
|
|
5969
6083
|
});
|
|
5970
|
-
return res;
|
|
5971
|
-
}
|
|
5972
|
-
toString() {
|
|
5973
|
-
const result = [];
|
|
5974
|
-
this.forEach(elem => result.push(elem));
|
|
5975
|
-
return 'SortedSet(' + result.toString() + ')';
|
|
5976
|
-
}
|
|
5977
|
-
copy(data) {
|
|
5978
|
-
const result = new SortedSet(this.comparator);
|
|
5979
|
-
result.data = data;
|
|
5980
|
-
return result;
|
|
5981
|
-
}
|
|
5982
|
-
}
|
|
5983
|
-
class SortedSetIterator {
|
|
5984
|
-
constructor(iter) {
|
|
5985
|
-
this.iter = iter;
|
|
5986
6084
|
}
|
|
5987
|
-
|
|
5988
|
-
return this.
|
|
5989
|
-
}
|
|
5990
|
-
hasNext() {
|
|
5991
|
-
return this.iter.hasNext();
|
|
6085
|
+
isEmpty() {
|
|
6086
|
+
return isEmpty(this.inner);
|
|
5992
6087
|
}
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
* Compares two sorted sets for equality using their natural ordering. The
|
|
5996
|
-
* method computes the intersection and invokes `onAdd` for every element that
|
|
5997
|
-
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
5998
|
-
* `before` but missing from `after`.
|
|
5999
|
-
*
|
|
6000
|
-
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
6001
|
-
* n), where n is the size of the two lists.
|
|
6002
|
-
*
|
|
6003
|
-
* @param before - The elements that exist in the original set.
|
|
6004
|
-
* @param after - The elements to diff against the original set.
|
|
6005
|
-
* @param comparator - The comparator for the elements in before and after.
|
|
6006
|
-
* @param onAdd - A function to invoke for every element that is part of `
|
|
6007
|
-
* after` but not `before`.
|
|
6008
|
-
* @param onRemove - A function to invoke for every element that is part of
|
|
6009
|
-
* `before` but not `after`.
|
|
6010
|
-
*/
|
|
6011
|
-
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
6012
|
-
const beforeIt = before.getIterator();
|
|
6013
|
-
const afterIt = after.getIterator();
|
|
6014
|
-
let beforeValue = advanceIterator(beforeIt);
|
|
6015
|
-
let afterValue = advanceIterator(afterIt);
|
|
6016
|
-
// Walk through the two sets at the same time, using the ordering defined by
|
|
6017
|
-
// `comparator`.
|
|
6018
|
-
while (beforeValue || afterValue) {
|
|
6019
|
-
let added = false;
|
|
6020
|
-
let removed = false;
|
|
6021
|
-
if (beforeValue && afterValue) {
|
|
6022
|
-
const cmp = comparator(beforeValue, afterValue);
|
|
6023
|
-
if (cmp < 0) {
|
|
6024
|
-
// The element was removed if the next element in our ordered
|
|
6025
|
-
// walkthrough is only in `before`.
|
|
6026
|
-
removed = true;
|
|
6027
|
-
}
|
|
6028
|
-
else if (cmp > 0) {
|
|
6029
|
-
// The element was added if the next element in our ordered walkthrough
|
|
6030
|
-
// is only in `after`.
|
|
6031
|
-
added = true;
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6034
|
-
else if (beforeValue != null) {
|
|
6035
|
-
removed = true;
|
|
6036
|
-
}
|
|
6037
|
-
else {
|
|
6038
|
-
added = true;
|
|
6039
|
-
}
|
|
6040
|
-
if (added) {
|
|
6041
|
-
onAdd(afterValue);
|
|
6042
|
-
afterValue = advanceIterator(afterIt);
|
|
6043
|
-
}
|
|
6044
|
-
else if (removed) {
|
|
6045
|
-
onRemove(beforeValue);
|
|
6046
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6047
|
-
}
|
|
6048
|
-
else {
|
|
6049
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6050
|
-
afterValue = advanceIterator(afterIt);
|
|
6051
|
-
}
|
|
6088
|
+
size() {
|
|
6089
|
+
return this.innerSize;
|
|
6052
6090
|
}
|
|
6053
|
-
}
|
|
6054
|
-
/**
|
|
6055
|
-
* Returns the next element from the iterator or `undefined` if none available.
|
|
6056
|
-
*/
|
|
6057
|
-
function advanceIterator(it) {
|
|
6058
|
-
return it.hasNext() ? it.getNext() : undefined;
|
|
6059
6091
|
}
|
|
6060
6092
|
|
|
6061
6093
|
/**
|
|
@@ -6079,8 +6111,12 @@ function mutableDocumentMap() {
|
|
|
6079
6111
|
return EMPTY_MUTABLE_DOCUMENT_MAP;
|
|
6080
6112
|
}
|
|
6081
6113
|
const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
6082
|
-
function documentMap() {
|
|
6083
|
-
|
|
6114
|
+
function documentMap(...docs) {
|
|
6115
|
+
let map = EMPTY_DOCUMENT_MAP;
|
|
6116
|
+
for (const doc of docs) {
|
|
6117
|
+
map = map.insert(doc.key, doc);
|
|
6118
|
+
}
|
|
6119
|
+
return map;
|
|
6084
6120
|
}
|
|
6085
6121
|
function newOverlayMap() {
|
|
6086
6122
|
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
@@ -9973,9 +10009,9 @@ class MemoryIndexManager {
|
|
|
9973
10009
|
// Field indices are not supported with memory persistence.
|
|
9974
10010
|
return PersistencePromise.resolve(null);
|
|
9975
10011
|
}
|
|
9976
|
-
|
|
10012
|
+
getIndexType(transaction, target) {
|
|
9977
10013
|
// Field indices are not supported with memory persistence.
|
|
9978
|
-
return PersistencePromise.resolve(
|
|
10014
|
+
return PersistencePromise.resolve(0 /* NONE */);
|
|
9979
10015
|
}
|
|
9980
10016
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9981
10017
|
// Field indices are not supported with memory persistence.
|
|
@@ -9985,6 +10021,9 @@ class MemoryIndexManager {
|
|
|
9985
10021
|
// Field indices are not supported with memory persistence.
|
|
9986
10022
|
return PersistencePromise.resolve(null);
|
|
9987
10023
|
}
|
|
10024
|
+
getMinOffset(transaction, target) {
|
|
10025
|
+
return PersistencePromise.resolve(IndexOffset.min());
|
|
10026
|
+
}
|
|
9988
10027
|
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
9989
10028
|
// Field indices are not supported with memory persistence.
|
|
9990
10029
|
return PersistencePromise.resolve();
|
|
@@ -10160,7 +10199,7 @@ class IndexedDbIndexManager {
|
|
|
10160
10199
|
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10161
10200
|
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10162
10201
|
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10163
|
-
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded,
|
|
10202
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
|
|
10164
10203
|
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10165
10204
|
return indexEntries
|
|
10166
10205
|
.loadFirst(indexRange, target.limit)
|
|
@@ -10198,22 +10237,18 @@ class IndexedDbIndexManager {
|
|
|
10198
10237
|
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10199
10238
|
// combined with the values from the query bounds.
|
|
10200
10239
|
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10201
|
-
Math.max(lowerBounds
|
|
10240
|
+
Math.max(lowerBounds.length, upperBounds.length);
|
|
10202
10241
|
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10203
10242
|
const indexRanges = [];
|
|
10204
10243
|
for (let i = 0; i < totalScans; ++i) {
|
|
10205
10244
|
const arrayValue = arrayValues
|
|
10206
10245
|
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10207
10246
|
: EMPTY_VALUE;
|
|
10208
|
-
const lowerBound = lowerBounds
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
: this.generateEmptyBound(indexId + 1);
|
|
10214
|
-
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10215
|
-
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10216
|
-
/* inclusive= */ true))));
|
|
10247
|
+
const lowerBound = this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive);
|
|
10248
|
+
const upperBound = this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive);
|
|
10249
|
+
const notInBound = notInValues.map(notIn => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10250
|
+
/* inclusive= */ true));
|
|
10251
|
+
indexRanges.push(...this.createRange(lowerBound, upperBound, notInBound));
|
|
10217
10252
|
}
|
|
10218
10253
|
return indexRanges;
|
|
10219
10254
|
}
|
|
@@ -10227,25 +10262,38 @@ class IndexedDbIndexManager {
|
|
|
10227
10262
|
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10228
10263
|
return inclusive ? entry.successor() : entry;
|
|
10229
10264
|
}
|
|
10230
|
-
/**
|
|
10231
|
-
* Generates an empty bound that scopes the index scan to the current index
|
|
10232
|
-
* and user.
|
|
10233
|
-
*/
|
|
10234
|
-
generateEmptyBound(indexId) {
|
|
10235
|
-
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10236
|
-
}
|
|
10237
10265
|
getFieldIndex(transaction, target) {
|
|
10238
10266
|
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10239
10267
|
const collectionGroup = target.collectionGroup != null
|
|
10240
10268
|
? target.collectionGroup
|
|
10241
10269
|
: target.path.lastSegment();
|
|
10242
10270
|
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10271
|
+
// Return the index with the most number of segments.
|
|
10272
|
+
let index = null;
|
|
10273
|
+
for (const candidate of indexes) {
|
|
10274
|
+
const matches = targetIndexMatcher.servedByIndex(candidate);
|
|
10275
|
+
if (matches &&
|
|
10276
|
+
(!index || candidate.fields.length > index.fields.length)) {
|
|
10277
|
+
index = candidate;
|
|
10278
|
+
}
|
|
10279
|
+
}
|
|
10280
|
+
return index;
|
|
10247
10281
|
});
|
|
10248
10282
|
}
|
|
10283
|
+
getIndexType(transaction, target) {
|
|
10284
|
+
let indexType = 2 /* FULL */;
|
|
10285
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
|
|
10286
|
+
return this.getFieldIndex(transaction, target).next(index => {
|
|
10287
|
+
if (!index) {
|
|
10288
|
+
indexType = 0 /* NONE */;
|
|
10289
|
+
}
|
|
10290
|
+
else if (indexType !== 0 /* NONE */ &&
|
|
10291
|
+
index.fields.length < targetGetSegmentCount(target)) {
|
|
10292
|
+
indexType = 1 /* PARTIAL */;
|
|
10293
|
+
}
|
|
10294
|
+
});
|
|
10295
|
+
}).next(() => indexType);
|
|
10296
|
+
}
|
|
10249
10297
|
/**
|
|
10250
10298
|
* Returns the byte encoded form of the directional values in the field index.
|
|
10251
10299
|
* Returns `null` if the document does not have all fields specified in the
|
|
@@ -10308,9 +10356,6 @@ class IndexedDbIndexManager {
|
|
|
10308
10356
|
* queries, a list of possible values is returned.
|
|
10309
10357
|
*/
|
|
10310
10358
|
encodeBound(fieldIndex, target, bound) {
|
|
10311
|
-
if (bound == null) {
|
|
10312
|
-
return null;
|
|
10313
|
-
}
|
|
10314
10359
|
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10315
10360
|
}
|
|
10316
10361
|
/** Returns the byte representation for the provided encoders. */
|
|
@@ -10505,7 +10550,7 @@ class IndexedDbIndexManager {
|
|
|
10505
10550
|
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10506
10551
|
*/
|
|
10507
10552
|
createRange(lower, upper, notInValues) {
|
|
10508
|
-
// The
|
|
10553
|
+
// The notIn values need to be sorted and unique so that we can return a
|
|
10509
10554
|
// sorted set of non-overlapping ranges.
|
|
10510
10555
|
notInValues = notInValues
|
|
10511
10556
|
.sort((l, r) => indexEntryComparator(l, r))
|
|
@@ -10551,6 +10596,20 @@ class IndexedDbIndexManager {
|
|
|
10551
10596
|
}
|
|
10552
10597
|
return ranges;
|
|
10553
10598
|
}
|
|
10599
|
+
getMinOffset(transaction, target) {
|
|
10600
|
+
let offset;
|
|
10601
|
+
return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
|
|
10602
|
+
return this.getFieldIndex(transaction, target).next(index => {
|
|
10603
|
+
if (!index) {
|
|
10604
|
+
offset = IndexOffset.min();
|
|
10605
|
+
}
|
|
10606
|
+
else if (!offset ||
|
|
10607
|
+
indexOffsetComparator(index.indexState.offset, offset) < 0) {
|
|
10608
|
+
offset = index.indexState.offset;
|
|
10609
|
+
}
|
|
10610
|
+
});
|
|
10611
|
+
}).next(() => offset);
|
|
10612
|
+
}
|
|
10554
10613
|
}
|
|
10555
10614
|
/**
|
|
10556
10615
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -13721,7 +13780,7 @@ class LocalStoreImpl {
|
|
|
13721
13780
|
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13722
13781
|
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13723
13782
|
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
13724
|
-
this.queryEngine.
|
|
13783
|
+
this.queryEngine.initialize(this.localDocuments, this.indexManager);
|
|
13725
13784
|
}
|
|
13726
13785
|
collectGarbage(garbageCollector) {
|
|
13727
13786
|
return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
|
|
@@ -15463,10 +15522,30 @@ class MemoryEagerDelegate {
|
|
|
15463
15522
|
* limitations under the License.
|
|
15464
15523
|
*/
|
|
15465
15524
|
/**
|
|
15466
|
-
*
|
|
15467
|
-
*
|
|
15468
|
-
*
|
|
15469
|
-
*
|
|
15525
|
+
* The Firestore query engine.
|
|
15526
|
+
*
|
|
15527
|
+
* Firestore queries can be executed in three modes. The Query Engine determines
|
|
15528
|
+
* what mode to use based on what data is persisted. The mode only determines
|
|
15529
|
+
* the runtime complexity of the query - the result set is equivalent across all
|
|
15530
|
+
* implementations.
|
|
15531
|
+
*
|
|
15532
|
+
* The Query engine will use indexed-based execution if a user has configured
|
|
15533
|
+
* any index that can be used to execute query (via `setIndexConfiguration()`).
|
|
15534
|
+
* Otherwise, the engine will try to optimize the query by re-using a previously
|
|
15535
|
+
* persisted query result. If that is not possible, the query will be executed
|
|
15536
|
+
* via a full collection scan.
|
|
15537
|
+
*
|
|
15538
|
+
* Index-based execution is the default when available. The query engine
|
|
15539
|
+
* supports partial indexed execution and merges the result from the index
|
|
15540
|
+
* lookup with documents that have not yet been indexed. The index evaluation
|
|
15541
|
+
* matches the backend's format and as such, the SDK can use indexing for all
|
|
15542
|
+
* queries that the backend supports.
|
|
15543
|
+
*
|
|
15544
|
+
* If no index exists, the query engine tries to take advantage of the target
|
|
15545
|
+
* document mapping in the TargetCache. These mappings exists for all queries
|
|
15546
|
+
* that have been synced with the backend at least once and allow the query
|
|
15547
|
+
* engine to only read documents that previously matched a query plus any
|
|
15548
|
+
* documents that were edited after the query was last listened to.
|
|
15470
15549
|
*
|
|
15471
15550
|
* There are some cases when this optimization is not guaranteed to produce
|
|
15472
15551
|
* the same results as full collection scans. In these cases, query
|
|
@@ -15481,16 +15560,41 @@ class MemoryEagerDelegate {
|
|
|
15481
15560
|
* - Queries that have never been CURRENT or free of limbo documents.
|
|
15482
15561
|
*/
|
|
15483
15562
|
class QueryEngine {
|
|
15563
|
+
constructor() {
|
|
15564
|
+
this.initialized = false;
|
|
15565
|
+
}
|
|
15484
15566
|
/** Sets the document view to query against. */
|
|
15485
|
-
|
|
15567
|
+
initialize(localDocuments, indexManager) {
|
|
15486
15568
|
this.localDocumentsView = localDocuments;
|
|
15569
|
+
this.indexManager = indexManager;
|
|
15570
|
+
this.initialized = true;
|
|
15487
15571
|
}
|
|
15488
15572
|
/** Returns all local documents matching the specified query. */
|
|
15489
15573
|
getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15574
|
+
return this.performQueryUsingIndex(transaction, query)
|
|
15575
|
+
.next(result => result
|
|
15576
|
+
? result
|
|
15577
|
+
: this.performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion))
|
|
15578
|
+
.next(result => result ? result : this.executeFullCollectionScan(transaction, query));
|
|
15579
|
+
}
|
|
15580
|
+
/**
|
|
15581
|
+
* Performs an indexed query that evaluates the query based on a collection's
|
|
15582
|
+
* persisted index values. Returns `null` if an index is not available.
|
|
15583
|
+
*/
|
|
15584
|
+
performQueryUsingIndex(transaction, query) {
|
|
15585
|
+
{
|
|
15586
|
+
return PersistencePromise.resolve(null);
|
|
15587
|
+
}
|
|
15588
|
+
}
|
|
15589
|
+
/**
|
|
15590
|
+
* Performs a query based on the target's persisted query mapping. Returns
|
|
15591
|
+
* `null` if the mapping is not available or cannot be used.
|
|
15592
|
+
*/
|
|
15593
|
+
performQueryUsingRemoteKeys(transaction, query, remoteKeys, lastLimboFreeSnapshotVersion) {
|
|
15594
|
+
if (queryMatchesAllDocuments(query)) {
|
|
15595
|
+
// Queries that match all documents don't benefit from using
|
|
15596
|
+
// key-based lookups. It is more efficient to scan all documents in a
|
|
15597
|
+
// collection, rather than to perform individual lookups.
|
|
15494
15598
|
return this.executeFullCollectionScan(transaction, query);
|
|
15495
15599
|
}
|
|
15496
15600
|
// Queries that have never seen a snapshot without limbo free documents
|
|
@@ -15500,8 +15604,7 @@ class QueryEngine {
|
|
|
15500
15604
|
}
|
|
15501
15605
|
return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
|
|
15502
15606
|
const previousResults = this.applyQuery(query, documents);
|
|
15503
|
-
if ((
|
|
15504
|
-
this.needsRefill(query.limitType, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15607
|
+
if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15505
15608
|
return this.executeFullCollectionScan(transaction, query);
|
|
15506
15609
|
}
|
|
15507
15610
|
if (getLogLevel() <= LogLevel.DEBUG) {
|
|
@@ -15509,15 +15612,7 @@ class QueryEngine {
|
|
|
15509
15612
|
}
|
|
15510
15613
|
// Retrieve all results for documents that were updated since the last
|
|
15511
15614
|
// limbo-document free remote snapshot.
|
|
15512
|
-
return this.
|
|
15513
|
-
// We merge `previousResults` into `updateResults`, since
|
|
15514
|
-
// `updateResults` is already a DocumentMap. If a document is
|
|
15515
|
-
// contained in both lists, then its contents are the same.
|
|
15516
|
-
previousResults.forEach(doc => {
|
|
15517
|
-
updatedResults = updatedResults.insert(doc.key, doc);
|
|
15518
|
-
});
|
|
15519
|
-
return updatedResults;
|
|
15520
|
-
});
|
|
15615
|
+
return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
|
|
15521
15616
|
});
|
|
15522
15617
|
}
|
|
15523
15618
|
/** Applies the query filter and sorting to the provided documents. */
|
|
@@ -15536,6 +15631,7 @@ class QueryEngine {
|
|
|
15536
15631
|
* Determines if a limit query needs to be refilled from cache, making it
|
|
15537
15632
|
* ineligible for index-free execution.
|
|
15538
15633
|
*
|
|
15634
|
+
* @param query The query.
|
|
15539
15635
|
* @param sortedPreviousResults - The documents that matched the query when it
|
|
15540
15636
|
* was last synchronized, sorted by the query's comparator.
|
|
15541
15637
|
* @param remoteKeys - The document keys that matched the query at the last
|
|
@@ -15543,10 +15639,14 @@ class QueryEngine {
|
|
|
15543
15639
|
* @param limboFreeSnapshotVersion - The version of the snapshot when the
|
|
15544
15640
|
* query was last synchronized.
|
|
15545
15641
|
*/
|
|
15546
|
-
needsRefill(
|
|
15547
|
-
|
|
15548
|
-
|
|
15642
|
+
needsRefill(query, sortedPreviousResults, remoteKeys, limboFreeSnapshotVersion) {
|
|
15643
|
+
if (query.limit === null) {
|
|
15644
|
+
// Queries without limits do not need to be refilled.
|
|
15645
|
+
return false;
|
|
15646
|
+
}
|
|
15549
15647
|
if (remoteKeys.size !== sortedPreviousResults.size) {
|
|
15648
|
+
// The query needs to be refilled if a previously matching document no
|
|
15649
|
+
// longer matches.
|
|
15550
15650
|
return true;
|
|
15551
15651
|
}
|
|
15552
15652
|
// Limit queries are not eligible for index-free query execution if there is
|
|
@@ -15557,7 +15657,7 @@ class QueryEngine {
|
|
|
15557
15657
|
// the boundary of the limit itself did not change and documents from cache
|
|
15558
15658
|
// will continue to be "rejected" by this boundary. Therefore, we can ignore
|
|
15559
15659
|
// any modifications that don't affect the last document.
|
|
15560
|
-
const docAtLimitEdge = limitType === "F" /* First */
|
|
15660
|
+
const docAtLimitEdge = query.limitType === "F" /* First */
|
|
15561
15661
|
? sortedPreviousResults.last()
|
|
15562
15662
|
: sortedPreviousResults.first();
|
|
15563
15663
|
if (!docAtLimitEdge) {
|
|
@@ -15573,6 +15673,22 @@ class QueryEngine {
|
|
|
15573
15673
|
}
|
|
15574
15674
|
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15575
15675
|
}
|
|
15676
|
+
/**
|
|
15677
|
+
* Combines the results from an indexed execution with the remaining documents
|
|
15678
|
+
* that have not yet been indexed.
|
|
15679
|
+
*/
|
|
15680
|
+
appendRemainingResults(transaction, indexedResults, query, offset) {
|
|
15681
|
+
// Retrieve all results for documents that were updated since the offset.
|
|
15682
|
+
return this.localDocumentsView
|
|
15683
|
+
.getDocumentsMatchingQuery(transaction, query, offset)
|
|
15684
|
+
.next(remainingResults => {
|
|
15685
|
+
// Merge with existing results
|
|
15686
|
+
indexedResults.forEach(d => {
|
|
15687
|
+
remainingResults = remainingResults.insert(d.key, d);
|
|
15688
|
+
});
|
|
15689
|
+
return remainingResults;
|
|
15690
|
+
});
|
|
15691
|
+
}
|
|
15576
15692
|
}
|
|
15577
15693
|
|
|
15578
15694
|
/**
|
|
@@ -21832,10 +21948,12 @@ class View {
|
|
|
21832
21948
|
//
|
|
21833
21949
|
// Note that this should never get used in a refill (when previousChanges is
|
|
21834
21950
|
// set), because there will only be adds -- no deletes or updates.
|
|
21835
|
-
const lastDocInLimit =
|
|
21951
|
+
const lastDocInLimit = this.query.limitType === "F" /* First */ &&
|
|
21952
|
+
oldDocumentSet.size === this.query.limit
|
|
21836
21953
|
? oldDocumentSet.last()
|
|
21837
21954
|
: null;
|
|
21838
|
-
const firstDocInLimit =
|
|
21955
|
+
const firstDocInLimit = this.query.limitType === "L" /* Last */ &&
|
|
21956
|
+
oldDocumentSet.size === this.query.limit
|
|
21839
21957
|
? oldDocumentSet.first()
|
|
21840
21958
|
: null;
|
|
21841
21959
|
docChanges.inorderTraversal((key, entry) => {
|
|
@@ -21908,9 +22026,9 @@ class View {
|
|
|
21908
22026
|
}
|
|
21909
22027
|
});
|
|
21910
22028
|
// Drop documents out to meet limit/limitToLast requirement.
|
|
21911
|
-
if (
|
|
22029
|
+
if (this.query.limit !== null) {
|
|
21912
22030
|
while (newDocumentSet.size > this.query.limit) {
|
|
21913
|
-
const oldDoc =
|
|
22031
|
+
const oldDoc = this.query.limitType === "F" /* First */
|
|
21914
22032
|
? newDocumentSet.last()
|
|
21915
22033
|
: newDocumentSet.first();
|
|
21916
22034
|
newDocumentSet = newDocumentSet.delete(oldDoc.key);
|
|
@@ -23911,18 +24029,18 @@ class Transaction$2 {
|
|
|
23911
24029
|
* See the License for the specific language governing permissions and
|
|
23912
24030
|
* limitations under the License.
|
|
23913
24031
|
*/
|
|
23914
|
-
const DEFAULT_MAX_ATTEMPTS_COUNT = 5;
|
|
23915
24032
|
/**
|
|
23916
24033
|
* TransactionRunner encapsulates the logic needed to run and retry transactions
|
|
23917
24034
|
* with backoff.
|
|
23918
24035
|
*/
|
|
23919
24036
|
class TransactionRunner {
|
|
23920
|
-
constructor(asyncQueue, datastore, updateFunction, deferred) {
|
|
24037
|
+
constructor(asyncQueue, datastore, options, updateFunction, deferred) {
|
|
23921
24038
|
this.asyncQueue = asyncQueue;
|
|
23922
24039
|
this.datastore = datastore;
|
|
24040
|
+
this.options = options;
|
|
23923
24041
|
this.updateFunction = updateFunction;
|
|
23924
24042
|
this.deferred = deferred;
|
|
23925
|
-
this.attemptsRemaining =
|
|
24043
|
+
this.attemptsRemaining = options.maxAttempts;
|
|
23926
24044
|
this.backoff = new ExponentialBackoff(this.asyncQueue, "transaction_retry" /* TransactionRetry */);
|
|
23927
24045
|
}
|
|
23928
24046
|
/** Runs the transaction and sets the result on deferred. */
|
|
@@ -24280,11 +24398,11 @@ function firestoreClientAddSnapshotsInSyncListener(client, observer) {
|
|
|
24280
24398
|
* been committed. For this reason, it is required that all reads are
|
|
24281
24399
|
* performed before any writes. Transactions must be performed while online.
|
|
24282
24400
|
*/
|
|
24283
|
-
function firestoreClientTransaction(client, updateFunction) {
|
|
24401
|
+
function firestoreClientTransaction(client, updateFunction, options) {
|
|
24284
24402
|
const deferred = new Deferred();
|
|
24285
24403
|
client.asyncQueue.enqueueAndForget(async () => {
|
|
24286
24404
|
const datastore = await getDatastore(client);
|
|
24287
|
-
new TransactionRunner(client.asyncQueue, datastore, updateFunction, deferred).run();
|
|
24405
|
+
new TransactionRunner(client.asyncQueue, datastore, options, updateFunction, deferred).run();
|
|
24288
24406
|
});
|
|
24289
24407
|
return deferred.promise;
|
|
24290
24408
|
}
|
|
@@ -27041,7 +27159,8 @@ function snapshotEqual(left, right) {
|
|
|
27041
27159
|
* limitations under the License.
|
|
27042
27160
|
*/
|
|
27043
27161
|
function validateHasExplicitOrderByForLimitToLast(query) {
|
|
27044
|
-
if (
|
|
27162
|
+
if (query.limitType === "L" /* Last */ &&
|
|
27163
|
+
query.explicitOrderBy.length === 0) {
|
|
27045
27164
|
throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
|
|
27046
27165
|
}
|
|
27047
27166
|
}
|
|
@@ -27483,6 +27602,31 @@ function validateOrderByAndInequalityMatch(baseQuery, inequality, orderBy) {
|
|
|
27483
27602
|
}
|
|
27484
27603
|
}
|
|
27485
27604
|
|
|
27605
|
+
/**
|
|
27606
|
+
* @license
|
|
27607
|
+
* Copyright 2022 Google LLC
|
|
27608
|
+
*
|
|
27609
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
27610
|
+
* you may not use this file except in compliance with the License.
|
|
27611
|
+
* You may obtain a copy of the License at
|
|
27612
|
+
*
|
|
27613
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
27614
|
+
*
|
|
27615
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
27616
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
27617
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
27618
|
+
* See the License for the specific language governing permissions and
|
|
27619
|
+
* limitations under the License.
|
|
27620
|
+
*/
|
|
27621
|
+
const DEFAULT_TRANSACTION_OPTIONS = {
|
|
27622
|
+
maxAttempts: 5
|
|
27623
|
+
};
|
|
27624
|
+
function validateTransactionOptions(options) {
|
|
27625
|
+
if (options.maxAttempts < 1) {
|
|
27626
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Max attempts must be at least 1');
|
|
27627
|
+
}
|
|
27628
|
+
}
|
|
27629
|
+
|
|
27486
27630
|
/**
|
|
27487
27631
|
* @license
|
|
27488
27632
|
* Copyright 2020 Google LLC
|
|
@@ -28204,10 +28348,12 @@ class Transaction extends Transaction$1 {
|
|
|
28204
28348
|
* `updateFunction `is returned here. Otherwise, if the transaction failed, a
|
|
28205
28349
|
* rejected promise with the corresponding failure error is returned.
|
|
28206
28350
|
*/
|
|
28207
|
-
function runTransaction(firestore, updateFunction) {
|
|
28351
|
+
function runTransaction(firestore, updateFunction, options) {
|
|
28208
28352
|
firestore = cast(firestore, Firestore);
|
|
28353
|
+
const optionsWithDefaults = Object.assign(Object.assign({}, DEFAULT_TRANSACTION_OPTIONS), options);
|
|
28354
|
+
validateTransactionOptions(optionsWithDefaults);
|
|
28209
28355
|
const client = ensureFirestoreConfigured(firestore);
|
|
28210
|
-
return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)));
|
|
28356
|
+
return firestoreClientTransaction(client, internalTransaction => updateFunction(new Transaction(firestore, internalTransaction)), optionsWithDefaults);
|
|
28211
28357
|
}
|
|
28212
28358
|
|
|
28213
28359
|
/**
|