@firebase/firestore 3.4.7 → 3.4.8-canary.19e31a231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/firestore/src/core/query.d.ts +2 -4
- package/dist/firestore/src/core/target.d.ts +6 -4
- package/dist/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/firestore/src/model/collections.d.ts +1 -1
- package/dist/firestore/src/model/field_index.d.ts +6 -0
- package/dist/firestore/src/model/values.d.ts +14 -2
- package/dist/firestore/src/util/misc.d.ts +3 -0
- package/dist/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/index.esm2017.js +4649 -4498
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +4941 -4800
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1764 -1577
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1764 -1577
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +4495 -4344
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +26 -4
- package/dist/lite/firestore/src/core/query.d.ts +2 -4
- package/dist/lite/firestore/src/core/target.d.ts +6 -4
- package/dist/lite/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/lite/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/lite/firestore/src/model/collections.d.ts +1 -1
- package/dist/lite/firestore/src/model/field_index.d.ts +6 -0
- package/dist/lite/firestore/src/model/values.d.ts +14 -2
- package/dist/lite/firestore/src/util/misc.d.ts +3 -0
- package/dist/lite/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/lite/index.browser.esm2017.js +123 -117
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +203 -197
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +24 -10
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +24 -10
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +1155 -1149
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/packages/firestore/src/core/query.d.ts +2 -4
- package/dist/lite/packages/firestore/src/core/target.d.ts +6 -4
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/lite/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/lite/packages/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/lite/packages/firestore/src/model/collections.d.ts +1 -1
- package/dist/lite/packages/firestore/src/model/field_index.d.ts +6 -0
- package/dist/lite/packages/firestore/src/model/values.d.ts +14 -2
- package/dist/lite/packages/firestore/src/util/misc.d.ts +3 -0
- package/dist/lite/packages/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/packages/firestore/dist/index.esm2017.d.ts +171 -171
- package/dist/packages/firestore/src/core/query.d.ts +2 -4
- package/dist/packages/firestore/src/core/target.d.ts +6 -4
- package/dist/packages/firestore/src/local/index_manager.d.ts +27 -5
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +15 -10
- package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +9 -0
- package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +8 -2
- package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +9 -2
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +1 -1
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +6 -4
- package/dist/packages/firestore/src/local/query_engine.d.ts +44 -5
- package/dist/packages/firestore/src/local/simple_db.d.ts +2 -0
- package/dist/packages/firestore/src/model/collections.d.ts +1 -1
- package/dist/packages/firestore/src/model/field_index.d.ts +6 -0
- package/dist/packages/firestore/src/model/values.d.ts +14 -2
- package/dist/packages/firestore/src/util/misc.d.ts +3 -0
- package/dist/packages/firestore/test/unit/local/counting_query_engine.d.ts +2 -1
- package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +5 -4
- package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +0 -4
- package/dist/private.d.ts +26 -4
- package/package.json +9 -9
package/dist/index.node.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.8-canary.19e31a231";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
61
61
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
62
62
|
User.MOCK_USER = new User('mock-user');
|
|
63
63
|
|
|
64
|
-
const version = "9.6.
|
|
64
|
+
const version = "9.6.11-canary.19e31a231";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @license
|
|
@@ -1393,13 +1393,14 @@ const DbIndexEntryKeyPath = [
|
|
|
1393
1393
|
'uid',
|
|
1394
1394
|
'arrayValue',
|
|
1395
1395
|
'directionalValue',
|
|
1396
|
+
'orderedDocumentKey',
|
|
1396
1397
|
'documentKey'
|
|
1397
1398
|
];
|
|
1398
1399
|
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1399
1400
|
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1400
1401
|
'indexId',
|
|
1401
1402
|
'uid',
|
|
1402
|
-
'
|
|
1403
|
+
'orderedDocumentKey'
|
|
1403
1404
|
];
|
|
1404
1405
|
/** Name of the IndexedDb object store. */
|
|
1405
1406
|
const DbDocumentOverlayStore = 'documentOverlays';
|
|
@@ -2285,9 +2286,7 @@ class SimpleDbStore {
|
|
|
2285
2286
|
cursor.continue(controller.skipToKey);
|
|
2286
2287
|
}
|
|
2287
2288
|
};
|
|
2288
|
-
}).next(() =>
|
|
2289
|
-
return PersistencePromise.waitFor(results);
|
|
2290
|
-
});
|
|
2289
|
+
}).next(() => PersistencePromise.waitFor(results));
|
|
2291
2290
|
}
|
|
2292
2291
|
options(indexOrRange, range) {
|
|
2293
2292
|
let indexName = undefined;
|
|
@@ -3322,7 +3321,7 @@ function typeOrder(value) {
|
|
|
3322
3321
|
return 4 /* ServerTimestampValue */;
|
|
3323
3322
|
}
|
|
3324
3323
|
else if (isMaxValue(value)) {
|
|
3325
|
-
return
|
|
3324
|
+
return 9007199254740991 /* MaxValue */;
|
|
3326
3325
|
}
|
|
3327
3326
|
return 10 /* ObjectValue */;
|
|
3328
3327
|
}
|
|
@@ -3534,6 +3533,15 @@ function compareArrays(left, right) {
|
|
|
3534
3533
|
return primitiveComparator(leftArray.length, rightArray.length);
|
|
3535
3534
|
}
|
|
3536
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
|
+
}
|
|
3537
3545
|
const leftMap = left.fields || {};
|
|
3538
3546
|
const leftKeys = Object.keys(leftMap);
|
|
3539
3547
|
const rightMap = right.fields || {};
|
|
@@ -3781,27 +3789,31 @@ function valuesGetUpperBound(value) {
|
|
|
3781
3789
|
return fail();
|
|
3782
3790
|
}
|
|
3783
3791
|
}
|
|
3784
|
-
function
|
|
3785
|
-
|
|
3786
|
-
|
|
3792
|
+
function lowerBoundCompare(left, right) {
|
|
3793
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3794
|
+
if (cmp !== 0) {
|
|
3795
|
+
return cmp;
|
|
3787
3796
|
}
|
|
3788
|
-
|
|
3789
|
-
return
|
|
3797
|
+
if (left.inclusive && !right.inclusive) {
|
|
3798
|
+
return -1;
|
|
3790
3799
|
}
|
|
3791
|
-
else {
|
|
3792
|
-
return
|
|
3800
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3801
|
+
return 1;
|
|
3793
3802
|
}
|
|
3803
|
+
return 0;
|
|
3794
3804
|
}
|
|
3795
|
-
function
|
|
3796
|
-
|
|
3797
|
-
|
|
3805
|
+
function upperBoundCompare(left, right) {
|
|
3806
|
+
const cmp = valueCompare(left.value, right.value);
|
|
3807
|
+
if (cmp !== 0) {
|
|
3808
|
+
return cmp;
|
|
3798
3809
|
}
|
|
3799
|
-
|
|
3800
|
-
return
|
|
3810
|
+
if (left.inclusive && !right.inclusive) {
|
|
3811
|
+
return 1;
|
|
3801
3812
|
}
|
|
3802
|
-
else {
|
|
3803
|
-
return
|
|
3813
|
+
else if (!left.inclusive && right.inclusive) {
|
|
3814
|
+
return -1;
|
|
3804
3815
|
}
|
|
3816
|
+
return 0;
|
|
3805
3817
|
}
|
|
3806
3818
|
|
|
3807
3819
|
/**
|
|
@@ -4196,6 +4208,17 @@ function fieldIndexGetArraySegment(fieldIndex) {
|
|
|
4196
4208
|
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4197
4209
|
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4198
4210
|
}
|
|
4211
|
+
/**
|
|
4212
|
+
* Returns the order of the document key component for the given index.
|
|
4213
|
+
*
|
|
4214
|
+
* PORTING NOTE: This is only used in the Web IndexedDb implementation.
|
|
4215
|
+
*/
|
|
4216
|
+
function fieldIndexGetKeyOrder(fieldIndex) {
|
|
4217
|
+
const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
|
|
4218
|
+
return directionalSegments.length === 0
|
|
4219
|
+
? 0 /* ASCENDING */
|
|
4220
|
+
: directionalSegments[directionalSegments.length - 1].kind;
|
|
4221
|
+
}
|
|
4199
4222
|
/** Returns a debug representation of the field index */
|
|
4200
4223
|
function fieldIndexToString(fieldIndex) {
|
|
4201
4224
|
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
@@ -4300,7 +4323,7 @@ function indexOffsetComparator(left, right) {
|
|
|
4300
4323
|
|
|
4301
4324
|
/**
|
|
4302
4325
|
* @license
|
|
4303
|
-
* Copyright
|
|
4326
|
+
* Copyright 2017 Google LLC
|
|
4304
4327
|
*
|
|
4305
4328
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4306
4329
|
* you may not use this file except in compliance with the License.
|
|
@@ -4314,589 +4337,445 @@ function indexOffsetComparator(left, right) {
|
|
|
4314
4337
|
* See the License for the specific language governing permissions and
|
|
4315
4338
|
* limitations under the License.
|
|
4316
4339
|
*/
|
|
4317
|
-
//
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
this.
|
|
4322
|
-
this.
|
|
4323
|
-
this.filters = filters;
|
|
4324
|
-
this.limit = limit;
|
|
4325
|
-
this.startAt = startAt;
|
|
4326
|
-
this.endAt = endAt;
|
|
4327
|
-
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;
|
|
4328
4346
|
}
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
* NOTE: you should always construct `Target` from `Query.toTarget` instead of
|
|
4335
|
-
* using this factory method, because `Query` provides an implicit `orderBy`
|
|
4336
|
-
* property.
|
|
4337
|
-
*/
|
|
4338
|
-
function newTarget(path, collectionGroup = null, orderBy = [], filters = [], limit = null, startAt = null, endAt = null) {
|
|
4339
|
-
return new TargetImpl(path, collectionGroup, orderBy, filters, limit, startAt, endAt);
|
|
4340
|
-
}
|
|
4341
|
-
function canonifyTarget(target) {
|
|
4342
|
-
const targetImpl = debugCast(target);
|
|
4343
|
-
if (targetImpl.memoizedCanonicalId === null) {
|
|
4344
|
-
let str = targetImpl.path.canonicalString();
|
|
4345
|
-
if (targetImpl.collectionGroup !== null) {
|
|
4346
|
-
str += '|cg:' + targetImpl.collectionGroup;
|
|
4347
|
-
}
|
|
4348
|
-
str += '|f:';
|
|
4349
|
-
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
|
|
4350
|
-
str += '|ob:';
|
|
4351
|
-
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
|
|
4352
|
-
if (!isNullOrUndefined(targetImpl.limit)) {
|
|
4353
|
-
str += '|l:';
|
|
4354
|
-
str += targetImpl.limit;
|
|
4355
|
-
}
|
|
4356
|
-
if (targetImpl.startAt) {
|
|
4357
|
-
str += '|lb:';
|
|
4358
|
-
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
|
|
4359
|
-
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
|
|
4360
|
-
}
|
|
4361
|
-
if (targetImpl.endAt) {
|
|
4362
|
-
str += '|ub:';
|
|
4363
|
-
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
4364
|
-
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4365
|
-
}
|
|
4366
|
-
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));
|
|
4367
4352
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
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));
|
|
4374
4358
|
}
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
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;
|
|
4379
4375
|
}
|
|
4380
|
-
if
|
|
4381
|
-
|
|
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;
|
|
4382
4398
|
}
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
.map(o => stringifyOrderBy(o))
|
|
4386
|
-
.join(', ')}]`;
|
|
4399
|
+
isEmpty() {
|
|
4400
|
+
return this.root.isEmpty();
|
|
4387
4401
|
}
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
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;
|
|
4392
4405
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
str += target.endAt.position.map(p => canonicalId(p)).join(',');
|
|
4406
|
+
// Returns the minimum key in the map.
|
|
4407
|
+
minKey() {
|
|
4408
|
+
return this.root.minKey();
|
|
4397
4409
|
}
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
if (left.limit !== right.limit) {
|
|
4402
|
-
return false;
|
|
4410
|
+
// Returns the maximum key in the map.
|
|
4411
|
+
maxKey() {
|
|
4412
|
+
return this.root.maxKey();
|
|
4403
4413
|
}
|
|
4404
|
-
|
|
4405
|
-
|
|
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);
|
|
4406
4420
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4421
|
+
forEach(fn) {
|
|
4422
|
+
this.inorderTraversal((k, v) => {
|
|
4423
|
+
fn(k, v);
|
|
4409
4424
|
return false;
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
if (left.filters.length !== right.filters.length) {
|
|
4413
|
-
return false;
|
|
4425
|
+
});
|
|
4414
4426
|
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4427
|
+
toString() {
|
|
4428
|
+
const descriptions = [];
|
|
4429
|
+
this.inorderTraversal((k, v) => {
|
|
4430
|
+
descriptions.push(`${k}:${v}`);
|
|
4417
4431
|
return false;
|
|
4418
|
-
}
|
|
4432
|
+
});
|
|
4433
|
+
return `{${descriptions.join(', ')}}`;
|
|
4419
4434
|
}
|
|
4420
|
-
|
|
4421
|
-
|
|
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);
|
|
4422
4442
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4443
|
+
// Returns an iterator over the SortedMap.
|
|
4444
|
+
getIterator() {
|
|
4445
|
+
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
4425
4446
|
}
|
|
4426
|
-
|
|
4427
|
-
return false;
|
|
4447
|
+
getIteratorFrom(key) {
|
|
4448
|
+
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
4428
4449
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
function targetIsDocumentTarget(target) {
|
|
4432
|
-
return (DocumentKey.isDocumentKey(target.path) &&
|
|
4433
|
-
target.collectionGroup === null &&
|
|
4434
|
-
target.filters.length === 0);
|
|
4435
|
-
}
|
|
4436
|
-
/** Returns the field filters that target the given field path. */
|
|
4437
|
-
function targetGetFieldFiltersForPath(target, path) {
|
|
4438
|
-
return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
|
|
4439
|
-
}
|
|
4440
|
-
/**
|
|
4441
|
-
* Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
|
|
4442
|
-
* filters. Returns `null` if there are no such filters.
|
|
4443
|
-
*/
|
|
4444
|
-
function targetGetArrayValues(target, fieldIndex) {
|
|
4445
|
-
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
4446
|
-
if (segment === undefined) {
|
|
4447
|
-
return null;
|
|
4450
|
+
getReverseIterator() {
|
|
4451
|
+
return new SortedMapIterator(this.root, null, this.comparator, true);
|
|
4448
4452
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
|
|
4452
|
-
return fieldFilter.value.arrayValue.values || [];
|
|
4453
|
-
case "array-contains" /* ARRAY_CONTAINS */:
|
|
4454
|
-
return [fieldFilter.value];
|
|
4455
|
-
// Remaining filters are not array filters.
|
|
4456
|
-
}
|
|
4453
|
+
getReverseIteratorFrom(key) {
|
|
4454
|
+
return new SortedMapIterator(this.root, key, this.comparator, true);
|
|
4457
4455
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
case "in" /* IN */:
|
|
4471
|
-
// Encode equality prefix, which is encoded in the index value before
|
|
4472
|
-
// the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
|
|
4473
|
-
// `value != 'ab'`).
|
|
4474
|
-
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4475
|
-
break;
|
|
4476
|
-
case "not-in" /* NOT_IN */:
|
|
4477
|
-
case "!=" /* NOT_EQUAL */:
|
|
4478
|
-
// NotIn/NotEqual is always a suffix. There cannot be any remaining
|
|
4479
|
-
// segments and hence we can return early here.
|
|
4480
|
-
values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
|
|
4481
|
-
return Array.from(values.values());
|
|
4482
|
-
// Remaining filters cannot be used as notIn bounds.
|
|
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;
|
|
4483
4468
|
}
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
filterValue = valuesGetLowerBound(fieldFilter.value);
|
|
4509
|
-
break;
|
|
4510
|
-
case "==" /* EQUAL */:
|
|
4511
|
-
case "in" /* IN */:
|
|
4512
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4513
|
-
filterValue = fieldFilter.value;
|
|
4514
|
-
break;
|
|
4515
|
-
case ">" /* GREATER_THAN */:
|
|
4516
|
-
filterValue = fieldFilter.value;
|
|
4517
|
-
filterInclusive = false;
|
|
4518
|
-
break;
|
|
4519
|
-
case "!=" /* NOT_EQUAL */:
|
|
4520
|
-
case "not-in" /* NOT_IN */:
|
|
4521
|
-
filterValue = MIN_VALUE;
|
|
4522
|
-
break;
|
|
4523
|
-
// Remaining filters cannot be used as lower bounds.
|
|
4524
|
-
}
|
|
4525
|
-
if (valuesMax(segmentValue, filterValue) === filterValue) {
|
|
4526
|
-
segmentValue = filterValue;
|
|
4527
|
-
segmentInclusive = filterInclusive;
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
// If there is a startAt bound, compare the values against the existing
|
|
4531
|
-
// boundary to see if we can narrow the scope.
|
|
4532
|
-
if (target.startAt !== null) {
|
|
4533
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4534
|
-
const orderBy = target.orderBy[i];
|
|
4535
|
-
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4536
|
-
const cursorValue = target.startAt.position[i];
|
|
4537
|
-
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
|
|
4538
|
-
segmentValue = cursorValue;
|
|
4539
|
-
segmentInclusive = target.startAt.inclusive;
|
|
4540
|
-
}
|
|
4541
|
-
break;
|
|
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;
|
|
4542
4493
|
}
|
|
4543
4494
|
}
|
|
4544
4495
|
}
|
|
4545
|
-
if (segmentValue === undefined) {
|
|
4546
|
-
// No lower bound exists
|
|
4547
|
-
return null;
|
|
4548
|
-
}
|
|
4549
|
-
values.push(segmentValue);
|
|
4550
|
-
inclusive && (inclusive = segmentInclusive);
|
|
4551
4496
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
const values = [];
|
|
4561
|
-
let inclusive = true;
|
|
4562
|
-
// For each segment, retrieve an upper bound if there is a suitable filter or
|
|
4563
|
-
// endAt.
|
|
4564
|
-
for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
|
|
4565
|
-
let segmentValue = undefined;
|
|
4566
|
-
let segmentInclusive = true;
|
|
4567
|
-
// Process all filters to find a value for the current field segment
|
|
4568
|
-
for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
|
|
4569
|
-
let filterValue = undefined;
|
|
4570
|
-
let filterInclusive = true;
|
|
4571
|
-
switch (fieldFilter.op) {
|
|
4572
|
-
case ">=" /* GREATER_THAN_OR_EQUAL */:
|
|
4573
|
-
case ">" /* GREATER_THAN */:
|
|
4574
|
-
filterValue = valuesGetUpperBound(fieldFilter.value);
|
|
4575
|
-
filterInclusive = false;
|
|
4576
|
-
break;
|
|
4577
|
-
case "==" /* EQUAL */:
|
|
4578
|
-
case "in" /* IN */:
|
|
4579
|
-
case "<=" /* LESS_THAN_OR_EQUAL */:
|
|
4580
|
-
filterValue = fieldFilter.value;
|
|
4581
|
-
break;
|
|
4582
|
-
case "<" /* LESS_THAN */:
|
|
4583
|
-
filterValue = fieldFilter.value;
|
|
4584
|
-
filterInclusive = false;
|
|
4585
|
-
break;
|
|
4586
|
-
case "!=" /* NOT_EQUAL */:
|
|
4587
|
-
case "not-in" /* NOT_IN */:
|
|
4588
|
-
filterValue = MAX_VALUE;
|
|
4589
|
-
break;
|
|
4590
|
-
// Remaining filters cannot be used as upper bounds.
|
|
4591
|
-
}
|
|
4592
|
-
if (valuesMin(segmentValue, filterValue) === filterValue) {
|
|
4593
|
-
segmentValue = filterValue;
|
|
4594
|
-
segmentInclusive = filterInclusive;
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
// If there is a endAt bound, compare the values against the existing
|
|
4598
|
-
// boundary to see if we can narrow the scope.
|
|
4599
|
-
if (target.endAt !== null) {
|
|
4600
|
-
for (let i = 0; i < target.orderBy.length; ++i) {
|
|
4601
|
-
const orderBy = target.orderBy[i];
|
|
4602
|
-
if (orderBy.field.isEqual(segment.fieldPath)) {
|
|
4603
|
-
const cursorValue = target.endAt.position[i];
|
|
4604
|
-
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
|
|
4605
|
-
segmentValue = cursorValue;
|
|
4606
|
-
segmentInclusive = target.endAt.inclusive;
|
|
4607
|
-
}
|
|
4608
|
-
break;
|
|
4609
|
-
}
|
|
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;
|
|
4610
4505
|
}
|
|
4611
4506
|
}
|
|
4612
|
-
|
|
4613
|
-
|
|
4507
|
+
else {
|
|
4508
|
+
node = node.right;
|
|
4509
|
+
while (!node.isEmpty()) {
|
|
4510
|
+
this.nodeStack.push(node);
|
|
4511
|
+
node = node.left;
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
return result;
|
|
4515
|
+
}
|
|
4516
|
+
hasNext() {
|
|
4517
|
+
return this.nodeStack.length > 0;
|
|
4518
|
+
}
|
|
4519
|
+
peek() {
|
|
4520
|
+
if (this.nodeStack.length === 0) {
|
|
4614
4521
|
return null;
|
|
4615
4522
|
}
|
|
4616
|
-
|
|
4617
|
-
|
|
4523
|
+
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
4524
|
+
return { key: node.key, value: node.value };
|
|
4618
4525
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
class
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
constructor(field, op, value) {
|
|
4625
|
-
super();
|
|
4626
|
-
this.field = field;
|
|
4627
|
-
this.op = op;
|
|
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;
|
|
4628
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;
|
|
4629
4536
|
}
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
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;
|
|
4641
4566
|
}
|
|
4642
|
-
else
|
|
4643
|
-
return
|
|
4567
|
+
else {
|
|
4568
|
+
return this.left.min();
|
|
4644
4569
|
}
|
|
4645
|
-
|
|
4646
|
-
|
|
4570
|
+
}
|
|
4571
|
+
// Returns the maximum key in the tree.
|
|
4572
|
+
minKey() {
|
|
4573
|
+
return this.min().key;
|
|
4574
|
+
}
|
|
4575
|
+
// Returns the maximum key in the tree.
|
|
4576
|
+
maxKey() {
|
|
4577
|
+
if (this.right.isEmpty()) {
|
|
4578
|
+
return this.key;
|
|
4647
4579
|
}
|
|
4648
|
-
else
|
|
4649
|
-
return
|
|
4580
|
+
else {
|
|
4581
|
+
return this.right.maxKey();
|
|
4650
4582
|
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4583
|
+
}
|
|
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);
|
|
4653
4593
|
}
|
|
4654
4594
|
else {
|
|
4655
|
-
|
|
4595
|
+
n = n.copy(null, null, null, null, n.right.insert(key, value, comparator));
|
|
4656
4596
|
}
|
|
4597
|
+
return n.fixUp();
|
|
4657
4598
|
}
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
: new KeyFieldNotInFilter(field, value);
|
|
4662
|
-
}
|
|
4663
|
-
matches(doc) {
|
|
4664
|
-
const other = doc.data.field(this.field);
|
|
4665
|
-
// Types do not have to match in NOT_EQUAL filters.
|
|
4666
|
-
if (this.op === "!=" /* NOT_EQUAL */) {
|
|
4667
|
-
return (other !== null &&
|
|
4668
|
-
this.matchesComparison(valueCompare(other, this.value)));
|
|
4599
|
+
removeMin() {
|
|
4600
|
+
if (this.left.isEmpty()) {
|
|
4601
|
+
return LLRBNode.EMPTY;
|
|
4669
4602
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
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();
|
|
4674
4609
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
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();
|
|
4617
|
+
}
|
|
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
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
n = n.copy(null, null, null, null, n.right.remove(key, comparator));
|
|
4691
4637
|
}
|
|
4638
|
+
return n.fixUp();
|
|
4692
4639
|
}
|
|
4693
|
-
|
|
4694
|
-
return
|
|
4695
|
-
"<" /* LESS_THAN */,
|
|
4696
|
-
"<=" /* LESS_THAN_OR_EQUAL */,
|
|
4697
|
-
">" /* GREATER_THAN */,
|
|
4698
|
-
">=" /* GREATER_THAN_OR_EQUAL */,
|
|
4699
|
-
"!=" /* NOT_EQUAL */,
|
|
4700
|
-
"not-in" /* NOT_IN */
|
|
4701
|
-
].indexOf(this.op) >= 0);
|
|
4640
|
+
isRed() {
|
|
4641
|
+
return this.color;
|
|
4702
4642
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
}
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
}
|
|
4717
|
-
/** Returns a debug description for `filter`. */
|
|
4718
|
-
function stringifyFilter(filter) {
|
|
4719
|
-
return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(filter.value)}`;
|
|
4720
|
-
}
|
|
4721
|
-
/** Filter that matches on key fields (i.e. '__name__'). */
|
|
4722
|
-
class KeyFieldFilter extends FieldFilter {
|
|
4723
|
-
constructor(field, op, value) {
|
|
4724
|
-
super(field, op, value);
|
|
4725
|
-
this.key = DocumentKey.fromName(value.referenceValue);
|
|
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();
|
|
4648
|
+
}
|
|
4649
|
+
if (n.left.isRed() && n.left.left.isRed()) {
|
|
4650
|
+
n = n.rotateRight();
|
|
4651
|
+
}
|
|
4652
|
+
if (n.left.isRed() && n.right.isRed()) {
|
|
4653
|
+
n = n.colorFlip();
|
|
4654
|
+
}
|
|
4655
|
+
return n;
|
|
4726
4656
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
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();
|
|
4663
|
+
}
|
|
4664
|
+
return n;
|
|
4730
4665
|
}
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4666
|
+
moveRedRight() {
|
|
4667
|
+
let n = this.colorFlip();
|
|
4668
|
+
if (n.left.left.isRed()) {
|
|
4669
|
+
n = n.rotateRight();
|
|
4670
|
+
n = n.colorFlip();
|
|
4671
|
+
}
|
|
4672
|
+
return n;
|
|
4737
4673
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
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);
|
|
4740
4677
|
}
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
constructor(field, value) {
|
|
4745
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4746
|
-
this.keys = extractDocumentKeysFromArrayValue("not-in" /* NOT_IN */, value);
|
|
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);
|
|
4747
4681
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
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);
|
|
4750
4686
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
constructor(field, value) {
|
|
4761
|
-
super(field, "array-contains" /* ARRAY_CONTAINS */, value);
|
|
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;
|
|
4695
|
+
}
|
|
4762
4696
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
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);
|
|
4712
|
+
}
|
|
4766
4713
|
}
|
|
4767
|
-
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
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;
|
|
4772
4724
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
4725
|
+
get key() {
|
|
4726
|
+
throw fail();
|
|
4776
4727
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
class NotInFilter extends FieldFilter {
|
|
4780
|
-
constructor(field, value) {
|
|
4781
|
-
super(field, "not-in" /* NOT_IN */, value);
|
|
4728
|
+
get value() {
|
|
4729
|
+
throw fail();
|
|
4782
4730
|
}
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
return false;
|
|
4786
|
-
}
|
|
4787
|
-
const other = doc.data.field(this.field);
|
|
4788
|
-
return other !== null && !arrayValueContains(this.value.arrayValue, other);
|
|
4731
|
+
get color() {
|
|
4732
|
+
throw fail();
|
|
4789
4733
|
}
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
class ArrayContainsAnyFilter extends FieldFilter {
|
|
4793
|
-
constructor(field, value) {
|
|
4794
|
-
super(field, "array-contains-any" /* ARRAY_CONTAINS_ANY */, value);
|
|
4734
|
+
get left() {
|
|
4735
|
+
throw fail();
|
|
4795
4736
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
if (!isArray(other) || !other.arrayValue.values) {
|
|
4799
|
-
return false;
|
|
4800
|
-
}
|
|
4801
|
-
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
4737
|
+
get right() {
|
|
4738
|
+
throw fail();
|
|
4802
4739
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
*
|
|
4807
|
-
* The bound is specified with the given components representing a position and
|
|
4808
|
-
* whether it's just before or just after the position (relative to whatever the
|
|
4809
|
-
* query order is).
|
|
4810
|
-
*
|
|
4811
|
-
* The position represents a logical index position for a query. It's a prefix
|
|
4812
|
-
* of values for the (potentially implicit) order by clauses of a query.
|
|
4813
|
-
*
|
|
4814
|
-
* Bound provides a function to determine whether a document comes before or
|
|
4815
|
-
* after a bound. This is influenced by whether the position is just before or
|
|
4816
|
-
* just after the provided values.
|
|
4817
|
-
*/
|
|
4818
|
-
class Bound {
|
|
4819
|
-
constructor(position, inclusive) {
|
|
4820
|
-
this.position = position;
|
|
4821
|
-
this.inclusive = inclusive;
|
|
4740
|
+
// Returns a copy of the current node.
|
|
4741
|
+
copy(key, value, color, left, right) {
|
|
4742
|
+
return this;
|
|
4822
4743
|
}
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
*/
|
|
4827
|
-
class OrderBy {
|
|
4828
|
-
constructor(field, dir = "asc" /* ASCENDING */) {
|
|
4829
|
-
this.field = field;
|
|
4830
|
-
this.dir = dir;
|
|
4744
|
+
// Returns a copy of the tree, with the specified key/value added.
|
|
4745
|
+
insert(key, value, comparator) {
|
|
4746
|
+
return new LLRBNode(key, value);
|
|
4831
4747
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
return orderBy.field.canonicalString() + orderBy.dir;
|
|
4836
|
-
}
|
|
4837
|
-
function stringifyOrderBy(orderBy) {
|
|
4838
|
-
return `${orderBy.field.canonicalString()} (${orderBy.dir})`;
|
|
4839
|
-
}
|
|
4840
|
-
function orderByEquals(left, right) {
|
|
4841
|
-
return left.dir === right.dir && left.field.isEqual(right.field);
|
|
4842
|
-
}
|
|
4843
|
-
function boundCompareToDocument(bound, orderBy, doc) {
|
|
4844
|
-
let comparison = 0;
|
|
4845
|
-
for (let i = 0; i < bound.position.length; i++) {
|
|
4846
|
-
const orderByComponent = orderBy[i];
|
|
4847
|
-
const component = bound.position[i];
|
|
4848
|
-
if (orderByComponent.field.isKeyField()) {
|
|
4849
|
-
comparison = DocumentKey.comparator(DocumentKey.fromName(component.referenceValue), doc.key);
|
|
4850
|
-
}
|
|
4851
|
-
else {
|
|
4852
|
-
const docValue = doc.data.field(orderByComponent.field);
|
|
4853
|
-
comparison = valueCompare(component, docValue);
|
|
4854
|
-
}
|
|
4855
|
-
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
4856
|
-
comparison = comparison * -1;
|
|
4857
|
-
}
|
|
4858
|
-
if (comparison !== 0) {
|
|
4859
|
-
break;
|
|
4860
|
-
}
|
|
4748
|
+
// Returns a copy of the tree, with the specified key removed.
|
|
4749
|
+
remove(key, comparator) {
|
|
4750
|
+
return this;
|
|
4861
4751
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
/**
|
|
4865
|
-
* Returns true if a document sorts after a bound using the provided sort
|
|
4866
|
-
* order.
|
|
4867
|
-
*/
|
|
4868
|
-
function boundSortsAfterDocument(bound, orderBy, doc) {
|
|
4869
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4870
|
-
return bound.inclusive ? comparison >= 0 : comparison > 0;
|
|
4871
|
-
}
|
|
4872
|
-
/**
|
|
4873
|
-
* Returns true if a document sorts before a bound using the provided sort
|
|
4874
|
-
* order.
|
|
4875
|
-
*/
|
|
4876
|
-
function boundSortsBeforeDocument(bound, orderBy, doc) {
|
|
4877
|
-
const comparison = boundCompareToDocument(bound, orderBy, doc);
|
|
4878
|
-
return bound.inclusive ? comparison <= 0 : comparison < 0;
|
|
4879
|
-
}
|
|
4880
|
-
function boundEquals(left, right) {
|
|
4881
|
-
if (left === null) {
|
|
4882
|
-
return right === null;
|
|
4752
|
+
isEmpty() {
|
|
4753
|
+
return true;
|
|
4883
4754
|
}
|
|
4884
|
-
|
|
4755
|
+
inorderTraversal(action) {
|
|
4885
4756
|
return false;
|
|
4886
4757
|
}
|
|
4887
|
-
|
|
4888
|
-
left.position.length !== right.position.length) {
|
|
4758
|
+
reverseTraversal(action) {
|
|
4889
4759
|
return false;
|
|
4890
4760
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
const rightPosition = right.position[i];
|
|
4894
|
-
if (!valueEquals(leftPosition, rightPosition)) {
|
|
4895
|
-
return false;
|
|
4896
|
-
}
|
|
4761
|
+
minKey() {
|
|
4762
|
+
return null;
|
|
4897
4763
|
}
|
|
4898
|
-
|
|
4899
|
-
|
|
4764
|
+
maxKey() {
|
|
4765
|
+
return null;
|
|
4766
|
+
}
|
|
4767
|
+
isRed() {
|
|
4768
|
+
return false;
|
|
4769
|
+
}
|
|
4770
|
+
// For testing.
|
|
4771
|
+
checkMaxDepth() {
|
|
4772
|
+
return true;
|
|
4773
|
+
}
|
|
4774
|
+
check() {
|
|
4775
|
+
return 0;
|
|
4776
|
+
}
|
|
4777
|
+
} // end LLRBEmptyNode
|
|
4778
|
+
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
4900
4779
|
|
|
4901
4780
|
/**
|
|
4902
4781
|
* @license
|
|
@@ -4915,325 +4794,223 @@ function boundEquals(left, right) {
|
|
|
4915
4794
|
* limitations under the License.
|
|
4916
4795
|
*/
|
|
4917
4796
|
/**
|
|
4918
|
-
*
|
|
4919
|
-
*
|
|
4920
|
-
* query the RemoteStore results.
|
|
4797
|
+
* SortedSet is an immutable (copy-on-write) collection that holds elements
|
|
4798
|
+
* in order specified by the provided comparator.
|
|
4921
4799
|
*
|
|
4922
|
-
*
|
|
4800
|
+
* NOTE: if provided comparator returns 0 for two elements, we consider them to
|
|
4801
|
+
* be equal!
|
|
4923
4802
|
*/
|
|
4924
|
-
class
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
*/
|
|
4929
|
-
constructor(path, collectionGroup = null, explicitOrderBy = [], filters = [], limit = null, limitType = "F" /* First */, startAt = null, endAt = null) {
|
|
4930
|
-
this.path = path;
|
|
4931
|
-
this.collectionGroup = collectionGroup;
|
|
4932
|
-
this.explicitOrderBy = explicitOrderBy;
|
|
4933
|
-
this.filters = filters;
|
|
4934
|
-
this.limit = limit;
|
|
4935
|
-
this.limitType = limitType;
|
|
4936
|
-
this.startAt = startAt;
|
|
4937
|
-
this.endAt = endAt;
|
|
4938
|
-
this.memoizedOrderBy = null;
|
|
4939
|
-
// The corresponding `Target` of this `Query` instance.
|
|
4940
|
-
this.memoizedTarget = null;
|
|
4941
|
-
if (this.startAt) ;
|
|
4942
|
-
if (this.endAt) ;
|
|
4803
|
+
class SortedSet {
|
|
4804
|
+
constructor(comparator) {
|
|
4805
|
+
this.comparator = comparator;
|
|
4806
|
+
this.data = new SortedMap(this.comparator);
|
|
4943
4807
|
}
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
function newQuery(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt) {
|
|
4947
|
-
return new QueryImpl(path, collectionGroup, explicitOrderBy, filters, limit, limitType, startAt, endAt);
|
|
4948
|
-
}
|
|
4949
|
-
/** Creates a new Query for a query that matches all documents at `path` */
|
|
4950
|
-
function newQueryForPath(path) {
|
|
4951
|
-
return new QueryImpl(path);
|
|
4952
|
-
}
|
|
4953
|
-
/**
|
|
4954
|
-
* Helper to convert a collection group query into a collection query at a
|
|
4955
|
-
* specific path. This is used when executing collection group queries, since
|
|
4956
|
-
* we have to split the query into a set of collection queries at multiple
|
|
4957
|
-
* paths.
|
|
4958
|
-
*/
|
|
4959
|
-
function asCollectionQueryAtPath(query, path) {
|
|
4960
|
-
return new QueryImpl(path,
|
|
4961
|
-
/*collectionGroup=*/ null, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
4962
|
-
}
|
|
4963
|
-
/**
|
|
4964
|
-
* Returns true if this query does not specify any query constraints that
|
|
4965
|
-
* could remove results.
|
|
4966
|
-
*/
|
|
4967
|
-
function matchesAllDocuments(query) {
|
|
4968
|
-
return (query.filters.length === 0 &&
|
|
4969
|
-
query.limit === null &&
|
|
4970
|
-
query.startAt == null &&
|
|
4971
|
-
query.endAt == null &&
|
|
4972
|
-
(query.explicitOrderBy.length === 0 ||
|
|
4973
|
-
(query.explicitOrderBy.length === 1 &&
|
|
4974
|
-
query.explicitOrderBy[0].field.isKeyField())));
|
|
4975
|
-
}
|
|
4976
|
-
function hasLimitToFirst(query) {
|
|
4977
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "F" /* First */;
|
|
4978
|
-
}
|
|
4979
|
-
function hasLimitToLast(query) {
|
|
4980
|
-
return !isNullOrUndefined(query.limit) && query.limitType === "L" /* Last */;
|
|
4981
|
-
}
|
|
4982
|
-
function getFirstOrderByField(query) {
|
|
4983
|
-
return query.explicitOrderBy.length > 0
|
|
4984
|
-
? query.explicitOrderBy[0].field
|
|
4985
|
-
: null;
|
|
4986
|
-
}
|
|
4987
|
-
function getInequalityFilterField(query) {
|
|
4988
|
-
for (const filter of query.filters) {
|
|
4989
|
-
if (filter.isInequality()) {
|
|
4990
|
-
return filter.field;
|
|
4991
|
-
}
|
|
4808
|
+
has(elem) {
|
|
4809
|
+
return this.data.get(elem) !== null;
|
|
4992
4810
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
/**
|
|
4996
|
-
* Checks if any of the provided Operators are included in the query and
|
|
4997
|
-
* returns the first one that is, or null if none are.
|
|
4998
|
-
*/
|
|
4999
|
-
function findFilterOperator(query, operators) {
|
|
5000
|
-
for (const filter of query.filters) {
|
|
5001
|
-
if (operators.indexOf(filter.op) >= 0) {
|
|
5002
|
-
return filter.op;
|
|
5003
|
-
}
|
|
4811
|
+
first() {
|
|
4812
|
+
return this.data.minKey();
|
|
5004
4813
|
}
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
}
|
|
5014
|
-
/**
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
return query.collectionGroup !== null;
|
|
5029
|
-
}
|
|
5030
|
-
/**
|
|
5031
|
-
* Returns the implicit order by constraint that is used to execute the Query,
|
|
5032
|
-
* which can be different from the order by constraints the user provided (e.g.
|
|
5033
|
-
* the SDK and backend always orders by `__name__`).
|
|
5034
|
-
*/
|
|
5035
|
-
function queryOrderBy(query) {
|
|
5036
|
-
const queryImpl = debugCast(query);
|
|
5037
|
-
if (queryImpl.memoizedOrderBy === null) {
|
|
5038
|
-
queryImpl.memoizedOrderBy = [];
|
|
5039
|
-
const inequalityField = getInequalityFilterField(queryImpl);
|
|
5040
|
-
const firstOrderByField = getFirstOrderByField(queryImpl);
|
|
5041
|
-
if (inequalityField !== null && firstOrderByField === null) {
|
|
5042
|
-
// In order to implicitly add key ordering, we must also add the
|
|
5043
|
-
// inequality filter field for it to be a valid query.
|
|
5044
|
-
// Note that the default inequality field and key ordering is ascending.
|
|
5045
|
-
if (!inequalityField.isKeyField()) {
|
|
5046
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
|
|
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;
|
|
5047
4837
|
}
|
|
5048
|
-
|
|
4838
|
+
cb(elem.key);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
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);
|
|
5049
4848
|
}
|
|
5050
4849
|
else {
|
|
5051
|
-
|
|
5052
|
-
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5053
|
-
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5054
|
-
if (orderBy.field.isKeyField()) {
|
|
5055
|
-
foundKeyOrdering = true;
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
if (!foundKeyOrdering) {
|
|
5059
|
-
// The order of the implicit key ordering always matches the last
|
|
5060
|
-
// explicit order by
|
|
5061
|
-
const lastDirection = queryImpl.explicitOrderBy.length > 0
|
|
5062
|
-
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
|
|
5063
|
-
.dir
|
|
5064
|
-
: "asc" /* ASCENDING */;
|
|
5065
|
-
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), lastDirection));
|
|
5066
|
-
}
|
|
4850
|
+
iter = this.data.getIterator();
|
|
5067
4851
|
}
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
*/
|
|
5074
|
-
function queryToTarget(query) {
|
|
5075
|
-
const queryImpl = debugCast(query);
|
|
5076
|
-
if (!queryImpl.memoizedTarget) {
|
|
5077
|
-
if (queryImpl.limitType === "F" /* First */) {
|
|
5078
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, queryOrderBy(queryImpl), queryImpl.filters, queryImpl.limit, queryImpl.startAt, queryImpl.endAt);
|
|
5079
|
-
}
|
|
5080
|
-
else {
|
|
5081
|
-
// Flip the orderBy directions since we want the last results
|
|
5082
|
-
const orderBys = [];
|
|
5083
|
-
for (const orderBy of queryOrderBy(queryImpl)) {
|
|
5084
|
-
const dir = orderBy.dir === "desc" /* DESCENDING */
|
|
5085
|
-
? "asc" /* ASCENDING */
|
|
5086
|
-
: "desc" /* DESCENDING */;
|
|
5087
|
-
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;
|
|
5088
4857
|
}
|
|
5089
|
-
// We need to swap the cursors to match the now-flipped query ordering.
|
|
5090
|
-
const startAt = queryImpl.endAt
|
|
5091
|
-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
|
|
5092
|
-
: null;
|
|
5093
|
-
const endAt = queryImpl.startAt
|
|
5094
|
-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
|
|
5095
|
-
: null;
|
|
5096
|
-
// Now return as a LimitType.First query.
|
|
5097
|
-
queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
|
|
5098
4858
|
}
|
|
5099
4859
|
}
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), newFilters, query.limit, query.limitType, query.startAt, query.endAt);
|
|
5105
|
-
}
|
|
5106
|
-
function queryWithAddedOrderBy(query, orderBy) {
|
|
5107
|
-
// TODO(dimond): validate that orderBy does not list the same key twice.
|
|
5108
|
-
const newOrderBy = query.explicitOrderBy.concat([orderBy]);
|
|
5109
|
-
return new QueryImpl(query.path, query.collectionGroup, newOrderBy, query.filters.slice(), query.limit, query.limitType, query.startAt, query.endAt);
|
|
5110
|
-
}
|
|
5111
|
-
function queryWithLimit(query, limit, limitType) {
|
|
5112
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), limit, limitType, query.startAt, query.endAt);
|
|
5113
|
-
}
|
|
5114
|
-
function queryWithStartAt(query, bound) {
|
|
5115
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, bound, query.endAt);
|
|
5116
|
-
}
|
|
5117
|
-
function queryWithEndAt(query, bound) {
|
|
5118
|
-
return new QueryImpl(query.path, query.collectionGroup, query.explicitOrderBy.slice(), query.filters.slice(), query.limit, query.limitType, query.startAt, bound);
|
|
5119
|
-
}
|
|
5120
|
-
function queryEquals(left, right) {
|
|
5121
|
-
return (targetEquals(queryToTarget(left), queryToTarget(right)) &&
|
|
5122
|
-
left.limitType === right.limitType);
|
|
5123
|
-
}
|
|
5124
|
-
// TODO(b/29183165): This is used to get a unique string from a query to, for
|
|
5125
|
-
// example, use as a dictionary key, but the implementation is subject to
|
|
5126
|
-
// collisions. Make it collision-free.
|
|
5127
|
-
function canonifyQuery(query) {
|
|
5128
|
-
return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;
|
|
5129
|
-
}
|
|
5130
|
-
function stringifyQuery(query) {
|
|
5131
|
-
return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${query.limitType})`;
|
|
5132
|
-
}
|
|
5133
|
-
/** Returns whether `doc` matches the constraints of `query`. */
|
|
5134
|
-
function queryMatches(query, doc) {
|
|
5135
|
-
return (doc.isFoundDocument() &&
|
|
5136
|
-
queryMatchesPathAndCollectionGroup(query, doc) &&
|
|
5137
|
-
queryMatchesOrderBy(query, doc) &&
|
|
5138
|
-
queryMatchesFilters(query, doc) &&
|
|
5139
|
-
queryMatchesBounds(query, doc));
|
|
5140
|
-
}
|
|
5141
|
-
function queryMatchesPathAndCollectionGroup(query, doc) {
|
|
5142
|
-
const docPath = doc.key.path;
|
|
5143
|
-
if (query.collectionGroup !== null) {
|
|
5144
|
-
// NOTE: this.path is currently always empty since we don't expose Collection
|
|
5145
|
-
// Group queries rooted at a document path yet.
|
|
5146
|
-
return (doc.key.hasCollectionId(query.collectionGroup) &&
|
|
5147
|
-
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;
|
|
5148
4864
|
}
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
return query.path.isEqual(docPath);
|
|
4865
|
+
getIterator() {
|
|
4866
|
+
return new SortedSetIterator(this.data.getIterator());
|
|
5152
4867
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
return query.path.isImmediateParentOf(docPath);
|
|
4868
|
+
getIteratorFrom(key) {
|
|
4869
|
+
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5156
4870
|
}
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
*/
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
|
|
5166
|
-
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;
|
|
5167
4879
|
}
|
|
4880
|
+
return this.copy(this.data.remove(elem));
|
|
5168
4881
|
}
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
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)) {
|
|
4899
|
+
return false;
|
|
4900
|
+
}
|
|
4901
|
+
if (this.size !== other.size) {
|
|
5174
4902
|
return false;
|
|
5175
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;
|
|
5176
4931
|
}
|
|
5177
|
-
return true;
|
|
5178
4932
|
}
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
|
|
5183
|
-
return false;
|
|
4933
|
+
class SortedSetIterator {
|
|
4934
|
+
constructor(iter) {
|
|
4935
|
+
this.iter = iter;
|
|
5184
4936
|
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
4937
|
+
getNext() {
|
|
4938
|
+
return this.iter.getNext().key;
|
|
4939
|
+
}
|
|
4940
|
+
hasNext() {
|
|
4941
|
+
return this.iter.hasNext();
|
|
5188
4942
|
}
|
|
5189
|
-
return true;
|
|
5190
4943
|
}
|
|
5191
4944
|
/**
|
|
5192
|
-
*
|
|
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`.
|
|
5193
4949
|
*
|
|
5194
|
-
*
|
|
5195
|
-
*
|
|
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`.
|
|
5196
4960
|
*/
|
|
5197
|
-
function
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
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;
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
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
|
+
}
|
|
5002
|
+
}
|
|
5202
5003
|
}
|
|
5203
5004
|
/**
|
|
5204
|
-
* Returns
|
|
5205
|
-
* based on the Query's ordering constraint.
|
|
5005
|
+
* Returns the next element from the iterator or `undefined` if none available.
|
|
5206
5006
|
*/
|
|
5207
|
-
function
|
|
5208
|
-
return (
|
|
5209
|
-
let comparedOnKeyField = false;
|
|
5210
|
-
for (const orderBy of queryOrderBy(query)) {
|
|
5211
|
-
const comp = compareDocs(orderBy, d1, d2);
|
|
5212
|
-
if (comp !== 0) {
|
|
5213
|
-
return comp;
|
|
5214
|
-
}
|
|
5215
|
-
comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();
|
|
5216
|
-
}
|
|
5217
|
-
return 0;
|
|
5218
|
-
};
|
|
5219
|
-
}
|
|
5220
|
-
function compareDocs(orderBy, d1, d2) {
|
|
5221
|
-
const comparison = orderBy.field.isKeyField()
|
|
5222
|
-
? DocumentKey.comparator(d1.key, d2.key)
|
|
5223
|
-
: compareDocumentsByField(orderBy.field, d1, d2);
|
|
5224
|
-
switch (orderBy.dir) {
|
|
5225
|
-
case "asc" /* ASCENDING */:
|
|
5226
|
-
return comparison;
|
|
5227
|
-
case "desc" /* DESCENDING */:
|
|
5228
|
-
return -1 * comparison;
|
|
5229
|
-
default:
|
|
5230
|
-
return fail();
|
|
5231
|
-
}
|
|
5007
|
+
function advanceIterator(it) {
|
|
5008
|
+
return it.hasNext() ? it.getNext() : undefined;
|
|
5232
5009
|
}
|
|
5233
5010
|
|
|
5234
5011
|
/**
|
|
5235
5012
|
* @license
|
|
5236
|
-
* Copyright
|
|
5013
|
+
* Copyright 2019 Google LLC
|
|
5237
5014
|
*
|
|
5238
5015
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5239
5016
|
* you may not use this file except in compliance with the License.
|
|
@@ -5247,555 +5024,961 @@ function compareDocs(orderBy, d1, d2) {
|
|
|
5247
5024
|
* See the License for the specific language governing permissions and
|
|
5248
5025
|
* limitations under the License.
|
|
5249
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
|
+
}
|
|
5250
5040
|
/**
|
|
5251
|
-
*
|
|
5252
|
-
*
|
|
5253
|
-
*
|
|
5254
|
-
*
|
|
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.
|
|
5255
5047
|
*/
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
*/
|
|
5266
|
-
this.inner = {};
|
|
5267
|
-
/** The number of entries stored in the map */
|
|
5268
|
-
this.innerSize = 0;
|
|
5269
|
-
}
|
|
5270
|
-
/** Get a value for this key, or undefined if it does not exist. */
|
|
5271
|
-
get(key) {
|
|
5272
|
-
const id = this.mapKeyFn(key);
|
|
5273
|
-
const matches = this.inner[id];
|
|
5274
|
-
if (matches === undefined) {
|
|
5275
|
-
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;
|
|
5276
5057
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
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;
|
|
5281
5065
|
}
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
}
|
|
5287
|
-
/** Put this key and value in the map. */
|
|
5288
|
-
set(key, value) {
|
|
5289
|
-
const id = this.mapKeyFn(key);
|
|
5290
|
-
const matches = this.inner[id];
|
|
5291
|
-
if (matches === undefined) {
|
|
5292
|
-
this.inner[id] = [[key, value]];
|
|
5293
|
-
this.innerSize++;
|
|
5294
|
-
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(',');
|
|
5295
5070
|
}
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
return;
|
|
5301
|
-
}
|
|
5071
|
+
if (targetImpl.endAt) {
|
|
5072
|
+
str += '|ub:';
|
|
5073
|
+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
|
|
5074
|
+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
|
|
5302
5075
|
}
|
|
5303
|
-
|
|
5304
|
-
this.innerSize++;
|
|
5076
|
+
targetImpl.memoizedCanonicalId = str;
|
|
5305
5077
|
}
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
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])) {
|
|
5313
5119
|
return false;
|
|
5314
5120
|
}
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
if (matches.length === 1) {
|
|
5318
|
-
delete this.inner[id];
|
|
5319
|
-
}
|
|
5320
|
-
else {
|
|
5321
|
-
matches.splice(i, 1);
|
|
5322
|
-
}
|
|
5323
|
-
this.innerSize--;
|
|
5324
|
-
return true;
|
|
5325
|
-
}
|
|
5326
|
-
}
|
|
5121
|
+
}
|
|
5122
|
+
if (left.filters.length !== right.filters.length) {
|
|
5327
5123
|
return false;
|
|
5328
5124
|
}
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
}
|
|
5334
|
-
});
|
|
5125
|
+
for (let i = 0; i < left.filters.length; i++) {
|
|
5126
|
+
if (!filterEquals(left.filters[i], right.filters[i])) {
|
|
5127
|
+
return false;
|
|
5128
|
+
}
|
|
5335
5129
|
}
|
|
5336
|
-
|
|
5337
|
-
return
|
|
5130
|
+
if (left.collectionGroup !== right.collectionGroup) {
|
|
5131
|
+
return false;
|
|
5338
5132
|
}
|
|
5339
|
-
|
|
5340
|
-
return
|
|
5133
|
+
if (!left.path.isEqual(right.path)) {
|
|
5134
|
+
return false;
|
|
5341
5135
|
}
|
|
5342
|
-
|
|
5343
|
-
|
|
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
|
+
}
|
|
5344
5150
|
/**
|
|
5345
|
-
*
|
|
5346
|
-
*
|
|
5347
|
-
*
|
|
5348
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5349
|
-
* you may not use this file except in compliance with the License.
|
|
5350
|
-
* You may obtain a copy of the License at
|
|
5351
|
-
*
|
|
5352
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
5353
|
-
*
|
|
5354
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
5355
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
5356
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
5357
|
-
* See the License for the specific language governing permissions and
|
|
5358
|
-
* 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.
|
|
5359
5153
|
*/
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
this.comparator = comparator;
|
|
5365
|
-
this.root = root ? root : LLRBNode.EMPTY;
|
|
5154
|
+
function targetGetArrayValues(target, fieldIndex) {
|
|
5155
|
+
const segment = fieldIndexGetArraySegment(fieldIndex);
|
|
5156
|
+
if (segment === undefined) {
|
|
5157
|
+
return null;
|
|
5366
5158
|
}
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
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
|
+
}
|
|
5372
5167
|
}
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
indexOf(key) {
|
|
5399
|
-
// Number of nodes that were pruned when descending right
|
|
5400
|
-
let prunedNodes = 0;
|
|
5401
|
-
let node = this.root;
|
|
5402
|
-
while (!node.isEmpty()) {
|
|
5403
|
-
const cmp = this.comparator(key, node.key);
|
|
5404
|
-
if (cmp === 0) {
|
|
5405
|
-
return prunedNodes + node.left.size;
|
|
5406
|
-
}
|
|
5407
|
-
else if (cmp < 0) {
|
|
5408
|
-
node = node.left;
|
|
5409
|
-
}
|
|
5410
|
-
else {
|
|
5411
|
-
// Count all nodes left of the node plus the node itself
|
|
5412
|
-
prunedNodes += node.left.size + 1;
|
|
5413
|
-
node = node.right;
|
|
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.
|
|
5414
5193
|
}
|
|
5415
5194
|
}
|
|
5416
|
-
// Node not found
|
|
5417
|
-
return -1;
|
|
5418
|
-
}
|
|
5419
|
-
isEmpty() {
|
|
5420
|
-
return this.root.isEmpty();
|
|
5421
|
-
}
|
|
5422
|
-
// Returns the total number of nodes in the map.
|
|
5423
|
-
get size() {
|
|
5424
|
-
return this.root.size;
|
|
5425
|
-
}
|
|
5426
|
-
// Returns the minimum key in the map.
|
|
5427
|
-
minKey() {
|
|
5428
|
-
return this.root.minKey();
|
|
5429
|
-
}
|
|
5430
|
-
// Returns the maximum key in the map.
|
|
5431
|
-
maxKey() {
|
|
5432
|
-
return this.root.maxKey();
|
|
5433
|
-
}
|
|
5434
|
-
// Traverses the map in key order and calls the specified action function
|
|
5435
|
-
// for each key/value pair. If action returns true, traversal is aborted.
|
|
5436
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5437
|
-
// value returned by action.
|
|
5438
|
-
inorderTraversal(action) {
|
|
5439
|
-
return this.root.inorderTraversal(action);
|
|
5440
|
-
}
|
|
5441
|
-
forEach(fn) {
|
|
5442
|
-
this.inorderTraversal((k, v) => {
|
|
5443
|
-
fn(k, v);
|
|
5444
|
-
return false;
|
|
5445
|
-
});
|
|
5446
|
-
}
|
|
5447
|
-
toString() {
|
|
5448
|
-
const descriptions = [];
|
|
5449
|
-
this.inorderTraversal((k, v) => {
|
|
5450
|
-
descriptions.push(`${k}:${v}`);
|
|
5451
|
-
return false;
|
|
5452
|
-
});
|
|
5453
|
-
return `{${descriptions.join(', ')}}`;
|
|
5454
|
-
}
|
|
5455
|
-
// Traverses the map in reverse key order and calls the specified action
|
|
5456
|
-
// function for each key/value pair. If action returns true, traversal is
|
|
5457
|
-
// aborted.
|
|
5458
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5459
|
-
// value returned by action.
|
|
5460
|
-
reverseTraversal(action) {
|
|
5461
|
-
return this.root.reverseTraversal(action);
|
|
5462
|
-
}
|
|
5463
|
-
// Returns an iterator over the SortedMap.
|
|
5464
|
-
getIterator() {
|
|
5465
|
-
return new SortedMapIterator(this.root, null, this.comparator, false);
|
|
5466
|
-
}
|
|
5467
|
-
getIteratorFrom(key) {
|
|
5468
|
-
return new SortedMapIterator(this.root, key, this.comparator, false);
|
|
5469
5195
|
}
|
|
5470
|
-
|
|
5471
|
-
|
|
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);
|
|
5472
5214
|
}
|
|
5473
|
-
|
|
5474
|
-
|
|
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);
|
|
5475
5233
|
}
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
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;
|
|
5496
5282
|
}
|
|
5497
|
-
}
|
|
5498
|
-
else if (cmp === 0) {
|
|
5499
|
-
// This node is exactly equal to our start key. Push it on the stack,
|
|
5500
|
-
// but stop iterating;
|
|
5501
|
-
this.nodeStack.push(node);
|
|
5502
5283
|
break;
|
|
5503
5284
|
}
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
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;
|
|
5513
5336
|
}
|
|
5337
|
+
break;
|
|
5514
5338
|
}
|
|
5515
5339
|
}
|
|
5516
5340
|
}
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
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;
|
|
5526
5362
|
}
|
|
5527
5363
|
else {
|
|
5528
|
-
|
|
5529
|
-
while (!node.isEmpty()) {
|
|
5530
|
-
this.nodeStack.push(node);
|
|
5531
|
-
node = node.left;
|
|
5532
|
-
}
|
|
5364
|
+
fields = fields.add(fieldFilter.field);
|
|
5533
5365
|
}
|
|
5534
|
-
return result;
|
|
5535
5366
|
}
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
return null;
|
|
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);
|
|
5542
5372
|
}
|
|
5543
|
-
const node = this.nodeStack[this.nodeStack.length - 1];
|
|
5544
|
-
return { key: node.key, value: node.value };
|
|
5545
5373
|
}
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
class
|
|
5549
|
-
|
|
5550
|
-
|
|
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;
|
|
5551
5383
|
this.value = value;
|
|
5552
|
-
this.color = color != null ? color : LLRBNode.RED;
|
|
5553
|
-
this.left = left != null ? left : LLRBNode.EMPTY;
|
|
5554
|
-
this.right = right != null ? right : LLRBNode.EMPTY;
|
|
5555
|
-
this.size = this.left.size + 1 + this.right.size;
|
|
5556
5384
|
}
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
inorderTraversal(action) {
|
|
5569
|
-
return (this.left.inorderTraversal(action) ||
|
|
5570
|
-
action(this.key, this.value) ||
|
|
5571
|
-
this.right.inorderTraversal(action));
|
|
5572
|
-
}
|
|
5573
|
-
// Traverses the tree in reverse key order and calls the specified action
|
|
5574
|
-
// function for each node. If action returns true, traversal is aborted.
|
|
5575
|
-
// Returns the first truthy value returned by action, or the last falsey
|
|
5576
|
-
// value returned by action.
|
|
5577
|
-
reverseTraversal(action) {
|
|
5578
|
-
return (this.right.reverseTraversal(action) ||
|
|
5579
|
-
action(this.key, this.value) ||
|
|
5580
|
-
this.left.reverseTraversal(action));
|
|
5581
|
-
}
|
|
5582
|
-
// Returns the minimum node in the tree.
|
|
5583
|
-
min() {
|
|
5584
|
-
if (this.left.isEmpty()) {
|
|
5585
|
-
return this;
|
|
5586
|
-
}
|
|
5587
|
-
else {
|
|
5588
|
-
return this.left.min();
|
|
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);
|
|
5392
|
+
}
|
|
5393
|
+
else {
|
|
5394
|
+
return new KeyFieldFilter(field, op, value);
|
|
5395
|
+
}
|
|
5589
5396
|
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
minKey() {
|
|
5593
|
-
return this.min().key;
|
|
5594
|
-
}
|
|
5595
|
-
// Returns the maximum key in the tree.
|
|
5596
|
-
maxKey() {
|
|
5597
|
-
if (this.right.isEmpty()) {
|
|
5598
|
-
return this.key;
|
|
5397
|
+
else if (op === "array-contains" /* ARRAY_CONTAINS */) {
|
|
5398
|
+
return new ArrayContainsFilter(field, value);
|
|
5599
5399
|
}
|
|
5600
|
-
else {
|
|
5601
|
-
return
|
|
5400
|
+
else if (op === "in" /* IN */) {
|
|
5401
|
+
return new InFilter(field, value);
|
|
5602
5402
|
}
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
insert(key, value, comparator) {
|
|
5606
|
-
let n = this;
|
|
5607
|
-
const cmp = comparator(key, n.key);
|
|
5608
|
-
if (cmp < 0) {
|
|
5609
|
-
n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);
|
|
5403
|
+
else if (op === "not-in" /* NOT_IN */) {
|
|
5404
|
+
return new NotInFilter(field, value);
|
|
5610
5405
|
}
|
|
5611
|
-
else if (
|
|
5612
|
-
|
|
5406
|
+
else if (op === "array-contains-any" /* ARRAY_CONTAINS_ANY */) {
|
|
5407
|
+
return new ArrayContainsAnyFilter(field, value);
|
|
5613
5408
|
}
|
|
5614
5409
|
else {
|
|
5615
|
-
|
|
5410
|
+
return new FieldFilter(field, op, value);
|
|
5616
5411
|
}
|
|
5617
|
-
return n.fixUp();
|
|
5618
5412
|
}
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
let n = this;
|
|
5624
|
-
if (!n.left.isRed() && !n.left.left.isRed()) {
|
|
5625
|
-
n = n.moveRedLeft();
|
|
5626
|
-
}
|
|
5627
|
-
n = n.copy(null, null, null, n.left.removeMin(), null);
|
|
5628
|
-
return n.fixUp();
|
|
5413
|
+
static createKeyFieldInFilter(field, op, value) {
|
|
5414
|
+
return op === "in" /* IN */
|
|
5415
|
+
? new KeyFieldInFilter(field, value)
|
|
5416
|
+
: new KeyFieldNotInFilter(field, value);
|
|
5629
5417
|
}
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
n = n.moveRedLeft();
|
|
5637
|
-
}
|
|
5638
|
-
n = n.copy(null, null, null, n.left.remove(key, comparator), null);
|
|
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)));
|
|
5639
5424
|
}
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
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)));
|
|
5429
|
+
}
|
|
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();
|
|
5657
5446
|
}
|
|
5658
|
-
return n.fixUp();
|
|
5659
5447
|
}
|
|
5660
|
-
|
|
5661
|
-
return
|
|
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);
|
|
5662
5457
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
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);
|
|
5676
5481
|
}
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
n = n.copy(null, null, null, null, n.right.rotateRight());
|
|
5681
|
-
n = n.rotateLeft();
|
|
5682
|
-
n = n.colorFlip();
|
|
5683
|
-
}
|
|
5684
|
-
return n;
|
|
5482
|
+
matches(doc) {
|
|
5483
|
+
const comparison = DocumentKey.comparator(doc.key, this.key);
|
|
5484
|
+
return this.matchesComparison(comparison);
|
|
5685
5485
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
return n;
|
|
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);
|
|
5693
5492
|
}
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
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);
|
|
5527
|
+
}
|
|
5528
|
+
matches(doc) {
|
|
5529
|
+
const other = doc.data.field(this.field);
|
|
5530
|
+
return other !== null && arrayValueContains(this.value.arrayValue, other);
|
|
5531
|
+
}
|
|
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);
|
|
5537
|
+
}
|
|
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);
|
|
5544
|
+
}
|
|
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);
|
|
5550
|
+
}
|
|
5551
|
+
matches(doc) {
|
|
5552
|
+
const other = doc.data.field(this.field);
|
|
5553
|
+
if (!isArray(other) || !other.arrayValue.values) {
|
|
5554
|
+
return false;
|
|
5555
|
+
}
|
|
5556
|
+
return other.arrayValue.values.some(val => arrayValueContains(this.value.arrayValue, val));
|
|
5557
|
+
}
|
|
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);
|
|
5605
|
+
}
|
|
5606
|
+
else {
|
|
5607
|
+
const docValue = doc.data.field(orderByComponent.field);
|
|
5608
|
+
comparison = valueCompare(component, docValue);
|
|
5609
|
+
}
|
|
5610
|
+
if (orderByComponent.dir === "desc" /* DESCENDING */) {
|
|
5611
|
+
comparison = comparison * -1;
|
|
5612
|
+
}
|
|
5613
|
+
if (comparison !== 0) {
|
|
5614
|
+
break;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
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;
|
|
5638
|
+
}
|
|
5639
|
+
else if (right === null) {
|
|
5640
|
+
return false;
|
|
5641
|
+
}
|
|
5642
|
+
if (left.inclusive !== right.inclusive ||
|
|
5643
|
+
left.position.length !== right.position.length) {
|
|
5644
|
+
return false;
|
|
5645
|
+
}
|
|
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;
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
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) ;
|
|
5698
|
+
}
|
|
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;
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
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;
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
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));
|
|
5796
|
+
}
|
|
5797
|
+
queryImpl.memoizedOrderBy.push(new OrderBy(FieldPath$1.keyField(), "asc" /* ASCENDING */));
|
|
5798
|
+
}
|
|
5799
|
+
else {
|
|
5800
|
+
let foundKeyOrdering = false;
|
|
5801
|
+
for (const orderBy of queryImpl.explicitOrderBy) {
|
|
5802
|
+
queryImpl.memoizedOrderBy.push(orderBy);
|
|
5803
|
+
if (orderBy.field.isKeyField()) {
|
|
5804
|
+
foundKeyOrdering = true;
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
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
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
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);
|
|
5828
|
+
}
|
|
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);
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
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));
|
|
5697
5897
|
}
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
return
|
|
5898
|
+
else if (DocumentKey.isDocumentKey(query.path)) {
|
|
5899
|
+
// exact match for document queries
|
|
5900
|
+
return query.path.isEqual(docPath);
|
|
5701
5901
|
}
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
return this.copy(null, null, !this.color, left, right);
|
|
5902
|
+
else {
|
|
5903
|
+
// shallow ancestor queries by default
|
|
5904
|
+
return query.path.isImmediateParentOf(docPath);
|
|
5706
5905
|
}
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
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) {
|
|
5714
5915
|
return false;
|
|
5715
5916
|
}
|
|
5716
5917
|
}
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
if (this.right.isRed()) {
|
|
5724
|
-
throw fail();
|
|
5725
|
-
}
|
|
5726
|
-
const blackDepth = this.left.check();
|
|
5727
|
-
if (blackDepth !== this.right.check()) {
|
|
5728
|
-
throw fail();
|
|
5729
|
-
}
|
|
5730
|
-
else {
|
|
5731
|
-
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;
|
|
5732
5924
|
}
|
|
5733
5925
|
}
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
// Represents an empty node (a leaf node in the Red-Black Tree).
|
|
5741
|
-
class LLRBEmptyNode {
|
|
5742
|
-
constructor() {
|
|
5743
|
-
this.size = 0;
|
|
5744
|
-
}
|
|
5745
|
-
get key() {
|
|
5746
|
-
throw fail();
|
|
5747
|
-
}
|
|
5748
|
-
get value() {
|
|
5749
|
-
throw fail();
|
|
5750
|
-
}
|
|
5751
|
-
get color() {
|
|
5752
|
-
throw fail();
|
|
5753
|
-
}
|
|
5754
|
-
get left() {
|
|
5755
|
-
throw fail();
|
|
5756
|
-
}
|
|
5757
|
-
get right() {
|
|
5758
|
-
throw fail();
|
|
5759
|
-
}
|
|
5760
|
-
// Returns a copy of the current node.
|
|
5761
|
-
copy(key, value, color, left, right) {
|
|
5762
|
-
return this;
|
|
5763
|
-
}
|
|
5764
|
-
// Returns a copy of the tree, with the specified key/value added.
|
|
5765
|
-
insert(key, value, comparator) {
|
|
5766
|
-
return new LLRBNode(key, value);
|
|
5767
|
-
}
|
|
5768
|
-
// Returns a copy of the tree, with the specified key removed.
|
|
5769
|
-
remove(key, comparator) {
|
|
5770
|
-
return this;
|
|
5771
|
-
}
|
|
5772
|
-
isEmpty() {
|
|
5773
|
-
return true;
|
|
5774
|
-
}
|
|
5775
|
-
inorderTraversal(action) {
|
|
5776
|
-
return false;
|
|
5777
|
-
}
|
|
5778
|
-
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)) {
|
|
5779
5932
|
return false;
|
|
5780
5933
|
}
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
}
|
|
5784
|
-
maxKey() {
|
|
5785
|
-
return null;
|
|
5786
|
-
}
|
|
5787
|
-
isRed() {
|
|
5934
|
+
if (query.endAt &&
|
|
5935
|
+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
|
|
5788
5936
|
return false;
|
|
5789
5937
|
}
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
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
|
+
}
|
|
5795
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();
|
|
5796
5980
|
}
|
|
5797
|
-
}
|
|
5798
|
-
LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
5981
|
+
}
|
|
5799
5982
|
|
|
5800
5983
|
/**
|
|
5801
5984
|
* @license
|
|
@@ -5814,218 +5997,97 @@ LLRBNode.EMPTY = new LLRBEmptyNode();
|
|
|
5814
5997
|
* limitations under the License.
|
|
5815
5998
|
*/
|
|
5816
5999
|
/**
|
|
5817
|
-
*
|
|
5818
|
-
*
|
|
5819
|
-
*
|
|
5820
|
-
*
|
|
5821
|
-
* 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.
|
|
5822
6004
|
*/
|
|
5823
|
-
class
|
|
5824
|
-
constructor(
|
|
5825
|
-
this.
|
|
5826
|
-
this.
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
/** Iterates elements in order defined by "comparator" */
|
|
5844
|
-
forEach(cb) {
|
|
5845
|
-
this.data.inorderTraversal((k, v) => {
|
|
5846
|
-
cb(k);
|
|
5847
|
-
return false;
|
|
5848
|
-
});
|
|
5849
|
-
}
|
|
5850
|
-
/** Iterates over `elem`s such that: range[0] <= elem < range[1]. */
|
|
5851
|
-
forEachInRange(range, cb) {
|
|
5852
|
-
const iter = this.data.getIteratorFrom(range[0]);
|
|
5853
|
-
while (iter.hasNext()) {
|
|
5854
|
-
const elem = iter.getNext();
|
|
5855
|
-
if (this.comparator(elem.key, range[1]) >= 0) {
|
|
5856
|
-
return;
|
|
5857
|
-
}
|
|
5858
|
-
cb(elem.key);
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
/**
|
|
5862
|
-
* Iterates over `elem`s such that: start <= elem until false is returned.
|
|
5863
|
-
*/
|
|
5864
|
-
forEachWhile(cb, start) {
|
|
5865
|
-
let iter;
|
|
5866
|
-
if (start !== undefined) {
|
|
5867
|
-
iter = this.data.getIteratorFrom(start);
|
|
5868
|
-
}
|
|
5869
|
-
else {
|
|
5870
|
-
iter = this.data.getIterator();
|
|
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;
|
|
6018
|
+
}
|
|
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;
|
|
5871
6025
|
}
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
if (!result) {
|
|
5876
|
-
return;
|
|
6026
|
+
for (const [otherKey, value] of matches) {
|
|
6027
|
+
if (this.equalsFn(otherKey, key)) {
|
|
6028
|
+
return value;
|
|
5877
6029
|
}
|
|
5878
6030
|
}
|
|
6031
|
+
return undefined;
|
|
5879
6032
|
}
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
const iter = this.data.getIteratorFrom(elem);
|
|
5883
|
-
return iter.hasNext() ? iter.getNext().key : null;
|
|
5884
|
-
}
|
|
5885
|
-
getIterator() {
|
|
5886
|
-
return new SortedSetIterator(this.data.getIterator());
|
|
5887
|
-
}
|
|
5888
|
-
getIteratorFrom(key) {
|
|
5889
|
-
return new SortedSetIterator(this.data.getIteratorFrom(key));
|
|
5890
|
-
}
|
|
5891
|
-
/** Inserts or updates an element */
|
|
5892
|
-
add(elem) {
|
|
5893
|
-
return this.copy(this.data.remove(elem).insert(elem, true));
|
|
6033
|
+
has(key) {
|
|
6034
|
+
return this.get(key) !== undefined;
|
|
5894
6035
|
}
|
|
5895
|
-
/**
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
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;
|
|
5899
6044
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
let result = this;
|
|
5907
|
-
// Make sure `result` always refers to the larger one of the two sets.
|
|
5908
|
-
if (result.size < other.size) {
|
|
5909
|
-
result = other;
|
|
5910
|
-
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
|
+
}
|
|
5911
6051
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
});
|
|
5915
|
-
return result;
|
|
6052
|
+
matches.push([key, value]);
|
|
6053
|
+
this.innerSize++;
|
|
5916
6054
|
}
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
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) {
|
|
5922
6062
|
return false;
|
|
5923
6063
|
}
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
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;
|
|
5931
6074
|
}
|
|
5932
6075
|
}
|
|
5933
|
-
return
|
|
6076
|
+
return false;
|
|
5934
6077
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
6078
|
+
forEach(fn) {
|
|
6079
|
+
forEach(this.inner, (_, entries) => {
|
|
6080
|
+
for (const [k, v] of entries) {
|
|
6081
|
+
fn(k, v);
|
|
6082
|
+
}
|
|
5939
6083
|
});
|
|
5940
|
-
return res;
|
|
5941
|
-
}
|
|
5942
|
-
toString() {
|
|
5943
|
-
const result = [];
|
|
5944
|
-
this.forEach(elem => result.push(elem));
|
|
5945
|
-
return 'SortedSet(' + result.toString() + ')';
|
|
5946
|
-
}
|
|
5947
|
-
copy(data) {
|
|
5948
|
-
const result = new SortedSet(this.comparator);
|
|
5949
|
-
result.data = data;
|
|
5950
|
-
return result;
|
|
5951
|
-
}
|
|
5952
|
-
}
|
|
5953
|
-
class SortedSetIterator {
|
|
5954
|
-
constructor(iter) {
|
|
5955
|
-
this.iter = iter;
|
|
5956
6084
|
}
|
|
5957
|
-
|
|
5958
|
-
return this.
|
|
5959
|
-
}
|
|
5960
|
-
hasNext() {
|
|
5961
|
-
return this.iter.hasNext();
|
|
6085
|
+
isEmpty() {
|
|
6086
|
+
return isEmpty(this.inner);
|
|
5962
6087
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
* Compares two sorted sets for equality using their natural ordering. The
|
|
5966
|
-
* method computes the intersection and invokes `onAdd` for every element that
|
|
5967
|
-
* is in `after` but not `before`. `onRemove` is invoked for every element in
|
|
5968
|
-
* `before` but missing from `after`.
|
|
5969
|
-
*
|
|
5970
|
-
* The method creates a copy of both `before` and `after` and runs in O(n log
|
|
5971
|
-
* n), where n is the size of the two lists.
|
|
5972
|
-
*
|
|
5973
|
-
* @param before - The elements that exist in the original set.
|
|
5974
|
-
* @param after - The elements to diff against the original set.
|
|
5975
|
-
* @param comparator - The comparator for the elements in before and after.
|
|
5976
|
-
* @param onAdd - A function to invoke for every element that is part of `
|
|
5977
|
-
* after` but not `before`.
|
|
5978
|
-
* @param onRemove - A function to invoke for every element that is part of
|
|
5979
|
-
* `before` but not `after`.
|
|
5980
|
-
*/
|
|
5981
|
-
function diffSortedSets(before, after, comparator, onAdd, onRemove) {
|
|
5982
|
-
const beforeIt = before.getIterator();
|
|
5983
|
-
const afterIt = after.getIterator();
|
|
5984
|
-
let beforeValue = advanceIterator(beforeIt);
|
|
5985
|
-
let afterValue = advanceIterator(afterIt);
|
|
5986
|
-
// Walk through the two sets at the same time, using the ordering defined by
|
|
5987
|
-
// `comparator`.
|
|
5988
|
-
while (beforeValue || afterValue) {
|
|
5989
|
-
let added = false;
|
|
5990
|
-
let removed = false;
|
|
5991
|
-
if (beforeValue && afterValue) {
|
|
5992
|
-
const cmp = comparator(beforeValue, afterValue);
|
|
5993
|
-
if (cmp < 0) {
|
|
5994
|
-
// The element was removed if the next element in our ordered
|
|
5995
|
-
// walkthrough is only in `before`.
|
|
5996
|
-
removed = true;
|
|
5997
|
-
}
|
|
5998
|
-
else if (cmp > 0) {
|
|
5999
|
-
// The element was added if the next element in our ordered walkthrough
|
|
6000
|
-
// is only in `after`.
|
|
6001
|
-
added = true;
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
|
-
else if (beforeValue != null) {
|
|
6005
|
-
removed = true;
|
|
6006
|
-
}
|
|
6007
|
-
else {
|
|
6008
|
-
added = true;
|
|
6009
|
-
}
|
|
6010
|
-
if (added) {
|
|
6011
|
-
onAdd(afterValue);
|
|
6012
|
-
afterValue = advanceIterator(afterIt);
|
|
6013
|
-
}
|
|
6014
|
-
else if (removed) {
|
|
6015
|
-
onRemove(beforeValue);
|
|
6016
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6017
|
-
}
|
|
6018
|
-
else {
|
|
6019
|
-
beforeValue = advanceIterator(beforeIt);
|
|
6020
|
-
afterValue = advanceIterator(afterIt);
|
|
6021
|
-
}
|
|
6088
|
+
size() {
|
|
6089
|
+
return this.innerSize;
|
|
6022
6090
|
}
|
|
6023
|
-
}
|
|
6024
|
-
/**
|
|
6025
|
-
* Returns the next element from the iterator or `undefined` if none available.
|
|
6026
|
-
*/
|
|
6027
|
-
function advanceIterator(it) {
|
|
6028
|
-
return it.hasNext() ? it.getNext() : undefined;
|
|
6029
6091
|
}
|
|
6030
6092
|
|
|
6031
6093
|
/**
|
|
@@ -6049,8 +6111,12 @@ function mutableDocumentMap() {
|
|
|
6049
6111
|
return EMPTY_MUTABLE_DOCUMENT_MAP;
|
|
6050
6112
|
}
|
|
6051
6113
|
const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
|
|
6052
|
-
function documentMap() {
|
|
6053
|
-
|
|
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;
|
|
6054
6120
|
}
|
|
6055
6121
|
function newOverlayMap() {
|
|
6056
6122
|
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
@@ -9943,9 +10009,9 @@ class MemoryIndexManager {
|
|
|
9943
10009
|
// Field indices are not supported with memory persistence.
|
|
9944
10010
|
return PersistencePromise.resolve(null);
|
|
9945
10011
|
}
|
|
9946
|
-
|
|
10012
|
+
getIndexType(transaction, target) {
|
|
9947
10013
|
// Field indices are not supported with memory persistence.
|
|
9948
|
-
return PersistencePromise.resolve(
|
|
10014
|
+
return PersistencePromise.resolve(0 /* NONE */);
|
|
9949
10015
|
}
|
|
9950
10016
|
getFieldIndexes(transaction, collectionGroup) {
|
|
9951
10017
|
// Field indices are not supported with memory persistence.
|
|
@@ -9955,6 +10021,9 @@ class MemoryIndexManager {
|
|
|
9955
10021
|
// Field indices are not supported with memory persistence.
|
|
9956
10022
|
return PersistencePromise.resolve(null);
|
|
9957
10023
|
}
|
|
10024
|
+
getMinOffset(transaction, target) {
|
|
10025
|
+
return PersistencePromise.resolve(IndexOffset.min());
|
|
10026
|
+
}
|
|
9958
10027
|
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
9959
10028
|
// Field indices are not supported with memory persistence.
|
|
9960
10029
|
return PersistencePromise.resolve();
|
|
@@ -10021,8 +10090,9 @@ const EMPTY_VALUE = new Uint8Array(0);
|
|
|
10021
10090
|
* data as it supports multi-tab access.
|
|
10022
10091
|
*/
|
|
10023
10092
|
class IndexedDbIndexManager {
|
|
10024
|
-
constructor(user) {
|
|
10093
|
+
constructor(user, databaseId) {
|
|
10025
10094
|
this.user = user;
|
|
10095
|
+
this.databaseId = databaseId;
|
|
10026
10096
|
/**
|
|
10027
10097
|
* An in-memory copy of the index entries we've already written since the SDK
|
|
10028
10098
|
* launched. Used to avoid re-writing the same entry repeatedly.
|
|
@@ -10118,7 +10188,8 @@ class IndexedDbIndexManager {
|
|
|
10118
10188
|
return PersistencePromise.resolve(null);
|
|
10119
10189
|
}
|
|
10120
10190
|
else {
|
|
10121
|
-
let
|
|
10191
|
+
let existingKeys = documentKeySet();
|
|
10192
|
+
const result = [];
|
|
10122
10193
|
return PersistencePromise.forEach(indexes, (index, subTarget) => {
|
|
10123
10194
|
logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
|
|
10124
10195
|
const arrayValues = targetGetArrayValues(subTarget, index);
|
|
@@ -10128,13 +10199,17 @@ class IndexedDbIndexManager {
|
|
|
10128
10199
|
const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
|
|
10129
10200
|
const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
|
|
10130
10201
|
const notInEncoded = this.encodeValues(index, subTarget, notInValues);
|
|
10131
|
-
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded,
|
|
10202
|
+
const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, lowerBound.inclusive, upperBoundEncoded, upperBound.inclusive, notInEncoded);
|
|
10132
10203
|
return PersistencePromise.forEach(indexRanges, (indexRange) => {
|
|
10133
10204
|
return indexEntries
|
|
10134
10205
|
.loadFirst(indexRange, target.limit)
|
|
10135
10206
|
.next(entries => {
|
|
10136
10207
|
entries.forEach(entry => {
|
|
10137
|
-
|
|
10208
|
+
const documentKey = DocumentKey.fromSegments(entry.documentKey);
|
|
10209
|
+
if (!existingKeys.has(documentKey)) {
|
|
10210
|
+
existingKeys = existingKeys.add(documentKey);
|
|
10211
|
+
result.push(documentKey);
|
|
10212
|
+
}
|
|
10138
10213
|
});
|
|
10139
10214
|
});
|
|
10140
10215
|
});
|
|
@@ -10162,22 +10237,18 @@ class IndexedDbIndexManager {
|
|
|
10162
10237
|
// index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
|
|
10163
10238
|
// combined with the values from the query bounds.
|
|
10164
10239
|
const totalScans = (arrayValues != null ? arrayValues.length : 1) *
|
|
10165
|
-
Math.max(lowerBounds
|
|
10240
|
+
Math.max(lowerBounds.length, upperBounds.length);
|
|
10166
10241
|
const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
|
|
10167
10242
|
const indexRanges = [];
|
|
10168
10243
|
for (let i = 0; i < totalScans; ++i) {
|
|
10169
10244
|
const arrayValue = arrayValues
|
|
10170
10245
|
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
|
|
10171
10246
|
: EMPTY_VALUE;
|
|
10172
|
-
const lowerBound = lowerBounds
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
: this.generateEmptyBound(indexId + 1);
|
|
10178
|
-
indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
|
|
10179
|
-
) => this.generateLowerBound(indexId, arrayValue, notIn,
|
|
10180
|
-
/* 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));
|
|
10181
10252
|
}
|
|
10182
10253
|
return indexRanges;
|
|
10183
10254
|
}
|
|
@@ -10191,25 +10262,38 @@ class IndexedDbIndexManager {
|
|
|
10191
10262
|
const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
|
|
10192
10263
|
return inclusive ? entry.successor() : entry;
|
|
10193
10264
|
}
|
|
10194
|
-
/**
|
|
10195
|
-
* Generates an empty bound that scopes the index scan to the current index
|
|
10196
|
-
* and user.
|
|
10197
|
-
*/
|
|
10198
|
-
generateEmptyBound(indexId) {
|
|
10199
|
-
return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
|
|
10200
|
-
}
|
|
10201
10265
|
getFieldIndex(transaction, target) {
|
|
10202
10266
|
const targetIndexMatcher = new TargetIndexMatcher(target);
|
|
10203
10267
|
const collectionGroup = target.collectionGroup != null
|
|
10204
10268
|
? target.collectionGroup
|
|
10205
10269
|
: target.path.lastSegment();
|
|
10206
10270
|
return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
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;
|
|
10211
10281
|
});
|
|
10212
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
|
+
}
|
|
10213
10297
|
/**
|
|
10214
10298
|
* Returns the byte encoded form of the directional values in the field index.
|
|
10215
10299
|
* Returns `null` if the document does not have all fields specified in the
|
|
@@ -10233,6 +10317,15 @@ class IndexedDbIndexManager {
|
|
|
10233
10317
|
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
|
|
10234
10318
|
return encoder.encodedBytes();
|
|
10235
10319
|
}
|
|
10320
|
+
/**
|
|
10321
|
+
* Returns an encoded form of the document key that sorts based on the key
|
|
10322
|
+
* ordering of the field index.
|
|
10323
|
+
*/
|
|
10324
|
+
encodeDirectionalKey(fieldIndex, documentKey) {
|
|
10325
|
+
const encoder = new IndexByteEncoder();
|
|
10326
|
+
FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
|
|
10327
|
+
return encoder.encodedBytes();
|
|
10328
|
+
}
|
|
10236
10329
|
/**
|
|
10237
10330
|
* Encodes the given field values according to the specification in `target`.
|
|
10238
10331
|
* For IN queries, a list of possible values is returned.
|
|
@@ -10263,9 +10356,6 @@ class IndexedDbIndexManager {
|
|
|
10263
10356
|
* queries, a list of possible values is returned.
|
|
10264
10357
|
*/
|
|
10265
10358
|
encodeBound(fieldIndex, target, bound) {
|
|
10266
|
-
if (bound == null) {
|
|
10267
|
-
return null;
|
|
10268
|
-
}
|
|
10269
10359
|
return this.encodeValues(fieldIndex, target, bound.position);
|
|
10270
10360
|
}
|
|
10271
10361
|
/** Returns the byte representation for the provided encoders. */
|
|
@@ -10356,7 +10446,7 @@ class IndexedDbIndexManager {
|
|
|
10356
10446
|
return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
|
|
10357
10447
|
const newEntries = this.computeIndexEntries(doc, fieldIndex);
|
|
10358
10448
|
if (!existingEntries.isEqual(newEntries)) {
|
|
10359
|
-
return this.updateEntries(transaction, doc, existingEntries, newEntries);
|
|
10449
|
+
return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
|
|
10360
10450
|
}
|
|
10361
10451
|
return PersistencePromise.resolve();
|
|
10362
10452
|
});
|
|
@@ -10364,24 +10454,26 @@ class IndexedDbIndexManager {
|
|
|
10364
10454
|
});
|
|
10365
10455
|
});
|
|
10366
10456
|
}
|
|
10367
|
-
addIndexEntry(transaction, document, indexEntry) {
|
|
10457
|
+
addIndexEntry(transaction, document, fieldIndex, indexEntry) {
|
|
10368
10458
|
const indexEntries = indexEntriesStore(transaction);
|
|
10369
10459
|
return indexEntries.put({
|
|
10370
10460
|
indexId: indexEntry.indexId,
|
|
10371
10461
|
uid: this.uid,
|
|
10372
10462
|
arrayValue: indexEntry.arrayValue,
|
|
10373
10463
|
directionalValue: indexEntry.directionalValue,
|
|
10374
|
-
|
|
10464
|
+
orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
|
|
10465
|
+
documentKey: document.key.path.toArray()
|
|
10375
10466
|
});
|
|
10376
10467
|
}
|
|
10377
|
-
deleteIndexEntry(transaction, document, indexEntry) {
|
|
10468
|
+
deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
|
|
10378
10469
|
const indexEntries = indexEntriesStore(transaction);
|
|
10379
10470
|
return indexEntries.delete([
|
|
10380
10471
|
indexEntry.indexId,
|
|
10381
10472
|
this.uid,
|
|
10382
10473
|
indexEntry.arrayValue,
|
|
10383
10474
|
indexEntry.directionalValue,
|
|
10384
|
-
|
|
10475
|
+
this.encodeDirectionalKey(fieldIndex, document.key),
|
|
10476
|
+
document.key.path.toArray()
|
|
10385
10477
|
]);
|
|
10386
10478
|
}
|
|
10387
10479
|
getExistingIndexEntries(transaction, documentKey, fieldIndex) {
|
|
@@ -10393,7 +10485,7 @@ class IndexedDbIndexManager {
|
|
|
10393
10485
|
range: IDBKeyRange.only([
|
|
10394
10486
|
fieldIndex.indexId,
|
|
10395
10487
|
this.uid,
|
|
10396
|
-
|
|
10488
|
+
this.encodeDirectionalKey(fieldIndex, documentKey)
|
|
10397
10489
|
])
|
|
10398
10490
|
}, (_, entry) => {
|
|
10399
10491
|
results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
|
|
@@ -10426,15 +10518,15 @@ class IndexedDbIndexManager {
|
|
|
10426
10518
|
* that are no longer referenced in `newEntries` and adding all newly added
|
|
10427
10519
|
* entries.
|
|
10428
10520
|
*/
|
|
10429
|
-
updateEntries(transaction, document, existingEntries, newEntries) {
|
|
10521
|
+
updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
|
|
10430
10522
|
logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
|
|
10431
10523
|
const promises = [];
|
|
10432
10524
|
diffSortedSets(existingEntries, newEntries, indexEntryComparator,
|
|
10433
10525
|
/* onAdd= */ entry => {
|
|
10434
|
-
promises.push(this.addIndexEntry(transaction, document, entry));
|
|
10526
|
+
promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
|
|
10435
10527
|
},
|
|
10436
10528
|
/* onRemove= */ entry => {
|
|
10437
|
-
promises.push(this.deleteIndexEntry(transaction, document, entry));
|
|
10529
|
+
promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
|
|
10438
10530
|
});
|
|
10439
10531
|
return PersistencePromise.waitFor(promises);
|
|
10440
10532
|
}
|
|
@@ -10458,7 +10550,7 @@ class IndexedDbIndexManager {
|
|
|
10458
10550
|
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
|
|
10459
10551
|
*/
|
|
10460
10552
|
createRange(lower, upper, notInValues) {
|
|
10461
|
-
// The
|
|
10553
|
+
// The notIn values need to be sorted and unique so that we can return a
|
|
10462
10554
|
// sorted set of non-overlapping ranges.
|
|
10463
10555
|
notInValues = notInValues
|
|
10464
10556
|
.sort((l, r) => indexEntryComparator(l, r))
|
|
@@ -10491,17 +10583,33 @@ class IndexedDbIndexManager {
|
|
|
10491
10583
|
this.uid,
|
|
10492
10584
|
bounds[i].arrayValue,
|
|
10493
10585
|
bounds[i].directionalValue,
|
|
10494
|
-
|
|
10586
|
+
EMPTY_VALUE,
|
|
10587
|
+
[]
|
|
10495
10588
|
], [
|
|
10496
10589
|
bounds[i + 1].indexId,
|
|
10497
10590
|
this.uid,
|
|
10498
10591
|
bounds[i + 1].arrayValue,
|
|
10499
10592
|
bounds[i + 1].directionalValue,
|
|
10500
|
-
|
|
10593
|
+
EMPTY_VALUE,
|
|
10594
|
+
[]
|
|
10501
10595
|
]));
|
|
10502
10596
|
}
|
|
10503
10597
|
return ranges;
|
|
10504
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
|
+
}
|
|
10505
10613
|
}
|
|
10506
10614
|
/**
|
|
10507
10615
|
* Helper to get a typed SimpleDbStore for the collectionParents
|
|
@@ -12251,12 +12359,31 @@ function dbCollectionGroupKey(collectionGroup, offset) {
|
|
|
12251
12359
|
}
|
|
12252
12360
|
/**
|
|
12253
12361
|
* Comparator that compares document keys according to the primary key sorting
|
|
12254
|
-
* used by the `DbRemoteDocumentDocument` store (by
|
|
12255
|
-
* document ID).
|
|
12362
|
+
* used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
|
|
12363
|
+
* and then document ID).
|
|
12364
|
+
*
|
|
12365
|
+
* Visible for testing.
|
|
12256
12366
|
*/
|
|
12257
12367
|
function dbKeyComparator(l, r) {
|
|
12258
|
-
const
|
|
12259
|
-
|
|
12368
|
+
const left = l.path.toArray();
|
|
12369
|
+
const right = r.path.toArray();
|
|
12370
|
+
// The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
|
|
12371
|
+
let cmp = 0;
|
|
12372
|
+
for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
|
|
12373
|
+
cmp = primitiveComparator(left[i], right[i]);
|
|
12374
|
+
if (cmp) {
|
|
12375
|
+
return cmp;
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
cmp = primitiveComparator(left.length, right.length);
|
|
12379
|
+
if (cmp) {
|
|
12380
|
+
return cmp;
|
|
12381
|
+
}
|
|
12382
|
+
cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
|
|
12383
|
+
if (cmp) {
|
|
12384
|
+
return cmp;
|
|
12385
|
+
}
|
|
12386
|
+
return primitiveComparator(left[left.length - 1], right[right.length - 1]);
|
|
12260
12387
|
}
|
|
12261
12388
|
/**
|
|
12262
12389
|
* Schema Version for the Web client:
|
|
@@ -13157,7 +13284,7 @@ class IndexedDbPersistence {
|
|
|
13157
13284
|
return this.remoteDocumentCache;
|
|
13158
13285
|
}
|
|
13159
13286
|
getIndexManager(user) {
|
|
13160
|
-
return new IndexedDbIndexManager(user);
|
|
13287
|
+
return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
|
|
13161
13288
|
}
|
|
13162
13289
|
getDocumentOverlayCache(user) {
|
|
13163
13290
|
return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
|
|
@@ -13653,7 +13780,7 @@ class LocalStoreImpl {
|
|
|
13653
13780
|
this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
|
|
13654
13781
|
this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
|
|
13655
13782
|
this.remoteDocuments.setIndexManager(this.indexManager);
|
|
13656
|
-
this.queryEngine.
|
|
13783
|
+
this.queryEngine.initialize(this.localDocuments, this.indexManager);
|
|
13657
13784
|
}
|
|
13658
13785
|
collectGarbage(garbageCollector) {
|
|
13659
13786
|
return this.persistence.runTransaction('Collect garbage', 'readwrite-primary', txn => garbageCollector.collect(txn, this.targetDataByTarget));
|
|
@@ -15395,10 +15522,30 @@ class MemoryEagerDelegate {
|
|
|
15395
15522
|
* limitations under the License.
|
|
15396
15523
|
*/
|
|
15397
15524
|
/**
|
|
15398
|
-
*
|
|
15399
|
-
*
|
|
15400
|
-
*
|
|
15401
|
-
*
|
|
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.
|
|
15402
15549
|
*
|
|
15403
15550
|
* There are some cases when this optimization is not guaranteed to produce
|
|
15404
15551
|
* the same results as full collection scans. In these cases, query
|
|
@@ -15413,16 +15560,41 @@ class MemoryEagerDelegate {
|
|
|
15413
15560
|
* - Queries that have never been CURRENT or free of limbo documents.
|
|
15414
15561
|
*/
|
|
15415
15562
|
class QueryEngine {
|
|
15563
|
+
constructor() {
|
|
15564
|
+
this.initialized = false;
|
|
15565
|
+
}
|
|
15416
15566
|
/** Sets the document view to query against. */
|
|
15417
|
-
|
|
15567
|
+
initialize(localDocuments, indexManager) {
|
|
15418
15568
|
this.localDocumentsView = localDocuments;
|
|
15569
|
+
this.indexManager = indexManager;
|
|
15570
|
+
this.initialized = true;
|
|
15419
15571
|
}
|
|
15420
15572
|
/** Returns all local documents matching the specified query. */
|
|
15421
15573
|
getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion, remoteKeys) {
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
|
|
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.
|
|
15426
15598
|
return this.executeFullCollectionScan(transaction, query);
|
|
15427
15599
|
}
|
|
15428
15600
|
// Queries that have never seen a snapshot without limbo free documents
|
|
@@ -15432,8 +15604,7 @@ class QueryEngine {
|
|
|
15432
15604
|
}
|
|
15433
15605
|
return this.localDocumentsView.getDocuments(transaction, remoteKeys).next(documents => {
|
|
15434
15606
|
const previousResults = this.applyQuery(query, documents);
|
|
15435
|
-
if ((
|
|
15436
|
-
this.needsRefill(query.limitType, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15607
|
+
if (this.needsRefill(query, previousResults, remoteKeys, lastLimboFreeSnapshotVersion)) {
|
|
15437
15608
|
return this.executeFullCollectionScan(transaction, query);
|
|
15438
15609
|
}
|
|
15439
15610
|
if (getLogLevel() <= LogLevel.DEBUG) {
|
|
@@ -15441,15 +15612,7 @@ class QueryEngine {
|
|
|
15441
15612
|
}
|
|
15442
15613
|
// Retrieve all results for documents that were updated since the last
|
|
15443
15614
|
// limbo-document free remote snapshot.
|
|
15444
|
-
return this.
|
|
15445
|
-
// We merge `previousResults` into `updateResults`, since
|
|
15446
|
-
// `updateResults` is already a DocumentMap. If a document is
|
|
15447
|
-
// contained in both lists, then its contents are the same.
|
|
15448
|
-
previousResults.forEach(doc => {
|
|
15449
|
-
updatedResults = updatedResults.insert(doc.key, doc);
|
|
15450
|
-
});
|
|
15451
|
-
return updatedResults;
|
|
15452
|
-
});
|
|
15615
|
+
return this.appendRemainingResults(transaction, previousResults, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID));
|
|
15453
15616
|
});
|
|
15454
15617
|
}
|
|
15455
15618
|
/** Applies the query filter and sorting to the provided documents. */
|
|
@@ -15468,6 +15631,7 @@ class QueryEngine {
|
|
|
15468
15631
|
* Determines if a limit query needs to be refilled from cache, making it
|
|
15469
15632
|
* ineligible for index-free execution.
|
|
15470
15633
|
*
|
|
15634
|
+
* @param query The query.
|
|
15471
15635
|
* @param sortedPreviousResults - The documents that matched the query when it
|
|
15472
15636
|
* was last synchronized, sorted by the query's comparator.
|
|
15473
15637
|
* @param remoteKeys - The document keys that matched the query at the last
|
|
@@ -15475,10 +15639,14 @@ class QueryEngine {
|
|
|
15475
15639
|
* @param limboFreeSnapshotVersion - The version of the snapshot when the
|
|
15476
15640
|
* query was last synchronized.
|
|
15477
15641
|
*/
|
|
15478
|
-
needsRefill(
|
|
15479
|
-
|
|
15480
|
-
|
|
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
|
+
}
|
|
15481
15647
|
if (remoteKeys.size !== sortedPreviousResults.size) {
|
|
15648
|
+
// The query needs to be refilled if a previously matching document no
|
|
15649
|
+
// longer matches.
|
|
15482
15650
|
return true;
|
|
15483
15651
|
}
|
|
15484
15652
|
// Limit queries are not eligible for index-free query execution if there is
|
|
@@ -15489,7 +15657,7 @@ class QueryEngine {
|
|
|
15489
15657
|
// the boundary of the limit itself did not change and documents from cache
|
|
15490
15658
|
// will continue to be "rejected" by this boundary. Therefore, we can ignore
|
|
15491
15659
|
// any modifications that don't affect the last document.
|
|
15492
|
-
const docAtLimitEdge = limitType === "F" /* First */
|
|
15660
|
+
const docAtLimitEdge = query.limitType === "F" /* First */
|
|
15493
15661
|
? sortedPreviousResults.last()
|
|
15494
15662
|
: sortedPreviousResults.first();
|
|
15495
15663
|
if (!docAtLimitEdge) {
|
|
@@ -15505,6 +15673,22 @@ class QueryEngine {
|
|
|
15505
15673
|
}
|
|
15506
15674
|
return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
|
|
15507
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
|
+
}
|
|
15508
15692
|
}
|
|
15509
15693
|
|
|
15510
15694
|
/**
|
|
@@ -21764,10 +21948,12 @@ class View {
|
|
|
21764
21948
|
//
|
|
21765
21949
|
// Note that this should never get used in a refill (when previousChanges is
|
|
21766
21950
|
// set), because there will only be adds -- no deletes or updates.
|
|
21767
|
-
const lastDocInLimit =
|
|
21951
|
+
const lastDocInLimit = this.query.limitType === "F" /* First */ &&
|
|
21952
|
+
oldDocumentSet.size === this.query.limit
|
|
21768
21953
|
? oldDocumentSet.last()
|
|
21769
21954
|
: null;
|
|
21770
|
-
const firstDocInLimit =
|
|
21955
|
+
const firstDocInLimit = this.query.limitType === "L" /* Last */ &&
|
|
21956
|
+
oldDocumentSet.size === this.query.limit
|
|
21771
21957
|
? oldDocumentSet.first()
|
|
21772
21958
|
: null;
|
|
21773
21959
|
docChanges.inorderTraversal((key, entry) => {
|
|
@@ -21840,9 +22026,9 @@ class View {
|
|
|
21840
22026
|
}
|
|
21841
22027
|
});
|
|
21842
22028
|
// Drop documents out to meet limit/limitToLast requirement.
|
|
21843
|
-
if (
|
|
22029
|
+
if (this.query.limit !== null) {
|
|
21844
22030
|
while (newDocumentSet.size > this.query.limit) {
|
|
21845
|
-
const oldDoc =
|
|
22031
|
+
const oldDoc = this.query.limitType === "F" /* First */
|
|
21846
22032
|
? newDocumentSet.last()
|
|
21847
22033
|
: newDocumentSet.first();
|
|
21848
22034
|
newDocumentSet = newDocumentSet.delete(oldDoc.key);
|
|
@@ -26973,7 +27159,8 @@ function snapshotEqual(left, right) {
|
|
|
26973
27159
|
* limitations under the License.
|
|
26974
27160
|
*/
|
|
26975
27161
|
function validateHasExplicitOrderByForLimitToLast(query) {
|
|
26976
|
-
if (
|
|
27162
|
+
if (query.limitType === "L" /* Last */ &&
|
|
27163
|
+
query.explicitOrderBy.length === 0) {
|
|
26977
27164
|
throw new FirestoreError(Code.UNIMPLEMENTED, 'limitToLast() queries require specifying at least one orderBy() clause');
|
|
26978
27165
|
}
|
|
26979
27166
|
}
|