17
17
import static com .google .firebase .firestore .model .Values .max ;
18
18
import static com .google .firebase .firestore .model .Values .min ;
19
19
20
+ import android .util .Pair ;
20
21
import androidx .annotation .Nullable ;
22
+ import com .google .firebase .firestore .core .OrderBy .Direction ;
21
23
import com .google .firebase .firestore .model .DocumentKey ;
22
24
import com .google .firebase .firestore .model .FieldIndex ;
23
25
import com .google .firebase .firestore .model .FieldPath ;
@@ -185,65 +187,18 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
185
187
186
188
// For each segment, retrieve a lower bound if there is a suitable filter or startAt.
187
189
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 );
190
194
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 ) {
241
196
// No lower bound exists
242
197
return null ;
243
198
}
244
199
245
- values .add (segmentValue );
246
- inclusive &= segmentInclusive ;
200
+ values .add (segmentBound . first );
201
+ inclusive &= segmentBound . second ;
247
202
}
248
203
249
204
return new Bound (values , inclusive );
@@ -259,75 +214,159 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
259
214
260
215
// For each segment, retrieve an upper bound if there is a suitable filter or endAt.
261
216
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 );
264
221
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
+ }
269
226
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
+ }
292
230
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.
297
271
}
298
272
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 ();
311
289
}
312
290
}
313
291
}
292
+ }
314
293
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.
318
335
}
319
336
320
- values .add (segmentValue );
321
- inclusive &= segmentInclusive ;
337
+ if (min (segmentValue , filterValue ) == filterValue ) {
338
+ segmentValue = filterValue ;
339
+ segmentInclusive = filterInclusive ;
340
+ }
322
341
}
323
342
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 );
325
359
}
326
360
327
361
public List <OrderBy > getOrderBy () {
328
362
return this .orderBys ;
329
363
}
330
364
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
+
331
370
/** Returns a canonical string representing this target. */
332
371
public String getCanonicalId () {
333
372
if (memoizedCanonicalId != null ) {
0 commit comments