@@ -501,13 +501,14 @@ public function deleteDocument(string $collection, string $id): bool
501501 * @param int $offset
502502 * @param array $orderAttributes
503503 * @param array $orderTypes
504- * @param array $orderAfter
504+ * @param array $cursor
505+ * @param string $cursorDirection
505506 *
506507 * @return array
507508 * @throws Exception
508509 * @throws PDOException
509510 */
510- public function find (string $ collection , array $ queries = [], int $ limit = 25 , int $ offset = 0 , array $ orderAttributes = [], array $ orderTypes = [], array $ orderAfter = []): array
511+ public function find (string $ collection , array $ queries = [], int $ limit = 25 , int $ offset = 0 , array $ orderAttributes = [], array $ orderTypes = [], array $ cursor = [], string $ cursorDirection = Database:: CURSOR_AFTER ): array
511512 {
512513 $ name = $ this ->filter ($ collection );
513514 $ roles = Authorization::getRoles ();
@@ -517,35 +518,54 @@ public function find(string $collection, array $queries = [], int $limit = 25, i
517518 foreach ($ orderAttributes as $ i => $ attribute ) {
518519 $ attribute = $ this ->filter ($ attribute );
519520 $ orderType = $ this ->filter ($ orderTypes [$ i ] ?? Database::ORDER_ASC );
520- $ orders [] = $ attribute .' ' .$ orderType ;
521521
522522 // Get most dominant/first order attribute
523- if ($ i === 0 && !empty ($ orderAfter )) {
523+ if ($ i === 0 && !empty ($ cursor )) {
524+ $ orderOperatorInternalId = Query::TYPE_GREATER ; // To preserve natural order
524525 $ orderOperator = $ orderType === Database::ORDER_DESC ? Query::TYPE_LESSER : Query::TYPE_GREATER ;
525526
527+ if ($ cursorDirection === Database::CURSOR_BEFORE ) {
528+ $ orderType = $ orderType === Database::ORDER_ASC ? Database::ORDER_DESC : Database::ORDER_ASC ;
529+ $ orderOperatorInternalId = $ orderType === Database::ORDER_ASC ? Query::TYPE_LESSER : Query::TYPE_GREATER ;
530+ $ orderOperator = $ orderType === Database::ORDER_DESC ? Query::TYPE_LESSER : Query::TYPE_GREATER ;
531+ }
532+
526533 $ where [] = "(
527- {$ attribute } {$ this ->getSQLOperator ($ orderOperator )} :after
534+ {$ attribute } {$ this ->getSQLOperator ($ orderOperator )} :cursor
528535 OR (
529- {$ attribute } = :after
536+ {$ attribute } = :cursor
530537 AND
531- _id > {$ orderAfter ['$internalId ' ]}
538+ _id { $ this -> getSQLOperator ( $ orderOperatorInternalId )} {$ cursor ['$internalId ' ]}
532539 )
533540 ) " ;
541+ } else if ($ cursorDirection === Database::CURSOR_BEFORE ) {
542+ $ orderType = $ orderType === Database::ORDER_ASC ? Database::ORDER_DESC : Database::ORDER_ASC ;
534543 }
544+
545+ $ orders [] = $ attribute .' ' .$ orderType ;
535546 }
536547
537548 // Allow after pagination without any order
538- if (empty ($ orderAttributes ) && !empty ($ orderAfter )) {
549+ if (empty ($ orderAttributes ) && !empty ($ cursor )) {
539550 $ orderType = $ orderTypes [0 ] ?? Database::ORDER_ASC ;
540- $ orderOperator = $ orderType === Database::ORDER_DESC ? Query::TYPE_LESSER : Query::TYPE_GREATER ;
541- $ where [] = "( _id {$ this ->getSQLOperator ($ orderOperator )} {$ orderAfter ['$internalId ' ]} ) " ;
551+ $ orderOperator = $ cursorDirection === Database::CURSOR_AFTER ? (
552+ $ orderType === Database::ORDER_DESC ? Query::TYPE_LESSER : Query::TYPE_GREATER
553+ ) : (
554+ $ orderType === Database::ORDER_DESC ? Query::TYPE_GREATER : Query::TYPE_LESSER
555+ );
556+ $ where [] = "( _id {$ this ->getSQLOperator ($ orderOperator )} {$ cursor ['$internalId ' ]} ) " ;
542557 }
543558
544559 // Allow order type without any order attribute, fallback to the natural order (_id)
545560 if (empty ($ orderAttributes ) && !empty ($ orderTypes )) {
546- $ orders [] = '_id ' .$ this ->filter ($ orderTypes [0 ] ?? Database::ORDER_ASC );
561+ $ order = $ orderTypes [0 ] ?? Database::ORDER_ASC ;
562+ if ($ cursorDirection === Database::CURSOR_BEFORE ) {
563+ $ order = $ order === Database::ORDER_ASC ? Database::ORDER_DESC : Database::ORDER_ASC ;
564+ }
565+
566+ $ orders [] = '_id ' .$ this ->filter ($ order );
547567 } else {
548- $ orders [] = '_id ' .Database::ORDER_ASC ; // Enforce last ORDER by '_id'
568+ $ orders [] = '_id ' .( $ cursorDirection === Database::CURSOR_AFTER ? Database:: ORDER_ASC : Database:: ORDER_DESC ) ; // Enforce last ORDER by '_id'
549569 }
550570
551571 $ permissions = (Authorization::$ status ) ? $ this ->getSQLPermissions ($ roles ) : '1=1 ' ; // Disable join when no authorization required
@@ -574,12 +594,12 @@ public function find(string $collection, array $queries = [], int $limit = 25, i
574594 }
575595 }
576596
577- if (!empty ($ orderAfter ) && !empty ($ orderAttributes ) && array_key_exists (0 , $ orderAttributes )) {
597+ if (!empty ($ cursor ) && !empty ($ orderAttributes ) && array_key_exists (0 , $ orderAttributes )) {
578598 $ attribute = $ orderAttributes [0 ];
579- if (is_null ($ orderAfter [$ attribute ] ?? null )) {
599+ if (is_null ($ cursor [$ attribute ] ?? null )) {
580600 throw new Exception ("Order attribute ' {$ attribute }' is empty. " );
581601 }
582- $ stmt ->bindValue (':after ' , $ orderAfter [$ attribute ], $ this ->getPDOType ($ orderAfter [$ attribute ]));
602+ $ stmt ->bindValue (':cursor ' , $ cursor [$ attribute ], $ this ->getPDOType ($ cursor [$ attribute ]));
583603 }
584604
585605 $ stmt ->bindValue (':limit ' , $ limit , PDO ::PARAM_INT );
@@ -601,6 +621,10 @@ public function find(string $collection, array $queries = [], int $limit = 25, i
601621 $ value = new Document ($ value );
602622 }
603623
624+ if ($ cursorDirection === Database::CURSOR_BEFORE ) {
625+ $ results = array_reverse ($ results ); //TODO: check impact on array_reverse
626+ }
627+
604628 return $ results ;
605629 }
606630
0 commit comments