diff --git a/CHANGES.md b/CHANGES.md index 42e3293c5c..527b37f13d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,23 @@ +# Version 1.4.0 + +## Features + +- Add error attribute to indexes and attributes [#4575](https://github.com/appwrite/appwrite/pull/4575) +- Add new index validation rules [#5710](https://github.com/appwrite/appwrite/pull/5710) + +## Fixes + +- Fix cascading deletes across multiple levels [DB #269](https://github.com/utopia-php/database/pull/269) +- Fix identical two-way keys not throwing duplicate exceptions [DB #273](https://github.com/utopia-php/database/pull/273) +- Fix search wildcards [DB #279](https://github.com/utopia-php/database/pull/279) +- Fix permissions returning as an object instead of list [DB #281](https://github.com/utopia-php/database/pull/281) +- Fix missing collection not found error [DB #282](https://github.com/utopia-php/database/pull/282) + +## Changes + +- Improve permission indexes [DB #248](https://github.com/utopia-php/database/pull/248) +- Validators back-ported to Utopia [#5439](https://github.com/appwrite/appwrite/pull/5439) + # Version 1.3.7 ## Bugs diff --git a/app/config/collections.php b/app/config/collections.php index 85a279faa5..b5c9daccc8 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -274,6 +274,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('error'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('size'), 'type' => Database::VAR_INTEGER, @@ -461,6 +472,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('error'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('attributes'), 'type' => Database::VAR_STRING, diff --git a/app/config/errors.php b/app/config/errors.php index 9fadde6e93..1ff8d2e8e7 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -487,6 +487,11 @@ return [ 'description' => 'Index with the requested ID already exists.', 'code' => 409, ], + Exception::INDEX_INVALID => [ + 'name' => Exception::INDEX_INVALID, + 'description' => 'Index invalid.', + 'code' => 400, + ], /** Project Errors */ Exception::PROJECT_NOT_FOUND => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 39cb851b2b..49c5ef3376 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -16,9 +16,9 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Template\Template; use Appwrite\URL\URL as URLParser; use Appwrite\Utopia\Database\Validator\CustomId; -use Appwrite\Utopia\Database\Validator\Queries; -use Appwrite\Utopia\Database\Validator\Query\Limit; -use Appwrite\Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; @@ -1411,7 +1411,7 @@ App::get('/v1/account/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('user') ->inject('locale') @@ -1656,7 +1656,7 @@ App::patch('/v1/account/email') try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); - } catch (Duplicate $th) { + } catch (Duplicate) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index c4ff448ce4..f64b4d07c0 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1,56 +1,57 @@ createCollection('database_' . $database->getInternalId(), $attributes, $indexes); - } catch (DuplicateException $th) { + } catch (DuplicateException) { throw new Exception(Exception::DATABASE_ALREADY_EXISTS); } @@ -470,10 +471,9 @@ App::get('/v1/databases') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { - /** @var Query $cursor */ $databaseId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('databases', $databaseId); @@ -530,7 +530,7 @@ App::get('/v1/databases/:databaseId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') @@ -638,7 +638,7 @@ App::put('/v1/databases/:databaseId') ->setAttribute('name', $name) ->setAttribute('enabled', $enabled) ->setAttribute('search', implode(' ', [$databaseId, $name]))); - } catch (AuthorizationException $exception) { + } catch (AuthorizationException) { throw new Exception(Exception::USER_UNAUTHORIZED); } catch (StructureException $exception) { throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage()); @@ -801,7 +801,7 @@ App::get('/v1/databases/:databaseId/collections') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -875,7 +875,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') @@ -2346,6 +2346,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } + $collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId); if ($collection->isEmpty()) { @@ -2426,21 +2427,28 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') $lengths[$i] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; } + $index = new Document([ + '$id' => ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key), + 'key' => $key, + 'status' => 'processing', // processing, available, failed, deleting, stuck + 'databaseInternalId' => $db->getInternalId(), + 'databaseId' => $databaseId, + 'collectionInternalId' => $collection->getInternalId(), + 'collectionId' => $collectionId, + 'type' => $type, + 'attributes' => $attributes, + 'lengths' => $lengths, + 'orders' => $orders, + ]); + + $validator = new IndexValidator($dbForProject->getAdapter()->getMaxIndexLength()); + if (!$validator->isValid($collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND))) { + throw new Exception(Exception::INDEX_INVALID, $validator->getDescription()); + } + try { - $index = $dbForProject->createDocument('indexes', new Document([ - '$id' => ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key), - 'key' => $key, - 'status' => 'processing', // processing, available, failed, deleting, stuck - 'databaseInternalId' => $db->getInternalId(), - 'databaseId' => $databaseId, - 'collectionInternalId' => $collection->getInternalId(), - 'collectionId' => $collectionId, - 'type' => $type, - 'attributes' => $attributes, - 'lengths' => $lengths, - 'orders' => $orders, - ])); - } catch (DuplicateException $th) { + $index = $dbForProject->createDocument('indexes', $index); + } catch (DuplicateException) { throw new Exception(Exception::INDEX_ALREADY_EXISTS); } @@ -2910,7 +2918,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $queries = Query::parseQueries($queries); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { $documentId = $cursor->getValue(); @@ -3028,7 +3036,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen } // Validate queries - $queriesValidator = new DocumentValidator($collection->getAttribute('attributes')); + $queriesValidator = new DocumentQueriesValidator($collection->getAttribute('attributes')); $validQueries = $queriesValidator->isValid($queries); if (!$validQueries) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription()); @@ -3101,7 +3109,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index bc9f28d0cc..e383ec83fc 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -136,7 +136,7 @@ App::get('/v1/functions') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -812,7 +812,7 @@ App::get('/v1/functions/:functionId/deployments') $queries[] = Query::equal('resourceType', ['functions']); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -1246,7 +1246,7 @@ App::get('/v1/functions/:functionId/executions') $queries[] = Query::equal('functionId', [$function->getId()]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 0cd22eebe8..b5c37ce969 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -203,7 +203,7 @@ App::get('/v1/projects') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -900,7 +900,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') } $webhook = $dbForConsole->findOne('webhooks', [ - Query::equal('_uid', [$webhookId]), + Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -942,7 +942,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); $webhook = $dbForConsole->findOne('webhooks', [ - Query::equal('_uid', [$webhookId]), + Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -988,7 +988,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') } $webhook = $dbForConsole->findOne('webhooks', [ - Query::equal('_uid', [$webhookId]), + Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1026,7 +1026,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') } $webhook = $dbForConsole->findOne('webhooks', [ - Query::equal('_uid', [$webhookId]), + Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1148,7 +1148,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - Query::equal('_uid', [$keyId]), + Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1185,7 +1185,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - Query::equal('_uid', [$keyId]), + Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1228,7 +1228,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - Query::equal('_uid', [$keyId]), + Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1350,7 +1350,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - Query::equal('_uid', [$platformId]), + Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1387,7 +1387,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - Query::equal('_uid', [$platformId]), + Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1431,7 +1431,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - Query::equal('_uid', [$platformId]), + Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1568,7 +1568,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') } $domain = $dbForConsole->findOne('domains', [ - Query::equal('_uid', [$domainId]), + Query::equal('$id', [$domainId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1602,7 +1602,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } $domain = $dbForConsole->findOne('domains', [ - Query::equal('_uid', [$domainId]), + Query::equal('$id', [$domainId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); @@ -1662,7 +1662,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') } $domain = $dbForConsole->findOne('domains', [ - Query::equal('_uid', [$domainId]), + Query::equal('$id', [$domainId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f8d0e1de38..6ad75c4795 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -167,7 +167,7 @@ App::get('/v1/storage/buckets') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -708,7 +708,7 @@ App::get('/v1/storage/buckets/:bucketId/files') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 338af70406..155567a90e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -12,11 +12,11 @@ use Appwrite\Network\Validator\Email; use Utopia\Validator\Host; use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\CustomId; -use Appwrite\Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Memberships; use Appwrite\Utopia\Database\Validator\Queries\Teams; -use Appwrite\Utopia\Database\Validator\Query\Limit; -use Appwrite\Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; @@ -148,7 +148,7 @@ App::get('/v1/teams') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -629,7 +629,7 @@ App::get('/v1/teams/:teamId/memberships') $queries[] = Query::equal('teamId', [$teamId]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -1002,7 +1002,7 @@ App::get('/v1/teams/:teamId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('teamId', '', new UID(), 'Team ID.') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index cd217afe31..2eae494758 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -8,10 +8,10 @@ use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Network\Validator\Email; use Appwrite\Utopia\Database\Validator\CustomId; -use Appwrite\Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Users; -use Appwrite\Utopia\Database\Validator\Query\Limit; -use Appwrite\Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Audit\Audit; @@ -386,7 +386,7 @@ App::get('/v1/users') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -557,7 +557,7 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User ID.') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') diff --git a/app/init.php b/app/init.php index 0b6efe52c0..2936529c0d 100644 --- a/app/init.php +++ b/app/init.php @@ -1085,7 +1085,7 @@ App::setResource('schema', function ($utopia, $dbForProject) { $complexity = function (int $complexity, array $args) { $queries = Query::parseQueries($args['queries'] ?? []); - $query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null; + $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; return $complexity * $limit; diff --git a/app/workers/audits.php b/app/workers/audits.php index b25430ec41..b86649543b 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -40,7 +40,6 @@ class AuditsV1 extends Worker $dbForProject = $this->getProjectDB($project->getId()); $audit = new Audit($dbForProject); $audit->log( - userInternalId: $user->getInternalId(), userId: $user->getId(), // Pass first, most verbose event pattern event: $event, diff --git a/app/workers/databases.php b/app/workers/databases.php index 4b64925dbf..ecdb667f0d 100644 --- a/app/workers/databases.php +++ b/app/workers/databases.php @@ -7,7 +7,7 @@ use Appwrite\Resque\Worker; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Helpers\ID; +use Utopia\Database\Exception as DatabaseException; require_once __DIR__ . '/../init.php'; @@ -29,11 +29,11 @@ class DatabaseV1 extends Worker $database = new Document($this->args['database'] ?? []); if ($collection->isEmpty()) { - throw new Exception('Missing collection'); + throw new DatabaseException('Missing collection'); } if ($document->isEmpty()) { - throw new Exception('Missing document'); + throw new DatabaseException('Missing document'); } switch (strval($type)) { @@ -100,7 +100,7 @@ class DatabaseV1 extends Worker case Database::VAR_RELATIONSHIP: $relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']); if ($relatedCollection->isEmpty()) { - throw new Exception('Collection not found'); + throw new DatabaseException('Collection not found'); } if ( @@ -114,7 +114,7 @@ class DatabaseV1 extends Worker onDelete: $options['onDelete'], ) ) { - throw new Exception('Failed to create Attribute'); + throw new DatabaseException('Failed to create Attribute'); } if ($options['twoWay']) { @@ -129,13 +129,28 @@ class DatabaseV1 extends Worker } $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available')); - } catch (\Throwable $th) { - Console::error($th->getMessage()); - $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); + } catch (\Exception $e) { + Console::error($e->getMessage()); - if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) { - $relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']); - $dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'failed')); + if ($e instanceof DatabaseException) { + $attribute->setAttribute('error', $e->getMessage()); + if (isset($relatedAttribute)) { + $relatedAttribute->setAttribute('error', $e->getMessage()); + } + } + + $dbForProject->updateDocument( + 'attributes', + $attribute->getId(), + $attribute->setAttribute('status', 'failed') + ); + + if (isset($relatedAttribute)) { + $dbForProject->updateDocument( + 'attributes', + $relatedAttribute->getId(), + $relatedAttribute->setAttribute('status', 'failed') + ); } } finally { $target = Realtime::fromPayload( @@ -200,21 +215,21 @@ class DatabaseV1 extends Worker try { if ($status !== 'failed') { - if ($type === Database::VAR_RELATIONSHIP) { + if ($type === Database::VAR_RELATIONSHIP) { if ($options['twoWay']) { $relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']); if ($relatedCollection->isEmpty()) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); + throw new DatabaseException('Collection not found'); } $relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']); } if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { $dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'stuck')); - throw new Exception('Failed to delete Relationship'); + throw new DatabaseException('Failed to delete Relationship'); } } elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { - throw new Exception('Failed to delete Attribute'); + throw new DatabaseException('Failed to delete Attribute'); } } @@ -223,9 +238,27 @@ class DatabaseV1 extends Worker if (!$relatedAttribute->isEmpty()) { $dbForProject->deleteDocument('attributes', $relatedAttribute->getId()); } - } catch (\Throwable $th) { - Console::error($th->getMessage()); - $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck')); + } catch (\Exception $e) { + Console::error($e->getMessage()); + + if ($e instanceof DatabaseException) { + $attribute->setAttribute('error', $e->getMessage()); + if (!$relatedAttribute->isEmpty()) { + $relatedAttribute->setAttribute('error', $e->getMessage()); + } + } + $dbForProject->updateDocument( + 'attributes', + $attribute->getId(), + $attribute->setAttribute('status', 'stuck') + ); + if (!$relatedAttribute->isEmpty()) { + $dbForProject->updateDocument( + 'attributes', + $relatedAttribute->getId(), + $relatedAttribute->setAttribute('status', 'stuck') + ); + } } finally { $target = Realtime::fromPayload( // Pass first, most verbose event pattern @@ -275,8 +308,7 @@ class DatabaseV1 extends Worker $index ->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN) ->setAttribute('lengths', $lengths, Document::SET_TYPE_ASSIGN) - ->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN) - ; + ->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN); // Check if an index exists with the same attributes and orders $exists = false; @@ -314,6 +346,7 @@ class DatabaseV1 extends Worker * @param Document $collection * @param Document $index * @param string $projectId + * @throws \Exception */ protected function createIndex(Document $database, Document $collection, Document $index, string $projectId): void { @@ -335,12 +368,20 @@ class DatabaseV1 extends Worker try { if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) { - throw new Exception('Failed to create Index'); + throw new DatabaseException('Failed to create Index'); } $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available')); - } catch (\Throwable $th) { - Console::error($th->getMessage()); - $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); + } catch (\Exception $e) { + Console::error($e->getMessage()); + + if ($e instanceof DatabaseException) { + $index->setAttribute('error', $e->getMessage()); + } + $dbForProject->updateDocument( + 'indexes', + $index->getId(), + $index->setAttribute('status', 'failed') + ); } finally { $target = Realtime::fromPayload( // Pass first, most verbose event pattern @@ -388,12 +429,20 @@ class DatabaseV1 extends Worker try { if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { - throw new Exception('Failed to delete index'); + throw new DatabaseException('Failed to delete index'); } $dbForProject->deleteDocument('indexes', $index->getId()); - } catch (\Throwable $th) { - Console::error($th->getMessage()); - $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck')); + } catch (\Exception $e) { + Console::error($e->getMessage()); + + if ($e instanceof DatabaseException) { + $index->setAttribute('error', $e->getMessage()); + } + $dbForProject->updateDocument( + 'indexes', + $index->getId(), + $index->setAttribute('status', 'stuck') + ); } finally { $target = Realtime::fromPayload( // Pass first, most verbose event pattern diff --git a/composer.json b/composer.json index 0ba26540bd..792e757ff8 100644 --- a/composer.json +++ b/composer.json @@ -43,13 +43,13 @@ "ext-sockets": "*", "appwrite/php-clamav": "1.1.*", "appwrite/php-runtimes": "0.11.*", - "utopia-php/abuse": "0.25.*", + "utopia-php/abuse": "0.26.*", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "0.26.*", + "utopia-php/audit": "0.28.*", "utopia-php/cache": "0.8.*", "utopia-php/cli": "0.13.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.36.*", + "utopia-php/database": "0.37.*", "utopia-php/domains": "1.1.*", "utopia-php/framework": "0.28.*", "utopia-php/image": "0.5.*", diff --git a/composer.lock b/composer.lock index 4fae7f5619..9796216c04 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": "3eadbfe5543aafdf8682ea0465159e3c", + "content-hash": "0f20fb41e9b250b6763af1b734bb8d2d", "packages": [ { "name": "adhocore/jwt", @@ -1802,23 +1802,23 @@ }, { "name": "utopia-php/abuse", - "version": "0.25.0", + "version": "0.26.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "49a180cab5316cddec9676d900d5112d03e97ffc" + "reference": "fb73180f0588bc8826b85d433393b983bdc37cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/49a180cab5316cddec9676d900d5112d03e97ffc", - "reference": "49a180cab5316cddec9676d900d5112d03e97ffc", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/fb73180f0588bc8826b85d433393b983bdc37cfa", + "reference": "fb73180f0588bc8826b85d433393b983bdc37cfa", "shasum": "" }, "require": { "ext-curl": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "0.36.*" + "utopia-php/database": "0.37.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1845,9 +1845,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.25.0" + "source": "https://github.com/utopia-php/abuse/tree/0.26.0" }, - "time": "2023-04-27T15:43:47+00:00" + "time": "2023-06-15T00:53:36+00:00" }, { "name": "utopia-php/analytics", @@ -1906,21 +1906,21 @@ }, { "name": "utopia-php/audit", - "version": "0.26.0", + "version": "0.28.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "e7228080f14df28737fbb050c180c26d86ac0403" + "reference": "abf4124bec20b6ab3555869b64afe5b274e37165" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/e7228080f14df28737fbb050c180c26d86ac0403", - "reference": "e7228080f14df28737fbb050c180c26d86ac0403", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/abf4124bec20b6ab3555869b64afe5b274e37165", + "reference": "abf4124bec20b6ab3555869b64afe5b274e37165", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.36.*" + "utopia-php/database": "0.37.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1947,9 +1947,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.26.0" + "source": "https://github.com/utopia-php/audit/tree/0.28.0" }, - "time": "2023-04-27T15:43:50+00:00" + "time": "2023-06-15T00:52:49+00:00" }, { "name": "utopia-php/cache", @@ -2106,16 +2106,16 @@ }, { "name": "utopia-php/database", - "version": "0.36.1", + "version": "0.37.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef" + "reference": "4035d3f7e3393385eabc7816055047659c6fb4d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f6ab65e59a199da5155c114564077b1ab8c4daef", - "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef", + "url": "https://api.github.com/repos/utopia-php/database/zipball/4035d3f7e3393385eabc7816055047659c6fb4d3", + "reference": "4035d3f7e3393385eabc7816055047659c6fb4d3", "shasum": "" }, "require": { @@ -2126,8 +2126,6 @@ "utopia-php/mongo": "0.2.*" }, "require-dev": { - "ext-mongodb": "*", - "ext-redis": "*", "fakerphp/faker": "^1.14", "laravel/pint": "1.4.*", "mongodb/mongodb": "1.8.0", @@ -2158,9 +2156,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.36.1" + "source": "https://github.com/utopia-php/database/tree/0.37.1" }, - "time": "2023-04-27T08:39:55+00:00" + "time": "2023-06-15T06:36:27+00:00" }, { "name": "utopia-php/domains", diff --git a/docs/tutorials/add-route.md b/docs/tutorials/add-route.md index 574e7454c6..43b55921c7 100644 --- a/docs/tutorials/add-route.md +++ b/docs/tutorials/add-route.md @@ -157,7 +157,7 @@ As the name implies, `param()` is used to define a request parameter. ```php App::get('/v1/account/logs') - ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ``` ### 6. inject diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index ebd12851e2..f27d22d0f5 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -154,6 +154,7 @@ class Exception extends \Exception public const INDEX_NOT_FOUND = 'index_not_found'; public const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded'; public const INDEX_ALREADY_EXISTS = 'index_already_exists'; + public const INDEX_INVALID = 'index_invalid'; /** Projects */ public const PROJECT_NOT_FOUND = 'project_not_found'; diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php index 9a6eb123c5..9d2705baa7 100644 --- a/src/Appwrite/GraphQL/Types/Mapper.php +++ b/src/Appwrite/GraphQL/Types/Mapper.php @@ -254,14 +254,14 @@ class Mapper case 'Appwrite\Utopia\Database\Validator\Queries\Collections': case 'Appwrite\Utopia\Database\Validator\Queries\Databases': case 'Appwrite\Utopia\Database\Validator\Queries\Deployments': - case 'Appwrite\Utopia\Database\Validator\Queries\Documents': + case 'Utopia\Database\Validator\Queries\Documents': case 'Appwrite\Utopia\Database\Validator\Queries\Executions': case 'Appwrite\Utopia\Database\Validator\Queries\Files': case 'Appwrite\Utopia\Database\Validator\Queries\Functions': case 'Appwrite\Utopia\Database\Validator\Queries\Memberships': case 'Utopia\Database\Validator\Permissions': case 'Appwrite\Utopia\Database\Validator\Queries\Projects': - case 'Appwrite\Utopia\Database\Validator\Queries': + case 'Utopia\Database\Validator\Queries': case 'Utopia\Database\Validator\Roles': case 'Appwrite\Utopia\Database\Validator\Queries\Teams': case 'Appwrite\Utopia\Database\Validator\Queries\Users': diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 46db262d3b..bf0293d5af 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -345,7 +345,7 @@ class OpenAPI3 extends Format case 'Appwrite\Utopia\Database\Validator\Queries\Collections': case 'Appwrite\Utopia\Database\Validator\Queries\Databases': case 'Appwrite\Utopia\Database\Validator\Queries\Deployments': - case 'Appwrite\Utopia\Database\Validator\Queries\Documents': + case 'Utopia\Database\Validator\Queries\Documents': case 'Appwrite\Utopia\Database\Validator\Queries\Executions': case 'Appwrite\Utopia\Database\Validator\Queries\Files': case 'Appwrite\Utopia\Database\Validator\Queries\Functions': @@ -354,7 +354,7 @@ class OpenAPI3 extends Format case 'Appwrite\Utopia\Database\Validator\Queries\Teams': case 'Appwrite\Utopia\Database\Validator\Queries\Users': case 'Appwrite\Utopia\Database\Validator\Queries\Variables': - case 'Appwrite\Utopia\Database\Validator\Queries': + case 'Utopia\Database\Validator\Queries': $node['schema']['type'] = 'array'; $node['schema']['items'] = [ 'type' => 'string', diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index 422f164fa6..60974bef85 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -344,7 +344,7 @@ class Swagger2 extends Format case 'Appwrite\Utopia\Database\Validator\Queries\Collections': case 'Appwrite\Utopia\Database\Validator\Queries\Databases': case 'Appwrite\Utopia\Database\Validator\Queries\Deployments': - case 'Appwrite\Utopia\Database\Validator\Queries\Documents': + case 'Utopia\Database\Validator\Queries\Documents': case 'Appwrite\Utopia\Database\Validator\Queries\Executions': case 'Appwrite\Utopia\Database\Validator\Queries\Files': case 'Appwrite\Utopia\Database\Validator\Queries\Functions': @@ -353,7 +353,7 @@ class Swagger2 extends Format case 'Appwrite\Utopia\Database\Validator\Queries\Teams': case 'Appwrite\Utopia\Database\Validator\Queries\Users': case 'Appwrite\Utopia\Database\Validator\Queries\Variables': - case 'Appwrite\Utopia\Database\Validator\Queries': + case 'Utopia\Database\Validator\Queries': $node['type'] = 'array'; $node['collectionFormat'] = 'multi'; $node['items'] = [ diff --git a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php deleted file mode 100644 index f247bb7e4f..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php +++ /dev/null @@ -1,111 +0,0 @@ - - */ - protected array $attributes = []; - - /** - * @var array - */ - protected array $indexes = []; - - /** - * Expression constructor - * - * This Queries Validator filters indexes for only available indexes - * - * @param array $attributes - * @param array $indexes - * @param Base ...$validators - * @throws \Exception - */ - public function __construct(array $attributes = [], array $indexes = [], Base ...$validators) - { - $this->attributes = $attributes; - - $this->indexes[] = new Document([ - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['$id'] - ]); - - $this->indexes[] = new Document([ - 'type' => Database::INDEX_KEY, - 'attributes' => ['$createdAt'] - ]); - - $this->indexes[] = new Document([ - 'type' => Database::INDEX_KEY, - 'attributes' => ['$updatedAt'] - ]); - - foreach ($indexes ?? [] as $index) { - $this->indexes[] = $index; - } - - parent::__construct(...$validators); - } - - /** - * Is valid. - * - * Returns false if: - * 1. any query in $value is invalid based on $validator - * 2. there is no index with an exact match of the filters - * 3. there is no index with an exact match of the order attributes - * - * Otherwise, returns true. - * - * @param mixed $value - * @return bool - */ - public function isValid($value): bool - { - if (!parent::isValid($value)) { - return false; - } - - $queries = []; - foreach ($value as $query) { - if (!$query instanceof Query) { - $query = Query::parse($query); - } - - $queries[] = $query; - } - - $grouped = Query::groupByType($queries); - $filters = $grouped['filters']; - - foreach ($filters as $filter) { - if ($filter->getMethod() === Query::TYPE_SEARCH) { - $matched = false; - - foreach ($this->indexes as $index) { - if ( - $index->getAttribute('type') === Database::INDEX_FULLTEXT - && $index->getAttribute('attributes') === [$filter->getAttribute()] - ) { - $matched = true; - } - } - - if (!$matched) { - $this->message = "Searching by attribute \"{$filter->getAttribute()}\" requires a fulltext index."; - return false; - } - } - } - - return true; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/OrderAttributes.php b/src/Appwrite/Utopia/Database/Validator/OrderAttributes.php deleted file mode 100644 index dcf38c90f1..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/OrderAttributes.php +++ /dev/null @@ -1,26 +0,0 @@ -getAttribute('status') === 'available'; - }); - - parent::__construct($attributes, $indexes, $strict); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php deleted file mode 100644 index 825b24e2bc..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ /dev/null @@ -1,135 +0,0 @@ - - */ - protected array $validators; - - /** - * Queries constructor - * - * @param Base ...$validators a list of validators - */ - public function __construct(Base ...$validators) - { - $this->validators = $validators; - } - - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription(): string - { - return $this->message; - } - - /** - * Is valid. - * - * Returns false if: - * 1. any query in $value is invalid based on $validator - * - * Otherwise, returns true. - * - * @param mixed $value - * @return bool - */ - public function isValid($value): bool - { - foreach ($value as $query) { - if (!$query instanceof Query) { - try { - $query = Query::parse($query); - } catch (\Throwable) { - $this->message = "Invalid query: {$query}"; - return false; - } - } - - $method = $query->getMethod(); - $methodType = match ($method) { - Query::TYPE_SELECT => Base::METHOD_TYPE_SELECT, - Query::TYPE_LIMIT => Base::METHOD_TYPE_LIMIT, - Query::TYPE_OFFSET => Base::METHOD_TYPE_OFFSET, - Query::TYPE_CURSORAFTER, - Query::TYPE_CURSORBEFORE => Base::METHOD_TYPE_CURSOR, - Query::TYPE_ORDERASC, - Query::TYPE_ORDERDESC => Base::METHOD_TYPE_ORDER, - Query::TYPE_EQUAL, - Query::TYPE_NOTEQUAL, - Query::TYPE_LESSER, - Query::TYPE_LESSEREQUAL, - Query::TYPE_GREATER, - Query::TYPE_GREATEREQUAL, - Query::TYPE_SEARCH, - Query::TYPE_IS_NULL, - Query::TYPE_IS_NOT_NULL, - Query::TYPE_BETWEEN, - Query::TYPE_STARTS_WITH, - Query::TYPE_ENDS_WITH => Base::METHOD_TYPE_FILTER, - default => '', - }; - - $methodIsValid = false; - foreach ($this->validators as $validator) { - if ($validator->getMethodType() !== $methodType) { - continue; - } - if (!$validator->isValid($query)) { - $this->message = 'Query not valid: ' . $validator->getDescription(); - return false; - } - - $methodIsValid = true; - } - - if (!$methodIsValid) { - $this->message = 'Query method not valid: ' . $method; - return false; - } - } - - return true; - } - - /** - * Is array - * - * Function will return true if object is array. - * - * @return bool - */ - public function isArray(): bool - { - return true; - } - - /** - * Get Type - * - * Returns validator type. - * - * @return string - */ - public function getType(): string - { - return self::TYPE_OBJECT; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 2f7fd5d60d..587862fa65 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -2,13 +2,13 @@ namespace Appwrite\Utopia\Database\Validator\Queries; -use Appwrite\Utopia\Database\Validator\Queries; -use Appwrite\Utopia\Database\Validator\Query\Limit; -use Appwrite\Utopia\Database\Validator\Query\Offset; -use Appwrite\Utopia\Database\Validator\Query\Cursor; -use Appwrite\Utopia\Database\Validator\Query\Filter; -use Appwrite\Utopia\Database\Validator\Query\Order; -use Appwrite\Utopia\Database\Validator\Query\Select; +use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Query\Cursor; +use Utopia\Database\Validator\Query\Filter; +use Utopia\Database\Validator\Query\Order; +use Utopia\Database\Validator\Query\Select; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -70,6 +70,6 @@ class Base extends Queries new Select($attributes), ]; - parent::__construct(...$validators); + parent::__construct($validators); } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Document.php b/src/Appwrite/Utopia/Database/Validator/Queries/Document.php deleted file mode 100644 index 3c630dfca9..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Document.php +++ /dev/null @@ -1,41 +0,0 @@ - '$id', - 'type' => Database::VAR_STRING, - 'array' => false, - ]); - $attributes[] = new \Utopia\Database\Document([ - 'key' => '$createdAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]); - $attributes[] = new \Utopia\Database\Document([ - 'key' => '$updatedAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]); - - $validators = [ - new Select($attributes), - ]; - - parent::__construct(...$validators); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Documents.php b/src/Appwrite/Utopia/Database/Validator/Queries/Documents.php deleted file mode 100644 index 7955ad35cc..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Documents.php +++ /dev/null @@ -1,52 +0,0 @@ - '$id', - 'type' => Database::VAR_STRING, - 'array' => false, - ]); - $attributes[] = new Document([ - 'key' => '$createdAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]); - $attributes[] = new Document([ - 'key' => '$updatedAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]); - - $validators = [ - new Limit(), - new Offset(), - new Cursor(), - new Filter($attributes), - new Order($attributes), - new Select($attributes), - ]; - - parent::__construct($attributes, $indexes, ...$validators); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Base.php b/src/Appwrite/Utopia/Database/Validator/Query/Base.php deleted file mode 100644 index d6f6df33f3..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Base.php +++ /dev/null @@ -1,62 +0,0 @@ -message; - } - - /** - * Is array - * - * Function will return true if object is array. - * - * @return bool - */ - public function isArray(): bool - { - return false; - } - - /** - * Get Type - * - * Returns validator type. - * - * @return string - */ - public function getType(): string - { - return self::TYPE_OBJECT; - } - - /** - * Returns what type of query this Validator is for - */ - abstract public function getMethodType(): string; -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php b/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php deleted file mode 100644 index 42bff08a1d..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php +++ /dev/null @@ -1,44 +0,0 @@ -getMethod(); - - if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { - $cursor = $query->getValue(); - $validator = new UID(); - if ($validator->isValid($cursor)) { - return true; - } - $this->message = 'Invalid cursor: ' . $validator->getDescription(); - return false; - } - - return false; - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_CURSOR; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php deleted file mode 100644 index 08847133de..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php +++ /dev/null @@ -1,141 +0,0 @@ -schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); - } - - $this->maxValuesCount = $maxValuesCount; - } - - protected function isValidAttribute($attribute): bool - { - if (\str_contains($attribute, '.')) { - // For relationships, just validate the top level. - // Utopia will validate each nested level during the recursive calls. - $attribute = \explode('.', $attribute)[0]; - - // TODO: Remove this when nested queries are supported - if (isset($this->schema[$attribute])) { - $this->message = 'Cannot query nested attribute on: ' . $attribute; - return false; - } - } - - // Search for attribute in schema - if (!isset($this->schema[$attribute])) { - $this->message = 'Attribute not found in schema: ' . $attribute; - return false; - } - - return true; - } - - protected function isValidAttributeAndValues(string $attribute, array $values): bool - { - if (!$this->isValidAttribute($attribute)) { - return false; - } - - if (\str_contains($attribute, '.')) { - // For relationships, just validate the top level. - // Utopia will validate each nested level during the recursive calls. - $attribute = \explode('.', $attribute)[0]; - } - - $attributeSchema = $this->schema[$attribute]; - - if (count($values) > $this->maxValuesCount) { - $this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute; - return false; - } - - // Extract the type of desired attribute from collection $schema - $attributeType = $attributeSchema['type']; - - foreach ($values as $value) { - $condition = match ($attributeType) { - Database::VAR_RELATIONSHIP => true, - Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING, - Database::VAR_FLOAT => (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER), - default => gettype($value) === $attributeType - }; - - if (!$condition) { - $this->message = 'Query type does not match expected: ' . $attributeType; - return false; - } - } - - return true; - } - - /** - * Is valid. - * - * Returns true if method is a filter method, attribute exists, and value matches attribute type - * - * Otherwise, returns false - * - * @param Query $value - * - * @return bool - */ - public function isValid($query): bool - { - // Validate method - $method = $query->getMethod(); - $attribute = $query->getAttribute(); - - switch ($method) { - case Query::TYPE_EQUAL: - case Query::TYPE_NOTEQUAL: - case Query::TYPE_LESSER: - case Query::TYPE_LESSEREQUAL: - case Query::TYPE_GREATER: - case Query::TYPE_GREATEREQUAL: - case Query::TYPE_SEARCH: - case Query::TYPE_STARTS_WITH: - case Query::TYPE_ENDS_WITH: - case Query::TYPE_BETWEEN: - case Query::TYPE_IS_NULL: - case Query::TYPE_IS_NOT_NULL: - $values = $query->getValues(); - return $this->isValidAttributeAndValues($attribute, $values); - - default: - return false; - } - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_FILTER; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Limit.php b/src/Appwrite/Utopia/Database/Validator/Query/Limit.php deleted file mode 100644 index 7a99825bcd..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Limit.php +++ /dev/null @@ -1,61 +0,0 @@ -maxLimit = $maxLimit; - } - - protected function isValidLimit($limit): bool - { - $validator = new Range(0, $this->maxLimit); - if ($validator->isValid($limit)) { - return true; - } - - $this->message = 'Invalid limit: ' . $validator->getDescription(); - return false; - } - - /** - * Is valid. - * - * Returns true if method is limit values are within range. - * - * @param Query $value - * - * @return bool - */ - public function isValid($query): bool - { - // Validate method - $method = $query->getMethod(); - - if ($method !== Query::TYPE_LIMIT) { - $this->message = 'Query method invalid: ' . $method; - return false; - } - - $limit = $query->getValue(); - return $this->isValidLimit($limit); - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_LIMIT; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Offset.php b/src/Appwrite/Utopia/Database/Validator/Query/Offset.php deleted file mode 100644 index 0bcbb909fa..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Offset.php +++ /dev/null @@ -1,60 +0,0 @@ -maxOffset = $maxOffset; - } - - protected function isValidOffset($offset): bool - { - $validator = new Range(0, $this->maxOffset); - if ($validator->isValid($offset)) { - return true; - } - - $this->message = 'Invalid offset: ' . $validator->getDescription(); - return false; - } - - /** - * Is valid. - * - * Returns true if method is offset and values are within range. - * - * @param Query $value - * - * @return bool - */ - public function isValid($query): bool - { - // Validate method - $method = $query->getMethod(); - - if ($method !== Query::TYPE_OFFSET) { - $this->message = 'Query method invalid: ' . $method; - return false; - } - - $offset = $query->getValue(); - return $this->isValidOffset($offset); - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_OFFSET; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Order.php b/src/Appwrite/Utopia/Database/Validator/Query/Order.php deleted file mode 100644 index 0c12d7ac44..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Order.php +++ /dev/null @@ -1,68 +0,0 @@ -schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); - } - } - - protected function isValidAttribute($attribute): bool - { - // Search for attribute in schema - if (!isset($this->schema[$attribute])) { - $this->message = 'Attribute not found in schema: ' . $attribute; - return false; - } - - return true; - } - - /** - * Is valid. - * - * Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid - * - * Otherwise, returns false - * - * @param Query $value - * - * @return bool - */ - public function isValid($query): bool - { - $method = $query->getMethod(); - $attribute = $query->getAttribute(); - - if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) { - if ($attribute === '') { - return true; - } - return $this->isValidAttribute($attribute); - } - - return false; - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_ORDER; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Select.php b/src/Appwrite/Utopia/Database/Validator/Query/Select.php deleted file mode 100644 index f1907f2e86..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query/Select.php +++ /dev/null @@ -1,60 +0,0 @@ -schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); - } - } - - /** - * Is valid. - * - * Returns true if method is TYPE_SELECT selections are valid - * - * Otherwise, returns false - * - * @param $query - * @return bool - */ - public function isValid($query): bool - { - /* @var $query Query */ - - if ($query->getMethod() !== Query::TYPE_SELECT) { - return false; - } - - foreach ($query->getValues() as $attribute) { - if (\str_contains($attribute, '.')) { - // For relationships, just validate the top level. - // Utopia will validate each nested level during the recursive calls. - $attribute = \explode('.', $attribute)[0]; - } - if (!isset($this->schema[$attribute]) && $attribute !== '*') { - $this->message = 'Attribute not found in schema: ' . $attribute; - return false; - } - } - return true; - } - - public function getMethodType(): string - { - return self::METHOD_TYPE_SELECT; - } -} diff --git a/src/Appwrite/Utopia/Request/Filters/V15.php b/src/Appwrite/Utopia/Request/Filters/V15.php index 50e5ec0db7..318096fe0d 100644 --- a/src/Appwrite/Utopia/Request/Filters/V15.php +++ b/src/Appwrite/Utopia/Request/Filters/V15.php @@ -200,11 +200,11 @@ class V15 extends Filter $operations = [ 'equal' => Query::TYPE_EQUAL, - 'notEqual' => Query::TYPE_NOTEQUAL, + 'notEqual' => Query::TYPE_NOT_EQUAL, 'lesser' => Query::TYPE_LESSER, - 'lesserEqual' => Query::TYPE_LESSEREQUAL, + 'lesserEqual' => Query::TYPE_LESSER_EQUAL, 'greater' => Query::TYPE_GREATER, - 'greaterEqual' => Query::TYPE_GREATEREQUAL, + 'greaterEqual' => Query::TYPE_GREATER_EQUAL, 'search' => Query::TYPE_SEARCH, ]; foreach ($content['queries'] as $i => $query) { diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index a05a40766e..9f9ceca317 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -28,6 +28,12 @@ class Attribute extends Model 'default' => '', 'example' => 'available', ]) + ->addRule('error', [ + 'type' => self::TYPE_STRING, + 'description' => 'Error message. Displays error generated on failure of creating or deleting an attribute.', + 'default' => '', + 'example' => 'string', + ]) ->addRule('required', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is attribute required?', diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php index b7ac626be9..3d3d1a3b52 100644 --- a/src/Appwrite/Utopia/Response/Model/Index.php +++ b/src/Appwrite/Utopia/Response/Model/Index.php @@ -28,6 +28,12 @@ class Index extends Model 'default' => '', 'example' => 'available', ]) + ->addRule('error', [ + 'type' => self::TYPE_STRING, + 'description' => 'Error message. Displays error generated on failure of creating or deleting an index.', + 'default' => '', + 'example' => 'string', + ]) ->addRule('attributes', [ 'type' => self::TYPE_STRING, 'description' => 'Index attributes.', diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 144adfb989..bd9443adec 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -174,7 +174,18 @@ trait DatabasesBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'key' => 'description', - 'size' => 256, + 'size' => 512, + 'required' => false, + 'default' => '', + ]); + + $tagline = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'tagline', + 'size' => 512, 'required' => false, 'default' => '', ]); @@ -241,10 +252,17 @@ trait DatabasesBase $this->assertEquals(202, $description['headers']['status-code']); $this->assertEquals($description['body']['key'], 'description'); $this->assertEquals($description['body']['type'], 'string'); - $this->assertEquals($description['body']['size'], 256); + $this->assertEquals($description['body']['size'], 512); $this->assertEquals($description['body']['required'], false); $this->assertEquals($description['body']['default'], ''); + $this->assertEquals(202, $tagline['headers']['status-code']); + $this->assertEquals($tagline['body']['key'], 'tagline'); + $this->assertEquals($tagline['body']['type'], 'string'); + $this->assertEquals($tagline['body']['size'], 512); + $this->assertEquals($tagline['body']['required'], false); + $this->assertEquals($tagline['body']['default'], ''); + $this->assertEquals(202, $releaseYear['headers']['status-code']); $this->assertEquals($releaseYear['body']['key'], 'releaseYear'); $this->assertEquals($releaseYear['body']['type'], 'integer'); @@ -282,17 +300,18 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); + ])); $this->assertIsArray($movies['body']['attributes']); - $this->assertCount(7, $movies['body']['attributes']); + $this->assertCount(8, $movies['body']['attributes']); $this->assertEquals($movies['body']['attributes'][0]['key'], $title['body']['key']); $this->assertEquals($movies['body']['attributes'][1]['key'], $description['body']['key']); - $this->assertEquals($movies['body']['attributes'][2]['key'], $releaseYear['body']['key']); - $this->assertEquals($movies['body']['attributes'][3]['key'], $duration['body']['key']); - $this->assertEquals($movies['body']['attributes'][4]['key'], $actors['body']['key']); - $this->assertEquals($movies['body']['attributes'][5]['key'], $datetime['body']['key']); - $this->assertEquals($movies['body']['attributes'][6]['key'], $relationship['body']['key']); + $this->assertEquals($movies['body']['attributes'][2]['key'], $tagline['body']['key']); + $this->assertEquals($movies['body']['attributes'][3]['key'], $releaseYear['body']['key']); + $this->assertEquals($movies['body']['attributes'][4]['key'], $duration['body']['key']); + $this->assertEquals($movies['body']['attributes'][5]['key'], $actors['body']['key']); + $this->assertEquals($movies['body']['attributes'][6]['key'], $datetime['body']['key']); + $this->assertEquals($movies['body']['attributes'][7]['key'], $relationship['body']['key']); return $data; } @@ -888,6 +907,7 @@ trait DatabasesBase public function testCreateIndexes(array $data): array { $databaseId = $data['databaseId']; + $titleIndex = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -956,7 +976,6 @@ trait DatabasesBase $this->assertEquals('available', $movies['body']['indexes'][1]['status']); $this->assertEquals('available', $movies['body']['indexes'][2]['status']); - $releaseWithDate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -973,6 +992,59 @@ trait DatabasesBase $this->assertCount(1, $releaseWithDate['body']['attributes']); $this->assertEquals('birthDay', $releaseWithDate['body']['attributes'][0]); + // Test for failure + $fulltextReleaseYear = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'releaseYearDated', + 'type' => 'fulltext', + 'attributes' => ['releaseYear'], + ]); + + $this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']); + $this->assertEquals($fulltextReleaseYear['body']['message'], 'Attribute "releaseYear" cannot be part of a FULLTEXT index, must be of type string'); + + $noAttributes = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'none', + 'type' => 'key', + 'attributes' => [], + ]); + + $this->assertEquals(400, $noAttributes['headers']['status-code']); + $this->assertEquals($noAttributes['body']['message'], 'No attributes provided for index'); + + $duplicates = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'duplicate', + 'type' => 'fulltext', + 'attributes' => ['releaseYear', 'releaseYear'], + ]); + + $this->assertEquals(400, $duplicates['headers']['status-code']); + $this->assertEquals($duplicates['body']['message'], 'Duplicate attributes provided'); + + $tooLong = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'tooLong', + 'type' => 'key', + 'attributes' => ['description', 'tagline'], + ]); + + $this->assertEquals(400, $tooLong['headers']['status-code']); + $this->assertStringContainsString('Index length is longer than the maximum', $tooLong['body']['message']); + return $data; } @@ -3419,7 +3491,7 @@ trait DatabasesBase ]); $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Query not valid: Cannot query nested attribute on: library', $documents['body']['message']); + $this->assertEquals('Invalid query: Cannot query nested attribute on: library', $documents['body']['message']); $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/library', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2b66277952..03722f2fd6 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -119,11 +119,11 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(0)' ] + 'queries' => [ 'limit(1)' ] ]); $this->assertEquals($response['headers']['status-code'], 200); - $this->assertCount(0, $response['body']['functions']); + $this->assertCount(1, $response['body']['functions']); $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ 'content-type' => 'application/json', @@ -675,11 +675,11 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(0)' ] + 'queries' => [ 'limit(1)' ] ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['executions']); + $this->assertCount(1, $response['body']['executions']); $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 858fec7d29..4f880338a4 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -1294,7 +1294,7 @@ class RealtimeCustomClientTest extends Scope $this->assertNotEmpty($deployment['body']['$id']); // Wait for deployment to be built. - sleep(5); + sleep(10); $response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 6e83a69844..e93d6cd6c0 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -313,10 +313,10 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(0)' ] + 'queries' => [ 'limit(1)' ] ]); $this->assertEquals(200, $files['headers']['status-code']); - $this->assertEquals(0, count($files['body']['files'])); + $this->assertEquals(1, count($files['body']['files'])); $files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 271e80a03f..0de99cc357 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -39,11 +39,11 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(0)' ] + 'queries' => [ 'limit(1)' ] ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['memberships']); + $this->assertCount(1, $response['body']['memberships']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', diff --git a/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php b/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php deleted file mode 100644 index 52375004ca..0000000000 --- a/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php +++ /dev/null @@ -1,121 +0,0 @@ -assertEquals(true, $validator->isValid([])); - } - - public function testInvalidQuery(): void - { - $validator = new IndexedQueries(); - - $this->assertEquals(false, $validator->isValid(["this.is.invalid"])); - } - - public function testInvalidMethod(): void - { - $validator = new IndexedQueries(); - $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - - $validator = new IndexedQueries([], [], new Limit()); - $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - } - - public function testInvalidValue(): void - { - $validator = new IndexedQueries([], [], new Limit()); - $this->assertEquals(false, $validator->isValid(['limit(-1)'])); - } - - public function testValid(): void - { - $attributes = [ - new Document([ - 'key' => 'name', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ]; - $indexes = [ - new Document([ - 'status' => 'available', - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - ]), - new Document([ - 'status' => 'available', - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['name'], - ]), - ]; - $validator = new IndexedQueries( - $attributes, - $indexes, - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), - ); - $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['search("name", "value")']), $validator->getDescription()); - } - - public function testMissingIndex(): void - { - $attributes = [ - new Document([ - 'key' => 'name', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ]; - $indexes = [ - new Document([ - 'status' => 'available', - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - ]), - ]; - $validator = new IndexedQueries( - $attributes, - $indexes, - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), - ); - $this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription()); - $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); - $this->assertEquals(false, $validator->isValid(['search("name", "value")']), $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/QueriesTest.php b/tests/unit/Utopia/Database/Validator/QueriesTest.php deleted file mode 100644 index 55e04c2b84..0000000000 --- a/tests/unit/Utopia/Database/Validator/QueriesTest.php +++ /dev/null @@ -1,76 +0,0 @@ -assertEquals(true, $validator->isValid([])); - } - - public function testInvalidQuery(): void - { - $validator = new Queries(); - - $this->assertEquals(false, $validator->isValid(["this.is.invalid"])); - } - - public function testInvalidMethod(): void - { - $validator = new Queries(); - $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - - $validator = new Queries(new Limit()); - $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - } - - public function testInvalidValue(): void - { - $validator = new Queries(new Limit()); - $this->assertEquals(false, $validator->isValid(['limit(-1)'])); - } - - public function testValid(): void - { - $attributes = [ - new Document([ - 'key' => 'name', - 'type' => Database::VAR_STRING, - 'array' => false, - ]) - ]; - $validator = new Queries( - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), - ); - $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/CursorTest.php b/tests/unit/Utopia/Database/Validator/Query/CursorTest.php deleted file mode 100644 index 0afc8baddd..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/CursorTest.php +++ /dev/null @@ -1,41 +0,0 @@ -validator = new Cursor(); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/FilterTest.php b/tests/unit/Utopia/Database/Validator/Query/FilterTest.php deleted file mode 100644 index 6e35435e32..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/FilterTest.php +++ /dev/null @@ -1,64 +0,0 @@ -validator = new Filter( - attributes: [ - new Document([ - 'key' => 'attr', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ], - ); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::isNotNull('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::isNull('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::startsWith('attr', 'super')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::endsWith('attr', 'man')), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::select(['attr'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/LimitTest.php b/tests/unit/Utopia/Database/Validator/Query/LimitTest.php deleted file mode 100644 index 1594d0db1f..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/LimitTest.php +++ /dev/null @@ -1,37 +0,0 @@ -validator = new Limit(100); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php b/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php deleted file mode 100644 index 4a29117e83..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php +++ /dev/null @@ -1,41 +0,0 @@ -validator = new Offset(5000); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/OrderTest.php b/tests/unit/Utopia/Database/Validator/Query/OrderTest.php deleted file mode 100644 index fe1b42d5c1..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/OrderTest.php +++ /dev/null @@ -1,55 +0,0 @@ -validator = new Order( - attributes: [ - new Document([ - 'key' => 'attr', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ], - ); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('dne')), false, $this->validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Query/SelectTest.php b/tests/unit/Utopia/Database/Validator/Query/SelectTest.php deleted file mode 100644 index ff2db35eb4..0000000000 --- a/tests/unit/Utopia/Database/Validator/Query/SelectTest.php +++ /dev/null @@ -1,45 +0,0 @@ -validator = new Select( - attributes: [ - new Document([ - 'key' => 'attr', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ], - ); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::select(['*', 'attr'])), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(1)), false, $this->validator->getDescription()); - } -}