|
13 | 13 | // limitations under the License. |
14 | 14 |
|
15 | 15 | import { |
| 16 | + And, |
16 | 17 | and, |
17 | 18 | Constant, |
18 | 19 | Expr, |
19 | 20 | Field, |
20 | 21 | FilterCondition, |
21 | 22 | FirestoreFunction, |
| 23 | + gt, |
| 24 | + gte, |
| 25 | + lt, |
| 26 | + lte, |
22 | 27 | not, |
23 | 28 | or, |
24 | 29 | Ordering |
@@ -85,6 +90,7 @@ import { Firestore } from '../api/database'; |
85 | 90 | import { doc } from '../lite-api/reference'; |
86 | 91 | import { Direction } from './order_by'; |
87 | 92 | import { CorePipeline } from './pipeline_run'; |
| 93 | +import { Bound } from './bound'; |
88 | 94 |
|
89 | 95 | /* eslint @typescript-eslint/no-explicit-any: 0 */ |
90 | 96 |
|
@@ -295,6 +301,16 @@ export function toPipelineFilterCondition( |
295 | 301 | throw new Error(`Failed to convert filter to pipeline conditions: ${f}`); |
296 | 302 | } |
297 | 303 |
|
| 304 | +function reverseOrderings(orderings: Ordering[]): Ordering[] { |
| 305 | + return orderings.map( |
| 306 | + o => |
| 307 | + new Ordering( |
| 308 | + o.expr, |
| 309 | + o.direction === 'ascending' ? 'descending' : 'ascending' |
| 310 | + ) |
| 311 | + ); |
| 312 | +} |
| 313 | + |
298 | 314 | export function toPipeline(query: Query, db: Firestore): Pipeline { |
299 | 315 | let pipeline: Pipeline; |
300 | 316 | if (isCollectionGroupQuery(query)) { |
@@ -323,28 +339,68 @@ export function toPipeline(query: Query, db: Firestore): Pipeline { |
323 | 339 | pipeline = pipeline.where(existsConditions[0]); |
324 | 340 | } |
325 | 341 |
|
326 | | - pipeline = pipeline.sort( |
327 | | - ...orders.map(order => |
328 | | - order.dir === Direction.ASCENDING |
329 | | - ? Field.of(order.field.canonicalString()).ascending() |
330 | | - : Field.of(order.field.canonicalString()).descending() |
331 | | - ) |
| 342 | + const orderings = orders.map(order => |
| 343 | + order.dir === Direction.ASCENDING |
| 344 | + ? Field.of(order.field.canonicalString()).ascending() |
| 345 | + : Field.of(order.field.canonicalString()).descending() |
332 | 346 | ); |
333 | 347 |
|
334 | | - // cursors and limits |
335 | | - if (query.startAt !== null || query.endAt !== null) { |
336 | | - throw new Error('Cursors are not supported yet.'); |
337 | | - } |
338 | 348 | if (query.limitType === LimitType.Last) { |
339 | | - throw new Error('Limit to last are not supported yet.'); |
340 | | - } |
341 | | - if (query.limit !== null) { |
342 | | - pipeline = pipeline.limit(query.limit); |
| 349 | + pipeline = pipeline.sort(...reverseOrderings(orderings)); |
| 350 | + // cursors |
| 351 | + if (query.startAt !== null) { |
| 352 | + pipeline = pipeline.where( |
| 353 | + whereConditionsFromCursor(query.startAt, orderings, 'before') |
| 354 | + ); |
| 355 | + } |
| 356 | + |
| 357 | + if (query.endAt !== null) { |
| 358 | + pipeline = pipeline.where( |
| 359 | + whereConditionsFromCursor(query.endAt, orderings, 'after') |
| 360 | + ); |
| 361 | + } |
| 362 | + |
| 363 | + pipeline = pipeline._limit(query.limit!, true); |
| 364 | + pipeline = pipeline.sort(...orderings); |
| 365 | + } else { |
| 366 | + pipeline = pipeline.sort(...orderings); |
| 367 | + if (query.startAt !== null) { |
| 368 | + pipeline = pipeline.where( |
| 369 | + whereConditionsFromCursor(query.startAt, orderings, 'after') |
| 370 | + ); |
| 371 | + } |
| 372 | + if (query.endAt !== null) { |
| 373 | + pipeline = pipeline.where( |
| 374 | + whereConditionsFromCursor(query.endAt, orderings, 'before') |
| 375 | + ); |
| 376 | + } |
| 377 | + |
| 378 | + if (query.limit !== null) { |
| 379 | + pipeline = pipeline.limit(query.limit); |
| 380 | + } |
343 | 381 | } |
344 | 382 |
|
345 | 383 | return pipeline; |
346 | 384 | } |
347 | 385 |
|
| 386 | +function whereConditionsFromCursor( |
| 387 | + bound: Bound, |
| 388 | + orderings: Ordering[], |
| 389 | + position: 'before' | 'after' |
| 390 | +): And { |
| 391 | + const cursors = bound.position.map(value => Constant._fromProto(value)); |
| 392 | + const filterFunc = position === 'before' ? lt : gt; |
| 393 | + const filterInclusiveFunc = position === 'before' ? lte : gte; |
| 394 | + const conditions = cursors.map((cursor, index) => { |
| 395 | + if (!!bound.inclusive && index === cursors.length - 1) { |
| 396 | + return filterInclusiveFunc(orderings[index].expr as Field, cursor); |
| 397 | + } else { |
| 398 | + return filterFunc(orderings[index].expr as Field, cursor); |
| 399 | + } |
| 400 | + }); |
| 401 | + return new And(conditions); |
| 402 | +} |
| 403 | + |
348 | 404 | function canonifyExpr(expr: Expr): string { |
349 | 405 | if (expr instanceof Field) { |
350 | 406 | return `fld(${expr.fieldName()})`; |
|
0 commit comments