1
0
Fork 0
mirror of synced 2024-07-07 23:46:11 +12:00

Merge pull request #5271 from appwrite/feat-realtions-jake

Feat relations updates
This commit is contained in:
Jake Barnby 2023-03-24 18:58:25 +13:00 committed by GitHub
commit f6aa4a4ab1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 891 additions and 440 deletions

View file

@ -302,7 +302,6 @@ function updateAttribute(
$dbForProject->updateRelationship(
collection: $collectionId,
key: $key,
newTwoWayKey: $options['twoWayKey'],
twoWay: $options['twoWay'],
onDelete: $options['onDelete'],
);
@ -1529,11 +1528,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_RELATIONSHIP)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('relatedCollectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('type', '', new WhiteList([Database::RELATION_ONE_TO_ONE, Database::RELATION_MANY_TO_ONE, Database::RELATION_MANY_TO_MANY, Database::RELATION_ONE_TO_MANY]), 'Relation type')
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
->param('twoWayKey', null, new Key(), 'Two Way Key', true)
->param('key', null, new Key(), 'Attribute Key.', true)
->param('twoWayKey', null, new Key(), 'Two Way Attribute Key.', true)
->param('onDelete', Database::RELATION_MUTATE_RESTRICT, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option', true)
->inject('response')
->inject('dbForProject')
@ -1542,10 +1541,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
->action(function (
string $databaseId,
string $collectionId,
string $key,
string $relatedCollectionId,
string $type,
bool $twoWay,
?string $key,
?string $twoWayKey,
string $onDelete,
Response $response,
@ -1553,6 +1552,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
EventDatabase $database,
Event $events
) {
$key ??= $relatedCollectionId;
$twoWayKey ??= $collectionId;
$attribute = createAttribute(
@ -2750,12 +2750,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$filterQueries = Query::groupByType($queries)['filters'];
// todo: temporary fix until Utopia will be ready !!!!
foreach ($filterQueries as $key => $query) {
if (\str_contains($query->getAttribute(), '.')) {
unset($filterQueries[$key]);
}
}
if ($documentSecurity && !$valid) {
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);

View file

@ -95,26 +95,30 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if ($type === Database::VAR_RELATIONSHIP) {
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedCollection->isEmpty()) {
throw new Exception('Missing collection');
}
if (
!$dbForProject->createRelationship(
collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
type: $options['relationType'],
twoWay: $options['twoWay'],
id: $key,
twoWayKey: $options['twoWayKey'],
onDelete: $options['onDelete'],
)
) {
throw new Exception('Failed to create Attribute');
}
} elseif (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Attribute');
switch ($type) {
case Database::VAR_RELATIONSHIP:
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedCollection->isEmpty()) {
throw new Exception('Collection not found');
}
if (
!$dbForProject->createRelationship(
collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
type: $options['relationType'],
twoWay: $options['twoWay'],
id: $key,
twoWayKey: $options['twoWayKey'],
onDelete: $options['onDelete'],
)
) {
throw new Exception('Failed to create Attribute');
}
break;
default:
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Attribute');
}
}
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
@ -151,6 +155,7 @@ class DatabaseV1 extends Worker
* @param Document $collection
* @param Document $attribute
* @param string $projectId
* @throws Throwable
*/
protected function deleteAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
{
@ -177,12 +182,16 @@ class DatabaseV1 extends Worker
try {
if ($status !== 'failed') {
if ($type === Database::VAR_RELATIONSHIP) {
if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete Attribute');
}
} elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete Attribute');
switch ($type) {
case Database::VAR_RELATIONSHIP:
if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete Attribute');
}
break;
default:
if (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new Exception('Failed to delete Attribute');
}
}
}
$dbForProject->deleteDocument('attributes', $attribute->getId());

View file

@ -43,13 +43,13 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/abuse": "0.21.*",
"utopia-php/abuse": "0.22.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.23.*",
"utopia-php/audit": "0.24.*",
"utopia-php/cache": "0.8.*",
"utopia-php/cli": "0.13.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "dev-feat-relationships as 0.33.0",
"utopia-php/database": "dev-feat-relationships as 0.34.0",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/framework": "0.28.*",

45
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": "65ec111f49af9c148eb69dc16aec7a10",
"content-hash": "db3f68f41b0387401b964f65bad3053c",
"packages": [
{
"name": "adhocore/jwt",
@ -1808,23 +1808,23 @@
},
{
"name": "utopia-php/abuse",
"version": "0.21.0",
"version": "0.22.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "7483068b192b27d698da9534c80091f69666d5eb"
"reference": "9360a026d18809e35523b254ab57e51055008203"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/7483068b192b27d698da9534c80091f69666d5eb",
"reference": "7483068b192b27d698da9534c80091f69666d5eb",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/9360a026d18809e35523b254ab57e51055008203",
"reference": "9360a026d18809e35523b254ab57e51055008203",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-pdo": "*",
"php": ">=8.0",
"utopia-php/database": "0.33.*"
"utopia-php/database": "0.34.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -1851,9 +1851,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.21.0"
"source": "https://github.com/utopia-php/abuse/tree/0.22.1"
},
"time": "2023-03-10T08:49:10+00:00"
"time": "2023-03-23T07:30:49+00:00"
},
{
"name": "utopia-php/analytics",
@ -1912,22 +1912,21 @@
},
{
"name": "utopia-php/audit",
"version": "0.23.0",
"version": "0.24.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "f16e893a22b93560d2af02afcb4761b3a940148a"
"reference": "237538b618506bbc0efc0e7884d49cdf52c20004"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/f16e893a22b93560d2af02afcb4761b3a940148a",
"reference": "f16e893a22b93560d2af02afcb4761b3a940148a",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/237538b618506bbc0efc0e7884d49cdf52c20004",
"reference": "237538b618506bbc0efc0e7884d49cdf52c20004",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": ">=8.0",
"utopia-php/database": "0.33.*"
"utopia-php/database": "0.34.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -1954,9 +1953,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.23.0"
"source": "https://github.com/utopia-php/audit/tree/0.24.1"
},
"time": "2023-03-10T08:51:26+00:00"
"time": "2023-03-23T07:29:11+00:00"
},
{
"name": "utopia-php/cache",
@ -2117,12 +2116,12 @@
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "3dac815092cf8dc8feff79b7db461c67518db875"
"reference": "c1df0b712746b1283737956a1b0b143dca8f8610"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/3dac815092cf8dc8feff79b7db461c67518db875",
"reference": "3dac815092cf8dc8feff79b7db461c67518db875",
"url": "https://api.github.com/repos/utopia-php/database/zipball/c1df0b712746b1283737956a1b0b143dca8f8610",
"reference": "c1df0b712746b1283737956a1b0b143dca8f8610",
"shasum": ""
},
"require": {
@ -2167,7 +2166,7 @@
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/feat-relationships"
},
"time": "2023-03-22T11:41:15+00:00"
"time": "2023-03-23T10:52:28+00:00"
},
{
"name": "utopia-php/domains",
@ -5662,8 +5661,8 @@
{
"package": "utopia-php/database",
"version": "dev-feat-relationships",
"alias": "0.33.0",
"alias_normalized": "0.33.0.0"
"alias": "0.34.0",
"alias_normalized": "0.34.0.0"
}
],
"minimum-stability": "stable",
@ -5693,5 +5692,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

View file

@ -83,8 +83,6 @@ services:
- ./docs:/usr/src/code/docs
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
depends_on:
- mariadb
- redis
@ -345,8 +343,6 @@ services:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
- ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- redis
- mariadb

View file

@ -62,12 +62,12 @@ class Base extends Queries
]);
$validators = [
new Select(),
new Limit(),
new Offset(),
new Cursor(),
new Filter($attributes),
new Order($attributes),
new Select($attributes),
];
parent::__construct(...$validators);

