Skip to content

Commit 2132831

Browse files
committed
fix(indexes): fail unique index creation over duplicate rows
Database::createIndex() now rethrows UniqueException instead of swallowing it with the "index already exists" DuplicateException, so a unique index over duplicate rows fails without leaving metadata behind. Memory and Redis throw UniqueException for that case, and oversized index rows map to LimitException on Postgres (54000) and MariaDB/MySQL (1071) instead of a raw PDOException.
1 parent 1c99c21 commit 2132831

5 files changed

Lines changed: 18 additions & 6 deletions

File tree

‎src/Database/Adapter/MariaDB.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,11 @@ protected function processException(PDOException $e): \Exception
18931893
return new DuplicateException('Index already exists', $e->getCode(), $e);
18941894
}
18951895

1896+
// Index key too long
1897+
if ($e->getCode() === '42000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1071) {
1898+
return new LimitException('Index key length exceeds the maximum', $e->getCode(), $e);
1899+
}
1900+
18961901
// Duplicate row
18971902
if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) {
18981903
$key = $this->getViolatedKey($e->getMessage());

‎src/Database/Adapter/Memory.php‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,10 +1005,8 @@ public function createIndex(string $collection, string $id, string $type, array
10051005
$hashTable = [];
10061006
if ($type === Database::INDEX_UNIQUE && ! empty($attributes)) {
10071007
// MariaDB rejects CREATE UNIQUE INDEX with errno 1062 when existing
1008-
// rows contain duplicates; Database::createIndex catches the resulting
1009-
// DuplicateException and treats it as an "orphan index" (the metadata
1010-
// is registered but the physical index is absent). Mirror that contract:
1011-
// throw DuplicateException so callers see identical end-state behavior.
1008+
// rows contain duplicates, which surfaces as UniqueException. Mirror
1009+
// that contract so Database::createIndex fails the same way.
10121010
// Build the hash table while we scan so we can reuse it for fast
10131011
// probes after the index lands — no second pass over the rows.
10141012
foreach ($this->data[$key]['documents'] as $docKey => $row) {
@@ -1026,7 +1024,7 @@ public function createIndex(string $collection, string $id, string $type, array
10261024
}
10271025
$hash = \serialize($signature);
10281026
if (isset($hashTable[$hash])) {
1029-
throw new DuplicateException('Cannot create unique index: existing rows already contain duplicate values');
1027+
throw new UniqueException('Cannot create unique index: existing rows already contain duplicate values');
10301028
}
10311029
$hashTable[$hash] = $docKey;
10321030
}

‎src/Database/Adapter/Postgres.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,6 +2198,11 @@ protected function processException(PDOException $e): \Exception
21982198
return new LimitException('Datetime field overflow', $e->getCode(), $e);
21992199
}
22002200

2201+
// Index row too large
2202+
if ($e->getCode() === '54000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7 && \str_contains($e->getMessage(), 'index row')) {
2203+
return new LimitException('Index row size exceeds the maximum', $e->getCode(), $e);
2204+
}
2205+
22012206
// Unknown table
22022207
if ($e->getCode() === '42P01' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) {
22032208
return new NotFoundException('Collection not found', $e->getCode(), $e);

‎src/Database/Adapter/Redis.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2728,7 +2728,7 @@ public function createIndex(string $collection, string $id, string $type, array
27282728
}
27292729
$hash = \serialize($signature);
27302730
if (isset($seen[$hash])) {
2731-
throw new DuplicateException('Cannot create unique index: existing rows already contain duplicate values');
2731+
throw new UniqueException('Cannot create unique index: existing rows already contain duplicate values');
27322732
}
27332733
$seen[$hash] = true;
27342734
}

‎src/Database/Database.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Utopia\Database\Exception\Structure as StructureException;
2323
use Utopia\Database\Exception\Timeout as TimeoutException;
2424
use Utopia\Database\Exception\Type as TypeException;
25+
use Utopia\Database\Exception\Unique as UniqueException;
2526
use Utopia\Database\Helpers\ID;
2627
use Utopia\Database\Helpers\Permission;
2728
use Utopia\Database\Helpers\Role;
@@ -4830,6 +4831,9 @@ public function createIndex(string $collection, string $id, string $type, array
48304831
if (!$created) {
48314832
throw new DatabaseException('Failed to create index');
48324833
}
4834+
} catch (UniqueException $e) {
4835+
// Existing rows violate the unique constraint, so no index was built.
4836+
throw $e;
48334837
} catch (DuplicateException) {
48344838
// Metadata check (lines above) already verified index is absent
48354839
// from metadata. A DuplicateException from the adapter means the

0 commit comments

Comments
 (0)