From 974b84ac40db11ff583d3a3a5da50a1566bdab91 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 11:04:31 +0300 Subject: [PATCH 1/6] cursor before logic --- src/Database/Adapter.php | 3 +-- src/Database/Adapter/MariaDB.php | 26 +++----------------------- src/Database/Adapter/Pool.php | 2 +- src/Database/Adapter/Postgres.php | 24 ++++-------------------- src/Database/Database.php | 25 ++++++++++++++++--------- 5 files changed, 25 insertions(+), 55 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 88fd7d64f..e746d5e74 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -766,12 +766,11 @@ abstract public function deleteDocuments(string $collection, array $sequences, a * @param array $orderAttributes * @param array $orderTypes * @param array $cursor - * @param string $cursorDirection * @param string $forPermission * * @return array */ - abstract public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, string $forPermission = Database::PERMISSION_READ): array; + abstract public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $forPermission = Database::PERMISSION_READ): array; /** * Sum an attribute diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 708926548..9e99385ad 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1498,14 +1498,13 @@ public function deleteDocument(string $collection, string $id): bool * @param array $orderAttributes * @param array $orderTypes * @param array $cursor - * @param string $cursorDirection * @param string $forPermission * @return array * @throws DatabaseException * @throws TimeoutException * @throws Exception */ - public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, string $forPermission = Database::PERMISSION_READ): array + public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $forPermission = Database::PERMISSION_READ): array { $name = $this->filter($collection); $roles = Authorization::getRoles(); @@ -1523,27 +1522,17 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $attribute = $this->filter($attribute); $orderType = $this->filter($orderTypes[$i] ?? Database::ORDER_ASC); - $direction = $orderType; - if ($cursorDirection === Database::CURSOR_BEFORE) { - $direction = ($direction === Database::ORDER_ASC) - ? Database::ORDER_DESC - : Database::ORDER_ASC; - } + $orders[] = "{$this->quote($attribute)} {$orderType}"; - $orders[] = "{$this->quote($attribute)} {$direction}"; + $operator = ($orderType === Database::ORDER_DESC) ? Query::TYPE_LESSER : Query::TYPE_GREATER; // Build pagination WHERE clause only if we have a cursor if (!empty($cursor)) { // Special case: No tie breaks. only 1 attribute and it's a unique primary key if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') { - $operator = ($direction === Database::ORDER_DESC) - ? Query::TYPE_LESSER - : Query::TYPE_GREATER; - $bindName = ":cursor_pk"; $binds[$bindName] = $cursor[$originalAttribute]; - $cursorWhere[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$this->getSQLOperator($operator)} {$bindName}"; break; } @@ -1561,11 +1550,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $conditions[] = "{$this->quote($alias)}.{$this->quote($prevAttr)} = {$bindName}"; } - // Add comparison for current attribute - $operator = ($direction === Database::ORDER_DESC) - ? Query::TYPE_LESSER - : Query::TYPE_GREATER; - $bindName = ":cursor_{$i}"; $binds[$bindName] = $cursor[$originalAttribute]; @@ -1663,10 +1647,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $results[$index] = new Document($results[$index]); } - if ($cursorDirection === Database::CURSOR_BEFORE) { - $results = \array_reverse($results); - } - return $results; } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 302338aa9..79fbe4ea9 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -260,7 +260,7 @@ public function deleteDocuments(string $collection, array $sequences, array $per return $this->delegate(__FUNCTION__, \func_get_args()); } - public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, string $forPermission = Database::PERMISSION_READ): array + public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $forPermission = Database::PERMISSION_READ): array { return $this->delegate(__FUNCTION__, \func_get_args()); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index aad33c42e..4cd6e7120 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1380,14 +1380,13 @@ public function deleteDocument(string $collection, string $id): bool * @param array $orderAttributes * @param array $orderTypes * @param array $cursor - * @param string $cursorDirection * @param string $forPermission * @return array * @throws DatabaseException * @throws TimeoutException * @throws Exception */ - public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, string $forPermission = Database::PERMISSION_READ): array + public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $forPermission = Database::PERMISSION_READ): array { $name = $this->filter($collection); $roles = Authorization::getRoles(); @@ -1405,27 +1404,17 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $attribute = $this->filter($attribute); $orderType = $this->filter($orderTypes[$i] ?? Database::ORDER_ASC); - $direction = $orderType; - if ($cursorDirection === Database::CURSOR_BEFORE) { - $direction = ($direction === Database::ORDER_ASC) - ? Database::ORDER_DESC - : Database::ORDER_ASC; - } + $orders[] = "{$this->quote($attribute)} {$orderType}"; - $orders[] = "{$this->quote($attribute)} {$direction}"; + $operator = ($orderType === Database::ORDER_DESC) ? Query::TYPE_LESSER : Query::TYPE_GREATER; // Build pagination WHERE clause only if we have a cursor if (!empty($cursor)) { - // Special case: only 1 attribute and it's a unique primary key + // Special case: No tie breaks. only 1 attribute and it's a unique primary key if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') { - $operator = ($direction === Database::ORDER_DESC) - ? Query::TYPE_LESSER - : Query::TYPE_GREATER; - $bindName = ":cursor_pk"; $binds[$bindName] = $cursor[$originalAttribute]; - $cursorWhere[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$this->getSQLOperator($operator)} {$bindName}"; break; } @@ -1443,11 +1432,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $conditions[] = "{$this->quote($alias)}.{$this->quote($prevAttr)} = {$bindName}"; } - // Add comparison for current attribute - $operator = ($direction === Database::ORDER_DESC) - ? Query::TYPE_LESSER - : Query::TYPE_GREATER; - $bindName = ":cursor_{$i}"; $binds[$bindName] = $cursor[$originalAttribute]; diff --git a/src/Database/Database.php b/src/Database/Database.php index 0658065cc..70aa90c7e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6028,14 +6028,18 @@ public function find(string $collection, array $queries = [], string $forPermiss $orderAttributes[] = '$sequence'; } - if (!empty($cursor)) { - foreach ($orderAttributes as $order) { - if ($cursor->getAttribute($order) === null) { - throw new OrderException( - message: "Order attribute '{$order}' is empty", - attribute: $order - ); - } + foreach ($orderAttributes as $i => $order) { + if (!empty($cursor) && $cursor->getAttribute($order) === null) { + throw new OrderException( + message: "Order attribute '{$order}' is empty", + attribute: $order + ); + } + + $orderTypes[$i] = $orderTypes[$i] ?? Database::ORDER_ASC; + + if ($cursorDirection === Database::CURSOR_BEFORE) { + $orderTypes[$i] = ($orderTypes[$i] === Database::ORDER_ASC) ? Database::ORDER_DESC : Database::ORDER_ASC; } } @@ -6105,12 +6109,15 @@ public function find(string $collection, array $queries = [], string $forPermiss $orderAttributes, $orderTypes, $cursor, - $cursorDirection, $forPermission ); $results = $skipAuth ? Authorization::skip($getResults) : $getResults(); + if ($cursorDirection === Database::CURSOR_BEFORE) { + $results = \array_reverse($results); + } + foreach ($results as &$node) { if ($this->resolveRelationships && (empty($selects) || !empty($nestedSelections))) { $node = $this->silent(fn () => $this->populateDocumentRelationships($collection, $node, $nestedSelections)); From 9a4639465184703a0a8584bf0945bd019ca166d7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 11:14:28 +0300 Subject: [PATCH 2/6] fix in_array --- phpunit.xml | 2 +- src/Database/Database.php | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 2a0531cfd..34365d48d 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/src/Database/Database.php b/src/Database/Database.php index 70aa90c7e..cb6f9e462 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6017,14 +6017,7 @@ public function find(string $collection, array $queries = [], string $forPermiss $cursor = $grouped['cursor']; $cursorDirection = $grouped['cursorDirection'] ?? Database::CURSOR_AFTER; - $uniqueOrderBy = false; - foreach ($orderAttributes as $order) { - if ($order === '$id' || $order === '$sequence') { - $uniqueOrderBy = true; - } - } - - if ($uniqueOrderBy === false) { + if (!in_array('$id', $orderAttributes) && !in_array('$sequence', $orderAttributes)) { $orderAttributes[] = '$sequence'; } From 4ee647bd5e28a7510fffc51d4f2847d58f777e02 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 11:15:00 +0300 Subject: [PATCH 3/6] stopOnFailure --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 34365d48d..2a0531cfd 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > From f520373f5cdf719a0e508805d0b2a83ffba8a574 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 11:17:57 +0300 Subject: [PATCH 4/6] Postgres remove reverse --- src/Database/Adapter/Postgres.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 4cd6e7120..f33bc3b56 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1532,10 +1532,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $results[$index] = new Document($results[$index]); } - if ($cursorDirection === Database::CURSOR_BEFORE) { - $results = \array_reverse($results); - } - return $results; } From c3daf68c3875fc5bab33b2a6d7c3e2e6279d3349 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 15:40:01 +0300 Subject: [PATCH 5/6] line --- src/Database/Adapter/MariaDB.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 9e99385ad..bb7ed5382 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1533,6 +1533,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') { $bindName = ":cursor_pk"; $binds[$bindName] = $cursor[$originalAttribute]; + $cursorWhere[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$this->getSQLOperator($operator)} {$bindName}"; break; } From 85b7ff68fc66e494bcdd345219d2316246ec0883 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Jul 2025 16:07:02 +0300 Subject: [PATCH 6/6] Remove comment --- src/Database/Adapter/MariaDB.php | 1 - src/Database/Adapter/Postgres.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index bb7ed5382..1d0d09026 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1527,7 +1527,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $operator = ($orderType === Database::ORDER_DESC) ? Query::TYPE_LESSER : Query::TYPE_GREATER; - // Build pagination WHERE clause only if we have a cursor if (!empty($cursor)) { // Special case: No tie breaks. only 1 attribute and it's a unique primary key if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') { diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index f33bc3b56..a290b1b9f 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1409,7 +1409,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $operator = ($orderType === Database::ORDER_DESC) ? Query::TYPE_LESSER : Query::TYPE_GREATER; - // Build pagination WHERE clause only if we have a cursor if (!empty($cursor)) { // Special case: No tie breaks. only 1 attribute and it's a unique primary key if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') {