View file

@ -61,7 +61,7 @@ class Filter extends Base
foreach ($values as $value) {
$condition = match ($attributeType) {
Database::VAR_RELATIONSHIP => true, // Todo: ?
Database::VAR_RELATIONSHIP => true,
Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING,
default => gettype($value) === $attributeType
};
@ -91,6 +91,7 @@ class Filter extends Base
// Validate method
$method = $query->getMethod();
$attribute = $query->getAttribute();
switch ($method) {
case Query::TYPE_EQUAL:
case Query::TYPE_NOTEQUAL:

View file

@ -41,6 +41,8 @@ class Select extends Base
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 !== '*') {

View file

@ -11,12 +11,6 @@ class AttributeRelationship extends Attribute
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_STRING,
'description' => 'Default value for attribute when not provided. Only null is optional',
'default' => null,
'example' => '',
])
->addRule('relatedCollection', [
'type' => self::TYPE_STRING,
'description' => 'The Id of the related collection',
@ -25,26 +19,26 @@ class AttributeRelationship extends Attribute
])
->addRule('relationType', [
'type' => self::TYPE_STRING,
'description' => 'The type of the relationship ',
'default' => null,
'description' => 'The type of the relationship',
'default' => '',
'example' => 'oneToOne|oneToMany|manyToOne|manyToMany',
])
->addRule('twoWay', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is the relationship two-way?',
'default' => null,
'example' => 'relationship',
'default' => false,
'example' => false,
])
->addRule('twoWayKey', [
'type' => self::TYPE_STRING,
'description' => 'The key of the two-way relationship',
'default' => null,
'default' => '',
'example' => 'string',
])
->addRule('onDelete', [
'type' => self::TYPE_STRING,
'description' => 'Action to take on related documents when parent document is deleted',
'default' => null,
'default' => 'restrict',
'example' => 'restrict|cascade|setNull',
])
;

File diff suppressed because it is too large Load diff

View file

@ -1165,7 +1165,7 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(202, $attribute['headers']['status-code']);
}
sleep(20);
sleep(10);
$collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',

View file

