@@ -19,13 +19,14 @@ import { compareDocumentsByField, Document } from '../model/document';
1919import { DocumentKey } from '../model/document_key' ;
2020import { FieldPath , ResourcePath } from '../model/path' ;
2121import { debugAssert , debugCast , fail } from '../util/assert' ;
22+ import { SortedSet } from '../util/sorted_set' ;
2223
2324import {
2425 Bound ,
2526 boundSortsAfterDocument ,
2627 boundSortsBeforeDocument
2728} from './bound' ;
28- import { Filter } from './filter' ;
29+ import { FieldFilter , Filter } from './filter' ;
2930import { Direction , OrderBy } from './order_by' ;
3031import {
3132 canonifyTarget ,
@@ -172,21 +173,18 @@ export function queryMatchesAllDocuments(query: Query): boolean {
172173 ) ;
173174}
174175
175- export function getFirstOrderByField ( query : Query ) : FieldPath | null {
176- return query . explicitOrderBy . length > 0
177- ? query . explicitOrderBy [ 0 ] . field
178- : null ;
179- }
180-
181- export function getInequalityFilterField ( query : Query ) : FieldPath | null {
182- for ( const filter of query . filters ) {
183- const result = filter . getFirstInequalityField ( ) ;
184- if ( result !== null ) {
185- return result ;
186- }
187- }
188-
189- return null ;
176+ // Returns the sorted set of inequality filter fields used in this query.
177+ export function getInequalityFilterFields ( query : Query ) : SortedSet < FieldPath > {
178+ let result = new SortedSet < FieldPath > ( FieldPath . comparator ) ;
179+ query . filters . forEach ( ( filter : Filter ) => {
180+ const subFilters = filter . getFlattenedFilters ( ) ;
181+ subFilters . forEach ( ( filter : FieldFilter ) => {
182+ if ( filter . isInequality ( ) ) {
183+ result = result . add ( filter . field ) ;
184+ }
185+ } ) ;
186+ } ) ;
187+ return result ;
190188}
191189
192190/**
@@ -228,45 +226,43 @@ export function queryNormalizedOrderBy(query: Query): OrderBy[] {
228226 const queryImpl = debugCast ( query , QueryImpl ) ;
229227 if ( queryImpl . memoizedNormalizedOrderBy === null ) {
230228 queryImpl . memoizedNormalizedOrderBy = [ ] ;
229+ const fieldsNormalized = new Set < string > ( ) ;
231230
232- const inequalityField = getInequalityFilterField ( queryImpl ) ;
233- const firstOrderByField = getFirstOrderByField ( queryImpl ) ;
234- if ( inequalityField !== null && firstOrderByField === null ) {
235- // In order to implicitly add key ordering, we must also add the
236- // inequality filter field for it to be a valid query.
237- // Note that the default inequality field and key ordering is ascending.
238- if ( ! inequalityField . isKeyField ( ) ) {
239- queryImpl . memoizedNormalizedOrderBy . push ( new OrderBy ( inequalityField ) ) ;
231+ // Any explicit order by fields should be added as is.
232+ for ( const orderBy of queryImpl . explicitOrderBy ) {
233+ queryImpl . memoizedNormalizedOrderBy . push ( orderBy ) ;
234+ fieldsNormalized . add ( orderBy . field . canonicalString ( ) ) ;
235+ }
236+
237+ // The order of the implicit ordering always matches the last explicit order by.
238+ const lastDirection =
239+ queryImpl . explicitOrderBy . length > 0
240+ ? queryImpl . explicitOrderBy [ queryImpl . explicitOrderBy . length - 1 ] . dir
241+ : Direction . ASCENDING ;
242+
243+ // Any inequality fields not explicitly ordered should be implicitly ordered in a lexicographical
244+ // order. When there are multiple inequality filters on the same field, the field should be added
245+ // only once.
246+ // Note: `SortedSet<FieldPath>` sorts the key field before other fields. However, we want the key
247+ // field to be sorted last.
248+ const inequalityFields : SortedSet < FieldPath > =
249+ getInequalityFilterFields ( queryImpl ) ;
250+ inequalityFields . forEach ( field => {
251+ if (
252+ ! fieldsNormalized . has ( field . canonicalString ( ) ) &&
253+ ! field . isKeyField ( )
254+ ) {
255+ queryImpl . memoizedNormalizedOrderBy ! . push (
256+ new OrderBy ( field , lastDirection )
257+ ) ;
240258 }
259+ } ) ;
260+
261+ // Add the document key field to the last if it is not explicitly ordered.
262+ if ( ! fieldsNormalized . has ( FieldPath . keyField ( ) . canonicalString ( ) ) ) {
241263 queryImpl . memoizedNormalizedOrderBy . push (
242- new OrderBy ( FieldPath . keyField ( ) , Direction . ASCENDING )
243- ) ;
244- } else {
245- debugAssert (
246- inequalityField === null ||
247- ( firstOrderByField !== null &&
248- inequalityField . isEqual ( firstOrderByField ) ) ,
249- 'First orderBy should match inequality field.'
264+ new OrderBy ( FieldPath . keyField ( ) , lastDirection )
250265 ) ;
251- let foundKeyOrdering = false ;
252- for ( const orderBy of queryImpl . explicitOrderBy ) {
253- queryImpl . memoizedNormalizedOrderBy . push ( orderBy ) ;
254- if ( orderBy . field . isKeyField ( ) ) {
255- foundKeyOrdering = true ;
256- }
257- }
258- if ( ! foundKeyOrdering ) {
259- // The order of the implicit key ordering always matches the last
260- // explicit order-by
261- const lastDirection =
262- queryImpl . explicitOrderBy . length > 0
263- ? queryImpl . explicitOrderBy [ queryImpl . explicitOrderBy . length - 1 ]
264- . dir
265- : Direction . ASCENDING ;
266- queryImpl . memoizedNormalizedOrderBy . push (
267- new OrderBy ( FieldPath . keyField ( ) , lastDirection )
268- ) ;
269- }
270266 }
271267 }
272268 return queryImpl . memoizedNormalizedOrderBy ;
@@ -350,16 +346,6 @@ function _queryToTarget(queryImpl: QueryImpl, orderBys: OrderBy[]): Target {
350346}
351347
352348export function queryWithAddedFilter ( query : Query , filter : Filter ) : Query {
353- const newInequalityField = filter . getFirstInequalityField ( ) ;
354- const queryInequalityField = getInequalityFilterField ( query ) ;
355-
356- debugAssert (
357- queryInequalityField == null ||
358- newInequalityField == null ||
359- newInequalityField . isEqual ( queryInequalityField ) ,
360- 'Query must only have one inequality field.'
361- ) ;
362-
363349 debugAssert (
364350 ! isDocumentQuery ( query ) ,
365351 'No filtering allowed for document query'
0 commit comments