Merge pull request #5710 from appwrite/feat-db-upgrade

Feat database upgrade
This commit is contained in:
Jake Barnby 2023-06-22 21:30:06 +12:00 committed by GitHub
commit 6db141acc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 378 additions and 1533 deletions

View file

@ -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

View file

@ -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,

View file

@ -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 => [

View file

@ -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);
}

View file

@ -1,56 +1,57 @@
<?php
use Utopia\App;
use Appwrite\Auth\Auth;
use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Collections;
use Appwrite\Utopia\Database\Validator\Queries\Databases;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Helpers\ID;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Integer;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\DateTime;
use Utopia\Database\Query;
use Utopia\Config\Config;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\Restricted as RestrictedException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Index as IndexValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Queries\Document as DocumentQueriesValidator;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Appwrite\Auth\Auth;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\IP;
use Utopia\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Response;
use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Collections;
use Appwrite\Utopia\Database\Validator\Queries\Databases;
use Appwrite\Utopia\Database\Validator\Queries\Document as DocumentValidator;
use Appwrite\Utopia\Database\Validator\Queries\Documents;
use Utopia\Config\Config;
use MaxMind\Db\Reader;
use Utopia\Validator\Integer;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
/**
* Create attribute of varying type
@ -434,7 +435,7 @@ App::post('/v1/databases')
]);
}
$dbForProject->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')

View file

@ -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 */

View file

@ -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()]),
]);

View file

@ -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 */

View file

@ -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')

View file

@ -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')

View file

@ -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;

View file

@ -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,

View file

@ -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

View file

@ -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.*",

44
composer.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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';

View file

@ -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':

View file

@ -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',

View file

@ -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'] = [

View file

@ -1,111 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
class IndexedQueries extends Queries
{
/**
* @var array<Document>
*/
protected array $attributes = [];
/**
* @var array<Document>
*/
protected array $indexes = [];
/**
* Expression constructor
*
* This Queries Validator filters indexes for only available indexes
*
* @param array<Document> $attributes
* @param array<Document> $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;
}
}

View file

@ -1,26 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\OrderAttributes as ValidatorOrderAttributes;
class OrderAttributes extends ValidatorOrderAttributes
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
*/
public function __construct($attributes, $indexes, $strict)
{
// Remove failed/stuck/processing indexes
$indexes = \array_filter($indexes, function ($index) {
return $index->getAttribute('status') === 'available';
});
parent::__construct($attributes, $indexes, $strict);
}
}

View file

@ -1,135 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Validator;
use Utopia\Database\Query;
class Queries extends Validator
{
/**
* @var string
*/
protected string $message = 'Invalid queries';
/**
* @var array<Base>
*/
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;
}
}

View file

@ -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);
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\Select;
use Utopia\Database\Database;
class Document extends Queries
{
/**
* Expression constructor
*
* @param array $attributes
* @throws \Exception
*/
public function __construct(array $attributes)
{
$attributes[] = new \Utopia\Database\Document([
'key' => '$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);
}
}

View file

@ -1,52 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\IndexedQueries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Appwrite\Utopia\Database\Validator\Query\Select;
use Utopia\Database\Database;
use Utopia\Database\Document;
class Documents extends IndexedQueries
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @throws \Exception
*/
public function __construct(array $attributes, array $indexes)
{
$attributes[] = new Document([
'key' => '$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);
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Validator;
use Utopia\Database\Query;
abstract class Base extends Validator
{
public const METHOD_TYPE_LIMIT = 'limit';
public const METHOD_TYPE_OFFSET = 'offset';
public const METHOD_TYPE_CURSOR = 'cursor';
public const METHOD_TYPE_ORDER = 'order';
public const METHOD_TYPE_FILTER = 'filter';
public const METHOD_TYPE_SELECT = 'select';
/**
* @var string
*/
protected $message = 'Invalid query';
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return $this->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;
}

View file

@ -1,44 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
class Cursor extends Base
{
/**
* Is valid.
*
* Returns true if method is cursorBefore or cursorAfter and value is not null
*
* Otherwise, returns false
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
// Validate method
$method = $query->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;
}
}

View file

@ -1,141 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Database;
use Utopia\Database\Query;
class Filter extends Base
{
/**
* @var string
*/
protected $message = 'Invalid query';
/**
* @var array
*/
protected $schema = [];
private int $maxValuesCount;
/**
* Query constructor
*
* @param int $maxValuesCount
*/
public function __construct(array $attributes = [], int $maxValuesCount = 100)
{
foreach ($attributes as $attribute) {
$this->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;
}
}

View file

@ -1,61 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Validator\Range;
class Limit extends Base
{
protected int $maxLimit;
/**
* Query constructor
*
* @param int $maxLimit
*/
public function __construct(int $maxLimit = PHP_INT_MAX)
{
$this->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;
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Database\Query;
use Utopia\Validator\Range;
class Offset extends Base
{
protected int $maxOffset;
/**
* Query constructor
*
* @param int $maxOffset
*/
public function __construct(int $maxOffset = PHP_INT_MAX)
{
$this->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;
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Validator;
class Order extends Base
{
/**
* @var array
*/
protected $schema = [];
/**
* Query constructor
*
*/
public function __construct(array $attributes = [])
{
foreach ($attributes as $attribute) {
$this->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;
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
class Select extends Base
{
protected array $schema = [];
/**
* Query constructor
*
*/
public function __construct(array $attributes = [])
{
foreach ($attributes as $attribute) {
$this->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;
}
}

View file

@ -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) {

View file

@ -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?',

View file

@ -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.',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -1,121 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\IndexedQueries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Order;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Database;
use Utopia\Database\Document;
class IndexedQueriesTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testEmptyQueries(): void
{
$validator = new IndexedQueries();
$this->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());
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Order;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Database;
use Utopia\Database\Document;
class QueriesTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testEmptyQueries(): void
{
$validator = new Queries();
$this->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());
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class CursorTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}

View file

@ -1,64 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class FilterTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class LimitTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class OffsetTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}

View file

@ -1,55 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class OrderTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Appwrite\Utopia\Database\Validator\Query\Select;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class SelectTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->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());
}
}