diff --git a/src/Database/Database.php b/src/Database/Database.php index 19c86dcda..49ecee7e4 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -7,6 +7,7 @@ use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; +use Utopia\Database\Exception\Dependency as DependencyException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; @@ -20,6 +21,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Index as IndexValidator; +use Utopia\Database\Validator\IndexDependency as IndexDependencyValidator; use Utopia\Database\Validator\PartialStructure; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Queries\Document as DocumentValidator; @@ -1898,6 +1900,18 @@ public function updateAttribute(string $collection, string $id, ?string $type = }, $index['attributes']); } } + + /** + * Check index dependency if we are changing the key + */ + $validator = new IndexDependencyValidator( + $collectionDoc->getAttribute('indexes', []), + $this->adapter->getSupportForCastIndexArray(), + ); + + if (! $validator->isValid($attribute)) { + throw new DependencyException($validator->getDescription()); + } } /** @@ -1998,6 +2012,17 @@ public function deleteAttribute(string $collection, string $id): bool throw new DatabaseException('Cannot delete relationship as an attribute'); } + if ($this->validate) { + $validator = new IndexDependencyValidator( + $collection->getAttribute('indexes', []), + $this->adapter->getSupportForCastIndexArray(), + ); + + if (! $validator->isValid($attribute)) { + throw new DependencyException($validator->getDescription()); + } + } + foreach ($indexes as $indexKey => $index) { $indexAttributes = $index->getAttribute('attributes', []); @@ -2074,6 +2099,17 @@ public function renameAttribute(string $collection, string $old, string $new): b throw new NotFoundException('Attribute not found'); } + if ($this->validate) { + $validator = new IndexDependencyValidator( + $collection->getAttribute('indexes', []), + $this->adapter->getSupportForCastIndexArray(), + ); + + if (! $validator->isValid($attribute)) { + throw new DependencyException($validator->getDescription()); + } + } + $attribute->setAttribute('$id', $new); $attribute->setAttribute('key', $new); diff --git a/src/Database/Validator/IndexDependency.php b/src/Database/Validator/IndexDependency.php new file mode 100644 index 000000000..7e8453b83 --- /dev/null +++ b/src/Database/Validator/IndexDependency.php @@ -0,0 +1,85 @@ + + */ + protected array $indexes; + + /** + * @param array $indexes + * @param bool $castIndexSupport + */ + public function __construct(array $indexes, bool $castIndexSupport) + { + $this->castIndexSupport = $castIndexSupport; + $this->indexes = $indexes; + } + + /** + * Returns validator description + */ + public function getDescription(): string + { + return $this->message; + } + + /** + * Is valid. + * + * @param Document $value + */ + public function isValid($value): bool + { + if (! $this->castIndexSupport) { + return true; + } + + if (! $value->getAttribute('array', false)) { + return true; + } + + $key = \strtolower($value->getAttribute('key', $value->getAttribute('$id'))); + + foreach ($this->indexes as $index) { + $attributes = $index->getAttribute('attributes', []); + foreach ($attributes as $attribute) { + if ($key === \strtolower($attribute)) { + return false; + } + } + } + + return true; + } + + /** + * Is array + * + * Function will return true if object is array. + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } +} diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 040b2005a..ae6ad4653 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -2958,21 +2958,39 @@ public function testArrayAttribute(): void $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); if ($this->getDatabase()->getAdapter()->getSupportForCastIndexArray()) { + /** + * Delete attribute + */ try { $database->deleteAttribute($collection, 'cards'); $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DependencyException::class, $e); - $this->assertEquals('Attribute cannot be deleted because it is used in an index', $e->getMessage()); + $this->assertEquals("Attribute can't be deleted or renamed because it is used in an index", $e->getMessage()); } + /** + * Rename attribute + */ try { $database->renameAttribute($collection, 'cards', 'cards_new'); $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DependencyException::class, $e); - $this->assertEquals('Attribute cannot be deleted because it is used in an index', $e->getMessage()); + $this->assertEquals("Attribute can't be deleted or renamed because it is used in an index", $e->getMessage()); } + + /** + * Update attribute + */ + try { + $database->updateAttribute($collection, id:'cards', newKey: 'cards_new'); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertInstanceOf(DependencyException::class, $e); + $this->assertEquals("Attribute can't be deleted or renamed because it is used in an index", $e->getMessage()); + } + } else { $this->assertTrue($database->renameAttribute($collection, 'cards', 'cards_new')); $this->assertTrue($database->deleteAttribute($collection, 'cards_new'));