change: events system.

This commit is contained in:
Darshan
2025-04-27 10:33:44 +05:30
parent 89ce5a6e10
commit 949f58522d
11 changed files with 254 additions and 235 deletions
+14 -14
View File
@@ -95,22 +95,22 @@ return [
'$model' => Response::MODEL_DATABASE,
'$resource' => true,
'$description' => 'This event triggers on any database event.',
'collections' => [
'tables' => [
'$model' => Response::MODEL_COLLECTION,
'$resource' => true,
'$description' => 'This event triggers on any collection event.',
'documents' => [
'$description' => 'This event triggers on any table event.',
'rows' => [
'$model' => Response::MODEL_DOCUMENT,
'$resource' => true,
'$description' => 'This event triggers on any documents event.',
'$description' => 'This event triggers on any rows event.',
'create' => [
'$description' => 'This event triggers when a document is created.',
'$description' => 'This event triggers when a row is created.',
],
'delete' => [
'$description' => 'This event triggers when a document is deleted.'
'$description' => 'This event triggers when a row is deleted.'
],
'update' => [
'$description' => 'This event triggers when a document is updated.'
'$description' => 'This event triggers when a row is updated.'
],
],
'indexes' => [
@@ -124,25 +124,25 @@ return [
'$description' => 'This event triggers when an index is deleted.'
]
],
'attributes' => [
'columns' => [
'$model' => Response::MODEL_ATTRIBUTE,
'$resource' => true,
'$description' => 'This event triggers on any attributes event.',
'$description' => 'This event triggers on any columns event.',
'create' => [
'$description' => 'This event triggers when an attribute is created.',
'$description' => 'This event triggers when an column is created.',
],
'delete' => [
'$description' => 'This event triggers when an attribute is deleted.'
'$description' => 'This event triggers when an column is deleted.'
]
],
'create' => [
'$description' => 'This event triggers when a collection is created.'
'$description' => 'This event triggers when a table is created.'
],
'delete' => [
'$description' => 'This event triggers when a collection is deleted.',
'$description' => 'This event triggers when a table is deleted.',
],
'update' => [
'$description' => 'This event triggers when a collection is updated.',
'$description' => 'This event triggers when a table is updated.',
]
],
'create' => [
+1 -1
View File
@@ -1395,7 +1395,7 @@ return [
'score' => 5,
'tagline' => 'Convert text to speech using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => ['databases.*.collections.*.documents.*.create'],
'events' => ['databases.*.tables.*.rows.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
+10 -11
View File
@@ -218,8 +218,8 @@ function createColumn(string $databaseId, string $tableId, Document $column, Res
$queueForDatabase
->setType(DATABASE_TYPE_CREATE_ATTRIBUTE)
->setDatabase($db)
->setCollection($table)
->setDocument($column);
->setTable($table)
->setRow($column);
$queueForEvents
->setContext('table', $table)
@@ -485,8 +485,7 @@ App::post('/v1/databases')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents) {
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
@@ -1280,7 +1279,7 @@ App::delete('/v1/databases/:databaseId/tables/:tableId')
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_COLLECTION)
->setDatabase($database)
->setCollection($table);
->setTable($table);
$queueForEvents
->setContext('database', $database)
@@ -2759,9 +2758,9 @@ App::delete('/v1/databases/:databaseId/tables/:tableId/columns/:key')
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_ATTRIBUTE)
->setCollection($table)
->setTable($table)
->setDatabase($db)
->setDocument($column);
->setRow($column);
// Select response model based on type and format
$type = $column->getAttribute('type');
@@ -2953,8 +2952,8 @@ App::post('/v1/databases/:databaseId/tables/:tableId/indexes')
$queueForDatabase
->setType(DATABASE_TYPE_CREATE_INDEX)
->setDatabase($db)
->setCollection($table)
->setDocument($index);
->setTable($table)
->setRow($index);
$queueForEvents
->setParam('databaseId', $databaseId)
@@ -3164,8 +3163,8 @@ App::delete('/v1/databases/:databaseId/tables/:tableId/indexes/:key')
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_INDEX)
->setDatabase($db)
->setCollection($table)
->setDocument($index);
->setTable($table)
->setRow($index);
$queueForEvents
->setParam('databaseId', $databaseId)
+5 -2
View File
@@ -18,6 +18,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Transformation\Adapter\Preview;
use Appwrite\Transformation\Transformation;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\DatabaseAliases;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
@@ -805,12 +806,14 @@ App::init()
if (version_compare($requestFormat, '1.6.0', '<')) {
$request->addFilter(new RequestV18());
}
// alias filters on 1.7.x, so we use `<=` and not just `<`
if (version_compare($requestFormat, '1.7.0', '<=')) {
if (version_compare($requestFormat, '1.7.0', '<')) {
$request->addFilter(new RequestV19());
}
}
// process on all databases endpoints!
$request->addFilter(new DatabaseAliases());
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
+18 -18
View File
@@ -10,8 +10,8 @@ class Database extends Event
{
protected string $type = '';
protected ?Document $database = null;
protected ?Document $collection = null;
protected ?Document $document = null;
protected ?Document $table = null;
protected ?Document $row = null;
public function __construct(protected Publisher $publisher)
{
@@ -55,48 +55,48 @@ class Database extends Event
}
/**
* Set the collection for this database event.
* Set the table for this database event.
*
* @param Document $collection
* @param Document $table
* @return self
*/
public function setCollection(Document $collection): self
public function setTable(Document $table): self
{
$this->collection = $collection;
$this->table = $table;
return $this;
}
/**
* Returns set collection for this event.
* Returns set table for this event.
*
* @return null|Document
*/
public function getCollection(): ?Document
public function getTable(): ?Document
{
return $this->collection;
return $this->table;
}
/**
* Set the document for this database event.
* Set the row for this database event.
*
* @param Document $document
* @param Document $row
* @return self
*/
public function setDocument(Document $document): self
public function setRow(Document $row): self
{
$this->document = $document;
$this->row = $row;
return $this;
}
/**
* Returns set document for this database event.
* Returns set row for this database event.
* @return null|Document
*/
public function getDocument(): ?Document
public function getRow(): ?Document
{
return $this->document;
return $this->row;
}
public function getQueue(): string
@@ -123,8 +123,8 @@ class Database extends Event
'project' => $this->project,
'user' => $this->user,
'type' => $this->type,
'collection' => $this->collection,
'document' => $this->document,
'table' => $this->table,
'row' => $this->row,
'database' => $this->database,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
];
+2 -2
View File
@@ -76,7 +76,7 @@ class Realtime extends Event
$payload = new Document($this->getPayload());
$db = $this->getContext('database');
$collection = $this->getContext('collection');
$table = $this->getContext('table');
$bucket = $this->getContext('bucket');
$target = RealtimeAdapter::fromPayload(
@@ -85,7 +85,7 @@ class Realtime extends Event
payload: $payload,
project: $this->getProject(),
database: $db,
collection: $collection,
table: $table,
bucket: $bucket,
);
+14 -21
View File
@@ -252,12 +252,12 @@ class Realtime extends Adapter
* @param Document $payload
* @param Document|null $project
* @param Document|null $database
* @param Document|null $collection
* @param Document|null $table
* @param Document|null $bucket
* @return array
* @throws \Exception
*/
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $table = null, Document $bucket = null): array
{
$channels = [];
$roles = [];
@@ -273,6 +273,7 @@ class Realtime extends Adapter
$roles = [Role::user(ID::custom($parts[1]))->toString()];
break;
case 'rules':
case 'migrations':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
@@ -297,26 +298,26 @@ class Realtime extends Adapter
$roles = [Role::team(ID::custom($parts[1]))->toString()];
break;
case 'databases':
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
if (in_array($parts[4] ?? [], ['columns', 'indexes'])) {
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
} elseif (($parts[4] ?? '') === 'documents') {
} elseif (($parts[4] ?? '') === 'rows') {
if ($database->isEmpty()) {
throw new \Exception('Database needs to be passed to Realtime for Document events in the Database.');
throw new \Exception('Database needs to be passed to Realtime for Row events in the Database.');
}
if ($collection->isEmpty()) {
throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.');
if ($table->isEmpty()) {
throw new \Exception('Table needs to be passed to Realtime for Row events in the Database.');
}
$channels[] = 'documents';
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents';
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents.' . $payload->getId();
$channels[] = 'rows';
$channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$collectionId') . '.rows';
$channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$collectionId') . '.rows.' . $payload->getId();
$roles = $collection->getAttribute('documentSecurity', false)
? \array_merge($collection->getRead(), $payload->getRead())
: $collection->getRead();
$roles = $table->getAttribute('documentSecurity', false)
? \array_merge($table->getRead(), $payload->getRead())
: $table->getRead();
}
break;
case 'buckets':
@@ -334,7 +335,6 @@ class Realtime extends Adapter
}
break;
case 'functions':
if ($parts[2] === 'executions') {
if (!empty($payload->getRead())) {
@@ -353,7 +353,6 @@ class Realtime extends Adapter
}
break;
case 'sites':
if ($parts[2] === 'deployments') {
$channels[] = 'console';
@@ -362,12 +361,6 @@ class Realtime extends Adapter
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
break;
case 'migrations':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
}
+142 -142
View File
@@ -60,8 +60,8 @@ class Databases extends Action
}
$type = $payload['type'];
$collection = new Document($payload['collection'] ?? []);
$document = new Document($payload['document'] ?? []);
$row = new Document($payload['row'] ?? []);
$table = new Document($payload['table'] ?? []);
$database = new Document($payload['database'] ?? []);
$log->addTag('projectId', $project->getId());
@@ -75,19 +75,19 @@ class Databases extends Action
match (\strval($type)) {
DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $project, $dbForProject),
DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $project, $dbForProject),
DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_COLLECTION => $this->deleteTable($database, $table, $project, $dbForProject),
DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createColumn($database, $table, $row, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteColumn($database, $table, $row, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $table, $row, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $table, $row, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
default => throw new Exception('No database operation for type: ' . \strval($type)),
};
}
/**
* @param Document $database
* @param Document $collection
* @param Document $attribute
* @param Document $table
* @param Document $column
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
@@ -98,64 +98,64 @@ class Databases extends Action
* @throws \Exception
* @throws \Throwable
*/
private function createAttribute(
private function createColumn(
Document $database,
Document $collection,
Document $attribute,
Document $table,
Document $column,
Document $project,
Database $dbForPlatform,
Database $dbForProject,
Realtime $queueForRealtime
): void {
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
if ($table->isEmpty()) {
throw new Exception('Missing table');
}
if ($attribute->isEmpty()) {
throw new Exception('Missing attribute');
if ($column->isEmpty()) {
throw new Exception('Missing column');
}
$projectId = $project->getId();
$event = "databases.[databaseId].collections.[collectionId].attributes.[attributeId].update";
$event = "databases.[databaseId].tables.[tableId].columns.[columnId].update";
/**
* TODO @christyjacob4 verify if this is still the case
* Fetch attribute from the database, since with Resque float values are loosing informations.
*/
$attribute = $dbForProject->getDocument('attributes', $attribute->getId());
$column = $dbForProject->getDocument('attributes', $column->getId());
if ($attribute->isEmpty()) {
if ($column->isEmpty()) {
// Attribute was deleted before job was processed
return;
}
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$type = $attribute->getAttribute('type', '');
$size = $attribute->getAttribute('size', 0);
$required = $attribute->getAttribute('required', false);
$default = $attribute->getAttribute('default', null);
$signed = $attribute->getAttribute('signed', true);
$array = $attribute->getAttribute('array', false);
$format = $attribute->getAttribute('format', '');
$formatOptions = $attribute->getAttribute('formatOptions', []);
$filters = $attribute->getAttribute('filters', []);
$options = $attribute->getAttribute('options', []);
$tableId = $table->getId();
$key = $column->getAttribute('key', '');
$type = $column->getAttribute('type', '');
$size = $column->getAttribute('size', 0);
$required = $column->getAttribute('required', false);
$default = $column->getAttribute('default', null);
$signed = $column->getAttribute('signed', true);
$array = $column->getAttribute('array', false);
$format = $column->getAttribute('format', '');
$formatOptions = $column->getAttribute('formatOptions', []);
$filters = $column->getAttribute('filters', []);
$options = $column->getAttribute('options', []);
$project = $dbForPlatform->getDocument('projects', $projectId);
$relatedAttribute = new Document();
$relatedCollection = new Document();
$relatedColumn = new Document();
$relatedTable = new Document();
try {
switch ($type) {
case Database::VAR_RELATIONSHIP:
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedCollection->isEmpty()) {
throw new DatabaseException('Collection not found');
$relatedTable = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedTable->isEmpty()) {
throw new DatabaseException('Table not found');
}
if (
!$dbForProject->createRelationship(
collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
collection: 'database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(),
relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedTable->getInternalId(),
type: $options['relationType'],
twoWay: $options['twoWay'],
id: $key,
@@ -163,61 +163,61 @@ class Databases extends Action
onDelete: $options['onDelete'],
)
) {
throw new DatabaseException('Failed to create Attribute');
throw new DatabaseException('Failed to create Column');
}
if ($options['twoWay']) {
$relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'available'));
$relatedColumn = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedTable->getInternalId() . '_' . $options['twoWayKey']);
$dbForProject->updateDocument('attributes', $relatedColumn->getId(), $relatedColumn->setAttribute('status', 'available'));
}
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');
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Column');
}
}
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
$dbForProject->updateDocument('attributes', $column->getId(), $column->setAttribute('status', 'available'));
} catch (\Throwable $e) {
Console::error($e->getMessage());
if ($e instanceof DatabaseException) {
$attribute->setAttribute('error', $e->getMessage());
if (! $relatedAttribute->isEmpty()) {
$relatedAttribute->setAttribute('error', $e->getMessage());
$column->setAttribute('error', $e->getMessage());
if (! $relatedColumn->isEmpty()) {
$relatedColumn->setAttribute('error', $e->getMessage());
}
}
$dbForProject->updateDocument(
'attributes',
$attribute->getId(),
$attribute->setAttribute('status', 'failed')
$column->getId(),
$column->setAttribute('status', 'failed')
);
if (! $relatedAttribute->isEmpty()) {
if (! $relatedColumn->isEmpty()) {
$dbForProject->updateDocument(
'attributes',
$relatedAttribute->getId(),
$relatedAttribute->setAttribute('status', 'failed')
$relatedColumn->getId(),
$relatedColumn->setAttribute('status', 'failed')
);
}
throw $e;
} finally {
$this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
$this->trigger($database, $table, $project, $event, $queueForRealtime, $column);
if (! $relatedCollection->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
if (! $relatedTable->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedTable->getId());
}
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $tableId);
}
}
/**
* @param Document $database
* @param Document $collection
* @param Document $attribute
* @param Document $table
* @param Document $column
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
@@ -228,24 +228,24 @@ class Databases extends Action
* @throws \Exception
* @throws \Throwable
**/
private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
private function deleteColumn(Document $database, Document $table, Document $column, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
if ($table->isEmpty()) {
throw new Exception('Missing collection');
}
if ($attribute->isEmpty()) {
if ($column->isEmpty()) {
throw new Exception('Missing attribute');
}
$projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$type = $attribute->getAttribute('type', '');
$event = 'databases.[databaseId].tables.[tableId].columns.[columnId].delete';
$tableId = $table->getId();
$key = $column->getAttribute('key', '');
$type = $column->getAttribute('type', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
$options = $attribute->getAttribute('options', []);
$relatedAttribute = new Document();
$relatedCollection = new Document();
$options = $column->getAttribute('options', []);
$relatedColumn = new Document();
$relatedTable = new Document();
// possible states at this point:
// - available: should not land in queue; controller flips these to 'deleting'
// - processing: hasn't finished creating
@@ -257,89 +257,89 @@ class Databases extends Action
try {
if ($type === Database::VAR_RELATIONSHIP) {
if ($options['twoWay']) {
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedCollection->isEmpty()) {
throw new DatabaseException('Collection not found');
$relatedTable = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
if ($relatedTable->isEmpty()) {
throw new DatabaseException('Table not found');
}
$relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
$relatedColumn = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedTable->getInternalId() . '_' . $options['twoWayKey']);
}
if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'stuck'));
if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(), $key)) {
$dbForProject->updateDocument('attributes', $relatedColumn->getId(), $relatedColumn->setAttribute('status', 'stuck'));
throw new DatabaseException('Failed to delete Relationship');
}
} elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
throw new DatabaseException('Failed to delete Attribute');
} elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(), $key)) {
throw new DatabaseException('Failed to delete Column');
}
$dbForProject->deleteDocument('attributes', $attribute->getId());
$dbForProject->deleteDocument('attributes', $column->getId());
if (!$relatedAttribute->isEmpty()) {
$dbForProject->deleteDocument('attributes', $relatedAttribute->getId());
if (!$relatedColumn->isEmpty()) {
$dbForProject->deleteDocument('attributes', $relatedColumn->getId());
}
} catch (NotFound $e) {
Console::error($e->getMessage());
$dbForProject->deleteDocument('attributes', $attribute->getId());
$dbForProject->deleteDocument('attributes', $column->getId());
if (!$relatedAttribute->isEmpty()) {
$dbForProject->deleteDocument('attributes', $relatedAttribute->getId());
if (!$relatedColumn->isEmpty()) {
$dbForProject->deleteDocument('attributes', $relatedColumn->getId());
}
} catch (\Throwable $e) {
Console::error($e->getMessage());
if ($e instanceof DatabaseException) {
$attribute->setAttribute('error', $e->getMessage());
if (!$relatedAttribute->isEmpty()) {
$relatedAttribute->setAttribute('error', $e->getMessage());
$column->setAttribute('error', $e->getMessage());
if (!$relatedColumn->isEmpty()) {
$relatedColumn->setAttribute('error', $e->getMessage());
}
}
$dbForProject->updateDocument(
'attributes',
$attribute->getId(),
$attribute->setAttribute('status', 'stuck')
$column->getId(),
$column->setAttribute('status', 'stuck')
);
if (!$relatedAttribute->isEmpty()) {
if (!$relatedColumn->isEmpty()) {
$dbForProject->updateDocument(
'attributes',
$relatedAttribute->getId(),
$relatedAttribute->setAttribute('status', 'stuck')
$relatedColumn->getId(),
$relatedColumn->setAttribute('status', 'stuck')
);
}
throw $e;
} finally {
$this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
$this->trigger($database, $table, $project, $event, $queueForRealtime, $column);
}
// The underlying database removes/rebuilds indexes when attribute is removed
// Update indexes table with changes
/** @var Document[] $indexes */
$indexes = $collection->getAttribute('indexes', []);
$indexes = $table->getAttribute('indexes', []);
foreach ($indexes as $index) {
/** @var string[] $attributes */
$attributes = $index->getAttribute('attributes');
/** @var string[] $columns */
$columns = $index->getAttribute('attributes');
$lengths = $index->getAttribute('lengths');
$orders = $index->getAttribute('orders');
$found = \array_search($key, $attributes);
$found = \array_search($key, $columns);
if ($found !== false) {
// If found, remove entry from attributes, lengths, and orders
// array_values wraps array_diff to reindex array keys
// when found attribute is removed from array
$attributes = \array_values(\array_diff($attributes, [$attributes[$found]]));
$columns = \array_values(\array_diff($columns, [$columns[$found]]));
$lengths = \array_values(\array_diff($lengths, isset($lengths[$found]) ? [$lengths[$found]] : []));
$orders = \array_values(\array_diff($orders, isset($orders[$found]) ? [$orders[$found]] : []));
if (empty($attributes)) {
if (empty($columns)) {
$dbForProject->deleteDocument('indexes', $index->getId());
} else {
$index
->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN)
->setAttribute('attributes', $columns, Document::SET_TYPE_ASSIGN)
->setAttribute('lengths', $lengths, Document::SET_TYPE_ASSIGN)
->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN);
@@ -357,7 +357,7 @@ class Databases extends Action
}
if ($exists) { // Delete the duplicate if created, else update in db
$this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
$this->deleteIndex($database, $table, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
} else {
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
@@ -365,17 +365,17 @@ class Databases extends Action
}
}
} finally {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $tableId);
if (! $relatedCollection->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
if (! $relatedTable->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedTable->getId());
}
}
}
/**
* @param Document $database
* @param Document $collection
* @param Document $table
* @param Document $index
* @param Document $project
* @param Database $dbForPlatform
@@ -388,9 +388,9 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
private function createIndex(Document $database, Document $table, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
if ($table->isEmpty()) {
throw new Exception('Missing collection');
}
if ($index->isEmpty()) {
@@ -398,8 +398,8 @@ class Databases extends Action
}
$projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update';
$collectionId = $collection->getId();
$event = 'databases.[databaseId].tables.[tableId].indexes.[indexId].update';
$collectionId = $table->getId();
$key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', '');
$attributes = $index->getAttribute('attributes', []);
@@ -408,7 +408,7 @@ class Databases extends Action
$project = $dbForPlatform->getDocument('projects', $projectId);
try {
if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
throw new DatabaseException('Failed to create Index');
}
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
@@ -425,14 +425,14 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$this->trigger($database, $table, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
}
}
/**
* @param Document $database
* @param Document $collection
* @param Document $table
* @param Document $index
* @param Document $project
* @param Database $dbForPlatform
@@ -445,9 +445,9 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
private function deleteIndex(Document $database, Document $table, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
if ($table->isEmpty()) {
throw new Exception('Missing collection');
}
if ($index->isEmpty()) {
@@ -455,13 +455,13 @@ class Databases extends Action
}
$projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete';
$event = 'databases.[databaseId].tables.[tableId].indexes.[indexId].delete';
$key = $index->getAttribute('key');
$status = $index->getAttribute('status', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
try {
if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $table->getInternalId(), $key)) {
throw new DatabaseException('Failed to delete index');
}
$dbForProject->deleteDocument('indexes', $index->getId());
@@ -481,8 +481,8 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
$this->trigger($database, $table, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $table->getId());
}
}
@@ -496,7 +496,7 @@ class Databases extends Action
protected function deleteDatabase(Document $database, Document $project, $dbForProject): void
{
$this->deleteByGroup('database_' . $database->getInternalId(), [], $dbForProject, function ($collection) use ($database, $project, $dbForProject) {
$this->deleteCollection($database, $collection, $project, $dbForProject);
$this->deleteTable($database, $collection, $project, $dbForProject);
});
$dbForProject->deleteCollection('database_' . $database->getInternalId());
@@ -504,7 +504,7 @@ class Databases extends Action
/**
* @param Document $database
* @param Document $collection
* @param Document $table
* @param Document $project
* @param Database $dbForProject
* @return void
@@ -515,17 +515,17 @@ class Databases extends Action
* @throws Structure
* @throws Exception
*/
protected function deleteCollection(Document $database, Document $collection, Document $project, Database $dbForProject): void
protected function deleteTable(Document $database, Document $table, Document $project, Database $dbForProject): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
if ($table->isEmpty()) {
throw new Exception('Missing table');
}
$collectionId = $collection->getId();
$collectionInternalId = $collection->getInternalId();
$collectionId = $table->getId();
$collectionInternalId = $table->getInternalId();
$databaseInternalId = $database->getInternalId();
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $table->getInternalId());
/**
* Related collections relating to current collection
@@ -558,50 +558,50 @@ class Databases extends Action
/**
* @param string $collectionId
* @param string $tableId
* @param array $queries
* @param Database $database
* @param callable|null $callback
* @return void
* @throws Exception
*/
protected function deleteByGroup(string $collectionId, array $queries, Database $database, callable $callback = null): void
protected function deleteByGroup(string $tableId, array $queries, Database $database, callable $callback = null): void
{
$start = \microtime(true);
try {
$count = $database->deleteDocuments(
$collectionId,
$tableId,
$queries,
Database::DELETE_BATCH_SIZE,
$callback
);
} catch (\Throwable $th) {
$tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : '';
Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collectionId} {$tenant} :{$th->getMessage()}");
Console::error("Failed to delete rows for table:{$database->getNamespace()}_{$tableId} {$tenant} :{$th->getMessage()}");
return;
}
$end = \microtime(true);
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
Console::info("Deleted {$count} rows by group in " . ($end - $start) . " seconds");
}
/**
* @param Document $database
* @param Document $collection
* @param Document $table
* @param Document $project
* @param Realtime $queueForRealtime
* @param Document|null $attribute
* @param Document|null $column
* @param Document|null $index
* @return void
*/
protected function trigger(
Document $database,
Document $collection,
Document $project,
string $event,
Realtime $queueForRealtime,
Document|null $attribute = null,
Document $database,
Document $table,
Document $project,
string $event,
Realtime $queueForRealtime,
Document|null $column = null,
Document|null $index = null,
): void {
$queueForRealtime
@@ -609,14 +609,14 @@ class Databases extends Action
->setSubscribers(['console'])
->setEvent($event)
->setParam('databaseId', $database->getId())
->setParam('collectionId', $collection->getId());
->setParam('tableId', $table->getId());
if ($attribute !== null && !empty($attribute)) {
if (! empty($column)) {
$queueForRealtime
->setParam('attributeId', $attribute->getId())
->setPayload($attribute->getArrayCopy());
->setParam('columnId', $column->getId())
->setPayload($column->getArrayCopy());
}
if ($index !== null && !empty($index)) {
if (! empty($index)) {
$queueForRealtime
->setParam('indexId', $index->getId())
->setPayload($index->getArrayCopy());
@@ -0,0 +1,38 @@
<?php
namespace Appwrite\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
class DatabaseAliases extends Filter
{
// Map old params to new
private const PARAMS_MAP = [
'documentId' => 'rowId',
'attributes' => 'columns',
'collectionId' => 'tableId',
'attributeId' => 'columnId',
'relatedCollectionId' => 'relatedTableId'
];
public function parse(array $content, string $model): array
{
return $this->overrideDatabaseParams($content, $model);
}
protected function overrideDatabaseParams(array $content, string $model): array
{
if (!str_starts_with($model, 'databases.')) {
return $content;
}
$intersect = array_intersect_key(self::PARAMS_MAP, $content);
foreach ($intersect as $oldKey => $newKey) {
$content[$newKey] = $content[$oldKey];
unset($content[$oldKey]);
}
return $content;
}
}
+9 -23
View File
@@ -6,32 +6,18 @@ use Appwrite\Utopia\Request\Filter;
class V19 extends Filter
{
// Convert 1.6 params to 1.7
public function parse(array $content, string $model): array
{
return $this->overrideDatabaseParams($content, $model);
}
// Database terminology change handling.
protected function overrideDatabaseParams(array $content, string $model): array
{
if (!str_starts_with($model, 'databases.')) {
return $content;
}
$map = [
'collectionId' => 'tableId',
'attributeId' => 'columnId',
'attributes' => 'columns',
'documentId' => 'rowId',
'relatedCollectionId' => 'relatedTableId'
];
foreach ($map as $oldKey => $newKey) {
if (isset($content[$oldKey])) {
$content[$newKey] = $content[$oldKey];
unset($content[$oldKey]);
}
/*
Uncomment with first request filter; current is just a copy of V18
switch ($model) {
case 'functions.create':
$content['something'] = $content['somethingElse'] ?? "";
unset($content['something']);
break;
}
*/
return $content;
}
@@ -49,7 +49,7 @@ class Webhook extends Model
'type' => self::TYPE_STRING,
'description' => 'Webhook trigger events.',
'default' => [],
'example' => 'database.collections.update',
'example' => 'database.tables.update',
'array' => true,
])
->addRule('security', [