@ -26,6 +26,8 @@ trait Base
public static string $CREATE_IP_ATTRIBUTE = 'create_ip_attribute';
public static string $CREATE_ENUM_ATTRIBUTE = 'create_enum_attribute';
public static string $CREATE_DATETIME_ATTRIBUTE = 'create_datetime_attribute';
public static string $CREATE_RELATIONSHIP_ATTRIBUTE = 'create_relationship_attribute';
public static string $UPDATE_STRING_ATTRIBUTE = 'update_string_attribute';
public static string $UPDATE_INTEGER_ATTRIBUTE = 'update_integer_attribute';
public static string $UPDATE_FLOAT_ATTRIBUTE = 'update_float_attribute';
@ -35,6 +37,8 @@ trait Base
public static string $UPDATE_IP_ATTRIBUTE = 'update_ip_attribute';
public static string $UPDATE_ENUM_ATTRIBUTE = 'update_enum_attribute';
public static string $UPDATE_DATETIME_ATTRIBUTE = 'update_datetime_attribute';
public static string $UPDATE_RELATIONSHIP_ATTRIBUTE = 'update_relationship_attribute';
public static string $GET_ATTRIBUTES = 'get_attributes';
public static string $GET_ATTRIBUTE = 'get_attribute';
public static string $DELETE_ATTRIBUTE = 'delete_attribute';
@ -460,6 +464,17 @@ trait Base
array
}
}';
case self::$CREATE_RELATIONSHIP_ATTRIBUTE:
return 'mutation createRelationshipAttribute($databaseId: String!, $collectionId: String!, $relatedCollectionId: String!, $type: String!, $twoWay: Boolean, $key: String, $twoWayKey: String, $onDelete: String){
databasesCreateRelationshipAttribute(databaseId: $databaseId, collectionId: $collectionId, relatedCollectionId: $relatedCollectionId, type: $type, twoWay: $twoWay, key: $key, twoWayKey: $twoWayKey, onDelete: $onDelete) {
relatedCollection
relationType
twoWay
key
twoWayKey
onDelete
}
}';
case self::$UPDATE_STRING_ATTRIBUTE:
return 'mutation updateStringAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
databasesUpdateStringAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
@ -528,6 +543,17 @@ trait Base
default
}
}';
case self::$UPDATE_RELATIONSHIP_ATTRIBUTE:
return 'mutation updateRelationshipAttribute($databaseId: String!, $collectionId: String!, $key: String!, $twoWay: Boolean, $onDelete: String){
databasesUpdateRelationshipAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, twoWay: $twoWay, onDelete: $onDelete) {
relatedCollection
relationType
twoWay
key
twoWayKey
onDelete
}
}';
case self::$CREATE_INDEX:
return 'mutation createIndex($databaseId: String!, $collectionId: String!, $key: String!, $type: String!, $attributes: [String!]!, $orders: [String!]){
databasesCreateIndex(databaseId: $databaseId, collectionId: $collectionId, key: $key, type: $type, attributes: $attributes, orders: $orders) {

View file

@ -7,6 +7,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@ -75,9 +76,36 @@ class DatabaseServerTest extends Scope
$collection = $collection['body']['data']['databasesCreateCollection'];
$this->assertEquals('Actors', $collection['name']);
$gqlPayload = [
'query' => $query,
'variables' => [
'databaseId' => $database['_id'],
'collectionId' => 'movies',
'name' => 'Movies',
'documentSecurity' => false,
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
]
];
$collection2 = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
$this->assertIsArray($collection2['body']['data']);
$this->assertArrayNotHasKey('errors', $collection2['body']);
$collection2 = $collection2['body']['data']['databasesCreateCollection'];
$this->assertEquals('Movies', $collection2['name']);
return [
'database' => $database,
'collection' => $collection,
'collection2' => $collection2,
];
}
@ -570,6 +598,69 @@ class DatabaseServerTest extends Scope
return $data;
}
/**
* @depends testCreateCollection
*/
public function testCreateRelationshipAttribute(array $data): array
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_RELATIONSHIP_ATTRIBUTE);
$gqlPayload = [
'query' => $query,
'variables' => [
'databaseId' => $data['database']['_id'],
'collectionId' => $data['collection2']['_id'], // Movies
'relatedCollectionId' => $data['collection']['_id'], // Actors
'type' => Database::RELATION_ONE_TO_MANY,
'twoWay' => true,
'key' => 'actors',
'twoWayKey' => 'movie'
]
];
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
$this->assertArrayNotHasKey('errors', $attribute['body']);
$this->assertIsArray($attribute['body']['data']);
$this->assertIsArray($attribute['body']['data']['databasesCreateRelationshipAttribute']);
return $data;
}
/**
* @depends testCreateRelationshipAttribute
*/
public function testUpdateRelationshipAttribute(array $data): array
{
sleep(3);
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$UPDATE_RELATIONSHIP_ATTRIBUTE);
$gqlPayload = [
'query' => $query,
'variables' => [
'databaseId' => $data['database']['_id'],
'collectionId' => $data['collection2']['_id'],
'key' => 'actors',
'twoWay' => false,
]
];
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
$this->assertArrayNotHasKey('errors', $attribute['body']);
$this->assertIsArray($attribute['body']['data']);
$this->assertIsArray($attribute['body']['data']['databasesUpdateRelationshipAttribute']);
return $data;
}
/**
* @depends testCreateCollection
* @throws Exception