diff --git a/src/Database/Database.php b/src/Database/Database.php index 8292bf6bf..c530af9d5 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -690,9 +690,14 @@ public function createCollection(string $id, array $attributes = [], array $inde 'documentSecurity' => $documentSecurity ]); - $validator = new IndexValidator($this->adapter->getMaxIndexLength()); - if (!$validator->isValid($collection)) { - throw new DatabaseException($validator->getDescription()); + $validator = new IndexValidator( + $attributes, + $this->adapter->getMaxIndexLength() + ); + foreach ($indexes as $index) { + if (!$validator->isValid($index)) { + throw new DatabaseException($validator->getDescription()); + } } $this->adapter->createCollection($id, $attributes, $indexes); @@ -2047,11 +2052,6 @@ public function createIndex(string $collection, string $id, string $type, array $collection = $this->silent(fn () => $this->getCollection($collection)); - $validator = new IndexValidator($this->adapter->getMaxIndexLength()); - if (!$validator->isValid($collection)) { - throw new DatabaseException($validator->getDescription()); - } - // index IDs are case-insensitive $indexes = $collection->getAttribute('indexes', []); @@ -2089,17 +2089,23 @@ public function createIndex(string $collection, string $id, string $type, array throw new DatabaseException('Unknown index type: ' . $type . '. Must be one of ' . Database::INDEX_KEY . ', ' . Database::INDEX_UNIQUE . ', ' . Database::INDEX_ARRAY . ', ' . Database::INDEX_FULLTEXT); } - $collection->setAttribute('indexes', new Document([ + $index = new Document([ '$id' => ID::custom($id), 'key' => $id, 'type' => $type, 'attributes' => $attributes, 'lengths' => $lengths, 'orders' => $orders, - ]), Document::SET_TYPE_APPEND); + ]); + + $collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND); + + $validator = new IndexValidator( + $collection->getAttribute('attributes', []), + $this->adapter->getMaxIndexLength() + ); - $validator = new IndexValidator($this->adapter->getMaxIndexLength()); - if (!$validator->isValid($collection)) { + if (!$validator->isValid($index)) { throw new DatabaseException($validator->getDescription()); } diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index 4ebc4f98a..a0b6b80f5 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -15,14 +15,24 @@ class Index extends Validator /** * @var array $attributes */ - protected array $attributes = []; + protected array $attributes; /** + * @param array $attributes * @param int $maxLength */ - public function __construct(int $maxLength) + public function __construct(array $attributes, int $maxLength) { $this->maxLength = $maxLength; + + foreach ($attributes as $attribute) { + $key = \strtolower($attribute->getAttribute('key', $attribute->getAttribute('$id'))); + $this->attributes[$key] = $attribute; + } + foreach (Database::getInternalAttributes() as $attribute) { + $key = \strtolower($attribute->getAttribute('$id')); + $this->attributes[$key] = $attribute; + } } /** @@ -35,131 +45,115 @@ public function getDescription(): string } /** - * @param Document $collection + * @param Document $index * @return bool */ - public function checkAttributesNotFound(Document $collection): bool + public function checkAttributesNotFound(Document $index): bool { - foreach ($collection->getAttribute('indexes', []) as $index) { - foreach ($index->getAttribute('attributes', []) as $attributeName) { - if (!isset($this->attributes[$attributeName])) { - $this->message = 'Invalid index attribute "' . $attributeName . '" not found'; - return false; - } + foreach ($index->getAttribute('attributes', []) as $attribute) { + if (!isset($this->attributes[\strtolower($attribute)])) { + $this->message = 'Invalid index attribute "' . $attribute . '" not found'; + return false; } } - return true; } /** - * @param Document $collection + * @param Document $index * @return bool */ - public function checkEmptyIndexAttributes(Document $collection): bool + public function checkEmptyIndexAttributes(Document $index): bool { - foreach ($collection->getAttribute('indexes', []) as $index) { - if (empty($index->getAttribute('attributes', []))) { - $this->message = 'No attributes provided for index'; - return false; - } + if (empty($index->getAttribute('attributes', []))) { + $this->message = 'No attributes provided for index'; + return false; } - return true; } /** - * @param Document $collection + * @param Document $index * @return bool */ - public function checkDuplicatedAttributes(Document $collection): bool + public function checkDuplicatedAttributes(Document $index): bool { - foreach ($collection->getAttribute('indexes', []) as $index) { - $attributes = $index->getAttribute('attributes', []); - $orders = $index->getAttribute('orders', []); - $stack = []; - foreach ($attributes as $key => $attribute) { - $direction = $orders[$key] ?? 'asc'; - $value = strtolower($attribute . '|' . $direction); - if (in_array($value, $stack)) { - $this->message = 'Duplicate attributes provided'; - return false; - } - $stack[] = $value; + $attributes = $index->getAttribute('attributes', []); + $orders = $index->getAttribute('orders', []); + $stack = []; + foreach ($attributes as $key => $attribute) { + $direction = $orders[$key] ?? 'ASC'; + $value = \strtolower($attribute . '|' . $direction); + if (\in_array($value, $stack)) { + $this->message = 'Duplicate attributes provided'; + return false; } + $stack[] = $value; } - return true; } /** - * @param Document $collection + * @param Document $index * @return bool * @throws DatabaseException */ - public function checkFulltextIndexNonString(Document $collection): bool + public function checkFulltextIndexNonString(Document $index): bool { - foreach ($collection->getAttribute('indexes', []) as $index) { - if ($index->getAttribute('type') === Database::INDEX_FULLTEXT) { - foreach ($index->getAttribute('attributes', []) as $attributeName) { - $attribute = $this->attributes[$attributeName] ?? new Document([]); - if ($attribute->getAttribute('type', '') !== Database::VAR_STRING) { - $this->message = 'Attribute "'.$attribute->getAttribute('key', $attribute->getAttribute('$id')).'" cannot be part of a FULLTEXT index, must be of type string'; - return false; - } + if ($index->getAttribute('type') === Database::INDEX_FULLTEXT) { + foreach ($index->getAttribute('attributes', []) as $attribute) { + $attribute = $this->attributes[\strtolower($attribute)] ?? new Document(); + if ($attribute->getAttribute('type', '') !== Database::VAR_STRING) { + $this->message = 'Attribute "' . $attribute->getAttribute('key', $attribute->getAttribute('$id')) . '" cannot be part of a FULLTEXT index, must be of type string'; + return false; } } } - return true; } /** - * @param Document $collection + * @param Document $index * @return bool */ - public function checkIndexLength(Document $collection): bool + public function checkIndexLength(Document $index): bool { - foreach ($collection->getAttribute('indexes', []) as $index) { - if ($index->getAttribute('type') === Database::INDEX_FULLTEXT) { - continue; - } - - $total = 0; - $lengths = $index->getAttribute('lengths', []); - - foreach ($index->getAttribute('attributes', []) as $attributePosition => $attributeName) { - $attribute = $this->attributes[$attributeName]; - - switch ($attribute->getAttribute('type')) { - case Database::VAR_STRING: - $attributeSize = $attribute->getAttribute('size', 0); - $indexLength = $lengths[$attributePosition] ?? $attributeSize; - break; - - case Database::VAR_FLOAT: - $attributeSize = 2; // 8 bytes / 4 mb4 - $indexLength = 2; - break; - - default: - $attributeSize = 1; // 4 bytes / 4 mb4 - $indexLength = 1; - break; - } - - if ($indexLength > $attributeSize) { - $this->message = 'Index length '.$indexLength.' is larger than the size for '.$attributeName.': '.$attributeSize.'"'; - return false; - } + if ($index->getAttribute('type') === Database::INDEX_FULLTEXT) { + return true; + } - $total += $indexLength; + $total = 0; + $lengths = $index->getAttribute('lengths', []); + + foreach ($index->getAttribute('attributes', []) as $attributePosition => $attributeName) { + $attribute = $this->attributes[\strtolower($attributeName)]; + + switch ($attribute->getAttribute('type')) { + case Database::VAR_STRING: + $attributeSize = $attribute->getAttribute('size', 0); + $indexLength = $lengths[$attributePosition] ?? $attributeSize; + break; + case Database::VAR_FLOAT: + $attributeSize = 2; // 8 bytes / 4 mb4 + $indexLength = 2; + break; + default: + $attributeSize = 1; // 4 bytes / 4 mb4 + $indexLength = 1; + break; } - if ($total > $this->maxLength && $this->maxLength > 0) { - $this->message = 'Index length is longer than the maximum: ' . $this->maxLength; + if ($indexLength > $attributeSize) { + $this->message = 'Index length ' . $indexLength . ' is larger than the size for ' . $attributeName . ': ' . $attributeSize . '"'; return false; } + + $total += $indexLength; + } + + if ($total > $this->maxLength && $this->maxLength > 0) { + $this->message = 'Index length is longer than the maximum: ' . $this->maxLength; + return false; } return true; @@ -175,14 +169,6 @@ public function checkIndexLength(Document $collection): bool */ public function isValid($value): bool { - foreach ($value->getAttribute('attributes', []) as $attribute) { - $this->attributes[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute; - } - - foreach (Database::getInternalAttributes() as $attribute) { - $this->attributes[$attribute->getAttribute('$id')] = $attribute; - } - if (!$this->checkAttributesNotFound($value)) { return false; } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 34609d113..afb537ee9 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -122,10 +122,10 @@ public function testIndexValidation(): void 'indexes' => $indexes ]); - $validator = new Index(static::getDatabase()->getAdapter()->getMaxIndexLength()); + $validator = new Index($attributes, static::getDatabase()->getAdapter()->getMaxIndexLength()); $errorMessage = 'Index length 701 is larger than the size for title1: 700"'; - $this->assertFalse($validator->isValid($collection)); + $this->assertFalse($validator->isValid($indexes[0])); $this->assertEquals($errorMessage, $validator->getDescription()); try { @@ -149,7 +149,7 @@ public function testIndexValidation(): void if (static::getDatabase()->getAdapter()->getMaxIndexLength() > 0) { $errorMessage = 'Index length is longer than the maximum: ' . static::getDatabase()->getAdapter()->getMaxIndexLength(); - $this->assertFalse($validator->isValid($collection)); + $this->assertFalse($validator->isValid($indexes[0])); $this->assertEquals($errorMessage, $validator->getDescription()); try { @@ -189,8 +189,9 @@ public function testIndexValidation(): void 'indexes' => $indexes ]); + $validator = new Index($attributes, static::getDatabase()->getAdapter()->getMaxIndexLength()); $errorMessage = 'Attribute "integer" cannot be part of a FULLTEXT index, must be of type string'; - $this->assertFalse($validator->isValid($collection)); + $this->assertFalse($validator->isValid($indexes[0])); $this->assertEquals($errorMessage, $validator->getDescription()); try { diff --git a/tests/Database/Validator/IndexTest.php b/tests/Database/Validator/IndexTest.php index 4247ecd32..8f3aa70fb 100644 --- a/tests/Database/Validator/IndexTest.php +++ b/tests/Database/Validator/IndexTest.php @@ -24,8 +24,6 @@ public function tearDown(): void */ public function testAttributeNotFound(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -53,7 +51,9 @@ public function testAttributeNotFound(): void ], ]); - $this->assertFalse($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertFalse($validator->isValid($index)); $this->assertEquals('Invalid index attribute "not_exist" not found', $validator->getDescription()); } @@ -62,8 +62,6 @@ public function testAttributeNotFound(): void */ public function testFulltextWithNonString(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -102,7 +100,9 @@ public function testFulltextWithNonString(): void ], ]); - $this->assertFalse($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertFalse($validator->isValid($index)); $this->assertEquals('Attribute "date" cannot be part of a FULLTEXT index, must be of type string', $validator->getDescription()); } @@ -111,8 +111,6 @@ public function testFulltextWithNonString(): void */ public function testIndexLength(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -140,7 +138,9 @@ public function testIndexLength(): void ], ]); - $this->assertFalse($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertFalse($validator->isValid($index)); $this->assertEquals('Index length is longer than the maximum: 768', $validator->getDescription()); } @@ -149,8 +149,6 @@ public function testIndexLength(): void */ public function testMultipleIndexLength(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -187,15 +185,18 @@ public function testMultipleIndexLength(): void ], ]); - $this->assertTrue($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertTrue($validator->isValid($index)); - $collection->setAttribute('indexes', new Document([ + $index = new Document([ '$id' => ID::custom('index2'), 'type' => Database::INDEX_KEY, 'attributes' => ['title', 'description'], - ]), Document::SET_TYPE_APPEND); + ]); - $this->assertFalse($validator->isValid($collection)); + $collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND); + $this->assertFalse($validator->isValid($index)); $this->assertEquals('Index length is longer than the maximum: 768', $validator->getDescription()); } @@ -204,8 +205,6 @@ public function testMultipleIndexLength(): void */ public function testEmptyAttributes(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -233,7 +232,9 @@ public function testEmptyAttributes(): void ], ]); - $this->assertFalse($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertFalse($validator->isValid($index)); $this->assertEquals('No attributes provided for index', $validator->getDescription()); } @@ -242,8 +243,6 @@ public function testEmptyAttributes(): void */ public function testDuplicatedAttributes(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -271,7 +270,9 @@ public function testDuplicatedAttributes(): void ], ]); - $this->assertFalse($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertFalse($validator->isValid($index)); $this->assertEquals('Duplicate attributes provided', $validator->getDescription()); } @@ -280,8 +281,6 @@ public function testDuplicatedAttributes(): void */ public function testDuplicatedAttributesDifferentOrder(): void { - $validator = new Index(768); - $collection = new Document([ '$id' => ID::custom('test'), 'name' => 'test', @@ -309,6 +308,8 @@ public function testDuplicatedAttributesDifferentOrder(): void ], ]); - $this->assertTrue($validator->isValid($collection)); + $validator = new Index($collection->getAttribute('attributes'), 768); + $index = $collection->getAttribute('indexes')[0]; + $this->assertTrue($validator->isValid($index)); } }