Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
}
}

/**
Expand Down Expand Up @@ -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', []);

Expand Down Expand Up @@ -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);

Expand Down
85 changes: 85 additions & 0 deletions src/Database/Validator/IndexDependency.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Utopia\Database\Validator;

use Utopia\Database\Document;
use Utopia\Validator;

class IndexDependency extends Validator
{
protected string $message = "Attribute can't be deleted or renamed because it is used in an index";

protected bool $castIndexSupport;

/**
* @var array<Document>
*/
protected array $indexes;

/**
* @param array<Document> $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;
}
}
22 changes: 20 additions & 2 deletions tests/e2e/Adapter/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down
Loading