Skip to content

Commit 9d73ac2

Browse files
authored
Merge 8e353c6 into 44948ac
2 parents 44948ac + 8e353c6 commit 9d73ac2

File tree

5 files changed

+139
-93
lines changed

5 files changed

+139
-93
lines changed

packages/firestore/src/core/target.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ import {
3131
isReferenceValue,
3232
MAX_VALUE,
3333
MIN_VALUE,
34+
lowerBoundCompare,
3435
typeOrder,
36+
upperBoundCompare,
3537
valueCompare,
3638
valueEquals,
3739
valuesGetLowerBound,
38-
valuesGetUpperBound,
39-
valuesMax,
40-
valuesMin
40+
valuesGetUpperBound
4141
} from '../model/values';
4242
import { Value as ProtoValue } from '../protos/firestore_proto_api';
4343
import { debugAssert, debugCast, fail } from '../util/assert';
@@ -293,13 +293,13 @@ export function targetGetNotInValues(
293293

294294
/**
295295
* Returns a lower bound of field values that can be used as a starting point to
296-
* scan the index defined by `fieldIndex`. Returns `null` if no lower bound
296+
* scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound
297297
* exists.
298298
*/
299299
export function targetGetLowerBound(
300300
target: Target,
301301
fieldIndex: FieldIndex
302-
): Bound | null {
302+
): Bound {
303303
const values: ProtoValue[] = [];
304304
let inclusive = true;
305305

@@ -311,10 +311,6 @@ export function targetGetLowerBound(
311311
? targetGetAscendingBound(target, segment.fieldPath, target.startAt)
312312
: targetGetDescendingBound(target, segment.fieldPath, target.startAt);
313313

314-
if (!segmentBound.value) {
315-
// No lower bound exists
316-
return null;
317-
}
318314
values.push(segmentBound.value);
319315
inclusive &&= segmentBound.inclusive;
320316
}
@@ -323,13 +319,13 @@ export function targetGetLowerBound(
323319

324320
/**
325321
* Returns an upper bound of field values that can be used as an ending point
326-
* when scanning the index defined by `fieldIndex`. Returns `null` if no
322+
* when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no
327323
* upper bound exists.
328324
*/
329325
export function targetGetUpperBound(
330326
target: Target,
331327
fieldIndex: FieldIndex
332-
): Bound | null {
328+
): Bound {
333329
const values: ProtoValue[] = [];
334330
let inclusive = true;
335331

@@ -341,10 +337,6 @@ export function targetGetUpperBound(
341337
? targetGetDescendingBound(target, segment.fieldPath, target.endAt)
342338
: targetGetAscendingBound(target, segment.fieldPath, target.endAt);
343339

344-
if (!segmentBound.value) {
345-
// No upper bound exists
346-
return null;
347-
}
348340
values.push(segmentBound.value);
349341
inclusive &&= segmentBound.inclusive;
350342
}
@@ -360,13 +352,14 @@ function targetGetAscendingBound(
360352
target: Target,
361353
fieldPath: FieldPath,
362354
bound: Bound | null
363-
): { value: ProtoValue | undefined; inclusive: boolean } {
364-
let value: ProtoValue | undefined = undefined;
355+
): { value: ProtoValue; inclusive: boolean } {
356+
let value: ProtoValue = MIN_VALUE;
357+
365358
let inclusive = true;
366359

367360
// Process all filters to find a value for the current field segment
368361
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
369-
let filterValue: ProtoValue | undefined = undefined;
362+
let filterValue: ProtoValue = MIN_VALUE;
370363
let filterInclusive = true;
371364

372365
switch (fieldFilter.op) {
@@ -391,7 +384,12 @@ function targetGetAscendingBound(
391384
// Remaining filters cannot be used as lower bounds.
392385
}
393386

394-
if (valuesMax(value, filterValue) === filterValue) {
387+
if (
388+
lowerBoundCompare(
389+
{ value, inclusive },
390+
{ value: filterValue, inclusive: filterInclusive }
391+
) < 0
392+
) {
395393
value = filterValue;
396394
inclusive = filterInclusive;
397395
}
@@ -404,7 +402,12 @@ function targetGetAscendingBound(
404402
const orderBy = target.orderBy[i];
405403
if (orderBy.field.isEqual(fieldPath)) {
406404
const cursorValue = bound.position[i];
407-
if (valuesMax(value, cursorValue) === cursorValue) {
405+
if (
406+
lowerBoundCompare(
407+
{ value, inclusive },
408+
{ value: cursorValue, inclusive: bound.inclusive }
409+
) < 0
410+
) {
408411
value = cursorValue;
409412
inclusive = bound.inclusive;
410413
}
@@ -424,13 +427,13 @@ function targetGetDescendingBound(
424427
target: Target,
425428
fieldPath: FieldPath,
426429
bound: Bound | null
427-
): { value: ProtoValue | undefined; inclusive: boolean } {
428-
let value: ProtoValue | undefined = undefined;
430+
): { value: ProtoValue; inclusive: boolean } {
431+
let value: ProtoValue = MAX_VALUE;
429432
let inclusive = true;
430433

431434
// Process all filters to find a value for the current field segment
432435
for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
433-
let filterValue: ProtoValue | undefined = undefined;
436+
let filterValue: ProtoValue = MAX_VALUE;
434437
let filterInclusive = true;
435438

436439
switch (fieldFilter.op) {
@@ -456,7 +459,12 @@ function targetGetDescendingBound(
456459
// Remaining filters cannot be used as upper bounds.
457460
}
458461

459-
if (valuesMin(value, filterValue) === filterValue) {
462+
if (
463+
upperBoundCompare(
464+
{ value, inclusive },
465+
{ value: filterValue, inclusive: filterInclusive }
466+
) > 0
467+
) {
460468
value = filterValue;
461469
inclusive = filterInclusive;
462470
}
@@ -469,7 +477,12 @@ function targetGetDescendingBound(
469477
const orderBy = target.orderBy[i];
470478
if (orderBy.field.isEqual(fieldPath)) {
471479
const cursorValue = bound.position[i];
472-
if (valuesMin(value, cursorValue) === cursorValue) {
480+
if (
481+
upperBoundCompare(
482+
{ value, inclusive },
483+
{ value: cursorValue, inclusive: bound.inclusive }
484+
) > 0
485+
) {
473486
value = cursorValue;
474487
inclusive = bound.inclusive;
475488
}

packages/firestore/src/local/indexeddb_index_manager.ts

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ export class IndexedDbIndexManager implements IndexManager {
285285
index!.indexId,
286286
arrayValues,
287287
lowerBoundEncoded,
288-
!!lowerBound && lowerBound.inclusive,
288+
lowerBound.inclusive,
289289
upperBoundEncoded,
290-
!!upperBound && upperBound.inclusive,
290+
upperBound.inclusive,
291291
notInEncoded
292292
);
293293
return PersistencePromise.forEach(
@@ -331,9 +331,9 @@ export class IndexedDbIndexManager implements IndexManager {
331331
private generateIndexRanges(
332332
indexId: number,
333333
arrayValues: ProtoValue[] | null,
334-
lowerBounds: Uint8Array[] | null,
334+
lowerBounds: Uint8Array[],
335335
lowerBoundInclusive: boolean,
336-
upperBounds: Uint8Array[] | null,
336+
upperBounds: Uint8Array[],
337337
upperBoundInclusive: boolean,
338338
notInValues: Uint8Array[]
339339
): IDBKeyRange[] {
@@ -343,10 +343,7 @@ export class IndexedDbIndexManager implements IndexManager {
343343
// combined with the values from the query bounds.
344344
const totalScans =
345345
(arrayValues != null ? arrayValues.length : 1) *
346-
Math.max(
347-
lowerBounds != null ? lowerBounds.length : 1,
348-
upperBounds != null ? upperBounds.length : 1
349-
);
346+
Math.max(lowerBounds.length, upperBounds.length);
350347
const scansPerArrayElement =
351348
totalScans / (arrayValues != null ? arrayValues.length : 1);
352349

@@ -356,22 +353,18 @@ export class IndexedDbIndexManager implements IndexManager {
356353
? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
357354
: EMPTY_VALUE;
358355

359-
const lowerBound = lowerBounds
360-
? this.generateLowerBound(
361-
indexId,
362-
arrayValue,
363-
lowerBounds[i % scansPerArrayElement],
364-
lowerBoundInclusive
365-
)
366-
: this.generateEmptyBound(indexId);
367-
const upperBound = upperBounds
368-
? this.generateUpperBound(
369-
indexId,
370-
arrayValue,
371-
upperBounds[i % scansPerArrayElement],
372-
upperBoundInclusive
373-
)
374-
: this.generateEmptyBound(indexId + 1);
356+
const lowerBound = this.generateLowerBound(
357+
indexId,
358+
arrayValue,
359+
lowerBounds[i % scansPerArrayElement],
360+
lowerBoundInclusive
361+
);
362+
const upperBound = this.generateUpperBound(
363+
indexId,
364+
arrayValue,
365+
upperBounds[i % scansPerArrayElement],
366+
upperBoundInclusive
367+
);
375368

376369
const notInBound = notInValues.map(notIn =>
377370
this.generateLowerBound(
@@ -420,19 +413,6 @@ export class IndexedDbIndexManager implements IndexManager {
420413
return inclusive ? entry.successor() : entry;
421414
}
422415

423-
/**
424-
* Generates an empty bound that scopes the index scan to the current index
425-
* and user.
426-
*/
427-
private generateEmptyBound(indexId: number): IndexEntry {
428-
return new IndexEntry(
429-
indexId,
430-
DocumentKey.empty(),
431-
EMPTY_VALUE,
432-
EMPTY_VALUE
433-
);
434-
}
435-
436416
private getFieldIndex(
437417
transaction: PersistenceTransaction,
438418
target: Target
@@ -572,11 +552,8 @@ export class IndexedDbIndexManager implements IndexManager {
572552
private encodeBound(
573553
fieldIndex: FieldIndex,
574554
target: Target,
575-
bound: Bound | null
576-
): Uint8Array[] | null {
577-
if (bound == null) {
578-
return null;
579-
}
555+
bound: Bound
556+
): Uint8Array[] {
580557
return this.encodeValues(fieldIndex, target, bound.position);
581558
}
582559

packages/firestore/src/model/values.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function typeOrder(value: Value): TypeOrder {
7878
if (isServerTimestamp(value)) {
7979
return TypeOrder.ServerTimestampValue;
8080
} else if (isMaxValue(value)) {
81-
return TypeOrder.ArrayValue;
81+
return TypeOrder.MaxValue;
8282
}
8383
return TypeOrder.ObjectValue;
8484
} else {
@@ -350,6 +350,14 @@ function compareArrays(left: ArrayValue, right: ArrayValue): number {
350350
}
351351

352352
function compareMaps(left: MapValue, right: MapValue): number {
353+
if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
354+
return 0;
355+
} else if (left === MAX_VALUE.mapValue) {
356+
return 1;
357+
} else if (right === MAX_VALUE.mapValue) {
358+
return -1;
359+
}
360+
353361
const leftMap = left.fields || {};
354362
const leftKeys = Object.keys(leftMap);
355363
const rightMap = right.fields || {};
@@ -670,28 +678,38 @@ export function valuesGetUpperBound(value: Value): Value {
670678
}
671679
}
672680

673-
export function valuesMax(
674-
left: Value | undefined,
675-
right: Value | undefined
676-
): Value | undefined {
677-
if (left === undefined) {
678-
return right;
679-
} else if (right === undefined) {
680-
return left;
681-
} else {
682-
return valueCompare(left, right) > 0 ? left : right;
681+
export function lowerBoundCompare(
682+
left: { value: Value; inclusive: boolean },
683+
right: { value: Value; inclusive: boolean }
684+
): number {
685+
const cmp = valueCompare(left.value, right.value);
686+
if (cmp !== 0) {
687+
return cmp;
683688
}
689+
690+
if (left.inclusive && !right.inclusive) {
691+
return -1;
692+
} else if (!left.inclusive && right.inclusive) {
693+
return 1;
694+
}
695+
696+
return 0;
684697
}
685698

686-
export function valuesMin(
687-
left: Value | undefined,
688-
right: Value | undefined
689-
): Value | undefined {
690-
if (left === undefined) {
691-
return right;
692-
} else if (right === undefined) {
693-
return left;
694-
} else {
695-
return valueCompare(left, right) < 0 ? left : right;
699+
export function upperBoundCompare(
700+
left: { value: Value; inclusive: boolean },
701+
right: { value: Value; inclusive: boolean }
702+
): number {
703+
const cmp = valueCompare(left.value, right.value);
704+
if (cmp !== 0) {
705+
return cmp;
696706
}
707+
708+
if (left.inclusive && !right.inclusive) {
709+
return 1;
710+
} else if (!left.inclusive && right.inclusive) {
711+
return -1;
712+
}
713+
714+
return 0;
697715
}

packages/firestore/test/unit/local/index_manager.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,35 @@ describe('IndexedDbIndexManager', async () => {
886886
await verifyResults(queryWithRestrictedBound, 'coll/val4');
887887
});
888888

889+
it('cannot expand result set from a cursor', async () => {
890+
await indexManager.addFieldIndex(
891+
fieldIndex('coll', { fields: [['c', IndexKind.ASCENDING]] })
892+
);
893+
await indexManager.addFieldIndex(
894+
fieldIndex('coll', { fields: [['c', IndexKind.DESCENDING]] })
895+
);
896+
await addDoc('coll/val1', { 'a': 1, 'b': 1, 'c': 3 });
897+
await addDoc('coll/val2', { 'a': 2, 'b': 2, 'c': 2 });
898+
899+
let testingQuery = queryWithStartAt(
900+
queryWithAddedOrderBy(
901+
queryWithAddedFilter(query('coll'), filter('c', '>', 2)),
902+
orderBy('c', 'asc')
903+
),
904+
bound([2], /* inclusive= */ true)
905+
);
906+
await verifyResults(testingQuery, 'coll/val1');
907+
908+
testingQuery = queryWithStartAt(
909+
queryWithAddedOrderBy(
910+
queryWithAddedFilter(query('coll'), filter('c', '<', 3)),
911+
orderBy('c', 'desc')
912+
),
913+
bound([3], /* inclusive= */ true)
914+
);
915+
await verifyResults(testingQuery, 'coll/val2');
916+
});
917+
889918
it('support advances queries', async () => {
890919
// This test compares local query results with those received from the Java
891920
// Server SDK.

0 commit comments

Comments
 (0)