diff --git a/app/config/errors.php b/app/config/errors.php index 7c7f6dc9ec..4b2e58b08b 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -593,6 +593,11 @@ return [ 'description' => 'Database timed out. Try adjusting your queries or adding an index.', 'code' => 408 ], + Exception::DATABASE_QUERY_ORDER_NULL => [ + 'name' => Exception::DATABASE_QUERY_ORDER_NULL, + 'description' => 'The order attribute had a null value. Cursor pagination requires all documents order attribute values are non-null.', + 'code' => 400, + ], /** Collections */ Exception::COLLECTION_NOT_FOUND => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 20f64496ac..55bfd69442 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -43,6 +43,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -4845,8 +4846,11 @@ App::get('/v1/account/identities') } $filterQueries = Query::groupByType($queries)['filters']; - - $results = $dbForProject->find('identities', $queries); + try { + $results = $dbForProject->find('identities', $queries); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $total = $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4b9d3d18f2..28ee3ebdf9 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -31,6 +31,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Index as IndexException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; @@ -596,9 +597,15 @@ App::get('/v1/databases') $filterQueries = Query::groupByType($queries)['filters']; + try { + $databases = $dbForProject->find('databases', $queries); + $total = $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'databases' => $dbForProject->find('databases', $queries), - 'total' => $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT), + 'databases' => $databases, + 'total' => $total, ]), Response::MODEL_DATABASE_LIST); }); @@ -971,9 +978,15 @@ App::get('/v1/databases/:databaseId/collections') $filterQueries = Query::groupByType($queries)['filters']; + try { + $collections = $dbForProject->find('database_' . $database->getInternalId(), $queries); + $total = $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'collections' => $dbForProject->find('database_' . $database->getInternalId(), $queries), - 'total' => $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT), + 'collections' => $collections, + 'total' => $total, ]), Response::MODEL_COLLECTION_LIST); }); @@ -1982,9 +1995,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') } $filters = Query::groupByType($queries)['filters']; - - $attributes = $dbForProject->find('attributes', $queries); - $total = $dbForProject->count('attributes', $filters, APP_LIMIT_COUNT); + try { + $attributes = $dbForProject->find('attributes', $queries); + $total = $dbForProject->count('attributes', $filters, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ 'attributes' => $attributes, @@ -2980,9 +2996,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') } $filterQueries = Query::groupByType($queries)['filters']; + try { + $total = $dbForProject->count('indexes', $filterQueries, APP_LIMIT_COUNT); + $indexes = $dbForProject->find('indexes', $queries); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } + $response->dynamic(new Document([ - 'total' => $dbForProject->count('indexes', $filterQueries, APP_LIMIT_COUNT), - 'indexes' => $dbForProject->find('indexes', $queries), + 'total' => $total, + 'indexes' => $indexes, ]), Response::MODEL_INDEX_LIST); }); @@ -3445,9 +3468,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor->setValue($cursorDocument); } - - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); + try { + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $operations = 0; diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index c7f24f984a..ceb167f705 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -37,6 +37,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -483,10 +484,15 @@ App::get('/v1/functions') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $functions = $dbForProject->find('functions', $queries); + $total = $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'functions' => $dbForProject->find('functions', $queries), - 'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT), + 'functions' => $functions, + 'total' => $total, ]), Response::MODEL_FUNCTION_LIST); }); @@ -1537,9 +1543,12 @@ App::get('/v1/functions/:functionId/deployments') } $filterQueries = Query::groupByType($queries)['filters']; - - $results = $dbForProject->find('deployments', $queries); - $total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT); + try { + $results = $dbForProject->find('deployments', $queries); + $total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } foreach ($results as $result) { $build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', '')); @@ -2331,9 +2340,12 @@ App::get('/v1/functions/:functionId/executions') } $filterQueries = Query::groupByType($queries)['filters']; - - $results = $dbForProject->find('executions', $queries); - $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); + try { + $results = $dbForProject->find('executions', $queries); + $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 178266db60..9dbd2f3403 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -30,6 +30,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -951,10 +952,15 @@ App::get('/v1/messaging/providers') $cursor->setValue($cursorDocument); } - + try { + $providers = $dbForProject->find('providers', $queries); + $total = $dbForProject->count('providers', $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'providers' => $dbForProject->find('providers', $queries), - 'total' => $dbForProject->count('providers', $queries, APP_LIMIT_COUNT), + 'providers' => $providers, + 'total' => $total, ]), Response::MODEL_PROVIDER_LIST); }); @@ -2181,10 +2187,15 @@ App::get('/v1/messaging/topics') $cursor->setValue($cursorDocument[0]); } - + try { + $topics = $dbForProject->find('topics', $queries); + $total = $dbForProject->count('topics', $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'topics' => $dbForProject->find('topics', $queries), - 'total' => $dbForProject->count('topics', $queries, APP_LIMIT_COUNT), + 'topics' => $topics, + 'total' => $total, ]), Response::MODEL_TOPIC_LIST); }); @@ -2579,8 +2590,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $cursor->setValue($cursorDocument); } - - $subscribers = $dbForProject->find('subscribers', $queries); + try { + $subscribers = $dbForProject->find('subscribers', $queries); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { return function () use ($subscriber, $dbForProject) { @@ -3360,10 +3374,15 @@ App::get('/v1/messaging/messages') $cursor->setValue($cursorDocument); } - + try { + $messages = $dbForProject->find('messages', $queries); + $total = $dbForProject->count('messages', $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'messages' => $dbForProject->find('messages', $queries), - 'total' => $dbForProject->count('messages', $queries, APP_LIMIT_COUNT), + 'messages' => $messages, + 'total' => $total, ]), Response::MODEL_MESSAGE_LIST); }); @@ -3533,10 +3552,15 @@ App::get('/v1/messaging/messages/:messageId/targets') $cursor->setValue($cursorDocument); } - + try { + $targets = $dbForProject->find('targets', $queries); + $total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'targets' => $dbForProject->find('targets', $queries), - 'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT), + 'targets' => $targets, + 'total' => $total, ]), Response::MODEL_TARGET_LIST); }); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 4061f2c2c4..9d8d69bc1b 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -12,6 +12,7 @@ use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -347,10 +348,15 @@ App::get('/v1/migrations') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $migrations = $dbForProject->find('migrations', $queries); + $total = $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'migrations' => $dbForProject->find('migrations', $queries), - 'total' => $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT), + 'migrations' => $migrations, + 'total' => $total, ]), Response::MODEL_MIGRATION_LIST); }); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index b29bd227aa..22facc0b23 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -28,6 +28,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -357,10 +358,15 @@ App::get('/v1/projects') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $projects = $dbForPlatform->find('projects', $queries); + $total = $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'projects' => $dbForPlatform->find('projects', $queries), - 'total' => $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT), + 'projects' => $projects, + 'total' => $total, ]), Response::MODEL_PROJECT_LIST); }); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f13c9703c5..398369317f 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -24,6 +24,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\NotFound as NotFoundException; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -216,10 +217,15 @@ App::get('/v1/storage/buckets') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $buckets = $dbForProject->find('buckets', $queries); + $total = $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'buckets' => $dbForProject->find('buckets', $queries), - 'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT), + 'buckets' => $buckets, + 'total' => $total, ]), Response::MODEL_BUCKET_LIST); }); @@ -837,6 +843,8 @@ App::get('/v1/storage/buckets/:bucketId/files') } } catch (NotFoundException) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); } $response->dynamic(new Document([ diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 3e0e366b6b..e5d3bb8bd0 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -33,6 +33,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -204,9 +205,12 @@ App::get('/v1/teams') } $filterQueries = Query::groupByType($queries)['filters']; - - $results = $dbForProject->find('teams', $queries); - $total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT); + try { + $results = $dbForProject->find('teams', $queries); + $total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ 'teams' => $results, @@ -859,17 +863,20 @@ App::get('/v1/teams/:teamId/memberships') } $filterQueries = Query::groupByType($queries)['filters']; + try { + $memberships = $dbForProject->find( + collection: 'memberships', + queries: $queries, + ); + $total = $dbForProject->count( + collection: 'memberships', + queries: $filterQueries, + max: APP_LIMIT_COUNT + ); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } - $memberships = $dbForProject->find( - collection: 'memberships', - queries: $queries, - ); - - $total = $dbForProject->count( - collection: 'memberships', - queries: $filterQueries, - max: APP_LIMIT_COUNT - ); $memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId'))); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4a551b7478..4a02b94bdd 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -33,6 +33,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -630,10 +631,15 @@ App::get('/v1/users') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $users = $dbForProject->find('users', $queries); + $total = $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'users' => $dbForProject->find('users', $queries), - 'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT), + 'users' => $users, + 'total' => $total, ]), Response::MODEL_USER_LIST); }); @@ -980,10 +986,15 @@ App::get('/v1/users/:userId/targets') $cursor->setValue($cursorDocument); } - + try { + $targets = $dbForProject->find('targets', $queries); + $total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'targets' => $dbForProject->find('targets', $queries), - 'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT), + 'targets' => $targets, + 'total' => $total, ]), Response::MODEL_TARGET_LIST); }); @@ -1045,10 +1056,15 @@ App::get('/v1/users/identities') } $filterQueries = Query::groupByType($queries)['filters']; - + try { + $identities = $dbForProject->find('identities', $queries); + $total = $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ - 'identities' => $dbForProject->find('identities', $queries), - 'total' => $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT), + 'identities' => $identities, + 'total' => $total, ]), Response::MODEL_IDENTITY_LIST); }); diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 2c145febcc..e09367f790 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -19,6 +19,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception\Order as OrderException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -1113,9 +1114,12 @@ App::get('/v1/vcs/installations') } $filterQueries = Query::groupByType($queries)['filters']; - - $results = $dbForPlatform->find('installations', $queries); - $total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT); + try { + $results = $dbForPlatform->find('installations', $queries); + $total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT); + } catch (OrderException $e) { + throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); + } $response->dynamic(new Document([ 'installations' => $results, diff --git a/composer.json b/composer.json index 5b13227d2b..b3a1df7a59 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.65.*", + "utopia-php/database": "0.66.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index c8c1e3c192..84e7ac9cb3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51ff891ef6cee8a3f8c4e5187b7fd479", + "content-hash": "35fd85e8d566d20d8177266469c5ebcb", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.65.0", + "version": "0.66.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e589efdc5da1216523a758e8af358866d4fb563f" + "reference": "67d2ab418efba31dc76b3564cf043e2b3f98d027" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e589efdc5da1216523a758e8af358866d4fb563f", - "reference": "e589efdc5da1216523a758e8af358866d4fb563f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/67d2ab418efba31dc76b3564cf043e2b3f98d027", + "reference": "67d2ab418efba31dc76b3564cf043e2b3f98d027", "shasum": "" }, "require": { @@ -3547,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.65.0" + "source": "https://github.com/utopia-php/database/tree/0.66.0" }, - "time": "2025-04-14T07:39:01+00:00" + "time": "2025-04-16T07:10:27+00:00" }, { "name": "utopia-php/domains", diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d4f47ca177..2db5a840bc 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -175,6 +175,7 @@ class Exception extends \Exception public const DATABASE_NOT_FOUND = 'database_not_found'; public const DATABASE_ALREADY_EXISTS = 'database_already_exists'; public const DATABASE_TIMEOUT = 'database_timeout'; + public const DATABASE_QUERY_ORDER_NULL = 'database_query_order_null'; /** Collections */ public const COLLECTION_NOT_FOUND = 'collection_not_found'; diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index dca832ac01..0c7e1d386e 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -1641,9 +1641,54 @@ trait DatabasesBase $this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']); $this->assertCount(3, $documents['body']['documents']); + // changing description attribute to be null by default instead of empty string + $patchNull = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/attributes/string/description', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'default' => null, + 'required' => false, + ]); + // creating a dummy doc with null description + $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'title' => 'Dummy', + 'releaseYear' => 1944, + 'birthDay' => '1975-06-12 14:12:55+02:00', + 'actors' => [ + 'Dummy', + ], + ] + ]); + + $this->assertEquals(201, $document1['headers']['status-code']); + // fetching docs with cursor after the dummy doc with order attr description which is null + $documentsPaginated = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::orderAsc('dummy')->toString(), + Query::cursorAfter(new Document(['$id' => $document1['body']['$id']]))->toString() + ], + ]); + // should throw 400 as the order attr description of the selected doc is null + $this->assertEquals(400, $documentsPaginated['headers']['status-code']); + + // deleting the dummy doc created + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $document1['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); return ['documents' => $documents['body']['documents'], 'databaseId' => $databaseId]; } + /** * @depends testListDocuments */