Skip to content

Commit 7f95962

Browse files
Change Bound.before to Bound.inclusive (#5958)
1 parent 4cec423 commit 7f95962

File tree

8 files changed

+128
-86
lines changed

8 files changed

+128
-86
lines changed

packages/firestore/src/core/query.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ import {
3030
newTarget,
3131
Operator,
3232
OrderBy,
33-
sortsBeforeDocument,
33+
boundSortsBeforeDocument,
3434
stringifyTarget,
3535
Target,
36-
targetEquals
36+
targetEquals,
37+
boundSortsAfterDocument
3738
} from './target';
3839

3940
export const enum LimitType {
@@ -323,10 +324,10 @@ export function queryToTarget(query: Query): Target {
323324

324325
// We need to swap the cursors to match the now-flipped query ordering.
325326
const startAt = queryImpl.endAt
326-
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.before)
327+
? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
327328
: null;
328329
const endAt = queryImpl.startAt
329-
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.before)
330+
? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
330331
: null;
331332

332333
// Now return as a LimitType.First query.
@@ -512,13 +513,13 @@ function queryMatchesFilters(query: Query, doc: Document): boolean {
512513
function queryMatchesBounds(query: Query, doc: Document): boolean {
513514
if (
514515
query.startAt &&
515-
!sortsBeforeDocument(query.startAt, queryOrderBy(query), doc)
516+
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)
516517
) {
517518
return false;
518519
}
519520
if (
520521
query.endAt &&
521-
sortsBeforeDocument(query.endAt, queryOrderBy(query), doc)
522+
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)
522523
) {
523524
return false;
524525
}

packages/firestore/src/core/target.ts

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -105,28 +105,30 @@ export function canonifyTarget(target: Target): string {
105105
const targetImpl = debugCast(target, TargetImpl);
106106

107107
if (targetImpl.memoizedCanonicalId === null) {
108-
let canonicalId = targetImpl.path.canonicalString();
108+
let str = targetImpl.path.canonicalString();
109109
if (targetImpl.collectionGroup !== null) {
110-
canonicalId += '|cg:' + targetImpl.collectionGroup;
110+
str += '|cg:' + targetImpl.collectionGroup;
111111
}
112-
canonicalId += '|f:';
113-
canonicalId += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
114-
canonicalId += '|ob:';
115-
canonicalId += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
112+
str += '|f:';
113+
str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
114+
str += '|ob:';
115+
str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
116116

117117
if (!isNullOrUndefined(targetImpl.limit)) {
118-
canonicalId += '|l:';
119-
canonicalId += targetImpl.limit!;
118+
str += '|l:';
119+
str += targetImpl.limit!;
120120
}
121121
if (targetImpl.startAt) {
122-
canonicalId += '|lb:';
123-
canonicalId += canonifyBound(targetImpl.startAt);
122+
str += '|lb:';
123+
str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
124+
str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
124125
}
125126
if (targetImpl.endAt) {
126-
canonicalId += '|ub:';
127-
canonicalId += canonifyBound(targetImpl.endAt);
127+
str += '|ub:';
128+
str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
129+
str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
128130
}
129-
targetImpl.memoizedCanonicalId = canonicalId;
131+
targetImpl.memoizedCanonicalId = str;
130132
}
131133
return targetImpl.memoizedCanonicalId;
132134
}
@@ -150,10 +152,14 @@ export function stringifyTarget(target: Target): string {
150152
.join(', ')}]`;
151153
}
152154
if (target.startAt) {
153-
str += ', startAt: ' + canonifyBound(target.startAt);
155+
str += ', startAt: ';
156+
str += target.startAt.inclusive ? 'b:' : 'a:';
157+
str += target.startAt.position.map(p => canonicalId(p)).join(',');
154158
}
155159
if (target.endAt) {
156-
str += ', endAt: ' + canonifyBound(target.endAt);
160+
str += ', endAt: ';
161+
str += target.endAt.inclusive ? 'a:' : 'b:';
162+
str += target.endAt.position.map(p => canonicalId(p)).join(',');
157163
}
158164
return `Target(${str})`;
159165
}
@@ -351,7 +357,7 @@ export function targetGetLowerBound(
351357
const cursorValue = target.startAt.position[i];
352358
if (valuesMax(segmentValue, cursorValue) === cursorValue) {
353359
segmentValue = cursorValue;
354-
segmentInclusive = !target.startAt.before;
360+
segmentInclusive = target.startAt.inclusive;
355361
}
356362
break;
357363
}
@@ -365,7 +371,7 @@ export function targetGetLowerBound(
365371
values.push(segmentValue);
366372
inclusive &&= segmentInclusive;
367373
}
368-
return new Bound(values, !inclusive);
374+
return new Bound(values, inclusive);
369375
}
370376
/**
371377
* Returns an upper bound of field values that can be used as an ending point
@@ -436,7 +442,7 @@ export function targetGetUpperBound(
436442
const cursorValue = target.endAt.position[i];
437443
if (valuesMin(segmentValue, cursorValue) === cursorValue) {
438444
segmentValue = cursorValue;
439-
segmentInclusive = !target.endAt.before;
445+
segmentInclusive = target.endAt.inclusive;
440446
}
441447
break;
442448
}
@@ -451,7 +457,7 @@ export function targetGetUpperBound(
451457
inclusive &&= segmentInclusive;
452458
}
453459

454-
return new Bound(values, !inclusive);
460+
return new Bound(values, inclusive);
455461
}
456462

457463
export abstract class Filter {
@@ -772,7 +778,6 @@ export class ArrayContainsAnyFilter extends FieldFilter {
772778
}
773779
}
774780

775-
// TODO(indexing): Change Bound.before to "inclusive"
776781
/**
777782
* Represents a bound of a query.
778783
*
@@ -788,14 +793,7 @@ export class ArrayContainsAnyFilter extends FieldFilter {
788793
* just after the provided values.
789794
*/
790795
export class Bound {
791-
constructor(readonly position: ProtoValue[], readonly before: boolean) {}
792-
}
793-
794-
export function canonifyBound(bound: Bound): string {
795-
// TODO(b/29183165): Make this collision robust.
796-
return `${bound.before ? 'b' : 'a'}:${bound.position
797-
.map(p => canonicalId(p))
798-
.join(',')}`;
796+
constructor(readonly position: ProtoValue[], readonly inclusive: boolean) {}
799797
}
800798

801799
/**
@@ -821,15 +819,11 @@ export function orderByEquals(left: OrderBy, right: OrderBy): boolean {
821819
return left.dir === right.dir && left.field.isEqual(right.field);
822820
}
823821

824-
/**
825-
* Returns true if a document sorts before a bound using the provided sort
826-
* order.
827-
*/
828-
export function sortsBeforeDocument(
822+
function boundCompareToDocument(
829823
bound: Bound,
830824
orderBy: OrderBy[],
831825
doc: Document
832-
): boolean {
826+
): number {
833827
debugAssert(
834828
bound.position.length <= orderBy.length,
835829
"Bound has more components than query's orderBy"
@@ -862,7 +856,33 @@ export function sortsBeforeDocument(
862856
break;
863857
}
864858
}
865-
return bound.before ? comparison <= 0 : comparison < 0;
859+
return comparison;
860+
}
861+
862+
/**
863+
* Returns true if a document sorts after a bound using the provided sort
864+
* order.
865+
*/
866+
export function boundSortsAfterDocument(
867+
bound: Bound,
868+
orderBy: OrderBy[],
869+
doc: Document
870+
): boolean {
871+
const comparison = boundCompareToDocument(bound, orderBy, doc);
872+
return bound.inclusive ? comparison >= 0 : comparison > 0;
873+
}
874+
875+
/**
876+
* Returns true if a document sorts before a bound using the provided sort
877+
* order.
878+
*/
879+
export function boundSortsBeforeDocument(
880+
bound: Bound,
881+
orderBy: OrderBy[],
882+
doc: Document
883+
): boolean {
884+
const comparison = boundCompareToDocument(bound, orderBy, doc);
885+
return bound.inclusive ? comparison <= 0 : comparison < 0;
866886
}
867887

868888
export function boundEquals(left: Bound | null, right: Bound | null): boolean {
@@ -873,7 +893,7 @@ export function boundEquals(left: Bound | null, right: Bound | null): boolean {
873893
}
874894

875895
if (
876-
left.before !== right.before ||
896+
left.inclusive !== right.inclusive ||
877897
left.position.length !== right.position.length
878898
) {
879899
return false;

packages/firestore/src/lite-api/query.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ class QueryStartAtConstraint extends QueryConstraint {
282282
constructor(
283283
readonly type: 'startAt' | 'startAfter',
284284
private readonly _docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
285-
private readonly _before: boolean
285+
private readonly _inclusive: boolean
286286
) {
287287
super();
288288
}
@@ -292,7 +292,7 @@ class QueryStartAtConstraint extends QueryConstraint {
292292
query,
293293
this.type,
294294
this._docOrFields,
295-
this._before
295+
this._inclusive
296296
);
297297
return new Query(
298298
query.firestore,
@@ -325,7 +325,11 @@ export function startAt(...fieldValues: unknown[]): QueryConstraint;
325325
export function startAt(
326326
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
327327
): QueryConstraint {
328-
return new QueryStartAtConstraint('startAt', docOrFields, /*before=*/ true);
328+
return new QueryStartAtConstraint(
329+
'startAt',
330+
docOrFields,
331+
/*inclusive=*/ true
332+
);
329333
}
330334

331335
/**
@@ -356,15 +360,15 @@ export function startAfter(
356360
return new QueryStartAtConstraint(
357361
'startAfter',
358362
docOrFields,
359-
/*before=*/ false
363+
/*inclusive=*/ false
360364
);
361365
}
362366

363367
class QueryEndAtConstraint extends QueryConstraint {
364368
constructor(
365369
readonly type: 'endBefore' | 'endAt',
366370
private readonly _docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
367-
private readonly _before: boolean
371+
private readonly _inclusive: boolean
368372
) {
369373
super();
370374
}
@@ -374,7 +378,7 @@ class QueryEndAtConstraint extends QueryConstraint {
374378
query,
375379
this.type,
376380
this._docOrFields,
377-
this._before
381+
this._inclusive
378382
);
379383
return new Query(
380384
query.firestore,
@@ -407,7 +411,11 @@ export function endBefore(...fieldValues: unknown[]): QueryConstraint;
407411
export function endBefore(
408412
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
409413
): QueryConstraint {
410-
return new QueryEndAtConstraint('endBefore', docOrFields, /*before=*/ true);
414+
return new QueryEndAtConstraint(
415+
'endBefore',
416+
docOrFields,
417+
/*inclusive=*/ false
418+
);
411419
}
412420

413421
/**
@@ -433,15 +441,15 @@ export function endAt(...fieldValues: unknown[]): QueryConstraint;
433441
export function endAt(
434442
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
435443
): QueryConstraint {
436-
return new QueryEndAtConstraint('endAt', docOrFields, /*before=*/ false);
444+
return new QueryEndAtConstraint('endAt', docOrFields, /*inclusive=*/ true);
437445
}
438446

439447
/** Helper function to create a bound from a document or fields */
440448
function newQueryBoundFromDocOrFields<T>(
441449
query: Query,
442450
methodName: string,
443451
docOrFields: Array<unknown | DocumentSnapshot<T>>,
444-
before: boolean
452+
inclusive: boolean
445453
): Bound {
446454
docOrFields[0] = getModularInstance(docOrFields[0]);
447455

@@ -451,7 +459,7 @@ function newQueryBoundFromDocOrFields<T>(
451459
query.firestore._databaseId,
452460
methodName,
453461
docOrFields[0]._document,
454-
before
462+
inclusive
455463
);
456464
} else {
457465
const reader = newUserDataReader(query.firestore);
@@ -461,7 +469,7 @@ function newQueryBoundFromDocOrFields<T>(
461469
reader,
462470
methodName,
463471
docOrFields,
464-
before
472+
inclusive
465473
);
466474
}
467475
}
@@ -552,7 +560,7 @@ export function newQueryBoundFromDocument(
552560
databaseId: DatabaseId,
553561
methodName: string,
554562
doc: Document | null,
555-
before: boolean
563+
inclusive: boolean
556564
): Bound {
557565
if (!doc) {
558566
throw new FirestoreError(
@@ -598,7 +606,7 @@ export function newQueryBoundFromDocument(
598606
}
599607
}
600608
}
601-
return new Bound(components, before);
609+
return new Bound(components, inclusive);
602610
}
603611

604612
/**
@@ -610,7 +618,7 @@ export function newQueryBoundFromFields(
610618
dataReader: UserDataReader,
611619
methodName: string,
612620
values: unknown[],
613-
before: boolean
621+
inclusive: boolean
614622
): Bound {
615623
// Use explicit order by's because it has to match the query the user made
616624
const orderBy = query.explicitOrderBy;
@@ -661,7 +669,7 @@ export function newQueryBoundFromFields(
661669
}
662670
}
663671

664-
return new Bound(components, before);
672+
return new Bound(components, inclusive);
665673
}
666674

667675
/**

0 commit comments

Comments
 (0)