From 644ed827aace63cbdf8c6c64a3998c11b43e3383 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 5 Aug 2025 20:33:18 +1200 Subject: [PATCH 1/7] Merge pull request #648 from utopia-php/fix-dec-min-0 Fix min check --- src/Database/Database.php | 4 ++-- tests/e2e/Adapter/Scopes/DocumentTests.php | 28 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 3cba68080..ca9ccdabc 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5178,7 +5178,7 @@ public function increaseDocumentAttribute( } } - if ($max && ($document->getAttribute($attribute) + $value > $max)) { + if (!\is_null($max) && ($document->getAttribute($attribute) + $value > $max)) { throw new LimitException('Attribute value exceeds maximum limit: ' . $max); } @@ -5277,7 +5277,7 @@ public function decreaseDocumentAttribute( } } - if ($min && ($document->getAttribute($attribute) - $value < $min)) { + if (!\is_null($min) && ($document->getAttribute($attribute) - $value < $min)) { throw new LimitException('Attribute value exceeds minimum limit: ' . $min); } diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 50bbcba57..445d7b21a 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -11,6 +11,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Type as TypeException; use Utopia\Database\Helpers\ID; @@ -1041,8 +1042,31 @@ public function testDecreaseLimitMin(Document $document): void /** @var Database $database */ $database = static::getDatabase(); - $this->expectException(Exception::class); - $this->assertEquals(false, $database->decreaseDocumentAttribute('increase_decrease', $document->getId(), 'decrease', 10, 99)); + try { + $database->decreaseDocumentAttribute( + 'increase_decrease', + $document->getId(), + 'decrease', + 10, + 99 + ); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(LimitException::class, $e); + } + + try { + $database->decreaseDocumentAttribute( + 'increase_decrease', + $document->getId(), + 'decrease', + 1000, + 0 + ); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(LimitException::class, $e); + } } /** From 72c2a9c185f0f606e4792913a071f744cca21d42 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 5 Aug 2025 21:38:25 +1200 Subject: [PATCH 2/7] Revert "Merge pull request #627 from utopia-php/disable-array-index-mysql" This reverts commit 83278d663f9c63c25a11d9e71c7922da7ec48636, reversing changes made to eb2f759020bba617e99dd67973a9bd949b47f54e. --- src/Database/Adapter.php | 21 ++--- src/Database/Adapter/MySQL.php | 13 ---- src/Database/Adapter/Pool.php | 15 ++-- src/Database/Adapter/SQL.php | 5 -- src/Database/Database.php | 8 -- tests/e2e/Adapter/Scopes/AttributeTests.php | 82 +++++++++----------- tests/e2e/Adapter/Scopes/CollectionTests.php | 36 ++++----- tests/e2e/Adapter/Scopes/IndexTests.php | 4 +- 8 files changed, 64 insertions(+), 120 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index c4579715f..88fd7d64f 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -892,20 +892,6 @@ abstract public function getSupportForSchemaAttributes(): bool; */ abstract public function getSupportForIndex(): bool; - /** - * Is indexing array supported? - * - * @return bool - */ - abstract public function getSupportForIndexArray(): bool; - - /** - * Is cast index as array supported? - * - * @return bool - */ - abstract public function getSupportForCastIndexArray(): bool; - /** * Is unique index supported? * @@ -979,6 +965,13 @@ abstract public function getSupportForAttributeResizing(): bool; */ abstract public function getSupportForGetConnectionId(): bool; + /** + * Is cast index as array supported? + * + * @return bool + */ + abstract public function getSupportForCastIndexArray(): bool; + /** * Is upserting supported? * diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index e222930b0..b803dd74b 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -78,21 +78,8 @@ public function getSizeOfCollectionOnDisk(string $collection): int return $size; } - public function getSupportForIndexArray(): bool - { - /** - * Disabling index creation due to Mysql bug - * @link https://bugs.mysql.com/bug.php?id=111037 - */ - return false; - } - public function getSupportForCastIndexArray(): bool { - if (!$this->getSupportForIndexArray()) { - return false; - } - return true; } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 0a4e59018..302338aa9 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -335,16 +335,6 @@ public function getSupportForIndex(): bool return $this->delegate(__FUNCTION__, \func_get_args()); } - public function getSupportForIndexArray(): bool - { - return $this->delegate(__FUNCTION__, \func_get_args()); - } - - public function getSupportForCastIndexArray(): bool - { - return $this->delegate(__FUNCTION__, \func_get_args()); - } - public function getSupportForUniqueIndex(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); @@ -400,6 +390,11 @@ public function getSupportForGetConnectionId(): bool return $this->delegate(__FUNCTION__, \func_get_args()); } + public function getSupportForCastIndexArray(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + public function getSupportForUpserts(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 7b45fc102..31bc7e6a3 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1374,11 +1374,6 @@ public function getSupportForQueryContains(): bool */ abstract public function getSupportForJSONOverlaps(): bool; - public function getSupportForIndexArray(): bool - { - return true; - } - public function getSupportForCastIndexArray(): bool { return false; diff --git a/src/Database/Database.php b/src/Database/Database.php index ca9ccdabc..816750fb4 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1242,10 +1242,6 @@ public function createCollection(string $id, array $attributes = [], array $inde $isArray = $collectionAttribute->getAttribute('array', false); if ($isArray) { - if (!$this->adapter->getSupportForIndexArray()) { - throw new IndexException('Indexing an array attribute is not supported'); - } - if ($this->adapter->getMaxIndexLength() > 0) { $lengths[$i] = self::ARRAY_INDEX_LENGTH; } @@ -3079,10 +3075,6 @@ public function createIndex(string $collection, string $id, string $type, array $isArray = $collectionAttribute->getAttribute('array', false); if ($isArray) { - if (!$this->adapter->getSupportForIndexArray()) { - throw new IndexException('Indexing an array attribute is not supported'); - } - if ($this->adapter->getMaxIndexLength() > 0) { $lengths[$i] = self::ARRAY_INDEX_LENGTH; } diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index 65747cb9d..fa401db2a 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -1399,14 +1399,12 @@ public function testArrayAttribute(): void $this->assertEquals('Antony', $document->getAttribute('names')[1]); $this->assertEquals(100, $document->getAttribute('numbers')[1]); - if ($database->getAdapter()->getSupportForIndexArray()) { - /** - * functional index dependency cannot be dropped or rename - */ - $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); - } + /** + * functional index dependency cannot be dropped or rename + */ + $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); - if ($database->getAdapter()->getSupportForCastIndexArray()) { + if ($this->getDatabase()->getAdapter()->getSupportForCastIndexArray()) { /** * Delete attribute */ @@ -1445,24 +1443,22 @@ public function testArrayAttribute(): void $this->assertTrue($database->deleteAttribute($collection, 'cards_new')); } - if ($database->getAdapter()->getSupportForIndexArray()) { - try { - $database->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - if ($database->getAdapter()->getSupportForFulltextIndex()) { - $this->assertEquals('"Fulltext" index is forbidden on array attributes', $e->getMessage()); - } else { - $this->assertEquals('Fulltext index is not supported', $e->getMessage()); - } + try { + $database->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + if ($this->getDatabase()->getAdapter()->getSupportForFulltextIndex()) { + $this->assertEquals('"Fulltext" index is forbidden on array attributes', $e->getMessage()); + } else { + $this->assertEquals('Fulltext index is not supported', $e->getMessage()); } + } - try { - $database->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names'], [100,100]); - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - $this->assertEquals('An index may only contain one array attribute', $e->getMessage()); - } + try { + $database->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names'], [100,100]); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertEquals('An index may only contain one array attribute', $e->getMessage()); } $this->assertEquals(true, $database->createAttribute( @@ -1474,36 +1470,32 @@ public function testArrayAttribute(): void array: true )); - if ($database->getAdapter()->getSupportForIndexArray()) { - - - if ($database->getAdapter()->getMaxIndexLength() > 0) { - // If getMaxIndexLength() > 0 We clear length for array attributes - $database->createIndex($collection, 'indx1', Database::INDEX_KEY, ['long_size'], [], []); - $database->createIndex($collection, 'indx2', Database::INDEX_KEY, ['long_size'], [1000], []); - - try { - $database->createIndex($collection, 'indx_numbers', Database::INDEX_KEY, ['tv_show', 'numbers'], [], []); // [700, 255] - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - $this->assertEquals('Index length is longer than the maximum: ' . $database->getAdapter()->getMaxIndexLength(), $e->getMessage()); - } - } - - // We clear orders for array attributes - $database->createIndex($collection, 'indx3', Database::INDEX_KEY, ['names'], [255], ['desc']); + if ($database->getAdapter()->getMaxIndexLength() > 0) { + // If getMaxIndexLength() > 0 We clear length for array attributes + $database->createIndex($collection, 'indx1', Database::INDEX_KEY, ['long_size'], [], []); + $database->createIndex($collection, 'indx2', Database::INDEX_KEY, ['long_size'], [1000], []); try { - $database->createIndex($collection, 'indx4', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); + $database->createIndex($collection, 'indx_numbers', Database::INDEX_KEY, ['tv_show', 'numbers'], [], []); // [700, 255] $this->fail('Failed to throw exception'); } catch (Throwable $e) { - $this->assertEquals('Cannot set a length on "integer" attributes', $e->getMessage()); + $this->assertEquals('Index length is longer than the maximum: ' . $database->getAdapter()->getMaxIndexLength(), $e->getMessage()); } + } + + // We clear orders for array attributes + $database->createIndex($collection, 'indx3', Database::INDEX_KEY, ['names'], [255], ['desc']); - $this->assertTrue($database->createIndex($collection, 'indx6', Database::INDEX_KEY, ['age', 'names'], [null, 999], [])); - $this->assertTrue($database->createIndex($collection, 'indx7', Database::INDEX_KEY, ['age', 'booleans'], [0, 999], [])); + try { + $database->createIndex($collection, 'indx4', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertEquals('Cannot set a length on "integer" attributes', $e->getMessage()); } + $this->assertTrue($database->createIndex($collection, 'indx6', Database::INDEX_KEY, ['age', 'names'], [null, 999], [])); + $this->assertTrue($database->createIndex($collection, 'indx7', Database::INDEX_KEY, ['age', 'booleans'], [0, 999], [])); + if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { try { $database->find($collection, [ diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 041133675..731525f81 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -628,7 +628,6 @@ public function testRowSizeToLarge(): void public function testCreateCollectionWithSchemaIndexes(): void { - /** @var Database $database */ $database = static::getDatabase(); $attributes = [ @@ -651,6 +650,13 @@ public function testCreateCollectionWithSchemaIndexes(): void ]; $indexes = [ + new Document([ + '$id' => ID::custom('idx_cards'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['cards'], + 'lengths' => [500], // Will be changed to Database::ARRAY_INDEX_LENGTH (255) + 'orders' => [Database::ORDER_DESC], + ]), new Document([ '$id' => ID::custom('idx_username'), 'type' => Database::INDEX_KEY, @@ -667,16 +673,6 @@ public function testCreateCollectionWithSchemaIndexes(): void ]), ]; - if ($database->getAdapter()->getSupportForIndexArray()) { - $indexes[] = new Document([ - '$id' => ID::custom('idx_cards'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['cards'], - 'lengths' => [500], // Will be changed to Database::ARRAY_INDEX_LENGTH (255) - 'orders' => [Database::ORDER_DESC], - ]); - } - $collection = $database->createCollection( 'collection98', $attributes, @@ -686,18 +682,16 @@ public function testCreateCollectionWithSchemaIndexes(): void ] ); - $this->assertEquals($collection->getAttribute('indexes')[0]['attributes'][0], 'username'); - $this->assertEquals($collection->getAttribute('indexes')[0]['lengths'][0], null); + $this->assertEquals($collection->getAttribute('indexes')[0]['attributes'][0], 'cards'); + $this->assertEquals($collection->getAttribute('indexes')[0]['lengths'][0], Database::ARRAY_INDEX_LENGTH); + $this->assertEquals($collection->getAttribute('indexes')[0]['orders'][0], null); $this->assertEquals($collection->getAttribute('indexes')[1]['attributes'][0], 'username'); - $this->assertEquals($collection->getAttribute('indexes')[1]['lengths'][0], 99); - $this->assertEquals($collection->getAttribute('indexes')[1]['orders'][0], Database::ORDER_DESC); + $this->assertEquals($collection->getAttribute('indexes')[1]['lengths'][0], null); - if ($database->getAdapter()->getSupportForIndexArray()) { - $this->assertEquals($collection->getAttribute('indexes')[2]['attributes'][0], 'cards'); - $this->assertEquals($collection->getAttribute('indexes')[2]['lengths'][0], Database::ARRAY_INDEX_LENGTH); - $this->assertEquals($collection->getAttribute('indexes')[2]['orders'][0], null); - } + $this->assertEquals($collection->getAttribute('indexes')[2]['attributes'][0], 'username'); + $this->assertEquals($collection->getAttribute('indexes')[2]['lengths'][0], 99); + $this->assertEquals($collection->getAttribute('indexes')[2]['orders'][0], Database::ORDER_DESC); } public function testCollectionUpdate(): Document @@ -1039,7 +1033,6 @@ public function testSharedTables(): void /** * Default mode already tested, we'll test 'schema' and 'table' isolation here */ - /** @var Database $database */ $database = static::getDatabase(); $sharedTables = $database->getSharedTables(); $namespace = $database->getNamespace(); @@ -1243,7 +1236,6 @@ public function testCreateDuplicates(): void } public function testSharedTablesDuplicates(): void { - /** @var Database $database */ $database = static::getDatabase(); $sharedTables = $database->getSharedTables(); $namespace = $database->getNamespace(); diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index 72bb16904..f9ae46075 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -28,9 +28,7 @@ public function testCreateIndex(): void * Check ticks sounding cast index for reserved words */ $database->createAttribute('indexes', 'int', Database::VAR_INTEGER, 8, false, array:true); - if ($database->getAdapter()->getSupportForIndexArray()) { - $database->createIndex('indexes', 'indx8711', Database::INDEX_KEY, ['int'], [255]); - } + $database->createIndex('indexes', 'indx8711', Database::INDEX_KEY, ['int'], [255]); $database->createAttribute('indexes', 'name', Database::VAR_STRING, 10, false); From 43aaba72b5699a06351b25924c3417ff83e6bb4b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 11 Aug 2025 20:03:03 +1200 Subject: [PATCH 3/7] Merge pull request #657 from utopia-php/feat/skipfilters-optional-list Feat/skipfilters optional list --- src/Database/Database.php | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 816750fb4..7d4b99cee 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -344,6 +344,11 @@ class Database protected bool $filter = true; + /** + * @var array|null + */ + protected ?array $disabledFilters = []; + protected bool $validate = true; protected bool $preserveDates = false; @@ -809,17 +814,35 @@ public function disableFilters(): static * * @template T * @param callable(): T $callback + * @param array|null $filters * @return T */ - public function skipFilters(callable $callback): mixed + public function skipFilters(callable $callback, ?array $filters = null): mixed { - $initial = $this->filter; - $this->disableFilters(); + if (empty($filters)) { + $initial = $this->filter; + $this->disableFilters(); + + try { + return $callback(); + } finally { + $this->filter = $initial; + } + } + + $previous = $this->filter; + $previousDisabled = $this->disabledFilters; + $disabled = []; + foreach ($filters as $name) { + $disabled[$name] = true; + } + $this->disabledFilters = $disabled; try { return $callback(); } finally { - $this->filter = $initial; + $this->filter = $previous; + $this->disabledFilters = $previousDisabled; } } @@ -6560,6 +6583,10 @@ protected function decodeAttribute(string $filter, mixed $value, Document $docum return $value; } + if (!\is_null($this->disabledFilters) && isset($this->disabledFilters[$filter])) { + return $value; + } + if (!array_key_exists($filter, self::$filters) && !array_key_exists($filter, $this->instanceFilters)) { throw new NotFoundException("Filter \"{$filter}\" not found for attribute \"{$attribute}\""); } From 7bed756217116425e7ed2e648345b8d2f03e9620 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:08:33 +1200 Subject: [PATCH 4/7] Invalid argument for invalid value --- src/Database/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 7d4b99cee..ca1583fe5 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5149,7 +5149,7 @@ public function increaseDocumentAttribute( int|float|null $max = null ): Document { if ($value <= 0) { // Can be a float - throw new DatabaseException('Value must be numeric and greater than 0'); + throw new \InvalidArgumentException('Value must be numeric and greater than 0'); } $collection = $this->silent(fn () => $this->getCollection($collection)); @@ -5246,7 +5246,7 @@ public function decreaseDocumentAttribute( int|float|null $min = null ): Document { if ($value <= 0) { // Can be a float - throw new DatabaseException('Value must be numeric and greater than 0'); + throw new \InvalidArgumentException('Value must be numeric and greater than 0'); } $collection = $this->silent(fn () => $this->getCollection($collection)); From 6b1054eb5840a962b4d033c42b3b1c7981ad7377 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:11:45 +1200 Subject: [PATCH 5/7] Reapply "Merge pull request #627 from utopia-php/disable-array-index-mysql" This reverts commit 72c2a9c185f0f606e4792913a071f744cca21d42. --- src/Database/Adapter.php | 21 +++-- src/Database/Adapter/MySQL.php | 13 ++++ src/Database/Adapter/Pool.php | 15 ++-- src/Database/Adapter/SQL.php | 5 ++ src/Database/Database.php | 8 ++ tests/e2e/Adapter/Scopes/AttributeTests.php | 82 +++++++++++--------- tests/e2e/Adapter/Scopes/CollectionTests.php | 36 +++++---- tests/e2e/Adapter/Scopes/IndexTests.php | 4 +- 8 files changed, 120 insertions(+), 64 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 88fd7d64f..c4579715f 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -892,6 +892,20 @@ abstract public function getSupportForSchemaAttributes(): bool; */ abstract public function getSupportForIndex(): bool; + /** + * Is indexing array supported? + * + * @return bool + */ + abstract public function getSupportForIndexArray(): bool; + + /** + * Is cast index as array supported? + * + * @return bool + */ + abstract public function getSupportForCastIndexArray(): bool; + /** * Is unique index supported? * @@ -965,13 +979,6 @@ abstract public function getSupportForAttributeResizing(): bool; */ abstract public function getSupportForGetConnectionId(): bool; - /** - * Is cast index as array supported? - * - * @return bool - */ - abstract public function getSupportForCastIndexArray(): bool; - /** * Is upserting supported? * diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index b803dd74b..e222930b0 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -78,8 +78,21 @@ public function getSizeOfCollectionOnDisk(string $collection): int return $size; } + public function getSupportForIndexArray(): bool + { + /** + * Disabling index creation due to Mysql bug + * @link https://bugs.mysql.com/bug.php?id=111037 + */ + return false; + } + public function getSupportForCastIndexArray(): bool { + if (!$this->getSupportForIndexArray()) { + return false; + } + return true; } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 302338aa9..0a4e59018 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -335,6 +335,16 @@ public function getSupportForIndex(): bool return $this->delegate(__FUNCTION__, \func_get_args()); } + public function getSupportForIndexArray(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function getSupportForCastIndexArray(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + public function getSupportForUniqueIndex(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); @@ -390,11 +400,6 @@ public function getSupportForGetConnectionId(): bool return $this->delegate(__FUNCTION__, \func_get_args()); } - public function getSupportForCastIndexArray(): bool - { - return $this->delegate(__FUNCTION__, \func_get_args()); - } - public function getSupportForUpserts(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 31bc7e6a3..7b45fc102 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1374,6 +1374,11 @@ public function getSupportForQueryContains(): bool */ abstract public function getSupportForJSONOverlaps(): bool; + public function getSupportForIndexArray(): bool + { + return true; + } + public function getSupportForCastIndexArray(): bool { return false; diff --git a/src/Database/Database.php b/src/Database/Database.php index ca1583fe5..6c44be72c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1265,6 +1265,10 @@ public function createCollection(string $id, array $attributes = [], array $inde $isArray = $collectionAttribute->getAttribute('array', false); if ($isArray) { + if (!$this->adapter->getSupportForIndexArray()) { + throw new IndexException('Indexing an array attribute is not supported'); + } + if ($this->adapter->getMaxIndexLength() > 0) { $lengths[$i] = self::ARRAY_INDEX_LENGTH; } @@ -3098,6 +3102,10 @@ public function createIndex(string $collection, string $id, string $type, array $isArray = $collectionAttribute->getAttribute('array', false); if ($isArray) { + if (!$this->adapter->getSupportForIndexArray()) { + throw new IndexException('Indexing an array attribute is not supported'); + } + if ($this->adapter->getMaxIndexLength() > 0) { $lengths[$i] = self::ARRAY_INDEX_LENGTH; } diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index fa401db2a..65747cb9d 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -1399,12 +1399,14 @@ public function testArrayAttribute(): void $this->assertEquals('Antony', $document->getAttribute('names')[1]); $this->assertEquals(100, $document->getAttribute('numbers')[1]); - /** - * functional index dependency cannot be dropped or rename - */ - $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); + if ($database->getAdapter()->getSupportForIndexArray()) { + /** + * functional index dependency cannot be dropped or rename + */ + $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); + } - if ($this->getDatabase()->getAdapter()->getSupportForCastIndexArray()) { + if ($database->getAdapter()->getSupportForCastIndexArray()) { /** * Delete attribute */ @@ -1443,22 +1445,24 @@ public function testArrayAttribute(): void $this->assertTrue($database->deleteAttribute($collection, 'cards_new')); } - try { - $database->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - if ($this->getDatabase()->getAdapter()->getSupportForFulltextIndex()) { - $this->assertEquals('"Fulltext" index is forbidden on array attributes', $e->getMessage()); - } else { - $this->assertEquals('Fulltext index is not supported', $e->getMessage()); + if ($database->getAdapter()->getSupportForIndexArray()) { + try { + $database->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + if ($database->getAdapter()->getSupportForFulltextIndex()) { + $this->assertEquals('"Fulltext" index is forbidden on array attributes', $e->getMessage()); + } else { + $this->assertEquals('Fulltext index is not supported', $e->getMessage()); + } } - } - try { - $database->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names'], [100,100]); - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - $this->assertEquals('An index may only contain one array attribute', $e->getMessage()); + try { + $database->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names'], [100,100]); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertEquals('An index may only contain one array attribute', $e->getMessage()); + } } $this->assertEquals(true, $database->createAttribute( @@ -1470,32 +1474,36 @@ public function testArrayAttribute(): void array: true )); - if ($database->getAdapter()->getMaxIndexLength() > 0) { - // If getMaxIndexLength() > 0 We clear length for array attributes - $database->createIndex($collection, 'indx1', Database::INDEX_KEY, ['long_size'], [], []); - $database->createIndex($collection, 'indx2', Database::INDEX_KEY, ['long_size'], [1000], []); + if ($database->getAdapter()->getSupportForIndexArray()) { + + + if ($database->getAdapter()->getMaxIndexLength() > 0) { + // If getMaxIndexLength() > 0 We clear length for array attributes + $database->createIndex($collection, 'indx1', Database::INDEX_KEY, ['long_size'], [], []); + $database->createIndex($collection, 'indx2', Database::INDEX_KEY, ['long_size'], [1000], []); + + try { + $database->createIndex($collection, 'indx_numbers', Database::INDEX_KEY, ['tv_show', 'numbers'], [], []); // [700, 255] + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertEquals('Index length is longer than the maximum: ' . $database->getAdapter()->getMaxIndexLength(), $e->getMessage()); + } + } + + // We clear orders for array attributes + $database->createIndex($collection, 'indx3', Database::INDEX_KEY, ['names'], [255], ['desc']); try { - $database->createIndex($collection, 'indx_numbers', Database::INDEX_KEY, ['tv_show', 'numbers'], [], []); // [700, 255] + $database->createIndex($collection, 'indx4', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); $this->fail('Failed to throw exception'); } catch (Throwable $e) { - $this->assertEquals('Index length is longer than the maximum: ' . $database->getAdapter()->getMaxIndexLength(), $e->getMessage()); + $this->assertEquals('Cannot set a length on "integer" attributes', $e->getMessage()); } - } - - // We clear orders for array attributes - $database->createIndex($collection, 'indx3', Database::INDEX_KEY, ['names'], [255], ['desc']); - try { - $database->createIndex($collection, 'indx4', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); - $this->fail('Failed to throw exception'); - } catch (Throwable $e) { - $this->assertEquals('Cannot set a length on "integer" attributes', $e->getMessage()); + $this->assertTrue($database->createIndex($collection, 'indx6', Database::INDEX_KEY, ['age', 'names'], [null, 999], [])); + $this->assertTrue($database->createIndex($collection, 'indx7', Database::INDEX_KEY, ['age', 'booleans'], [0, 999], [])); } - $this->assertTrue($database->createIndex($collection, 'indx6', Database::INDEX_KEY, ['age', 'names'], [null, 999], [])); - $this->assertTrue($database->createIndex($collection, 'indx7', Database::INDEX_KEY, ['age', 'booleans'], [0, 999], [])); - if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { try { $database->find($collection, [ diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 731525f81..041133675 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -628,6 +628,7 @@ public function testRowSizeToLarge(): void public function testCreateCollectionWithSchemaIndexes(): void { + /** @var Database $database */ $database = static::getDatabase(); $attributes = [ @@ -650,13 +651,6 @@ public function testCreateCollectionWithSchemaIndexes(): void ]; $indexes = [ - new Document([ - '$id' => ID::custom('idx_cards'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['cards'], - 'lengths' => [500], // Will be changed to Database::ARRAY_INDEX_LENGTH (255) - 'orders' => [Database::ORDER_DESC], - ]), new Document([ '$id' => ID::custom('idx_username'), 'type' => Database::INDEX_KEY, @@ -673,6 +667,16 @@ public function testCreateCollectionWithSchemaIndexes(): void ]), ]; + if ($database->getAdapter()->getSupportForIndexArray()) { + $indexes[] = new Document([ + '$id' => ID::custom('idx_cards'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['cards'], + 'lengths' => [500], // Will be changed to Database::ARRAY_INDEX_LENGTH (255) + 'orders' => [Database::ORDER_DESC], + ]); + } + $collection = $database->createCollection( 'collection98', $attributes, @@ -682,16 +686,18 @@ public function testCreateCollectionWithSchemaIndexes(): void ] ); - $this->assertEquals($collection->getAttribute('indexes')[0]['attributes'][0], 'cards'); - $this->assertEquals($collection->getAttribute('indexes')[0]['lengths'][0], Database::ARRAY_INDEX_LENGTH); - $this->assertEquals($collection->getAttribute('indexes')[0]['orders'][0], null); + $this->assertEquals($collection->getAttribute('indexes')[0]['attributes'][0], 'username'); + $this->assertEquals($collection->getAttribute('indexes')[0]['lengths'][0], null); $this->assertEquals($collection->getAttribute('indexes')[1]['attributes'][0], 'username'); - $this->assertEquals($collection->getAttribute('indexes')[1]['lengths'][0], null); + $this->assertEquals($collection->getAttribute('indexes')[1]['lengths'][0], 99); + $this->assertEquals($collection->getAttribute('indexes')[1]['orders'][0], Database::ORDER_DESC); - $this->assertEquals($collection->getAttribute('indexes')[2]['attributes'][0], 'username'); - $this->assertEquals($collection->getAttribute('indexes')[2]['lengths'][0], 99); - $this->assertEquals($collection->getAttribute('indexes')[2]['orders'][0], Database::ORDER_DESC); + if ($database->getAdapter()->getSupportForIndexArray()) { + $this->assertEquals($collection->getAttribute('indexes')[2]['attributes'][0], 'cards'); + $this->assertEquals($collection->getAttribute('indexes')[2]['lengths'][0], Database::ARRAY_INDEX_LENGTH); + $this->assertEquals($collection->getAttribute('indexes')[2]['orders'][0], null); + } } public function testCollectionUpdate(): Document @@ -1033,6 +1039,7 @@ public function testSharedTables(): void /** * Default mode already tested, we'll test 'schema' and 'table' isolation here */ + /** @var Database $database */ $database = static::getDatabase(); $sharedTables = $database->getSharedTables(); $namespace = $database->getNamespace(); @@ -1236,6 +1243,7 @@ public function testCreateDuplicates(): void } public function testSharedTablesDuplicates(): void { + /** @var Database $database */ $database = static::getDatabase(); $sharedTables = $database->getSharedTables(); $namespace = $database->getNamespace(); diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index f9ae46075..72bb16904 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -28,7 +28,9 @@ public function testCreateIndex(): void * Check ticks sounding cast index for reserved words */ $database->createAttribute('indexes', 'int', Database::VAR_INTEGER, 8, false, array:true); - $database->createIndex('indexes', 'indx8711', Database::INDEX_KEY, ['int'], [255]); + if ($database->getAdapter()->getSupportForIndexArray()) { + $database->createIndex('indexes', 'indx8711', Database::INDEX_KEY, ['int'], [255]); + } $database->createAttribute('indexes', 'name', Database::VAR_STRING, 10, false); From 85f46c331fdac83ca35b78141431592b42ce3bcd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:12:41 +1200 Subject: [PATCH 6/7] Sync --- src/Database/Adapter/MySQL.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index e222930b0..52e4161fb 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -78,15 +78,6 @@ public function getSizeOfCollectionOnDisk(string $collection): int return $size; } - public function getSupportForIndexArray(): bool - { - /** - * Disabling index creation due to Mysql bug - * @link https://bugs.mysql.com/bug.php?id=111037 - */ - return false; - } - public function getSupportForCastIndexArray(): bool { if (!$this->getSupportForIndexArray()) { From 20998e268ec528ebbf06adb77ec4230f70469fa7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Aug 2025 19:15:06 +1200 Subject: [PATCH 7/7] Align --- src/Database/Adapter/MySQL.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index 52e4161fb..e0db5b15c 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -78,6 +78,14 @@ public function getSizeOfCollectionOnDisk(string $collection): int return $size; } + public function getSupportForIndexArray(): bool + { + /** + * @link https://bugs.mysql.com/bug.php?id=111037 + */ + return true; + } + public function getSupportForCastIndexArray(): bool { if (!$this->getSupportForIndexArray()) {