From 9975ab3e92e3d4bab9416f01a4cef3dc83ba9414 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Jan 2025 15:11:26 +0200 Subject: [PATCH 1/8] Attributes count limit --- src/Database/Adapter/SQL.php | 72 +++++---- tests/e2e/Adapter/Base.php | 281 +++++++++++++++-------------------- 2 files changed, 168 insertions(+), 185 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 4ab86c32f..e26e66669 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -473,57 +473,77 @@ public static function getDocumentSizeLimit(): int */ public function getAttributeWidth(Document $collection): int { - // Default collection has: - // `_id` int(11) => 4 bytes - // `_uid` char(255) => 1020 (255 bytes * 4 for utf8mb4) - // but this number seems to vary, so we give a +500 byte buffer - $total = 1500; + /** + * @link https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html + * + * `_id` bigint => 8 bytes + * `_uid` varchar(255) => 1021 (4 * 255 + 1) bytes + * `_tenant` int => 4 bytes + * `_createdAt` datetime(3) => 7 bytes + * `_updatedAt` datetime(3) => 7 bytes + * `_permissions` mediumtext => 20 + */ + + $total = 1067; $attributes = $collection->getAttributes()['attributes']; foreach ($attributes as $attribute) { + /** + * Json / Longtext + * only the pointer contributes 20 bytes + * data is stored externally + */ + + if($attribute['array'] ?? false){ + $total += 20; + continue; + } + switch ($attribute['type']) { case Database::VAR_STRING: + /** + * Text / Mediumtext / Longtext + * only the pointer contributes 20 bytes to the row size + * data is stored externally + */ + $total += match (true) { - // 8 bytes length + 4 bytes for LONGTEXT - $attribute['size'] > 16777215 => 12, - // 8 bytes length + 3 bytes for MEDIUMTEXT - $attribute['size'] > 65535 => 11, - // 8 bytes length + 2 bytes for TEXT - $attribute['size'] > $this->getMaxVarcharLength() => 10, - // $size = $size * 4; // utf8mb4 up to 4 bytes per char - // 8 bytes length + 2 bytes for VARCHAR(>255) - $attribute['size'] > 255 => ($attribute['size'] * 4) + 2, - // $size = $size * 4; // utf8mb4 up to 4 bytes per char - // 8 bytes length + 1 bytes for VARCHAR(<=255) - default => ($attribute['size'] * 4) + 1, + $attribute['size'] > $this->getMaxVarcharLength() => 20, + $attribute['size'] > 255 => $attribute['size'] * 4 + 2, // VARCHAR(>255) + 2 length + default => $attribute['size'] * 4 + 1, // VARCHAR(<=255) + 1 length }; + break; case Database::VAR_INTEGER: if ($attribute['size'] >= 8) { - $total += 8; // BIGINT takes 8 bytes + $total += 8; // BIGINT 8 bytes } else { - $total += 4; // INT takes 4 bytes + $total += 4; // INT 4 bytes } break; + case Database::VAR_FLOAT: - // DOUBLE takes 8 bytes - $total += 8; + $total += 8; // DOUBLE 8 bytes break; case Database::VAR_BOOLEAN: - // TINYINT(1) takes one byte - $total += 1; + $total += 1; // TINYINT(1) 1 bytes break; case Database::VAR_RELATIONSHIP: - // VARCHAR(255) - $total += Database::LENGTH_KEY * 4 + 2; + $total += Database::LENGTH_KEY * 4 + 1; // VARCHAR(<=255) break; case Database::VAR_DATETIME: - $total += 19; // 2022-06-26 14:46:24 + /** + * 1 byte year + month + * 1 byte for the day + * 3 bytes for the hour, minute, and second + * 2 bytes miliseconds DATETIME(3) + */ + $total += 7; break; default: throw new DatabaseException('Unknown type: ' . $attribute['type']); diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index ad117acd7..5dc0b6d26 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -93,6 +93,84 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } + public function testWidthLimit(): void + { + if (static::getDatabase()->getAdapter()::getDocumentSizeLimit() === 0) { + $this->expectNotToPerformAssertions(); + return; + } + + $collection = static::getDatabase()->createCollection('width_limit'); + + $init = static::getDatabase()->getAdapter()->getAttributeWidth($collection); + $this->assertEquals(1067, $init); + + $attribute = new Document([ + '$id' => ID::custom('varchar_100'), + 'type' => Database::VAR_STRING, + 'size' => 100, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(401, $res - $init); // 100 * 4 + 1 (length) + + $attribute = new Document([ + '$id' => ID::custom('json'), + 'type' => Database::VAR_STRING, + 'size' => 100, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => true, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(20, $res - $init); // Pointer of Json / Longtext (mariaDB) + + $attribute = new Document([ + '$id' => ID::custom('text'), + 'type' => Database::VAR_STRING, + 'size' => 20000, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(20, $res - $init); + + $attribute = new Document([ + '$id' => ID::custom('bigint'), + 'type' => Database::VAR_INTEGER, + 'size' => 8, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(8, $res - $init); + + $attribute = new Document([ + '$id' => ID::custom('date'), + 'type' => Database::VAR_DATETIME, + 'size' => 8, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(7, $res - $init); + } + /** * @throws LimitException * @throws DuplicateException @@ -5397,188 +5475,73 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void } } - public function testExceptionAttributeLimit(): void + public function testLimitForAttributes(): void { - if ($this->getDatabase()->getLimitForAttributes() > 0) { - // Load the collection up to the limit - $attributes = []; - for ($i = 0; $i < $this->getDatabase()->getLimitForAttributes(); $i++) { - $attributes[] = new Document([ - '$id' => ID::custom("test{$i}"), - 'type' => Database::VAR_INTEGER, - 'size' => 0, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - } - - static::getDatabase()->createCollection('attributeLimit', $attributes); - - $this->expectException(LimitException::class); - $this->assertEquals(false, static::getDatabase()->createAttribute('attributeLimit', "breaking", Database::VAR_INTEGER, 0, true)); + if (static::getDatabase()->getAdapter()->getLimitForAttributes() === 0) { + $this->expectNotToPerformAssertions(); + return; } - // Default assertion for other adapters - $this->assertEquals(1, 1); - } + $attributes = []; - /** - * @depends testExceptionAttributeLimit - */ - public function testCheckAttributeCountLimit(): void - { - if ($this->getDatabase()->getLimitForAttributes() > 0) { - $collection = static::getDatabase()->getCollection('attributeLimit'); + $limit = static::getDatabase()->getAdapter()->getLimitForAttributes() - 7; // Internal attributes + $limit =2000; - // create same attribute in testExceptionAttributeLimit - $attribute = new Document([ - '$id' => ID::custom('breaking'), + for ($i = 0; $i < $limit; $i++) { + $attributes[] = new Document([ + '$id' => ID::custom("attr_{$i}"), 'type' => Database::VAR_INTEGER, 'size' => 0, - 'required' => true, + 'required' => false, 'default' => null, 'signed' => true, 'array' => false, 'filters' => [], ]); - - $this->expectException(LimitException::class); - $this->assertEquals(false, static::getDatabase()->checkAttribute($collection, $attribute)); } - // Default assertion for other adapters - $this->assertEquals(1, 1); - } - - /** - * Using phpunit dataProviders to check that all these combinations of types/sizes throw exceptions - * https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers - * - * @return array> - */ - public function rowWidthExceedsMaximum(): array - { - return [ - // These combinations of attributes gets exactly to the 64k limit - // [$key, $stringSize, $stringCount, $intCount, $floatCount, $boolCount] - // [0, 1024, 15, 0, 731, 3], - // [1, 512, 31, 0, 0, 833], - // [2, 256, 62, 128, 0, 305], - // [3, 128, 125, 30, 24, 2], - // - // Taken 500 bytes off for tests - [0, 1024, 15, 0, 304, 3], - [1, 512, 31, 0, 0, 333], - [2, 256, 62, 103, 0, 5], - [3, 128, 124, 30, 12, 14], - ]; - } - - /** - * @dataProvider rowWidthExceedsMaximum - */ - public function testExceptionWidthLimit(int $key, int $stringSize, int $stringCount, int $intCount, int $floatCount, int $boolCount): void - { - if (static::getDatabase()->getAdapter()::getDocumentSizeLimit() > 0) { - $attributes = []; - - // Load the collection up to the limit - // Strings - for ($i = 0; $i < $stringCount; $i++) { - $attributes[] = new Document([ - '$id' => ID::custom("test_string{$i}"), - 'type' => Database::VAR_STRING, - 'size' => $stringSize, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - } + try { + static::getDatabase()->createCollection("attributes_limit", $attributes); + $this->fail('Failed to throw exception'); + } catch (\Exception $e) { + $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Attribute limit of 1017 exceeded. Cannot create collection.', $e->getMessage()); + } - // Integers - for ($i = 0; $i < $intCount; $i++) { - $attributes[] = new Document([ - '$id' => ID::custom("test_int{$i}"), - 'type' => Database::VAR_INTEGER, - 'size' => 0, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - } + /** + * Remove last attribute + */ - // Floats - for ($i = 0; $i < $floatCount; $i++) { - $attributes[] = new Document([ - '$id' => ID::custom("test_float{$i}"), - 'type' => Database::VAR_FLOAT, - 'size' => 0, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - } + array_pop($attributes); - // Booleans - for ($i = 0; $i < $boolCount; $i++) { - $attributes[] = new Document([ - '$id' => ID::custom("test_bool{$i}"), - 'type' => Database::VAR_BOOLEAN, - 'size' => 0, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - } + $collection = static::getDatabase()->createCollection("attributes_limit", $attributes); - $collection = static::getDatabase()->createCollection("widthLimit{$key}", $attributes); + $attribute = new Document([ + '$id' => ID::custom('breaking'), + 'type' => Database::VAR_STRING, + 'size' => 100, + 'required' => true, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); - $this->expectException(LimitException::class); - $this->assertEquals(false, static::getDatabase()->createAttribute("widthLimit{$key}", "breaking", Database::VAR_STRING, 100, true)); + try { + static::getDatabase()->checkAttribute($collection, $attribute); + $this->fail('Failed to throw exception'); + } catch (\Exception $e) { + $this->assertInstanceOf(LimitException::class, $e); } - // Default assertion for other adapters - $this->assertEquals(1, 1); - } - - /** - * @dataProvider rowWidthExceedsMaximum - * @depends testExceptionWidthLimit - */ - public function testCheckAttributeWidthLimit(int $key, int $stringSize, int $stringCount, int $intCount, int $floatCount, int $boolCount): void - { - if (static::getDatabase()->getAdapter()::getDocumentSizeLimit() > 0) { - $collection = static::getDatabase()->getCollection("widthLimit{$key}"); - - // create same attribute in testExceptionWidthLimit - $attribute = new Document([ - '$id' => ID::custom('breaking'), - 'type' => Database::VAR_STRING, - 'size' => 100, - 'required' => true, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - - $this->expectException(LimitException::class); - $this->assertEquals(false, static::getDatabase()->checkAttribute($collection, $attribute)); + try { + static::getDatabase()->createAttribute($collection->getId(), 'breaking', Database::VAR_STRING, 100, true); + $this->fail('Failed to throw exception'); + } catch (\Exception $e) { + $this->assertInstanceOf(LimitException::class, $e); } - // Default assertion for other adapters - $this->assertEquals(1, 1); + $this->assertEquals('shmuel', 'fogel'); } public function testExceptionIndexLimit(): void From fd82b7c199bf7beb3e61ed7cb6c47a2d1defa29e Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Jan 2025 16:44:11 +0200 Subject: [PATCH 2/8] Page size --- tests/e2e/Adapter/Base.php | 92 ++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 5dc0b6d26..ba2a1682c 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5475,17 +5475,16 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void } } - public function testLimitForAttributes(): void + public function testLimitForAttributesCount(): void { if (static::getDatabase()->getAdapter()->getLimitForAttributes() === 0) { $this->expectNotToPerformAssertions(); return; } - $attributes = []; - $limit = static::getDatabase()->getAdapter()->getLimitForAttributes() - 7; // Internal attributes - $limit =2000; + + $attributes = []; for ($i = 0; $i < $limit; $i++) { $attributes[] = new Document([ @@ -5501,9 +5500,9 @@ public function testLimitForAttributes(): void } try { - static::getDatabase()->createCollection("attributes_limit", $attributes); + static::getDatabase()->createCollection('attributes_limit', $attributes); $this->fail('Failed to throw exception'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertInstanceOf(LimitException::class, $e); $this->assertEquals('Attribute limit of 1017 exceeded. Cannot create collection.', $e->getMessage()); } @@ -5514,7 +5513,7 @@ public function testLimitForAttributes(): void array_pop($attributes); - $collection = static::getDatabase()->createCollection("attributes_limit", $attributes); + $collection = static::getDatabase()->createCollection('attributes_limit', $attributes); $attribute = new Document([ '$id' => ID::custom('breaking'), @@ -5530,18 +5529,93 @@ public function testLimitForAttributes(): void try { static::getDatabase()->checkAttribute($collection, $attribute); $this->fail('Failed to throw exception'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Column limit reached. Cannot create new attribute.', $e->getMessage()); } try { static::getDatabase()->createAttribute($collection->getId(), 'breaking', Database::VAR_STRING, 100, true); $this->fail('Failed to throw exception'); + } catch (\Throwable $e) { + $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Column limit reached. Cannot create new attribute.', $e->getMessage()); + } + } + + public function testLimitForAttributesRowSize(): void + { + if (static::getDatabase()->getAdapter()->getDocumentSizeLimit() === 0) { + $this->expectNotToPerformAssertions(); + return; + } + + $attributes = []; + + $attributes[] = new Document([ + '$id' => ID::custom('varchar_16000'), + 'type' => Database::VAR_STRING, + 'size' => 16000, + 'required' => true, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + + $attributes[] = new Document([ + '$id' => ID::custom('varchar_200'), + 'type' => Database::VAR_STRING, + 'size' => 200, + 'required' => true, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + + try { + static::getDatabase()->createCollection("attributes_row_size", $attributes); + $this->fail('Failed to throw exception'); + } catch (\Throwable $e) { + $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Document size limit of 65535 exceeded. Cannot create collection.', $e->getMessage()); + } + + /** + * Remove last attribute + */ + + array_pop($attributes); + + $collection = static::getDatabase()->createCollection("attributes_row_size", $attributes); + + $attribute = new Document([ + '$id' => ID::custom('breaking'), + 'type' => Database::VAR_STRING, + 'size' => 200, + 'required' => true, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + + try { + static::getDatabase()->checkAttribute($collection, $attribute); + $this->fail('Failed to throw exception'); } catch (\Exception $e) { $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Row width limit reached. Cannot create new attribute.', $e->getMessage()); } - $this->assertEquals('shmuel', 'fogel'); + try { + static::getDatabase()->createAttribute($collection->getId(), 'breaking', Database::VAR_STRING, 200, true); + $this->fail('Failed to throw exception'); + } catch (\Throwable $e) { + $this->assertInstanceOf(LimitException::class, $e); + $this->assertEquals('Row width limit reached. Cannot create new attribute.', $e->getMessage()); + } } public function testExceptionIndexLimit(): void From 8b6b6c4372f3f81e54084505f99c70b99fdc4f72 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Jan 2025 16:51:06 +0200 Subject: [PATCH 3/8] Revert names --- tests/e2e/Adapter/Base.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index ba2a1682c..9a39818c1 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5475,7 +5475,7 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void } } - public function testLimitForAttributesCount(): void + public function testExceptionAttributeLimit(): void { if (static::getDatabase()->getAdapter()->getLimitForAttributes() === 0) { $this->expectNotToPerformAssertions(); @@ -5543,7 +5543,7 @@ public function testLimitForAttributesCount(): void } } - public function testLimitForAttributesRowSize(): void + public function testExceptionWidthLimit(): void { if (static::getDatabase()->getAdapter()->getDocumentSizeLimit() === 0) { $this->expectNotToPerformAssertions(); From 49943c9eb9e156f0f4b3e4f07371d19cb36ca1b7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Jan 2025 16:59:42 +0200 Subject: [PATCH 4/8] more tests --- tests/e2e/Adapter/Base.php | 156 ++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 9a39818c1..c3f7caa8f 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -93,84 +93,6 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } - public function testWidthLimit(): void - { - if (static::getDatabase()->getAdapter()::getDocumentSizeLimit() === 0) { - $this->expectNotToPerformAssertions(); - return; - } - - $collection = static::getDatabase()->createCollection('width_limit'); - - $init = static::getDatabase()->getAdapter()->getAttributeWidth($collection); - $this->assertEquals(1067, $init); - - $attribute = new Document([ - '$id' => ID::custom('varchar_100'), - 'type' => Database::VAR_STRING, - 'size' => 100, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); - $this->assertEquals(401, $res - $init); // 100 * 4 + 1 (length) - - $attribute = new Document([ - '$id' => ID::custom('json'), - 'type' => Database::VAR_STRING, - 'size' => 100, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => true, - 'filters' => [], - ]); - $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); - $this->assertEquals(20, $res - $init); // Pointer of Json / Longtext (mariaDB) - - $attribute = new Document([ - '$id' => ID::custom('text'), - 'type' => Database::VAR_STRING, - 'size' => 20000, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); - $this->assertEquals(20, $res - $init); - - $attribute = new Document([ - '$id' => ID::custom('bigint'), - 'type' => Database::VAR_INTEGER, - 'size' => 8, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); - $this->assertEquals(8, $res - $init); - - $attribute = new Document([ - '$id' => ID::custom('date'), - 'type' => Database::VAR_DATETIME, - 'size' => 8, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]); - $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); - $this->assertEquals(7, $res - $init); - } - /** * @throws LimitException * @throws DuplicateException @@ -5475,6 +5397,84 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void } } + public function testWidthLimit(): void + { + if (static::getDatabase()->getAdapter()::getDocumentSizeLimit() === 0) { + $this->expectNotToPerformAssertions(); + return; + } + + $collection = static::getDatabase()->createCollection('width_limit'); + + $init = static::getDatabase()->getAdapter()->getAttributeWidth($collection); + $this->assertEquals(1067, $init); + + $attribute = new Document([ + '$id' => ID::custom('varchar_100'), + 'type' => Database::VAR_STRING, + 'size' => 100, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(401, $res - $init); // 100 * 4 + 1 (length) + + $attribute = new Document([ + '$id' => ID::custom('json'), + 'type' => Database::VAR_STRING, + 'size' => 100, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => true, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(20, $res - $init); // Pointer of Json / Longtext (mariaDB) + + $attribute = new Document([ + '$id' => ID::custom('text'), + 'type' => Database::VAR_STRING, + 'size' => 20000, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(20, $res - $init); + + $attribute = new Document([ + '$id' => ID::custom('bigint'), + 'type' => Database::VAR_INTEGER, + 'size' => 8, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(8, $res - $init); + + $attribute = new Document([ + '$id' => ID::custom('date'), + 'type' => Database::VAR_DATETIME, + 'size' => 8, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]); + $res = static::getDatabase()->getAdapter()->getAttributeWidth($collection->setAttribute('attributes', [$attribute])); + $this->assertEquals(7, $res - $init); + } + public function testExceptionAttributeLimit(): void { if (static::getDatabase()->getAdapter()->getLimitForAttributes() === 0) { From 098e6f0676cb875367953ba216d9f3f2326cc301 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Jan 2025 17:02:37 +0200 Subject: [PATCH 5/8] formatting --- src/Database/Adapter/SQL.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index e26e66669..9a153d6ef 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -495,7 +495,7 @@ public function getAttributeWidth(Document $collection): int * data is stored externally */ - if($attribute['array'] ?? false){ + if ($attribute['array'] ?? false) { $total += 20; continue; } From 38f9760c60f8e47f71ffce9593c90e49eb66c82d Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Jan 2025 16:20:48 +0200 Subject: [PATCH 6/8] Us adapter values --- src/Database/Adapter/SQL.php | 3 +-- src/Database/Database.php | 8 +++++--- tests/e2e/Adapter/Base.php | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 9a153d6ef..e5bcda7c1 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -416,8 +416,7 @@ public function getCountOfAttributes(Document $collection): int { $attributes = \count($collection->getAttribute('attributes') ?? []); - // +1 ==> virtual columns count as total, so add as buffer - return $attributes + static::getCountOfDefaultAttributes() + 1; + return $attributes + static::getCountOfDefaultAttributes(); } /** diff --git a/src/Database/Database.php b/src/Database/Database.php index 0350a658b..5b74c288c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5935,9 +5935,11 @@ private function validateSelections(Document $collection, array $queries): array */ public function getLimitForAttributes(): int { - // If negative, return 0 - // -1 ==> virtual columns count as total, so treat as buffer - return \max($this->adapter->getLimitForAttributes() - $this->adapter->getCountOfDefaultAttributes() - 1, 0); + if($this->adapter->getLimitForAttributes() === 0){ + return 0; + } + + return $this->adapter->getLimitForAttributes() - $this->adapter->getCountOfDefaultAttributes(); } /** diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index c3f7caa8f..9362f1130 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5482,11 +5482,11 @@ public function testExceptionAttributeLimit(): void return; } - $limit = static::getDatabase()->getAdapter()->getLimitForAttributes() - 7; // Internal attributes + $limit = static::getDatabase()->getAdapter()->getLimitForAttributes() - static::getDatabase()->getAdapter()::getCountOfDefaultAttributes(); $attributes = []; - for ($i = 0; $i < $limit; $i++) { + for ($i = 0; $i <= $limit; $i++) { $attributes[] = new Document([ '$id' => ID::custom("attr_{$i}"), 'type' => Database::VAR_INTEGER, From 28e026c0f8dbd376d36d53a0aa23a6301c14932a Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Jan 2025 16:38:44 +0200 Subject: [PATCH 7/8] Remove Database::getLimitForAttributes --- src/Database/Database.php | 15 --------------- tests/e2e/Adapter/Base.php | 5 ----- 2 files changed, 20 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 5b74c288c..e67498931 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5927,21 +5927,6 @@ private function validateSelections(Document $collection, array $queries): array return $selections; } - /** - * Get adapter attribute limit, accounting for internal metadata - * Returns 0 to indicate no limit - * - * @return int - */ - public function getLimitForAttributes(): int - { - if($this->adapter->getLimitForAttributes() === 0){ - return 0; - } - - return $this->adapter->getLimitForAttributes() - $this->adapter->getCountOfDefaultAttributes(); - } - /** * Get adapter index limit * diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 9362f1130..fc9af10e0 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5736,11 +5736,6 @@ public function testUniqueIndexDuplicateUpdate(): void static::getDatabase()->updateDocument('movies', $document->getId(), $document->setAttribute('name', 'Frozen')); } - public function testGetAttributeLimit(): void - { - $this->assertIsInt($this->getDatabase()->getLimitForAttributes()); - } - public function testGetIndexLimit(): void { $this->assertEquals(58, $this->getDatabase()->getLimitForIndexes()); From 18f74a231b7042620df7b3865dd97acd386cbef5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Jan 2025 17:36:39 +0200 Subject: [PATCH 8/8] Revert since used in Appwrite --- src/Database/Database.php | 15 +++++++++++++++ tests/e2e/Adapter/Base.php | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 948fbf373..19c86dcda 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5910,6 +5910,21 @@ private function validateSelections(Document $collection, array $queries): array return $selections; } + /** + * Get adapter attribute limit, accounting for internal metadata + * Returns 0 to indicate no limit + * + * @return int + */ + public function getLimitForAttributes(): int + { + if ($this->adapter->getLimitForAttributes() === 0) { + return 0; + } + + return $this->adapter->getLimitForAttributes() - $this->adapter->getCountOfDefaultAttributes(); + } + /** * Get adapter index limit * diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 507241f09..040b2005a 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5736,6 +5736,11 @@ public function testUniqueIndexDuplicateUpdate(): void static::getDatabase()->updateDocument('movies', $document->getId(), $document->setAttribute('name', 'Frozen')); } + public function testGetAttributeLimit(): void + { + $this->assertIsInt($this->getDatabase()->getLimitForAttributes()); + } + public function testGetIndexLimit(): void { $this->assertEquals(58, $this->getDatabase()->getLimitForIndexes());