6363import java .util .ArrayList ;
6464import java .util .Collections ;
6565import java .util .Comparator ;
66+ import java .util .HashSet ;
6667import java .util .Iterator ;
6768import java .util .List ;
6869import java .util .Objects ;
6970import java .util .Set ;
71+ import java .util .SortedSet ;
72+ import java .util .TreeSet ;
7073import java .util .concurrent .Executor ;
7174import java .util .concurrent .atomic .AtomicReference ;
7275import java .util .logging .Logger ;
@@ -285,7 +288,9 @@ boolean isInequalityFilter() {
285288 return operator .equals (GREATER_THAN )
286289 || operator .equals (GREATER_THAN_OR_EQUAL )
287290 || operator .equals (LESS_THAN )
288- || operator .equals (LESS_THAN_OR_EQUAL );
291+ || operator .equals (LESS_THAN_OR_EQUAL )
292+ || operator .equals (NOT_EQUAL )
293+ || operator .equals (NOT_IN );
289294 }
290295
291296 @ Nullable
@@ -327,6 +332,11 @@ static final class FieldOrder {
327332 this .direction = direction ;
328333 }
329334
335+ FieldOrder (String field , Direction direction ) {
336+ this .fieldReference = FieldPath .fromServerFormat (field ).toProto ();
337+ this .direction = direction ;
338+ }
339+
330340 Order toProto () {
331341 Order .Builder result = Order .newBuilder ();
332342 result .setField (fieldReference );
@@ -462,39 +472,57 @@ private static boolean isUnaryComparison(@Nullable Object value) {
462472 return value == null || value .equals (Double .NaN ) || value .equals (Float .NaN );
463473 }
464474
465- /** Computes the backend ordering semantics for DocumentSnapshot cursors. */
466- private ImmutableList <FieldOrder > createImplicitOrderBy () {
467- List <FieldOrder > implicitOrders = new ArrayList <>(options .getFieldOrders ());
468-
469- // If no explicit ordering is specified, use the first inequality to define an implicit order.
470- if (implicitOrders .isEmpty ()) {
471- for (FilterInternal filter : options .getFilters ()) {
472- FieldReference fieldReference = filter .getFirstInequalityField ();
473- if (fieldReference != null ) {
474- implicitOrders .add (new FieldOrder (fieldReference , Direction .ASCENDING ));
475- break ;
475+ /** Returns the sorted set of inequality filter fields used in this query. */
476+ private SortedSet <FieldPath > getInequalityFilterFields () {
477+ SortedSet <FieldPath > result = new TreeSet <>();
478+
479+ for (FilterInternal filter : options .getFilters ()) {
480+ for (FieldFilterInternal subFilter : filter .getFlattenedFilters ()) {
481+ if (subFilter .isInequalityFilter ()) {
482+ result .add (FieldPath .fromServerFormat (subFilter .fieldReference .getFieldPath ()));
476483 }
477484 }
478485 }
479486
480- boolean hasDocumentId = false ;
481- for (FieldOrder fieldOrder : implicitOrders ) {
482- if (FieldPath .isDocumentId (fieldOrder .fieldReference .getFieldPath ())) {
483- hasDocumentId = true ;
487+ return result ;
488+ }
489+
490+ /** Computes the backend ordering semantics for DocumentSnapshot cursors. */
491+ ImmutableList <FieldOrder > createImplicitOrderBy () {
492+ // Any explicit order by fields should be added as is.
493+ List <FieldOrder > result = new ArrayList <>(options .getFieldOrders ());
494+
495+ HashSet <String > fieldsNormalized = new HashSet <>();
496+ for (FieldOrder order : result ) {
497+ fieldsNormalized .add (order .fieldReference .getFieldPath ());
498+ }
499+
500+ /** The order of the implicit ordering always matches the last explicit order by. */
501+ Direction lastDirection =
502+ result .isEmpty () ? Direction .ASCENDING : result .get (result .size () - 1 ).direction ;
503+
504+ /**
505+ * Any inequality fields not explicitly ordered should be implicitly ordered in a
506+ * lexicographical order. When there are multiple inequality filters on the same field, the
507+ * field should be added only once.
508+ *
509+ * <p>Note: `SortedSet<FieldPath>` sorts the key field before other fields. However, we want the
510+ * key field to be sorted last.
511+ */
512+ SortedSet <FieldPath > inequalityFields = getInequalityFilterFields ();
513+ for (FieldPath field : inequalityFields ) {
514+ if (!fieldsNormalized .contains (field .toString ())
515+ && !FieldPath .isDocumentId (field .toString ())) {
516+ result .add (new FieldOrder (field .toProto (), lastDirection ));
484517 }
485518 }
486519
487- if (!hasDocumentId ) {
488- // Add implicit sorting by name, using the last specified direction.
489- Direction lastDirection =
490- implicitOrders .isEmpty ()
491- ? Direction .ASCENDING
492- : implicitOrders .get (implicitOrders .size () - 1 ).direction ;
493-
494- implicitOrders .add (new FieldOrder (FieldPath .documentId ().toProto (), lastDirection ));
520+ // Add the document key field to the last if it is not explicitly ordered.
521+ if (!fieldsNormalized .contains (FieldPath .documentId ().toString ())) {
522+ result .add (new FieldOrder (FieldPath .documentId ().toProto (), lastDirection ));
495523 }
496524
497- return ImmutableList .<FieldOrder >builder ().addAll (implicitOrders ).build ();
525+ return ImmutableList .<FieldOrder >builder ().addAll (result ).build ();
498526 }
499527
500528 private Cursor createCursor (
@@ -506,11 +534,12 @@ private Cursor createCursor(
506534 if (FieldPath .isDocumentId (path )) {
507535 fieldValues .add (documentSnapshot .getReference ());
508536 } else {
509- FieldPath fieldPath = FieldPath .fromDotSeparatedString (path );
537+ FieldPath fieldPath = FieldPath .fromServerFormat (path );
510538 Preconditions .checkArgument (
511539 documentSnapshot .contains (fieldPath ),
512540 "Field '%s' is missing in the provided DocumentSnapshot. Please provide a document "
513- + "that contains values for all specified orderBy() and where() constraints." );
541+ + "that contains values for all specified orderBy() and where() constraints." ,
542+ fieldPath );
514543 fieldValues .add (documentSnapshot .get (fieldPath ));
515544 }
516545 }
0 commit comments