Skip to content

Commit 8e621d5

Browse files
authored
fix: Add tests for multiple inequality support (#1878)
1 parent 81d4423 commit 8e621d5

File tree

3 files changed

+1002
-38
lines changed

3 files changed

+1002
-38
lines changed

dev/src/reference.ts

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -710,9 +710,6 @@ abstract class FilterInternal {
710710
/** Returns a list of all filters that are contained within this filter */
711711
abstract getFilters(): FilterInternal[];
712712

713-
/** Returns the field of the first filter that's an inequality, or null if none. */
714-
abstract getFirstInequalityField(): FieldPath | null;
715-
716713
/** Returns the proto representation of this filter */
717714
abstract toProto(): Filter;
718715

@@ -735,13 +732,6 @@ class CompositeFilterInternal extends FilterInternal {
735732
return this.filters;
736733
}
737734

738-
public getFirstInequalityField(): FieldPath | null {
739-
return (
740-
this.getFlattenedFilters().find(filter => filter.isInequalityFilter())
741-
?.field ?? null
742-
);
743-
}
744-
745735
public isConjunction(): boolean {
746736
return this.operator === 'AND';
747737
}
@@ -807,14 +797,6 @@ class FieldFilterInternal extends FilterInternal {
807797
return [this];
808798
}
809799

810-
getFirstInequalityField(): FieldPath | null {
811-
if (this.isInequalityFilter()) {
812-
return this.field;
813-
} else {
814-
return null;
815-
}
816-
}
817-
818800
/**
819801
* @param serializer The Firestore serializer
820802
* @param field The path of the property value to compare.
@@ -843,6 +825,8 @@ class FieldFilterInternal extends FilterInternal {
843825
case 'GREATER_THAN_OR_EQUAL':
844826
case 'LESS_THAN':
845827
case 'LESS_THAN_OR_EQUAL':
828+
case 'NOT_EQUAL':
829+
case 'NOT_IN':
846830
return true;
847831
default:
848832
return false;
@@ -1821,6 +1805,25 @@ export class Query<T = firestore.DocumentData> implements firestore.Query<T> {
18211805
);
18221806
}
18231807

1808+
/**
1809+
* Returns the sorted array of inequality filter fields used in this query.
1810+
*
1811+
* @return An array of inequality filter fields sorted lexicographically by FieldPath.
1812+
*/
1813+
private getInequalityFilterFields(): FieldPath[] {
1814+
const inequalityFields: FieldPath[] = [];
1815+
1816+
for (const filter of this._queryOptions.filters) {
1817+
for (const subFilter of filter.getFlattenedFilters()) {
1818+
if (subFilter.isInequalityFilter()) {
1819+
inequalityFields.push(subFilter.field);
1820+
}
1821+
}
1822+
}
1823+
1824+
return inequalityFields.sort((a, b) => a.compareTo(b));
1825+
}
1826+
18241827
/**
18251828
* Computes the backend ordering semantics for DocumentSnapshot cursors.
18261829
*
@@ -1855,29 +1858,36 @@ export class Query<T = firestore.DocumentData> implements firestore.Query<T> {
18551858
}
18561859

18571860
const fieldOrders = this._queryOptions.fieldOrders.slice();
1858-
1859-
// If no explicit ordering is specified, use the first inequality to
1860-
// define an implicit order.
1861-
if (fieldOrders.length === 0) {
1862-
for (const filter of this._queryOptions.filters) {
1863-
const fieldReference = filter.getFirstInequalityField();
1864-
if (fieldReference !== null) {
1865-
fieldOrders.push(new FieldOrder(fieldReference));
1866-
break;
1867-
}
1861+
const fieldsNormalized = new Set([
1862+
...fieldOrders.map(item => item.field.toString()),
1863+
]);
1864+
1865+
/** The order of the implicit ordering always matches the last explicit order by. */
1866+
const lastDirection =
1867+
fieldOrders.length === 0
1868+
? directionOperators.ASC
1869+
: fieldOrders[fieldOrders.length - 1].direction;
1870+
1871+
/**
1872+
* Any inequality fields not explicitly ordered should be implicitly ordered in a
1873+
* lexicographical order. When there are multiple inequality filters on the same field, the
1874+
* field should be added only once.
1875+
* Note: getInequalityFilterFields function sorts the key field before
1876+
* other fields. However, we want the key field to be sorted last.
1877+
*/
1878+
const inequalityFields = this.getInequalityFilterFields();
1879+
for (const field of inequalityFields) {
1880+
if (
1881+
!fieldsNormalized.has(field.toString()) &&
1882+
!field.isEqual(FieldPath.documentId())
1883+
) {
1884+
fieldOrders.push(new FieldOrder(field, lastDirection));
1885+
fieldsNormalized.add(field.toString());
18681886
}
18691887
}
18701888

1871-
const hasDocumentId = !!fieldOrders.find(fieldOrder =>
1872-
FieldPath.documentId().isEqual(fieldOrder.field)
1873-
);
1874-
if (!hasDocumentId) {
1875-
// Add implicit sorting by name, using the last specified direction.
1876-
const lastDirection =
1877-
fieldOrders.length === 0
1878-
? directionOperators.ASC
1879-
: fieldOrders[fieldOrders.length - 1].direction;
1880-
1889+
// Add the document key field to the last if it is not explicitly ordered.
1890+
if (!fieldsNormalized.has(FieldPath.documentId().toString())) {
18811891
fieldOrders.push(new FieldOrder(FieldPath.documentId(), lastDirection));
18821892
}
18831893

0 commit comments

Comments
 (0)