mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
refactor(insights): share index-create body between CE action and CTA
Move the body of the public databases create-index endpoint into a new final `createIndex(...)` helper on the abstract Indexes Action so both the HTTP-facing CE action and the insights CTA share one source of truth for index validation, persistence, and queue dispatch. The CTA's `Appwrite\Insights\CTA\Action` becomes a small interface (`getName()` + `execute()`); the dispatcher now calls `$action->execute(...)` directly instead of poking at Utopia Action internals via `getCallback()` and `getParams()`. The CTA's `Create` extends the CE Indexes `Create` so it inherits `createIndex()` for free, while keeping a no-op constructor to skip the HTTP route registration that runs in the parent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
1d215b2840
commit
719b1885bf
@@ -2,22 +2,43 @@
|
||||
|
||||
namespace Appwrite\Insights\CTA;
|
||||
|
||||
use Utopia\Platform\Action as PlatformAction;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
/**
|
||||
* Base class for CTA actions registered in the insights CTA registry.
|
||||
* Contract for CTA actions registered in the insights CTA registry.
|
||||
*
|
||||
* A CTA action is a named, parameter-validated callable invoked when a user triggers
|
||||
* a call-to-action attached to an insight. Subclasses declare their inputs via `param()`
|
||||
* and dependencies via `inject()`, and provide their executable body via `callback()`.
|
||||
* A CTA action is a named handler invoked when a user triggers a call-to-action
|
||||
* attached to an insight. Implementations validate `$params` themselves and return
|
||||
* the document produced by the action (e.g. a freshly-created index).
|
||||
*
|
||||
* Convention for `getName()`: dot-separated `domain.<sub>.verb` in camelCase, e.g. `databases.indexes.create`.
|
||||
* The required project scope is declared via `label('scope', '...')`.
|
||||
*/
|
||||
abstract class Action extends PlatformAction
|
||||
interface Action
|
||||
{
|
||||
/**
|
||||
* Unique, registered name for this action.
|
||||
*/
|
||||
abstract public static function getName(): string;
|
||||
public static function getName(): string;
|
||||
|
||||
/**
|
||||
* Run the action. Implementations may throw any `Appwrite\Extend\Exception` to
|
||||
* signal a failed execution; the returned Document is surfaced to the caller
|
||||
* in the CTA execution response.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function execute(
|
||||
array $params,
|
||||
Document $insight,
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
callable $getDatabasesDB,
|
||||
EventDatabase $queueForDatabase,
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization,
|
||||
): Document;
|
||||
}
|
||||
|
||||
@@ -5,41 +5,33 @@ namespace Appwrite\Insights\CTA\Action\Databases\Indexes;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Insights\CTA\Action;
|
||||
use Appwrite\Insights\CTA\Action as CTAAction;
|
||||
use Appwrite\Insights\Validator\CTAParams\DatabasesCreateIndex as DatabasesCreateIndexParams;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes\Create as IndexCreate;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Index as IndexValidator;
|
||||
|
||||
class Create extends Action
|
||||
class Create extends IndexCreate implements CTAAction
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return INSIGHT_CTA_ACTION_DATABASES_INDEXES_CREATE;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
$this
|
||||
->desc('Create a database index from an insight CTA.')
|
||||
->label('scope', 'collections.write')
|
||||
->param('params', [], new DatabasesCreateIndexParams(), 'CTA params describing the index to create.')
|
||||
->inject('dbForProject')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
return Response::MODEL_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function action(
|
||||
public function __construct()
|
||||
{
|
||||
// Skip the parent HTTP route registration — this CTA handler is invoked
|
||||
// directly through the insights CTA dispatcher, not via Utopia routing.
|
||||
}
|
||||
|
||||
public function execute(
|
||||
array $params,
|
||||
Document $insight,
|
||||
Document $project,
|
||||
@@ -47,152 +39,26 @@ class Create extends Action
|
||||
callable $getDatabasesDB,
|
||||
EventDatabase $queueForDatabase,
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
Authorization $authorization,
|
||||
): Document {
|
||||
$databaseId = (string) $params['databaseId'];
|
||||
$collectionId = (string) $params['collectionId'];
|
||||
$key = (string) $params['key'];
|
||||
$type = (string) $params['type'];
|
||||
$attributes = $params['attributes'];
|
||||
$orders = $params['orders'] ?? [];
|
||||
$lengths = $params['lengths'] ?? [];
|
||||
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
$validator = new DatabasesCreateIndexParams();
|
||||
if (!$validator->isValid($params)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND, params: [$collectionId]);
|
||||
}
|
||||
|
||||
$count = $dbForProject->count('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$db->getSequence()]),
|
||||
], 61);
|
||||
|
||||
$dbForDatabases = $getDatabasesDB($db);
|
||||
|
||||
if ($count >= $dbForDatabases->getLimitForIndexes()) {
|
||||
throw new Exception(Exception::INDEX_LIMIT_EXCEEDED, params: [$collectionId]);
|
||||
}
|
||||
|
||||
$oldAttributes = \array_map(
|
||||
fn ($a) => $a->getArrayCopy(),
|
||||
$collection->getAttribute('attributes')
|
||||
return $this->createIndex(
|
||||
(string) $params['databaseId'],
|
||||
(string) $params['collectionId'],
|
||||
(string) $params['key'],
|
||||
(string) $params['type'],
|
||||
$params['attributes'],
|
||||
$params['orders'] ?? [],
|
||||
$params['lengths'] ?? [],
|
||||
$dbForProject,
|
||||
$getDatabasesDB,
|
||||
$queueForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization,
|
||||
);
|
||||
|
||||
foreach ([
|
||||
['$id', Database::VAR_STRING, true, Database::LENGTH_KEY],
|
||||
['$createdAt', Database::VAR_DATETIME, false, 0],
|
||||
['$updatedAt', Database::VAR_DATETIME, false, 0],
|
||||
] as [$attributeKey, $attributeType, $required, $size]) {
|
||||
$oldAttributes[] = [
|
||||
'key' => $attributeKey,
|
||||
'type' => $attributeType,
|
||||
'status' => 'available',
|
||||
'required' => $required,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => $size,
|
||||
'signed' => $attributeType === Database::VAR_DATETIME ? false : true,
|
||||
];
|
||||
}
|
||||
|
||||
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
$attributeIndex = \array_search($attribute, \array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
throw new Exception(Exception::ATTRIBUTE_UNKNOWN, params: [$attribute]);
|
||||
}
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Cannot create an index for a relationship attribute: ' . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
if ($attributeStatus !== 'available') {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_AVAILABLE, params: [$oldAttributes[$attributeIndex]['key']]);
|
||||
}
|
||||
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index = new Document([
|
||||
'$id' => ID::custom($db->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'processing',
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $databaseId,
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collectionId,
|
||||
'type' => $type,
|
||||
'attributes' => $attributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$collection->getAttribute('indexes'),
|
||||
$dbForDatabases->getAdapter()->getMaxIndexLength(),
|
||||
$dbForDatabases->getAdapter()->getInternalIndexesKeys(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndexArray(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(),
|
||||
$dbForDatabases->getAdapter()->getSupportForVectors(),
|
||||
$dbForDatabases->getAdapter()->getSupportForAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObjectIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTrigramIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForUniqueIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForFulltextIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTTLIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObject()
|
||||
);
|
||||
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception(Exception::INDEX_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
$index = $dbForProject->createDocument('indexes', $index);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::INDEX_ALREADY_EXISTS, params: [$key]);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_CREATE_INDEX)
|
||||
->setDatabase($db)
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setContext('collection', $collection)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('indexId', $index->getId());
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
+194
@@ -2,7 +2,16 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Index as IndexValidator;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
|
||||
abstract class Action extends UtopiaAction
|
||||
@@ -152,4 +161,189 @@ abstract class Action extends UtopiaAction
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'collection' : 'table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build, validate, persist and queue a new index document for the current
|
||||
* API context. Shared by the public HTTP create-index actions and by the
|
||||
* insights CTA action that surfaces missing indexes to project members.
|
||||
*
|
||||
* @param array<string> $attributes
|
||||
* @param array<string> $orders
|
||||
* @param array<int|null> $lengths
|
||||
*/
|
||||
final public function createIndex(
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $key,
|
||||
string $type,
|
||||
array $attributes,
|
||||
array $orders,
|
||||
array $lengths,
|
||||
Database $dbForProject,
|
||||
callable $getDatabasesDB,
|
||||
EventDatabase $queueForDatabase,
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization,
|
||||
): Document {
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getGrandParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
|
||||
$count = $dbForProject->count('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$db->getSequence()]),
|
||||
], 61);
|
||||
|
||||
$dbForDatabases = $getDatabasesDB($db);
|
||||
|
||||
if ($count >= $dbForDatabases->getLimitForIndexes()) {
|
||||
throw new Exception($this->getLimitException(), params: [$collectionId]);
|
||||
}
|
||||
|
||||
$oldAttributes = \array_map(
|
||||
fn ($a) => $a->getArrayCopy(),
|
||||
$collection->getAttribute('attributes')
|
||||
);
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'status' => 'available',
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
];
|
||||
$oldAttributes[] = [
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0,
|
||||
];
|
||||
$oldAttributes[] = [
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0,
|
||||
];
|
||||
|
||||
$contextType = $this->getParentContext();
|
||||
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
$attributeIndex = \array_search($attribute, \array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
throw new Exception($this->getParentUnknownException(), params: [$attribute]);
|
||||
}
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
if ($attributeStatus !== 'available') {
|
||||
throw new Exception($this->getParentNotAvailableException(), params: [$oldAttributes[$attributeIndex]['key']]);
|
||||
}
|
||||
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index = new Document([
|
||||
'$id' => ID::custom($db->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'processing',
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $databaseId,
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collectionId,
|
||||
'type' => $type,
|
||||
'attributes' => $attributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$collection->getAttribute('indexes'),
|
||||
$dbForDatabases->getAdapter()->getMaxIndexLength(),
|
||||
$dbForDatabases->getAdapter()->getInternalIndexesKeys(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndexArray(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(),
|
||||
$dbForDatabases->getAdapter()->getSupportForVectors(),
|
||||
$dbForDatabases->getAdapter()->getSupportForAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObjectIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTrigramIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForUniqueIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForFulltextIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTTLIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObject()
|
||||
);
|
||||
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception($this->getInvalidTypeException(), $validator->getDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
$index = $dbForProject->createDocument('indexes', $index);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException(), params: [$key]);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_CREATE_INDEX)
|
||||
->setDatabase($db);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setTable($collection)
|
||||
->setRow($index);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('indexId', $index->getId())
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-168
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -12,12 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Index as IndexValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
@@ -86,170 +80,21 @@ class Create extends Action
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
// table or collection.
|
||||
throw new Exception($this->getGrandParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
|
||||
$count = $dbForProject->count('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$db->getSequence()])
|
||||
], 61);
|
||||
|
||||
$dbForDatabases = $getDatabasesDB($db);
|
||||
|
||||
$limit = $dbForDatabases->getLimitForIndexes();
|
||||
|
||||
if ($count >= $limit) {
|
||||
throw new Exception($this->getLimitException(), params: [$collectionId]);
|
||||
}
|
||||
|
||||
$oldAttributes = \array_map(
|
||||
fn ($a) => $a->getArrayCopy(),
|
||||
$collection->getAttribute('attributes')
|
||||
$index = $this->createIndex(
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$key,
|
||||
$type,
|
||||
$attributes,
|
||||
$orders,
|
||||
$lengths,
|
||||
$dbForProject,
|
||||
$getDatabasesDB,
|
||||
$queueForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization,
|
||||
);
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'status' => 'available',
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => Database::LENGTH_KEY
|
||||
];
|
||||
$oldAttributes[] = [
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0
|
||||
];
|
||||
$oldAttributes[] = [
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0
|
||||
];
|
||||
|
||||
$contextType = $this->getParentContext();
|
||||
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
// find attribute metadata in collection document
|
||||
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
throw new Exception($this->getParentUnknownException(), params: [$attribute]);
|
||||
}
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
if ($attributeStatus !== 'available') {
|
||||
throw new Exception($this->getParentNotAvailableException(), params: [$oldAttributes[$attributeIndex]['key']]);
|
||||
}
|
||||
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
// Because of a bug in MySQL, we cannot create indexes on array attributes for now, otherwise queries break.
|
||||
throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index = new Document([
|
||||
'$id' => ID::custom($db->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'processing', // processing, available, failed, deleting, stuck
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $databaseId,
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collectionId,
|
||||
'type' => $type,
|
||||
'attributes' => $attributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$collection->getAttribute('indexes'),
|
||||
$dbForDatabases->getAdapter()->getMaxIndexLength(),
|
||||
$dbForDatabases->getAdapter()->getInternalIndexesKeys(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndexArray(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(),
|
||||
$dbForDatabases->getAdapter()->getSupportForVectors(),
|
||||
$dbForDatabases->getAdapter()->getSupportForAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObjectIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTrigramIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForUniqueIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForFulltextIndex(),
|
||||
$dbForDatabases->getAdapter()->getSupportForTTLIndexes(),
|
||||
$dbForDatabases->getAdapter()->getSupportForObject()
|
||||
);
|
||||
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception($this->getInvalidTypeException(), $validator->getDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
$index = $dbForProject->createDocument('indexes', $index);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException(), params: [$key]);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_CREATE_INDEX)
|
||||
->setDatabase($db);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setTable($collection)
|
||||
->setRow($index);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('indexId', $index->getId())
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($index, $this->getResponseModel());
|
||||
|
||||
@@ -122,23 +122,11 @@ class Create extends Action
|
||||
throw new Exception(Exception::INSIGHT_CTA_NOT_FOUND);
|
||||
}
|
||||
|
||||
$paramsValidator = $action->getParams()['params']['validator'] ?? null;
|
||||
|
||||
if ($paramsValidator !== null && !$paramsValidator->isValid($params)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $paramsValidator->getDescription());
|
||||
}
|
||||
|
||||
$status = 'succeeded';
|
||||
$resultPayload = new \stdClass();
|
||||
|
||||
$callback = $action->getCallback();
|
||||
|
||||
if (!\is_callable($callback)) {
|
||||
throw new Exception(Exception::INSIGHT_CTA_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $callback(
|
||||
$result = $action->execute(
|
||||
$params,
|
||||
$insight,
|
||||
$project,
|
||||
@@ -146,9 +134,9 @@ class Create extends Action
|
||||
$getDatabasesDB,
|
||||
$queueForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
$authorization,
|
||||
);
|
||||
$resultPayload = $result instanceof Document ? $result->getArrayCopy() : (array) $result;
|
||||
$resultPayload = $result->getArrayCopy();
|
||||
} catch (Exception $e) {
|
||||
if ($e->getType() === Exception::GENERAL_NOT_IMPLEMENTED) {
|
||||
throw $e;
|
||||
|
||||
Reference in New Issue
Block a user