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
30 changes: 18 additions & 12 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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', []);

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

Expand Down
166 changes: 76 additions & 90 deletions src/Database/Validator/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@ class Index extends Validator
/**
* @var array<Document> $attributes
*/
protected array $attributes = [];
protected array $attributes;

/**
* @param array<Document> $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;
}
}

/**
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
9 changes: 5 additions & 4 deletions tests/Database/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading