1717import static com .google .firebase .firestore .model .Values .max ;
1818import static com .google .firebase .firestore .model .Values .min ;
1919
20+ import android .util .Pair ;
2021import androidx .annotation .Nullable ;
22+ import com .google .firebase .firestore .core .OrderBy .Direction ;
2123import com .google .firebase .firestore .model .DocumentKey ;
2224import com .google .firebase .firestore .model .FieldIndex ;
2325import com .google .firebase .firestore .model .FieldPath ;
@@ -185,65 +187,18 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
185187
186188 // For each segment, retrieve a lower bound if there is a suitable filter or startAt.
187189 for (FieldIndex .Segment segment : fieldIndex .getDirectionalSegments ()) {
188- Value segmentValue = null ;
189- boolean segmentInclusive = true ;
190+ Pair <Value , Boolean > segmentBound =
191+ segment .getKind ().equals (FieldIndex .Segment .Kind .ASCENDING )
192+ ? getAscendingBound (segment , startAt )
193+ : getDescendingBound (segment , startAt );
190194
191- // Process all filters to find a value for the current field segment
192- for (FieldFilter fieldFilter : getFieldFiltersForPath (segment .getFieldPath ())) {
193- Value filterValue = null ;
194- boolean filterInclusive = true ;
195-
196- switch (fieldFilter .getOperator ()) {
197- case LESS_THAN :
198- case LESS_THAN_OR_EQUAL :
199- filterValue = Values .getLowerBound (fieldFilter .getValue ().getValueTypeCase ());
200- break ;
201- case EQUAL :
202- case IN :
203- case GREATER_THAN_OR_EQUAL :
204- filterValue = fieldFilter .getValue ();
205- break ;
206- case GREATER_THAN :
207- filterValue = fieldFilter .getValue ();
208- filterInclusive = false ;
209- break ;
210- case NOT_EQUAL :
211- case NOT_IN :
212- filterValue = Values .MIN_VALUE ;
213- break ;
214- default :
215- // Remaining filters cannot be used as lower bounds.
216- }
217-
218- if (max (segmentValue , filterValue ) == filterValue ) {
219- segmentValue = filterValue ;
220- segmentInclusive = filterInclusive ;
221- }
222- }
223-
224- // If there is a startAt bound, compare the values against the existing boundary to see
225- // if we can narrow the scope.
226- if (startAt != null ) {
227- for (int i = 0 ; i < orderBys .size (); ++i ) {
228- OrderBy orderBy = this .orderBys .get (i );
229- if (orderBy .getField ().equals (segment .getFieldPath ())) {
230- Value cursorValue = startAt .getPosition ().get (i );
231- if (max (segmentValue , cursorValue ) == cursorValue ) {
232- segmentValue = cursorValue ;
233- segmentInclusive = startAt .isInclusive ();
234- }
235- break ;
236- }
237- }
238- }
239-
240- if (segmentValue == null ) {
195+ if (segmentBound .first == null ) {
241196 // No lower bound exists
242197 return null ;
243198 }
244199
245- values .add (segmentValue );
246- inclusive &= segmentInclusive ;
200+ values .add (segmentBound . first );
201+ inclusive &= segmentBound . second ;
247202 }
248203
249204 return new Bound (values , inclusive );
@@ -259,75 +214,159 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
259214
260215 // For each segment, retrieve an upper bound if there is a suitable filter or endAt.
261216 for (FieldIndex .Segment segment : fieldIndex .getDirectionalSegments ()) {
262- @ Nullable Value segmentValue = null ;
263- boolean segmentInclusive = true ;
217+ Pair <Value , Boolean > segmentBound =
218+ segment .getKind ().equals (FieldIndex .Segment .Kind .ASCENDING )
219+ ? getDescendingBound (segment , endAt )
220+ : getAscendingBound (segment , endAt );
264221
265- // Process all filters to find a value for the current field segment
266- for ( FieldFilter fieldFilter : getFieldFiltersForPath ( segment . getFieldPath ())) {
267- Value filterValue = null ;
268- boolean filterInclusive = true ;
222+ if ( segmentBound . first == null ) {
223+ // No upper bound exists
224+ return null ;
225+ }
269226
270- switch (fieldFilter .getOperator ()) {
271- case GREATER_THAN_OR_EQUAL :
272- case GREATER_THAN :
273- filterValue = Values .getUpperBound (fieldFilter .getValue ().getValueTypeCase ());
274- filterInclusive = false ;
275- break ;
276- case EQUAL :
277- case IN :
278- case LESS_THAN_OR_EQUAL :
279- filterValue = fieldFilter .getValue ();
280- break ;
281- case LESS_THAN :
282- filterValue = fieldFilter .getValue ();
283- filterInclusive = false ;
284- break ;
285- case NOT_EQUAL :
286- case NOT_IN :
287- filterValue = Values .MAX_VALUE ;
288- break ;
289- default :
290- // Remaining filters cannot be used as upper bounds.
291- }
227+ values .add (segmentBound .first );
228+ inclusive &= segmentBound .second ;
229+ }
292230
293- if (min (segmentValue , filterValue ) == filterValue ) {
294- segmentValue = filterValue ;
295- segmentInclusive = filterInclusive ;
296- }
231+ return new Bound (values , inclusive );
232+ }
233+
234+ /**
235+ * Returns the value for an ascending bound of `segment`.
236+ *
237+ * @param segment The segment to get the value for.
238+ * @param bound A bound to restrict the index range.
239+ * @return a Pair with a nullable Value and a boolean indicating whether the bound is inclusive
240+ */
241+ private Pair <Value , Boolean > getAscendingBound (
242+ FieldIndex .Segment segment , @ Nullable Bound bound ) {
243+ Value segmentValue = null ;
244+ boolean segmentInclusive = true ;
245+
246+ // Process all filters to find a value for the current field segment
247+ for (FieldFilter fieldFilter : getFieldFiltersForPath (segment .getFieldPath ())) {
248+ Value filterValue = null ;
249+ boolean filterInclusive = true ;
250+
251+ switch (fieldFilter .getOperator ()) {
252+ case LESS_THAN :
253+ case LESS_THAN_OR_EQUAL :
254+ filterValue = Values .getLowerBound (fieldFilter .getValue ().getValueTypeCase ());
255+ break ;
256+ case EQUAL :
257+ case IN :
258+ case GREATER_THAN_OR_EQUAL :
259+ filterValue = fieldFilter .getValue ();
260+ break ;
261+ case GREATER_THAN :
262+ filterValue = fieldFilter .getValue ();
263+ filterInclusive = false ;
264+ break ;
265+ case NOT_EQUAL :
266+ case NOT_IN :
267+ filterValue = Values .MIN_VALUE ;
268+ break ;
269+ default :
270+ // Remaining filters cannot be used as bound.
297271 }
298272
299- // If there is an endAt bound, compare the values against the existing boundary to see
300- // if we can narrow the scope.
301- if (endAt != null ) {
302- for (int i = 0 ; i < orderBys .size (); ++i ) {
303- OrderBy orderBy = this .orderBys .get (i );
304- if (orderBy .getField ().equals (segment .getFieldPath ())) {
305- Value cursorValue = endAt .getPosition ().get (i );
306- if (min (segmentValue , cursorValue ) == cursorValue ) {
307- segmentValue = cursorValue ;
308- segmentInclusive = endAt .isInclusive ();
309- }
310- break ;
273+ if (max (segmentValue , filterValue ) == filterValue ) {
274+ segmentValue = filterValue ;
275+ segmentInclusive = filterInclusive ;
276+ }
277+ }
278+
279+ // If there is an additional bound, compare the values against the existing range to see if we
280+ // can narrow the scope.
281+ if (bound != null ) {
282+ for (int i = 0 ; i < orderBys .size (); ++i ) {
283+ OrderBy orderBy = this .orderBys .get (i );
284+ if (orderBy .getField ().equals (segment .getFieldPath ())) {
285+ Value cursorValue = bound .getPosition ().get (i );
286+ if (max (segmentValue , cursorValue ) == cursorValue ) {
287+ segmentValue = cursorValue ;
288+ segmentInclusive = bound .isInclusive ();
311289 }
312290 }
313291 }
292+ }
314293
315- if (segmentValue == null ) {
316- // No upper bound exists
317- return null ;
294+ return new Pair <>(segmentValue , segmentInclusive );
295+ }
296+
297+ /**
298+ * Returns the value for a descending bound of `segment`.
299+ *
300+ * @param segment The segment to get the value for.
301+ * @param bound A bound to restrict the index range.
302+ * @return a Pair with a nullable Value and a boolean indicating whether the bound is inclusive
303+ */
304+ private Pair <Value , Boolean > getDescendingBound (
305+ FieldIndex .Segment segment , @ Nullable Bound bound ) {
306+ Value segmentValue = null ;
307+ boolean segmentInclusive = true ;
308+
309+ // Process all filters to find a value for the current field segment
310+ for (FieldFilter fieldFilter : getFieldFiltersForPath (segment .getFieldPath ())) {
311+ Value filterValue = null ;
312+ boolean filterInclusive = true ;
313+
314+ switch (fieldFilter .getOperator ()) {
315+ case GREATER_THAN_OR_EQUAL :
316+ case GREATER_THAN :
317+ filterValue = Values .getUpperBound (fieldFilter .getValue ().getValueTypeCase ());
318+ filterInclusive = false ;
319+ break ;
320+ case EQUAL :
321+ case IN :
322+ case LESS_THAN_OR_EQUAL :
323+ filterValue = fieldFilter .getValue ();
324+ break ;
325+ case LESS_THAN :
326+ filterValue = fieldFilter .getValue ();
327+ filterInclusive = false ;
328+ break ;
329+ case NOT_EQUAL :
330+ case NOT_IN :
331+ filterValue = Values .MAX_VALUE ;
332+ break ;
333+ default :
334+ // Remaining filters cannot be used as bound.
318335 }
319336
320- values .add (segmentValue );
321- inclusive &= segmentInclusive ;
337+ if (min (segmentValue , filterValue ) == filterValue ) {
338+ segmentValue = filterValue ;
339+ segmentInclusive = filterInclusive ;
340+ }
322341 }
323342
324- return new Bound (values , inclusive );
343+ // If there is an additional bound, compare the values against the existing range to see if we
344+ // can narrow the scope.
345+ if (bound != null ) {
346+ for (int i = 0 ; i < orderBys .size (); ++i ) {
347+ OrderBy orderBy = this .orderBys .get (i );
348+ if (orderBy .getField ().equals (segment .getFieldPath ())) {
349+ Value cursorValue = bound .getPosition ().get (i );
350+ if (min (segmentValue , cursorValue ) == cursorValue ) {
351+ segmentValue = cursorValue ;
352+ segmentInclusive = bound .isInclusive ();
353+ }
354+ }
355+ }
356+ }
357+
358+ return new Pair <>(segmentValue , segmentInclusive );
325359 }
326360
327361 public List <OrderBy > getOrderBy () {
328362 return this .orderBys ;
329363 }
330364
365+ /** Returns the order of the document key component. */
366+ public Direction getKeyOrder () {
367+ return this .orderBys .get (this .orderBys .size () - 1 ).getDirection ();
368+ }
369+
331370 /** Returns a canonical string representing this target. */
332371 public String getCanonicalId () {
333372 if (memoizedCanonicalId != null ) {
0 commit comments